Files
mini-openclaw/backend/xingbang/agents.py
T

163 lines
6.4 KiB
Python
Raw Normal View History

"""五智能体定义 — 复用 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
}