146 lines
4.7 KiB
Python
146 lines
4.7 KiB
Python
"""Xingbang API — 兴邦金租评审工作流 + 自进化模块的 HTTP 接口。
|
|||
|
|
|
||
|
|
端:
|
||
|
|
POST /api/xingbang/workflow 运行五智能体审批流水线(返回全部产出)
|
||
|
|
POST /api/xingbang/workflow/stream 流式运行(SSE, 逐步展示)
|
||
|
|
GET /api/xingbang/evolution 查看自进化洞察
|
||
|
|
GET /api/xingbang/trajectories 列出历史轨迹
|
||
|
|
GET /api/xingbang/trajectories/{id} 读取单条轨迹
|
||
|
|
|
||
|
|
复用 mini-OpenClaw 的 AgentManager.llm 与后端基础设施。
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from fastapi import APIRouter
|
||
|
|
from pydantic import BaseModel
|
||
|
|
from sse_starlette.sse import EventSourceResponse
|
||
|
|
|
||
|
|
from graph.agent import agent_manager
|
||
|
|
from xingbang.workflow import XingbangWorkflow
|
||
|
|
from xingbang.config import XINGBANG_CONFIG
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/api/xingbang", tags=["xingbang"])
|
||
|
|
|
||
|
|
|
||
|
|
# 懒加载单例工作流(复用 agent_manager.llm)
|
||
|
|
_workflow: XingbangWorkflow | None = None
|
||
|
|
|
||
|
|
|
||
|
|
def _get_workflow() -> XingbangWorkflow:
|
||
|
|
global _workflow
|
||
|
|
if _workflow is None:
|
||
|
|
if agent_manager._llm is None:
|
||
|
|
# 若 agent 未初始化, 手动初始化(需要 BASE_DIR)
|
||
|
|
from pathlib import Path as _P
|
||
|
|
_workflow = XingbangWorkflow(
|
||
|
|
llm=None,
|
||
|
|
base_dir=_P(__file__).resolve().parent.parent,
|
||
|
|
evolution_enabled=True,
|
||
|
|
)
|
||
|
|
raise RuntimeError("Agent 未初始化, 请先启动后端(会自动初始化 agent_manager)")
|
||
|
|
_workflow = XingbangWorkflow(
|
||
|
|
llm=agent_manager._llm,
|
||
|
|
base_dir=Path(__file__).resolve().parent.parent,
|
||
|
|
evolution_enabled=True,
|
||
|
|
)
|
||
|
|
return _workflow
|
||
|
|
|
||
|
|
|
||
|
|
class WorkflowRequest(BaseModel):
|
||
|
|
task: str # 客户/项目描述
|
||
|
|
customer: str = "" # 客户名(用于轨迹标注)
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/config")
|
||
|
|
async def get_config():
|
||
|
|
"""返回兴邦项目配置(数据源、五智能体、档位)。"""
|
||
|
|
return XINGBANG_CONFIG
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/workflow")
|
||
|
|
async def run_workflow(req: WorkflowRequest):
|
||
|
|
"""运行完整五智能体审批流水线。"""
|
||
|
|
wf = _get_workflow()
|
||
|
|
try:
|
||
|
|
result = await wf.run(req.task, customer=req.customer)
|
||
|
|
return result
|
||
|
|
except RuntimeError as e:
|
||
|
|
from fastapi import HTTPException
|
||
|
|
raise HTTPException(status_code=500, detail=str(e))
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/workflow/stream")
|
||
|
|
async def run_workflow_stream(req: WorkflowRequest):
|
||
|
|
"""流式运行(SSE), 逐步展示五智能体进度与产出。"""
|
||
|
|
wf = _get_workflow()
|
||
|
|
|
||
|
|
async def gen():
|
||
|
|
async for event in wf.run_stream(req.task, customer=req.customer):
|
||
|
|
yield {
|
||
|
|
"event": event.get("event", "event"),
|
||
|
|
"data": json.dumps(
|
||
|
|
{k: v for k, v in event.items() if k != "event"},
|
||
|
|
ensure_ascii=False,
|
||
|
|
),
|
||
|
|
}
|
||
|
|
|
||
|
|
return EventSourceResponse(gen())
|
||
|
|
|
||
|
|
|
||
|
|
class FeedbackRequest(BaseModel):
|
||
|
|
trajectory_id: str
|
||
|
|
feedback: str # "positive" | "negative"
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/evolution/feedback")
|
||
|
|
async def submit_feedback(req: FeedbackRequest):
|
||
|
|
"""提交用户反馈(Level 2 奖励信号), 追加进轨迹。"""
|
||
|
|
try:
|
||
|
|
traj_file = (
|
||
|
|
Path(__file__).resolve().parent.parent
|
||
|
|
/ "xingbang_evolution" / "trajectories" / f"{req.trajectory_id}.json"
|
||
|
|
)
|
||
|
|
if not traj_file.exists():
|
||
|
|
return {"error": f"轨迹不存在: {req.trajectory_id}"}
|
||
|
|
data = json.loads(traj_file.read_text(encoding="utf-8"))
|
||
|
|
data.setdefault("user_feedback", req.feedback)
|
||
|
|
data["user_feedback"] = req.feedback
|
||
|
|
traj_file.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||
|
|
return {"status": "ok", "trajectory_id": req.trajectory_id, "feedback": req.feedback}
|
||
|
|
except Exception as e:
|
||
|
|
return {"error": str(e)}
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/evolution")
|
||
|
|
async def get_evolution():
|
||
|
|
"""查看自进化洞察报告。"""
|
||
|
|
wf = _get_workflow()
|
||
|
|
report = wf.evolution.generate_insights()
|
||
|
|
return {"insights": report, "config": XINGBANG_CONFIG}
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/trajectories")
|
||
|
|
async def list_trajectories():
|
||
|
|
"""列出所有已保存轨迹。"""
|
||
|
|
wf = _get_workflow()
|
||
|
|
paths = wf.recorder.list_trajectories()
|
||
|
|
return {"count": len(paths),
|
||
|
|
"trajectories": [p.stem for p in paths]}
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/trajectories/{trajectory_id}")
|
||
|
|
async def get_trajectory(trajectory_id: str):
|
||
|
|
"""读取单条轨迹详情。"""
|
||
|
|
wf = _get_workflow()
|
||
|
|
traj = wf.recorder.load(trajectory_id)
|
||
|
|
if not traj:
|
||
|
|
from fastapi import HTTPException
|
||
|
|
raise HTTPException(status_code=404, detail="轨迹不存在")
|
||
|
|
return traj
|