Files
mini-openclaw/backend/xingbang/agents.py
T
NathanAlex7 c4ac5aa82e 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, 不含隐私

遵循兴邦三档分级审批制度, 数据源指向兴邦实验数据包
2026-09-01 16:58:44 +08:00

163 lines
6.4 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.
"""五智能体定义 — 复用 mini-OpenClaw 的 LLM 与工具,各自带差异化 system prompt。
每个智能体 = 一个独立的 LangChain create_agent:
- 共享底层 LLM(DeepSeek) 与读文件工具
- 差异化的 system prompt(角色、规则、输出约定)
- 专用工具: read_xingbang_data(读兴邦数据包)、read_output(读上游产出)
- 属于同一个全局 AgentManager 编排, 但各 agent 独立 invoke
轨迹采集: 每个 agent 的每次调用(输入/system/prompt/工具序列/输出)都会被
evidence 回调记录, 供 evolution 模块使用。
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any, Callable
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.tools import tool
from config import get_rag_mode
from graph.prompt_builder import build_system_prompt
from xingbang.config import agent_specs, XINGBANG_DATA_DIR, DATA_FILES
# ── 专用工具:读取兴邦多源数据包 ────────────────────────
@tool
def read_xingbang_data(filename: str) -> str:
"""读取兴邦金租客户的多源数据文件(JSON/CSV/MD/TXT),返回内容。
可用文件名(需带编号前缀): 01_客户申请信息.json, 02_信用评分卡.json,
03_风险评级报告.md, 04_授信额度测算.csv, 05_征信记录.json,
06_司法执行记录.json, 07_舆情风险评分.json, 08_反欺诈模型输出.json,
09_财务预警模型.csv, 10_财务分析报告.csv, 11_租赁物估值报告.md,
12_行业调研报告.md, 13_历史审批案例库.json, 14_公司制度要点.md,
15_银行流水扫描件.txt
"""
if not XINGBANG_DATA_DIR.exists():
return f"[错误] 兴邦数据目录不存在: {XINGBANG_DATA_DIR}"
path = XINGBANG_DATA_DIR / filename
if not path.exists():
return f"[错误] 文件不存在: {filename}"
content = path.read_text(encoding="utf-8", errors="ignore")
# 限制长度避免超上下文
if len(content) > 4000:
content = content[:4000] + "\n...[已截断]"
return content
@tool
def read_output(filename: str, base_dir: str = "") -> str:
"""读取当前兴邦工作区中上游智能体产出的 Markdown 文件(如
customer_intel.md, due_diligence.md, case_reference.md)。"""
file_path = Path(base_dir) / filename if base_dir else Path(filename)
if not file_path.exists():
return f"[错误] 上游产出文件不存在: {filename}"
content = file_path.read_text(encoding="utf-8", errors="ignore")
if len(content) > 8000:
content = content[:8000] + "\n...[已截断]"
return content
# ── 单个评审智能体 ─────────────────────────────────────
class XingbangAgent:
"""一个兴邦评审智能体: 差异化角色 + 复用 LLM + 工具。"""
def __init__(
self,
spec,
llm=None,
base_dir: Path | None = None,
output_dir: Path | None = None,
on_evidence: Callable[[str, dict], None] | None = None,
):
self.spec = spec
self._llm = llm
self._base_dir = base_dir
self._output_dir = output_dir or (base_dir / "xingbang_output" if base_dir else None)
self._on_evidence = on_evidence
self._agent = None
def _ensure_agent(self):
"""构建带专属 system prompt 的独立 agent(惰性)。"""
if self._agent is not None:
return self._agent
tools = [read_xingbang_data]
tools.append(read_output)
prompt = self.spec.system_prompt
# 追加读文件工具说明(需要 base_dir 才能读产出)
if self._base_dir:
prompt += (
f"\n\n[工作目录] 上游产出文件位于 {self._base_dir}。"
"读取上游产出时使用 read_output 工具并把 filename 设为产出文件名。"
)
self._agent = create_agent(model=self._llm, tools=tools, system_prompt=prompt)
return self._agent
def _emit(self, kind: str, data: dict):
if self._on_evidence:
self._on_evidence(kind, data)
async def run(self, task: str, context: str = "") -> dict:
"""执行该智能体任务,返回 {'output': 结果文本, 'file': 保存路径}。"""
self._emit("agent_start", {"agent": self.spec.key, "name": self.spec.name})
agent = self._ensure_agent()
messages = []
if context:
messages.append(HumanMessage(content=f"[上游上下文]\n{context}"))
messages.append(HumanMessage(content=task))
self._emit("prompt", {
"agent": self.spec.key,
"system": self.spec.system_prompt[:500],
"task": task[:300],
})
result = await agent.ainvoke({"messages": messages})
final = self._extract_text(result)
# 保存产出到文件
file_path = None
if self._output_dir:
self._output_dir.mkdir(parents=True, exist_ok=True)
file_path = self._output_dir / self.spec.output_file
file_path.write_text(final, encoding="utf-8")
self._emit("agent_end", {
"agent": self.spec.key,
"output_file": str(file_path) if file_path else None,
"output_preview": final[:300],
})
return {"output": final, "file": file_path}
@staticmethod
def _extract_text(result: dict) -> str:
messages = result.get("messages", [])
text_parts = []
for m in messages:
if hasattr(m, "content") and m.type == "ai" and m.content:
text_parts.append(m.content)
if text_parts:
return "\n\n".join(text_parts).strip()
content = result.get("output", "")
return content if content else "[智能体未生成有效输出]"
def build_agents(
llm,
base_dir: Path,
output_dir: Path | None = None,
on_evidence: Callable[[str, dict], None] | None = None,
keys: list[str] | None = None,
) -> dict[str, XingbangAgent]:
"""按流水线顺序构建五智能体实例。"""
specs = agent_specs()
order = keys or ["data_curator", "risk_scout", "case_finder",
"approval_advisor", "risk_monitor"]
return {
key: XingbangAgent(
spec=specs[key], llm=llm,
base_dir=base_dir, output_dir=output_dir, on_evidence=on_evidence,
)
for key in order
}