feat(xingbang): 兴邦金租 AI 评审平台模块(构建于 mini-OpenClaw)
新增兴邦金租评审项目模块, 作为 mini-OpenClaw 之上的完整交付: - 五智能体流水线(data_curator/risk_scout/case_finder/approval_advisor/risk_monitor) - evolution 自进化子系统(轨迹采集+奖励计算+模式归纳prompt增强) - /api/xingbang/* 端点(config/workflow/stream/evolution/trajectories) - 运行时产物(客户产出情报卡 + 进化轨迹数据)已 gitignore, 不含隐私 遵循兴邦三档分级审批制度, 数据源指向兴邦实验数据包
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user