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,229 @@
|
||||
"""XingbangWorkflow — 五智能体审批流水线 + 自进化闭环。
|
||||
|
||||
执行流程:
|
||||
data_curator → risk_scout → case_finder → approval_advisor → risk_monitor
|
||||
(case_finder 与 risk_scout 可并行, 此处先串行保证可追溯)
|
||||
|
||||
每个 agent 的产出写入输出目录, 供下游读取; 同时全程记录
|
||||
轨迹到 evolution 模块, 跑完后计算奖励并更新进化洞察。
|
||||
|
||||
API 风格: run() 返回完整结果; 也可用 run_stream() 逐步 yield 进度。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any, AsyncGenerator
|
||||
|
||||
from xingbang.config import agent_specs
|
||||
from xingbang.agents import build_agents, XingbangAgent
|
||||
from xingbang.evolution.trajectory import TrajectoryRecorder
|
||||
from xingbang.evolution.reward import compute_reward_v1
|
||||
from xingbang.evolution.optimizer import EvolutionEngine
|
||||
|
||||
|
||||
class XingbangWorkflow:
|
||||
"""兴邦五智能体审批工作流。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm,
|
||||
base_dir: Path,
|
||||
output_dir: Path | None = None,
|
||||
evolution_enabled: bool = True,
|
||||
):
|
||||
self._base_dir = Path(base_dir)
|
||||
self._output_dir = output_dir or (self._base_dir / "xingbang_output")
|
||||
self._output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 自进化模块
|
||||
self.evolution_enabled = evolution_enabled
|
||||
self._recorder = TrajectoryRecorder(self._base_dir, enabled=evolution_enabled)
|
||||
self._evolution = EvolutionEngine(self._base_dir, self._recorder, enabled=evolution_enabled)
|
||||
|
||||
# 五智能体(注入证据回调 → 轨迹)
|
||||
self._agents: dict[str, XingbangAgent] = build_agents(
|
||||
llm=llm,
|
||||
base_dir=self._output_dir,
|
||||
output_dir=self._output_dir,
|
||||
on_evidence=self._on_evidence,
|
||||
)
|
||||
|
||||
# 流水线顺序
|
||||
self._pipeline = ["data_curator", "risk_scout", "case_finder",
|
||||
"approval_advisor", "risk_monitor"]
|
||||
self._results: dict[str, dict] = {}
|
||||
|
||||
def _on_evidence(self, kind: str, data: dict):
|
||||
# 轨迹采集(保持简单: 在 workflow 层显式 add_step)
|
||||
pass
|
||||
|
||||
@property
|
||||
def recorder(self) -> TrajectoryRecorder:
|
||||
return self._recorder
|
||||
|
||||
@property
|
||||
def evolution(self) -> EvolutionEngine:
|
||||
return self._evolution
|
||||
|
||||
# ── 单次评审 ────────────────────────────────────────
|
||||
async def run(self, task: str, customer: str = "") -> dict[str, Any]:
|
||||
"""运行完整五智能体审批流水线,返回各步产出 + 轨迹 + 奖励。"""
|
||||
workflow_id = f"wf_{int(time.time())}_{uuid.uuid4().hex[:4]}"
|
||||
traj_id = self._recorder.begin(workflow_id=workflow_id, customer=customer)
|
||||
|
||||
# 第一步任务描述
|
||||
tasks = self._build_tasks(task)
|
||||
final_output = ""
|
||||
summary_parts = []
|
||||
|
||||
for key in self._pipeline:
|
||||
agent = self._agents[key]
|
||||
agent_task = tasks.get(key, task)
|
||||
|
||||
# 收集上游上下文(该 agent 依赖的产出)
|
||||
ctx = self._collect_context(key, agent_task)
|
||||
|
||||
t0 = time.time()
|
||||
try:
|
||||
result = await agent.run(agent_task, context=ctx)
|
||||
success = True
|
||||
output = result["output"]
|
||||
file = result.get("file")
|
||||
except Exception as e: # noqa
|
||||
success = False
|
||||
output = f"[执行失败] {e}"
|
||||
file = None
|
||||
|
||||
elapsed_ms = int((time.time() - t0) * 1000)
|
||||
|
||||
# 记录这一步到轨迹
|
||||
step = {
|
||||
"agent": key,
|
||||
"name": agent.spec.name,
|
||||
"input": agent_task[:300],
|
||||
"tools": ["read_xingbang_data", "read_output"],
|
||||
"output_file": str(file) if file else None,
|
||||
"output_preview": output[:200],
|
||||
"success": success,
|
||||
"time_ms": elapsed_ms,
|
||||
}
|
||||
self._recorder.add_step(step)
|
||||
self._results[key] = {"output": output, "file": file, "success": success}
|
||||
summary_parts.append(f"[{key}] {'✅成功' if success else '❌失败'}")
|
||||
final_output = output # 最后一环(风险哨兵)作为总结论
|
||||
|
||||
# 收尾: 计算奖励 + 保存进化洞察
|
||||
trajectory = self._recorder.finalize(
|
||||
success=all(self._results[k]["success"] for k in self._pipeline),
|
||||
summary=" | ".join(summary_parts),
|
||||
)
|
||||
reward = compute_reward_v1(trajectory)
|
||||
trajectory["cumulative_reward"] = reward
|
||||
insights_path = self._evolution.save_insights()
|
||||
|
||||
return {
|
||||
"workflow_id": workflow_id,
|
||||
"trajectory_id": traj_id,
|
||||
"customer": customer,
|
||||
"reward": reward,
|
||||
"results": {k: {kk: vv for kk, vv in v.items() if kk in ("output", "file", "success")}
|
||||
for k, v in self._results.items()},
|
||||
"final_summary": summary_parts,
|
||||
"evolution_insights_file": str(insights_path),
|
||||
}
|
||||
|
||||
# ── 逐步流式(供前端展示进度) ───────────────────────
|
||||
async def run_stream(self, task: str, customer: str = "") -> AsyncGenerator[dict, None]:
|
||||
"""逐步 yield 每步进度与产出,便于前端 SSE 展示。"""
|
||||
workflow_id = f"wf_{int(time.time())}_{uuid.uuid4().hex[:4]}"
|
||||
traj_id = self._recorder.begin(workflow_id=workflow_id, customer=customer)
|
||||
tasks = self._build_tasks(task)
|
||||
|
||||
for key in self._pipeline:
|
||||
agent = self._agents[key]
|
||||
agent_task = tasks.get(key, task)
|
||||
ctx = self._collect_context(key, agent_task)
|
||||
yield {"event": "agent_start", "agent": key, "name": agent.spec.name}
|
||||
|
||||
t0 = time.time()
|
||||
try:
|
||||
result = await agent.run(agent_task, context=ctx)
|
||||
success = True
|
||||
output = result["output"]
|
||||
file = result.get("file")
|
||||
except Exception as e: # noqa
|
||||
success = False
|
||||
output = f"[执行失败] {e}"
|
||||
file = None
|
||||
elapsed_ms = int((time.time() - t0) * 1000)
|
||||
|
||||
step = {
|
||||
"agent": key, "name": agent.spec.name, "input": agent_task[:300],
|
||||
"tools": ["read_xingbang_data", "read_output"],
|
||||
"output_file": str(file) if file else None,
|
||||
"output_preview": output[:200], "success": success, "time_ms": elapsed_ms,
|
||||
}
|
||||
self._recorder.add_step(step)
|
||||
self._results[key] = {"output": output, "file": file, "success": success}
|
||||
yield {"event": "agent_end", "agent": key, "name": agent.spec.name,
|
||||
"success": success, "output": output, "file": str(file) if file else None}
|
||||
|
||||
trajectory = self._recorder.finalize(success=True)
|
||||
reward = compute_reward_v1(trajectory)
|
||||
trajectory["cumulative_reward"] = reward
|
||||
insights_path = self._evolution.save_insights()
|
||||
yield {"event": "done", "workflow_id": workflow_id,
|
||||
"trajectory_id": traj_id, "reward": reward,
|
||||
"insights_file": str(insights_path)}
|
||||
|
||||
# ── 辅助 ────────────────────────────────────────────
|
||||
def _build_tasks(self, user_task: str) -> dict[str, str]:
|
||||
"""为每个智能体构造针对性的任务描述。"""
|
||||
return {
|
||||
"data_curator": (
|
||||
f"请读取兴邦数据包中的多源数据, 为客户『{user_task}』(申请评审) "
|
||||
f"生成结构化客户情报卡, 保存到 customer_intel.md。"
|
||||
),
|
||||
"risk_scout": (
|
||||
f"基于上游 customer_intel.md, 对客户『{user_task}』做尽调分析, "
|
||||
f"生成尽调报告初稿, 保存到 due_diligence.md。"
|
||||
),
|
||||
"case_finder": (
|
||||
f"检索历史案例库与制度规则, 为『{user_task}』这类项目找出最相关案例, "
|
||||
f"生成历史案例参考, 保存到 case_reference.md。"
|
||||
),
|
||||
"approval_advisor": (
|
||||
f"汇总情报卡/尽调/历史案例, 按三档分级制度为『{user_task}』"
|
||||
f"输出审批参考意见, 保存到 approval_opinion.md。"
|
||||
),
|
||||
"risk_monitor": (
|
||||
f"审批前动态复核最新风控数据, 为『{user_task}』输出风控复核结论, "
|
||||
f"保存到 risk_recheck.md。"
|
||||
),
|
||||
}
|
||||
|
||||
def _collect_context(self, agent_key: str, task: str) -> str:
|
||||
"""收集该 agent 依赖的上游产出作为上下文。"""
|
||||
spec = agent_specs()[agent_key]
|
||||
parts = []
|
||||
for dep in spec.inputs_from:
|
||||
res = self._results.get(self._dep_owner(dep))
|
||||
if res and res.get("output"):
|
||||
parts.append(f"[{dep}]\n{res['output'][:2000]}")
|
||||
if parts:
|
||||
return "\n\n".join(parts)
|
||||
return ""
|
||||
|
||||
def _dep_owner(self, output_file: str) -> str:
|
||||
"""根据产出文件名反查产生它的 agent key。"""
|
||||
mapping = {
|
||||
"customer_intel.md": "data_curator",
|
||||
"due_diligence.md": "risk_scout",
|
||||
"case_reference.md": "case_finder",
|
||||
"approval_opinion.md": "approval_advisor",
|
||||
"risk_recheck.md": "risk_monitor",
|
||||
}
|
||||
return mapping.get(output_file, "data_curator")
|
||||
Reference in New Issue
Block a user