AgentCoord/backend/server.py
liailing1026 23db6fc4a1 feat
2025-12-07 17:18:10 +08:00

434 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import nest_asyncio
nest_asyncio.apply()
import os, sys, functools
print = functools.partial(print, flush=True) # 全局 flush
sys.stdout.reconfigure(line_buffering=True) # 3.7+ 有效
import asyncio
from flask import Flask, request, jsonify
import json
from DataProcess import Add_Collaboration_Brief_FrontEnd
from AgentCoord.RehearsalEngine_V2.ExecutePlan import executePlan
from AgentCoord.PlanEngine.basePlan_Generator import generate_basePlan
from AgentCoord.PlanEngine.fill_stepTask import fill_stepTask
from AgentCoord.PlanEngine.fill_stepTask_TaskProcess import (
fill_stepTask_TaskProcess,
)
from AgentCoord.PlanEngine.branch_PlanOutline import branch_PlanOutline
from AgentCoord.PlanEngine.branch_TaskProcess import branch_TaskProcess
from AgentCoord.PlanEngine.AgentSelectModify import (
AgentSelectModify_init,
AgentSelectModify_addAspect,
)
import os
import yaml
import argparse
# initialize global variables
yaml_file = os.path.join(os.getcwd(), "config", "config.yaml")
yaml_data = {}
try:
with open(yaml_file, "r", encoding="utf-8") as file:
yaml_data = yaml.safe_load(file)
except Exception:
yaml_data = {}
USE_CACHE: bool = os.getenv("USE_CACHE")
if USE_CACHE is None:
USE_CACHE = yaml_data.get("USE_CACHE", False)
else:
USE_CACHE = USE_CACHE.lower() in ["true", "1", "yes"]
AgentBoard = None
AgentProfile_Dict = {}
Request_Cache: dict[str, str] = {}
app = Flask(__name__)
from jsonschema import validate, ValidationError
AGENT_SELECTION_SCHEMA = {
"type": "object",
"properties": {
"AgentSelectionPlan": {
"type": "array",
"items": {
"type": "string",
"minLength": 1, # 不允许空字符串
"pattern": r"^\S+$" # 不允许仅空白
},
"minItems": 1 # 至少选一个
}
},
"required": ["AgentSelectionPlan"],
"additionalProperties": False
}
BASE_PLAN_SCHEMA = {
"type": "object",
"properties": {
"Plan_Outline": {
"type": "array",
"items": {
"type": "object",
"properties": {
"StepName": {"type": "string"},
"TaskContent": {"type": "string"},
"InputObject_List":{"type": "array", "items": {"type": "string"}},
"OutputObject": {"type": "string"},
},
"required": ["StepName", "TaskContent", "InputObject_List", "OutputObject"],
"additionalProperties": False,
},
}
},
"required": ["Plan_Outline"],
"additionalProperties": False,
}
def safe_join(iterable, sep=""):
"""保证 join 前全是 strNone 变空串"""
return sep.join("" if x is None else str(x) for x in iterable)
def clean_agent_board(board):
"""把 AgentBoard 洗成只含 str 的字典列表"""
if not board:
return []
return [
{"Name": (a.get("Name") or "").strip(),
"Profile": (a.get("Profile") or "").strip()}
for a in board
if a and a.get("Name")
]
def clean_plan_outline(outline):
"""清洗 Plan_Outline 里的 None"""
if not isinstance(outline, list):
return []
for step in outline:
if not isinstance(step, dict):
continue
step["InputObject_List"] = [
str(i) for i in step.get("InputObject_List", []) if i is not None
]
step["OutputObject"] = str(step.get("OutputObject") or "")
step["StepName"] = str(step.get("StepName") or "")
step["TaskContent"] = str(step.get("TaskContent") or "")
return outline
@app.route("/fill_stepTask_TaskProcess", methods=["post"])
def Handle_fill_stepTask_TaskProcess():
incoming_data = request.get_json()
# print(f"[DEBUG] fill_stepTask_TaskProcess received data: {incoming_data}", flush=True)
# 验证必需参数
General_Goal = incoming_data.get("General Goal", "").strip()
stepTask_lackTaskProcess = incoming_data.get("stepTask_lackTaskProcess")
if not General_Goal:
return jsonify({"error": "General Goal is required and cannot be empty"}), 400
if not stepTask_lackTaskProcess:
return jsonify({"error": "stepTask_lackTaskProcess is required"}), 400
requestIdentifier = str(
(
"/fill_stepTask_TaskProcess",
General_Goal,
stepTask_lackTaskProcess,
)
)
if USE_CACHE:
if requestIdentifier in Request_Cache:
return jsonify(Request_Cache[requestIdentifier])
try:
filled_stepTask = fill_stepTask_TaskProcess(
General_Goal=General_Goal,
stepTask=stepTask_lackTaskProcess,
AgentProfile_Dict=AgentProfile_Dict,
)
except Exception as e:
print(f"[ERROR] fill_stepTask_TaskProcess failed: {e}", flush=True)
return jsonify({"error": str(e)}), 500
filled_stepTask = Add_Collaboration_Brief_FrontEnd(filled_stepTask)
Request_Cache[requestIdentifier] = filled_stepTask
response = jsonify(filled_stepTask)
return response
@app.route("/agentSelectModify_init", methods=["POST"])
def Handle_agentSelectModify_init():
incoming = request.get_json(silent=True) or {}
general_goal = (incoming.get("General Goal") or "").strip()
step_task = incoming.get("stepTask")
if not general_goal or not step_task:
return jsonify({"error": "Missing field"}), 400
if not AgentBoard: # 空 Board 直接返回
return jsonify({"AgentSelectionPlan": []})
req_id = str(("/agentSelectModify_init", general_goal, step_task))
if USE_CACHE and req_id in Request_Cache:
return jsonify(Request_Cache[req_id])
try:
clean_board = clean_agent_board(AgentBoard)
raw = AgentSelectModify_init(stepTask=step_task,
General_Goal=general_goal,
Agent_Board=clean_board)
if not isinstance(raw, dict):
raise ValueError("model returned non-dict")
plan = raw.get("AgentSelectionPlan") or []
cleaned = [str(x).strip() for x in plan if x is not None and str(x).strip()]
raw["AgentSelectionPlan"] = cleaned
validate(instance=raw, schema=AGENT_SELECTION_SCHEMA)
except Exception as exc:
print(f"[ERROR] AgentSelectModify_init: {exc}")
return jsonify({"error": str(exc)}), 500
if USE_CACHE:
Request_Cache[req_id] = raw
return jsonify(raw)
@app.route("/agentSelectModify_addAspect", methods=["post"])
def Handle_agentSelectModify_addAspect():
incoming_data = request.get_json()
requestIdentifier = str(
("/agentSelectModify_addAspect", incoming_data["aspectList"])
)
if USE_CACHE:
if requestIdentifier in Request_Cache:
return jsonify(Request_Cache[requestIdentifier])
scoreTable = AgentSelectModify_addAspect(
aspectList=incoming_data["aspectList"], Agent_Board=AgentBoard or []
)
Request_Cache[requestIdentifier] = scoreTable
response = jsonify(scoreTable)
return response
@app.route("/fill_stepTask", methods=["post"])
def Handle_fill_stepTask():
incoming_data = request.get_json()
# print(f"[DEBUG] fill_stepTask received data: {incoming_data}", flush=True)
# 验证必需参数
General_Goal = incoming_data.get("General Goal", "").strip()
stepTask = incoming_data.get("stepTask")
if not General_Goal:
return jsonify({"error": "General Goal is required and cannot be empty"}), 400
if not stepTask:
return jsonify({"error": "stepTask is required"}), 400
requestIdentifier = str(
(
"/fill_stepTask",
General_Goal,
stepTask,
)
)
if USE_CACHE:
if requestIdentifier in Request_Cache:
return jsonify(Request_Cache[requestIdentifier])
try:
filled_stepTask = fill_stepTask(
General_Goal=General_Goal,
stepTask=stepTask,
Agent_Board=AgentBoard,
AgentProfile_Dict=AgentProfile_Dict,
)
except Exception as e:
print(f"[ERROR] fill_stepTask failed: {e}", flush=True)
return jsonify({"error": str(e)}), 500
filled_stepTask = Add_Collaboration_Brief_FrontEnd(filled_stepTask)
Request_Cache[requestIdentifier] = filled_stepTask
response = jsonify(filled_stepTask)
return response
@app.route("/branch_PlanOutline", methods=["post"])
def Handle_branch_PlanOutline():
incoming_data = request.get_json()
requestIdentifier = str(
(
"/branch_PlanOutline",
incoming_data["branch_Number"],
incoming_data["Modification_Requirement"],
incoming_data["Existing_Steps"],
incoming_data["Baseline_Completion"],
incoming_data["Initial Input Object"],
incoming_data["General Goal"],
)
)
if USE_CACHE:
if requestIdentifier in Request_Cache:
return jsonify(Request_Cache[requestIdentifier])
branchList = branch_PlanOutline(
branch_Number=incoming_data["branch_Number"],
Modification_Requirement=incoming_data["Modification_Requirement"],
Existing_Steps=incoming_data["Existing_Steps"],
Baseline_Completion=incoming_data["Baseline_Completion"],
InitialObject_List=incoming_data["Initial Input Object"],
General_Goal=incoming_data["General Goal"],
)
branchList = Add_Collaboration_Brief_FrontEnd(branchList)
Request_Cache[requestIdentifier] = branchList
response = jsonify(branchList)
return response
@app.route("/branch_TaskProcess", methods=["post"])
def Handle_branch_TaskProcess():
incoming_data = request.get_json()
requestIdentifier = str(
(
"/branch_TaskProcess",
incoming_data["branch_Number"],
incoming_data["Modification_Requirement"],
incoming_data["Existing_Steps"],
incoming_data["Baseline_Completion"],
incoming_data["stepTaskExisting"],
incoming_data["General Goal"],
)
)
if USE_CACHE:
if requestIdentifier in Request_Cache:
return jsonify(Request_Cache[requestIdentifier])
branchList = branch_TaskProcess(
branch_Number=incoming_data["branch_Number"],
Modification_Requirement=incoming_data["Modification_Requirement"],
Existing_Steps=incoming_data["Existing_Steps"],
Baseline_Completion=incoming_data["Baseline_Completion"],
stepTaskExisting=incoming_data["stepTaskExisting"],
General_Goal=incoming_data["General Goal"],
AgentProfile_Dict=AgentProfile_Dict,
)
Request_Cache[requestIdentifier] = branchList
response = jsonify(branchList)
return response
@app.route("/generate_basePlan", methods=["POST"])
def Handle_generate_basePlan():
incoming = request.get_json(silent=True) or {}
general_goal = (incoming.get("General Goal") or "").strip()
initial_objs = incoming.get("Initial Input Object") or []
if not general_goal:
return jsonify({"error": "General Goal is required"}), 400
# 1. 空 Board 直接短路
if not AgentBoard:
print("[SKIP] AgentBoard empty")
out = Add_Collaboration_Brief_FrontEnd({"Plan_Outline": []})
return jsonify(out)
req_id = str(("/generate_basePlan", general_goal, initial_objs))
if USE_CACHE and req_id in Request_Cache:
return jsonify(Request_Cache[req_id])
try:
# 2. 洗 Board → 调模型 → 洗返回
clean_board = clean_agent_board(AgentBoard)
raw_plan = asyncio.run(
generate_basePlan(
General_Goal=general_goal,
Agent_Board=clean_board,
AgentProfile_Dict=AgentProfile_Dict,
InitialObject_List=initial_objs,
)
)
raw_plan["Plan_Outline"] = clean_plan_outline(raw_plan.get("Plan_Outline"))
validate(instance=raw_plan, schema=BASE_PLAN_SCHEMA) # 可选,二次校验
except Exception as exc:
print(f"[ERROR] generate_basePlan failed: {exc}")
return jsonify({"error": "model call failed", "detail": str(exc)}), 500
out = Add_Collaboration_Brief_FrontEnd(raw_plan)
if USE_CACHE:
Request_Cache[req_id] = out
return jsonify(out)
@app.route("/executePlan", methods=["post"])
def Handle_executePlan():
incoming_data = request.get_json()
requestIdentifier = str(
(
"/executePlan",
incoming_data["num_StepToRun"],
incoming_data["RehearsalLog"],
incoming_data["plan"],
)
)
if USE_CACHE:
if requestIdentifier in Request_Cache:
return jsonify(Request_Cache[requestIdentifier])
RehearsalLog = executePlan(
incoming_data["plan"],
incoming_data["num_StepToRun"],
incoming_data["RehearsalLog"],
AgentProfile_Dict,
)
Request_Cache[requestIdentifier] = RehearsalLog
response = jsonify(RehearsalLog)
return response
@app.route("/_saveRequestCashe", methods=["post"])
def Handle_saveRequestCashe():
with open(
os.path.join(os.getcwd(), "RequestCache", "Request_Cache.json"), "w"
) as json_file:
json.dump(Request_Cache, json_file, indent=4)
response = jsonify(
{"code": 200, "content": "request cashe sucessfully saved"}
)
return response
@app.route("/setAgents", methods=["POST"])
def set_agents():
global AgentBoard, AgentProfile_Dict
board_in = request.json or []
# 先清洗再赋值
AgentBoard = clean_agent_board(board_in)
AgentProfile_Dict = {a["Name"]: a["Profile"] for a in AgentBoard}
return jsonify({"code": 200, "content": "AgentBoard set successfully"})
def init():
global AgentBoard, AgentProfile_Dict, Request_Cache
with open(
os.path.join(os.getcwd(), "RequestCache", "Request_Cache.json"), "r"
) as json_file:
Request_Cache = json.load(json_file)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="start the backend for AgentCoord"
)
parser.add_argument(
"--port",
type=int,
default=8017,
help="set the port number, 8017 by defaul.",
)
args = parser.parse_args()
init()
app.run(host="0.0.0.0", port=args.port, debug=True)