chore: ui server updates (#951)

* ui server update

* update

* fix bugs

* updates

* use randomname

* fix

* fix bugs

* change time interval in debug server

* some change

* fix CI

* time interval change

* updates

* some changes

* fix

* fix curves

* one IP, one pointers to show msgs return progress

* enable /control

* fix bugs

* fix CI

* fix isort
This commit is contained in:
XianBW
2025-07-04 12:32:14 +08:00
committed by GitHub
parent ef6b5866c7
commit c335ebc25c
9 changed files with 392 additions and 107 deletions
+20 -4
View File
@@ -116,16 +116,18 @@ Only **2** Message in one loop
"evo_id": "0",
"content": [ // list of task_name & codes
{
"evo_id": "0",
"target_task_name": "task_1",
"codes": { // one or more codes
"workspace": { // one or more codes
"a.py": "...<python codes>",
"b.py": "...<python codes>",
//...
}
},
{
"evo_id": "0",
"target_task_name": "task_2",
"codes": {
"workspace": {
"a.py": "...<python codes>",
//...
}
@@ -155,6 +157,7 @@ Only **2** Message in one loop
"evo_id": "0",
"content": [ // list of feedbacks
{
"evo_id": "0",
"final_decision": "True", // True or False
"execution": "...",
"code": "...",
@@ -230,8 +233,21 @@ Each tag below appears only once per loop.
"reason": "...",
"exception": "...",
"observations": "...", // may not exists
"hypothesis_evaluation": "...", // may not exists
"hypothesis_evaluation": "...", // may not existsc
"new_hypothesis": "...", // may not exists
}
}
```
```
# TODO
## Session
- How to continue.
- show & copy trace_id(name)?
-
## Page
1. remove Medical, add Finance Whole Pipeline
2.
+99 -38
View File
@@ -2,7 +2,6 @@ import os
import random
import signal
import subprocess
import sys
from collections import defaultdict
from datetime import datetime, timezone
from pathlib import Path
@@ -12,46 +11,88 @@ import typer
from flask import Flask, jsonify, request, send_from_directory
from flask_cors import CORS
msgs_for_frontend = defaultdict(list)
from rdagent.log.storage import FileStorage
from rdagent.log.ui.conf import UI_SETTING
from rdagent.log.ui.storage import WebStorage
from rdagent.log.utils import is_valid_session
app = Flask(__name__, static_folder="./docs/_static")
app = Flask(__name__, static_folder=UI_SETTING.static_path)
CORS(app)
rdagent_processes = defaultdict()
server_port = 19899
log_folder_path = Path(UI_SETTING.trace_folder).absolute()
@app.route("/favicon.ico")
def favicon():
return send_from_directory("./docs/_static", "favicon.ico", mimetype="image/vnd.microsoft.icon")
return send_from_directory(app.static_folder, "favicon.ico", mimetype="image/vnd.microsoft.icon")
pointers = {id: 0 for id in msgs_for_frontend.keys()}
msgs_for_frontend = defaultdict(list)
pointers = defaultdict(lambda: defaultdict(int)) # pointers[trace_id][user_ip]
def read_trace(log_path: Path, id: str = "") -> None:
fs = FileStorage(log_path)
ws = WebStorage(port=1, path=log_path)
msgs_for_frontend[id] = []
last_timestamp = None
for msg in fs.iter_msg():
data = ws._obj_to_json(obj=msg.content, tag=msg.tag, id=id, timestamp=msg.timestamp.isoformat())
if data:
if isinstance(data, list):
for d in data:
msgs_for_frontend[id].append(d["msg"])
last_timestamp = msg.timestamp
else:
msgs_for_frontend[id].append(data["msg"])
last_timestamp = msg.timestamp
now = datetime.now(timezone.utc)
if last_timestamp and (now - last_timestamp).total_seconds() > 1800:
msgs_for_frontend[id].append({"tag": "END", "timestamp": now.isoformat(), "content": {}})
# load all traces from the log folder
for p in log_folder_path.glob("*/*/"):
if is_valid_session(p):
read_trace(p, id=str(p))
@app.route("/trace", methods=["POST"])
def update_trace():
global pointers, msgs_for_frontend
data = request.get_json()
trace_id = data.get("id")
return_all = data.get("all")
reset = data.get("reset")
msg_num = random.randint(1, 10)
app.logger.info(data)
log_folder_path = Path(UI_SETTING.trace_folder).absolute()
if not trace_id:
return jsonify({"error": "Trace ID is required"}), 400
trace_id = str(log_folder_path / trace_id)
user_ip = request.remote_addr
if reset:
pointers[trace_id] = 0
pointers[trace_id][user_ip] = 0
end_pointer = pointers[trace_id] + msg_num
start_pointer = pointers[trace_id][user_ip]
end_pointer = start_pointer + msg_num
if end_pointer > len(msgs_for_frontend[trace_id]) or return_all:
end_pointer = len(msgs_for_frontend[trace_id])
print(f"trace_id: {trace_id}, start_pointer: {pointers[trace_id]}, end_pointer: {end_pointer}")
returned_msgs = msgs_for_frontend[trace_id][pointers[trace_id] : end_pointer]
returned_msgs = msgs_for_frontend[trace_id][start_pointer:end_pointer]
pointers[trace_id] = end_pointer
pointers[trace_id][user_ip] = end_pointer
if returned_msgs:
app.logger.info([msg["tag"] for msg in returned_msgs])
return jsonify(returned_msgs), 200
@app.route("/upload", methods=["GET"])
@app.route("/upload", methods=["POST"])
def upload_file():
# 获取请求体中的字段
global rdagent_processes, server_port
@@ -62,11 +103,18 @@ def upload_file():
all_duration = request.form.get("all_duration")
# scenario = "Data Science Loop"
trace_name = randomname.get_name()
log_folder_path = Path("./RD-Agent_server_trace").absolute()
log_trace_path = (log_folder_path / scenario / trace_name).absolute()
if scenario == "Data Science":
competition = competition[10:] # Eg. MLE-Bench:aerial-cactus-competition
trace_name = f"{competition}-{randomname.get_name()}"
else:
trace_name = randomname.get_name()
trace_files_path = log_folder_path / scenario / "uploads" / trace_name
log_trace_path = (log_folder_path / scenario / trace_name).absolute()
stdout_path = log_folder_path / scenario / f"{trace_name}.stdout"
if not stdout_path.exists():
stdout_path.parent.mkdir(parents=True, exist_ok=True)
# save files
for file in files:
if file:
@@ -87,33 +135,34 @@ def upload_file():
else: # one file is uploaded
rfp = str(trace_files_path / files[0].filename)
cmds = ["rdagent", "general_model", "--report_file_path", rfp]
if scenario == "Medical Model Implementation":
cmds = ["rdagent", "med_model"]
if scenario == "Data Science Loop":
cmds = ["rdagent", "kaggle", "--competition", competition]
if scenario == "Finance Whole Pipeline":
cmds = ["rdagent", "fin_quant"]
if scenario == "Data Science":
cmds = ["rdagent", "data_science", "--competition", competition]
# time control parameters
if loop_n:
cmds += ["--loop_n", loop_n]
if scenario != "Finance Data Building (Reports)":
if loop_n:
cmds += ["--loop_n", loop_n]
if all_duration:
cmds += ["--all_duration", all_duration]
rdagent_processes[str(log_trace_path)] = subprocess.Popen(
cmds,
# stdout=subprocess.PIPE,
# stderr=subprocess.PIPE,
stdout=sys.stdout,
stderr=sys.stderr,
env={
"LOG_TRACE_PATH": str(log_trace_path),
"UI_SERVER_PORT": server_port,
},
)
cmds += ["--all_duration", f"{all_duration}h"]
app.logger.info(f"Started process for {log_trace_path} with parameters: {cmds}")
with stdout_path.open("w") as log_file:
rdagent_processes[str(log_trace_path)] = subprocess.Popen(
cmds,
stdout=log_file,
stderr=log_file,
env={
**os.environ,
"LOG_TRACE_PATH": str(log_trace_path),
"LOG_UI_SERVER_PORT": str(server_port),
},
)
return (
jsonify(
{
"id": str(log_trace_path),
"id": f"{scenario}/{trace_name}",
}
),
200,
@@ -124,6 +173,7 @@ def upload_file():
def receive_msgs():
try:
data = request.get_json()
# app.logger.info(data["msg"]["tag"])
if not data:
return jsonify({"error": "No JSON data received"}), 400
except Exception as e:
@@ -140,12 +190,13 @@ def receive_msgs():
@app.route("/control", methods=["POST"])
def control_process():
global rdagent_processes
global rdagent_processes, msgs_for_frontend
data = request.get_json()
app.logger.info(data)
if not data or "id" not in data or "action" not in data:
return jsonify({"error": "Missing 'id' or 'action' in request"}), 400
id = data["id"]
id = str(log_folder_path / data["id"])
action = data["action"]
if id not in rdagent_processes or rdagent_processes[id] is None:
@@ -175,13 +226,23 @@ def control_process():
else:
return jsonify({"error": "Unknown action"}), 400
except Exception as e:
return jsonify({"error": f"Failed to {action} process"}), 500
return jsonify({"error": f"Failed to {action} process, {e}"}), 500
@app.route("/test", methods=["GET"])
def test():
# return 'Hello, World!'
global msgs_for_frontend, pointers
msgs = {k: [i["tag"] for i in v] for k, v in msgs_for_frontend.items()}
pointers = pointers
return jsonify({"msgs": msgs, "pointers": pointers}), 200
@app.route("/", methods=["GET"])
def index():
# return 'Hello, World!'
return msgs_for_frontend
# return {k: [i["tag"] for i in v] for k, v in msgs_for_frontend.items()}
return send_from_directory(app.static_folder, "index.html")
@app.route("/<path:fn>", methods=["GET"])
+174
View File
@@ -0,0 +1,174 @@
import multiprocessing
import os
import random
import signal
import subprocess
import threading
import time
from collections import defaultdict
from datetime import datetime, timezone
from pathlib import Path
import randomname
import typer
from flask import Flask, jsonify, request, send_from_directory
from flask_cors import CORS
from rdagent.log.ui.conf import UI_SETTING
app = Flask(__name__, static_folder=UI_SETTING.static_path)
CORS(app)
rdagent_processes = defaultdict()
server_port = 19899
@app.route("/favicon.ico")
def favicon():
return send_from_directory(app.static_folder, "favicon.ico", mimetype="image/vnd.microsoft.icon")
msgs_for_frontend = defaultdict(list)
pointers = defaultdict(int)
@app.route("/trace", methods=["POST"])
def update_trace():
global pointers, msgs_for_frontend
data = request.get_json()
# app.logger.info(data)
trace_id = data.get("id")
return_all = data.get("all")
reset = data.get("reset")
msg_num = random.randint(1, 10)
if reset:
pointers[trace_id] = 0
end_pointer = pointers[trace_id] + msg_num
if end_pointer > len(msgs_for_frontend[trace_id]) or return_all:
end_pointer = len(msgs_for_frontend[trace_id])
returned_msgs = msgs_for_frontend[trace_id][pointers[trace_id] : end_pointer]
pointers[trace_id] = end_pointer
# if len(returned_msgs):
# app.logger.info(data)
# app.logger.info([i["tag"] for i in returned_msgs])
# try:
# import json
# resp = json.dumps(returned_msgs, ensure_ascii=False)
# except Exception as e:
# app.logger.error(f"Error in jsonify: {e}")
# for msg in returned_msgs:
# try:
# rr = json.dumps(msg, ensure_ascii=False)
# except Exception as e:
# app.logger.error(f"Error in jsonify individual message: {e}")
# app.logger.error(msg)
return jsonify(returned_msgs), 200
@app.route("/upload", methods=["POST"])
def upload_file():
# 获取请求体中的字段
global rdagent_processes, server_port, msgs_for_frontend
scenario = request.form.get("scenario")
files = request.files.getlist("files")
competition = request.form.get("competition")
loop_n = request.form.get("loops")
all_duration = request.form.get("all_duration")
log_folder_path = Path("/home/bowen/workspace/new_traces").absolute()
if scenario == "Data Science":
trace_path = log_folder_path / "o1-preview" / f"{competition[10:]}.1"
else:
trace_path = log_folder_path / scenario
id = f"{scenario}/{randomname.get_name()}"
def read_trace(log_path: Path, t: float = 0.2, id: str = "") -> None:
from rdagent.log.storage import FileStorage
from rdagent.log.ui.storage import WebStorage
fs = FileStorage(log_path)
ws = WebStorage(port=1, path=log_path)
msgs_for_frontend[id] = []
for msg in fs.iter_msg():
data = ws._obj_to_json(obj=msg.content, tag=msg.tag, id=id, timestamp=msg.timestamp.isoformat())
if data:
if isinstance(data, list):
for d in data:
time.sleep(t)
msgs_for_frontend[id].append(d["msg"])
else:
time.sleep(t)
msgs_for_frontend[id].append(data["msg"])
msgs_for_frontend[id].append({"tag": "END", "timestamp": datetime.now(timezone.utc).isoformat(), "content": {}})
# 启动后台线程,不阻塞 return
threading.Thread(target=read_trace, args=(trace_path, 0.5, id), daemon=True).start()
return jsonify({"id": id}), 200
@app.route("/receive", methods=["POST"])
def receive_msgs():
try:
data = request.get_json()
app.logger.info(data["msg"]["tag"])
if not data:
return jsonify({"error": "No JSON data received"}), 400
except Exception as e:
return jsonify({"error": "Internal Server Error"}), 500
if isinstance(data, list):
for d in data:
msgs_for_frontend[d["id"]].append(d["msg"])
else:
msgs_for_frontend[data["id"]].append(data["msg"])
return jsonify({"status": "success"}), 200
@app.route("/control", methods=["POST"])
def control_process():
global rdagent_processes
data = request.get_json()
app.logger.info(data)
if not data or "id" not in data or "action" not in data:
return jsonify({"error": "Missing 'id' or 'action' in request"}), 400
id = data["id"]
action = data["action"]
return jsonify({"status": "success", "message": f"Received action '{action}' for process with id '{id}'"})
@app.route("/test", methods=["GET"])
def test():
# return 'Hello, World!'
return {k: [i["tag"] for i in v] for k, v in msgs_for_frontend.items()}
@app.route("/", methods=["GET"])
def index():
# return 'Hello, World!'
# return {k: [i["tag"] for i in v] for k, v in msgs_for_frontend.items()}
return send_from_directory(app.static_folder, "index.html")
@app.route("/<path:fn>", methods=["GET"])
def server_static_files(fn):
return send_from_directory(app.static_folder, fn)
def main(port: int = 19899):
global server_port
server_port = port
app.run(debug=True, host="0.0.0.0", port=port)
if __name__ == "__main__":
typer.run(main)