feat: restore risk assessment workspace and improve page scrolling

This commit is contained in:
NathanAlex7
2026-09-07 02:28:37 +08:00
parent db5d6763f1
commit f2439ee5a3
65 changed files with 13612 additions and 2649 deletions
+150 -1
View File
@@ -3,6 +3,7 @@
import json
import os
import traceback
from pathlib import Path
from typing import AsyncGenerator
from fastapi import APIRouter
@@ -80,7 +81,7 @@ async def event_generator(message: str, session_id: str) -> AsyncGenerator[dict,
segments: list[dict] = [] # finalized segments
current_segment: dict = {"content": "", "tool_calls": []}
async for event in agent_manager.astream(message, history):
async for event in agent_manager.astream(message, history, session_id):
event_type = event.get("type", "unknown")
if event_type == "retrieval":
@@ -186,3 +187,151 @@ async def chat(request: ChatRequest):
# Non-streaming fallback
result = await agent_manager.ainvoke(request.message, request.session_id)
return {"reply": result}
@router.get("/chat/raw-traces")
async def get_raw_traces(session_id: str = "default", limit: int = 20):
"""获取指定 session 的原始 LLM 调用报文
Args:
session_id: 会话 ID
limit: 返回最近 N 条记录 (默认 20)
Returns:
{"traces": [...]} 原始报文列表,按时间倒序
"""
try:
from graph.raw_trace import load_raw_traces
# 从 agent_manager 获取 raw_logs_dir
raw_logs_dir = agent_manager._raw_logs_dir
if not raw_logs_dir:
return {"traces": []}
traces = load_raw_traces(session_id, raw_logs_dir, limit=limit)
return {"traces": traces}
except Exception as e:
traceback.print_exc()
return {"traces": [], "error": str(e)}
@router.get("/chat/trajectory")
async def get_trajectory(session_id: str = "default"):
"""获取会话完整交互轨迹 - 包含完整的 human/assistant/tool 消息流
Args:
session_id: 会话 ID
Returns:
{
"session_id": "...",
"messages": [
{"role": "human", "content": "...", "timestamp": ...},
{"role": "assistant", "content": "...", "timestamp": ..., "tool_calls": [...]},
{"role": "tool", "name": "...", "content": "...", "timestamp": ...},
{"role": "assistant", "content": "...", "timestamp": ...}
],
"steps": [...] // 保留旧格式用于向后兼容
}
"""
try:
import json
from graph.raw_trace import load_raw_traces
raw_logs_dir = agent_manager._raw_logs_dir
if not raw_logs_dir:
# 无 raw_logs_dir,返回空
return {"session_id": session_id, "messages": [], "steps": []}
# 优先读取完整消息流日志文件
msgs_log_file = raw_logs_dir / f"{session_id}.msgs.jsonl"
messages = []
if msgs_log_file.exists():
# 从 msgs.jsonl 读取完整消息流(包含 tool 消息)
with open(msgs_log_file, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line:
try:
msg = json.loads(line)
messages.append(msg)
except json.JSONDecodeError:
continue
else:
# 回退到旧逻辑:从 session 文件读取(无 tool 消息)
session_messages = session_manager.load_session(session_id)
for msg in session_messages:
messages.append({
"role": msg["role"],
"content": msg["content"],
"timestamp": None,
})
# 构建旧格式 steps(向后兼容)
steps = []
traces = load_raw_traces(session_id, raw_logs_dir, limit=1000)
traces.sort(key=lambda x: x.get("timestamp_start", 0))
# 按 user/assistant 消息构建 steps(忽略 tool 消息)
session_messages = session_manager.load_session(session_id)
assistant_count = sum(1 for m in session_messages if m["role"] == "assistant")
for msg_idx, msg in enumerate(session_messages):
step = {
"role": msg["role"],
"content": msg["content"],
"timestamp": None,
}
if msg["role"] == "assistant":
llm_calls = []
assistant_seq = sum(1 for m in session_messages[:msg_idx+1] if m["role"] == "assistant")
traces_per_assistant = len(traces) / max(assistant_count, 1)
start_trace = int((assistant_seq - 1) * traces_per_assistant)
end_trace = int(assistant_seq * traces_per_assistant)
for i in range(start_trace, min(end_trace, len(traces))):
trace = traces[i]
llm_call = {
"model": trace.get("request", {}).get("model", "unknown"),
"finish_reason": trace.get("finish_reason", "unknown"),
"request_messages": trace.get("request", {}).get("messages", []),
"response_text": "",
"tool_calls_list": [],
"usage": trace.get("usage", {}),
"duration_ms": trace.get("duration_ms", 0),
}
response_gens = trace.get("response", {}).get("generations", [])
if response_gens:
llm_call["response_text"] = response_gens[0].get("text", "")
msg_data = response_gens[0].get("message", {})
if isinstance(msg_data, dict) and "tool_calls" in msg_data:
tool_calls = msg_data["tool_calls"]
if tool_calls:
llm_call["tool_calls_list"] = [
{
"name": tc.get("name", "") if isinstance(tc, dict) else getattr(tc, "name", ""),
"args": tc.get("args", {}) if isinstance(tc, dict) else getattr(tc, "args", {}),
}
for tc in tool_calls
]
llm_calls.append(llm_call)
if step["timestamp"] is None:
step["timestamp"] = trace.get("timestamp_start")
step["llm_calls"] = llm_calls
steps.append(step)
return {
"session_id": session_id,
"messages": messages, # 新格式:完整消息流(含 tool)
"steps": steps, # 旧格式:向后兼容
}
except Exception as e:
traceback.print_exc()
return {"session_id": session_id, "messages": [], "steps": [], "error": str(e)}
File diff suppressed because it is too large Load Diff
+617
View File
@@ -0,0 +1,617 @@
"""
Agent 1: 风险研判助手 API(双轨驱动版)
"""
from fastapi import APIRouter
from pydantic import BaseModel
from typing import List, Dict, Any, Optional, Union
from datetime import datetime
router = APIRouter(tags=["risk-assess"])
# ==================== 请求/响应模型 ====================
class RiskAssessRequest(BaseModel):
customer_name: str
project_type: str
amount: float
debt_ratio: float
credit_query_count: int
litigation_count: int
external_guarantee: float
# ==================== API端点 ====================
@router.post("/analyze")
async def analyze_risk(req: dict):
"""风险研判分析(双轨驱动)- 返回伪造的完整数据"""
# 直接从dict中获取数据
customer_name = req.get("customer_name", "")
project_type = req.get("project_type", "")
amount = float(req.get("amount", 0))
debt_ratio = float(req.get("debt_ratio", 0))
credit_query_count = int(req.get("credit_query_count", 0))
litigation_count = int(req.get("litigation_count", 0))
external_guarantee = float(req.get("external_guarantee", 0))
# 生成完整的伪造数据
risk_items = []
# 风险项1: 资产负债率
if req.debt_ratio >= 0.60:
risk_items.append({
"level": "high",
"title": "资产负债率接近红线",
"description": f"{req.customer_name}资产负债率为 {req.debt_ratio*100:.1f}%,距离70%红线仅剩 {(0.70-req.debt_ratio)*100:.1f}% 空间。",
"evidence": {
"rule_based": {
"source": "《融资租赁评审手册》第12条",
"page": 12,
"extracted_by": "AI自动提取(已通过人工审核)",
"confidence": 0.87
},
"data_based": {
"sample_size": 23,
"time_range": "过去12个月",
"decision_stats": [
{"decision": "降额通过", "percentage": 60.9, "count": 14, "avg_processing_days": 4.2},
{"decision": "退回补充材料", "percentage": 26.1, "count": 6, "avg_processing_days": 7.5},
{"decision": "附加条件通过", "percentage": 13.0, "count": 3, "avg_processing_days": 5.8}
],
"similar_cases": [
{
"case_id": "CAS-2025-08-156",
"customer_name": "华东光伏设备公司",
"decision": "降额通过(降15%)",
"mitigation": "增加法人个人担保,提供政府补贴承诺函"
},
{
"case_id": "CAS-2025-07-089",
"customer_name": "绿能公交运营公司",
"decision": "降额通过(降12%)",
"mitigation": "调整还款期限,追加车辆抵押"
},
{
"case_id": "CAS-2025-06-234",
"customer_name": "中部新能源投资",
"decision": "附加条件通过",
"mitigation": "要求季度现金流报告,设置财务预警指标"
}
]
}
},
"suggestion": "建议业务经理提前准备降额方案或增加抵押物,避免评审会直接退回。",
"smart_recommendation": {
"primary_action": f"主动降低授信额度12-15%(约 {req.amount * 0.12:.1f}-{req.amount * 0.15:.1f} 亿元),调整后负债率可控",
"alternative_actions": [
f"增加抵押物或第三方担保,覆盖率≥授信额度的20%(约 {req.amount * 0.20:.1f} 亿元)",
"提供详细现金流测算报告,证明偿债能力充足",
"调整还款期限,降低单期还款压力"
],
"reference_case": {
"case_id": "CAS-2025-08-156",
"customer_name": "华东光伏设备公司",
"scenario": f"债务率 {req.debt_ratio*100:.1f}%,新能源行业,申请 {req.amount:.1f} 亿授信",
"outcome": "降额15%后通过,增加法人担保 + 政府补贴承诺函"
}
}
})
# 风险项2: 征信查询
if req.credit_query_count >= 6:
risk_items.append({
"level": "medium",
"title": "征信查询次数偏多",
"description": f"{req.customer_name}近6个月征信查询 {req.credit_query_count} 次,高于行业平均(4次)。",
"evidence": {
"rule_based": {
"source": "《融资租赁评审手册》第8条 - 征信管理",
"page": 8,
"extracted_by": "AI自动提取(已通过人工审核)",
"confidence": 0.82
},
"data_based": {
"sample_size": 15,
"time_range": "过去12个月",
"decision_stats": [
{"decision": "正常通过", "percentage": 53.3, "count": 8, "avg_processing_days": 3.5},
{"decision": "要求补充说明", "percentage": 33.3, "count": 5, "avg_processing_days": 5.2},
{"decision": "降额通过", "percentage": 13.3, "count": 2, "avg_processing_days": 6.0}
],
"similar_cases": [
{
"case_id": "CAS-2025-09-012",
"customer_name": "东方物流集团",
"decision": "正常通过",
"mitigation": "提供多头融资说明(用于业务扩张),核实无实质风险"
}
]
}
},
"suggestion": "建议业务经理核实客户是否存在多头融资,了解征信查询原因(业务扩张 vs 资金紧张)。",
"smart_recommendation": {
"primary_action": "要求客户提供征信查询原因说明书,核实多头融资情况",
"alternative_actions": [
"提供资金用途说明和还款计划,证明融资用于业务扩张",
"补充近6个月银行流水,证明现金流充足"
],
"reference_case": {
"case_id": "CAS-2025-09-012",
"customer_name": "东方物流集团",
"scenario": f"征信查询 {req.credit_query_count} 次,用于扩张业务",
"outcome": "提供业务扩张说明后正常通过,核实无资金链风险"
}
}
})
# 风险项3: 涉诉记录
if req.litigation_count >= 2:
risk_items.append({
"level": "medium",
"title": "存在涉诉记录",
"description": f"{req.customer_name}存在 {req.litigation_count} 起涉诉记录,需核实诉讼性质和金额。",
"evidence": {
"rule_based": {
"source": "《融资租赁评审手册》第10条 - 法律风险评估",
"page": 10,
"extracted_by": "AI自动提取(已通过人工审核)",
"confidence": 0.85
},
"data_based": {
"sample_size": 12,
"time_range": "过去12个月",
"decision_stats": [
{"decision": "正常通过", "percentage": 58.3, "count": 7, "avg_processing_days": 4.0},
{"decision": "要求提供诉讼详情", "percentage": 33.3, "count": 4, "avg_processing_days": 6.5},
{"decision": "附加条件通过", "percentage": 8.3, "count": 1, "avg_processing_days": 8.0}
],
"similar_cases": [
{
"case_id": "CAS-2025-08-203",
"customer_name": "华泰建设工程",
"decision": "正常通过",
"mitigation": "确认为小额合同纠纷(<50万),不影响偿债能力"
}
]
}
},
"suggestion": "建议业务经理提供每起诉讼的详细情况(原告/被告、金额、进展),评估对财务的影响。",
"smart_recommendation": {
"primary_action": "提供诉讼清单(案由、金额、当前状态),说明对财务状况的影响评估",
"alternative_actions": [
"如为小额合同纠纷(<50万/起),提供说明后影响较小",
"提供法律顾问意见书,说明诉讼风险可控"
],
"reference_case": {
"case_id": "CAS-2025-08-203",
"customer_name": "华泰建设工程",
"scenario": f"{req.litigation_count} 起小额合同纠纷,总金额<100万",
"outcome": "确认为日常经营纠纷,不影响偿债能力,正常通过"
}
}
})
# 风险项4: 对外担保
guarantee_ratio = req.external_guarantee / req.amount if req.amount > 0 else 0
if guarantee_ratio >= 0.8:
risk_items.append({
"level": "high",
"title": "对外担保金额较大",
"description": f"{req.customer_name}对外担保 {req.external_guarantee:.1f} 亿元,为本次申请金额的 {guarantee_ratio:.1f} 倍。",
"evidence": {
"rule_based": {
"source": "《融资租赁评审手册》第11条 - 担保风险",
"page": 11,
"extracted_by": "AI自动提取(已通过人工审核)",
"confidence": 0.88
},
"data_based": {
"sample_size": 18,
"time_range": "过去12个月",
"decision_stats": [
{"decision": "要求提供担保详情", "percentage": 66.7, "count": 12, "avg_processing_days": 5.8},
{"decision": "降额通过", "percentage": 22.2, "count": 4, "avg_processing_days": 7.2},
{"decision": "附加条件通过", "percentage": 11.1, "count": 2, "avg_processing_days": 6.5}
],
"similar_cases": [
{
"case_id": "CAS-2025-07-234",
"customer_name": "华信集团",
"decision": "附加条件通过",
"mitigation": "提供被担保方财务报告,设置代偿预警机制"
}
]
}
},
"suggestion": "建议业务经理提供对外担保明细(被担保方、金额、期限),评估代偿风险。",
"smart_recommendation": {
"primary_action": "提供对外担保明细清单,包括被担保方资质、财务状况、代偿可能性评估",
"alternative_actions": [
"说明被担保方为关联公司/优质客户,代偿风险低",
f"考虑降低授信额度至 {req.amount * 0.8:.1f} 亿元,降低风险敞口"
],
"reference_case": {
"case_id": "CAS-2025-07-234",
"customer_name": "华信集团",
"scenario": f"对外担保为申请额度的 {guarantee_ratio:.1f} 倍",
"outcome": "提供被担保方财务报告,设置代偿预警,附加条件通过"
}
}
})
# 风险项5: 行业风险
if req.project_type == "对公新能源":
risk_items.append({
"level": "medium",
"title": "新能源行业政策依赖性",
"description": f"{req.customer_name}属于新能源行业,收入可能依赖政府补贴,需关注补贴到位情况。",
"evidence": {
"rule_based": {
"source": "《行业风险指引》第3条 - 新能源行业",
"page": 3,
"extracted_by": "AI自动提取(已通过人工审核)",
"confidence": 0.90
},
"data_based": {
"sample_size": 31,
"time_range": "过去12个月",
"decision_stats": [
{"decision": "正常通过", "percentage": 48.4, "count": 15, "avg_processing_days": 4.5},
{"decision": "要求补贴承诺函", "percentage": 38.7, "count": 12, "avg_processing_days": 6.2},
{"decision": "附加条件通过", "percentage": 12.9, "count": 4, "avg_processing_days": 7.0}
],
"similar_cases": [
{
"case_id": "CAS-2025-09-067",
"customer_name": "绿动公交集团",
"decision": "正常通过",
"mitigation": "补贴延迟但现金流充足,运营收入稳定"
}
]
}
},
"suggestion": "建议业务经理提供政府补贴承诺函、历史补贴到位情况,说明现金流不完全依赖补贴。",
"smart_recommendation": {
"primary_action": "要求提供地方政府补贴承诺函,核实历史补贴到位率(建议≥80%)",
"alternative_actions": [
"提供运营收入构成,证明补贴占比<40%,现金流多元化",
"说明补贴延迟应对预案(如银行授信、应收账款保理)"
],
"reference_case": {
"case_id": "CAS-2025-09-067",
"customer_name": "绿动公交集团",
"scenario": "新能源公交运营,补贴延迟但运营收入稳定",
"outcome": "证明运营收入占比65%,补贴延迟不影响还款,正常通过"
}
}
})
# 统计汇总
high_count = sum(1 for r in risk_items if r["level"] == "high")
medium_count = sum(1 for r in risk_items if r["level"] == "medium")
low_count = sum(1 for r in risk_items if r["level"] == "low")
# 提取关注领域
focus_areas = []
if any("资产负债率" in r["title"] for r in risk_items):
focus_areas.append("财务杠杆")
if any("征信" in r["title"] for r in risk_items):
focus_areas.append("信用状况")
if any("涉诉" in r["title"] for r in risk_items):
focus_areas.append("法律风险")
if any("担保" in r["title"] for r in risk_items):
focus_areas.append("对外担保")
if any("行业" in r["title"] or "政策" in r["title"] for r in risk_items):
focus_areas.append("行业风险")
if not focus_areas:
focus_areas = ["租赁物合规", "现场尽调"]
# 计算综合置信度
total_confidence = sum(r["evidence"]["rule_based"]["confidence"] for r in risk_items) / len(risk_items) if risk_items else 0.95
# 计算数据覆盖
total_samples = sum(r["evidence"]["data_based"]["sample_size"] for r in risk_items)
data_coverage = f"{total_samples} 个案例"
return {
"customer_name": req.customer_name,
"analysis_timestamp": datetime.now().isoformat(),
"risk_items": risk_items,
"summary": {
"high_count": high_count,
"medium_count": medium_count,
"low_count": low_count,
"focus_areas": focus_areas,
"overall_confidence": round(total_confidence, 2),
"data_coverage": data_coverage
}
}
@router.get("/test")
async def test_endpoint():
"""测试端点 - 返回完整的伪造数据"""
return {
"customer_name": "远东新能源公交公司",
"analysis_timestamp": datetime.now().isoformat(),
"risk_items": [
{
"level": "high",
"title": "资产负债率接近红线",
"description": "远东新能源公交公司资产负债率为 65.0%,距离70%红线仅剩 5.0% 空间。",
"evidence": {
"rule_based": {
"source": "《融资租赁评审手册》第12条",
"page": 12,
"extracted_by": "AI自动提取(已通过人工审核)",
"confidence": 0.87
},
"data_based": {
"sample_size": 23,
"time_range": "过去12个月",
"decision_stats": [
{"decision": "降额通过", "percentage": 60.9, "count": 14, "avg_processing_days": 4.2},
{"decision": "退回补充材料", "percentage": 26.1, "count": 6, "avg_processing_days": 7.5},
{"decision": "附加条件通过", "percentage": 13.0, "count": 3, "avg_processing_days": 5.8}
],
"similar_cases": [
{
"case_id": "CAS-2025-08-156",
"customer_name": "华东光伏设备公司",
"decision": "降额通过(降15%)",
"mitigation": "增加法人个人担保,提供政府补贴承诺函"
},
{
"case_id": "CAS-2025-07-089",
"customer_name": "绿能公交运营公司",
"decision": "降额通过(降12%)",
"mitigation": "调整还款期限,追加车辆抵押"
},
{
"case_id": "CAS-2025-06-234",
"customer_name": "中部新能源投资",
"decision": "附加条件通过",
"mitigation": "要求季度现金流报告,设置财务预警指标"
}
]
}
},
"suggestion": "建议业务经理提前准备降额方案或增加抵押物,避免评审会直接退回。",
"smart_recommendation": {
"primary_action": "主动降低授信额度12-15%(约 0.36-0.45 亿元),调整后负债率可控",
"alternative_actions": [
"增加抵押物或第三方担保,覆盖率≥授信额度的20%(约 0.60 亿元)",
"提供详细现金流测算报告,证明偿债能力充足",
"调整还款期限,降低单期还款压力"
],
"reference_case": {
"case_id": "CAS-2025-08-156",
"customer_name": "华东光伏设备公司",
"scenario": "债务率 65.0%,新能源行业,申请 3.0 亿授信",
"outcome": "降额15%后通过,增加法人担保 + 政府补贴承诺函"
}
}
},
{
"level": "medium",
"title": "征信查询次数偏多",
"description": "远东新能源公交公司近6个月征信查询 8 次,高于行业平均(4次)。",
"evidence": {
"rule_based": {
"source": "《融资租赁评审手册》第8条 - 征信管理",
"page": 8,
"extracted_by": "AI自动提取(已通过人工审核)",
"confidence": 0.82
},
"data_based": {
"sample_size": 15,
"time_range": "过去12个月",
"decision_stats": [
{"decision": "正常通过", "percentage": 53.3, "count": 8, "avg_processing_days": 3.5},
{"decision": "要求补充说明", "percentage": 33.3, "count": 5, "avg_processing_days": 5.2},
{"decision": "降额通过", "percentage": 13.3, "count": 2, "avg_processing_days": 6.0}
],
"similar_cases": [
{
"case_id": "CAS-2025-09-012",
"customer_name": "东方物流集团",
"decision": "正常通过",
"mitigation": "提供多头融资说明(用于业务扩张),核实无实质风险"
},
{
"case_id": "CAS-2025-07-145",
"customer_name": "鑫盛商贸公司",
"decision": "要求补充说明",
"mitigation": "说明征信查询原因,提供现金流充足证明"
}
]
}
},
"suggestion": "建议业务经理核实客户是否存在多头融资,了解征信查询原因(业务扩张 vs 资金紧张)。",
"smart_recommendation": {
"primary_action": "要求客户提供征信查询原因说明书,核实多头融资情况",
"alternative_actions": [
"提供资金用途说明和还款计划,证明融资用于业务扩张",
"补充近6个月银行流水,证明现金流充足",
"如为临时资金需求,提供订单合同等业务凭证"
],
"reference_case": {
"case_id": "CAS-2025-09-012",
"customer_name": "东方物流集团",
"scenario": "征信查询 8 次,用于扩张业务",
"outcome": "提供业务扩张说明后正常通过,核实无资金链风险"
}
}
},
{
"level": "medium",
"title": "存在涉诉记录",
"description": "远东新能源公交公司存在 2 起涉诉记录,需核实诉讼性质和金额。",
"evidence": {
"rule_based": {
"source": "《融资租赁评审手册》第10条 - 法律风险评估",
"page": 10,
"extracted_by": "AI自动提取(已通过人工审核)",
"confidence": 0.85
},
"data_based": {
"sample_size": 12,
"time_range": "过去12个月",
"decision_stats": [
{"decision": "正常通过", "percentage": 58.3, "count": 7, "avg_processing_days": 4.0},
{"decision": "要求提供诉讼详情", "percentage": 33.3, "count": 4, "avg_processing_days": 6.5},
{"decision": "附加条件通过", "percentage": 8.3, "count": 1, "avg_processing_days": 8.0}
],
"similar_cases": [
{
"case_id": "CAS-2025-08-203",
"customer_name": "华泰建设工程",
"decision": "正常通过",
"mitigation": "确认为小额合同纠纷(<50万),不影响偿债能力"
},
{
"case_id": "CAS-2025-06-178",
"customer_name": "顺丰供应链公司",
"decision": "要求提供诉讼详情",
"mitigation": "提供案件进展说明,评估财务影响<3%"
}
]
}
},
"suggestion": "建议业务经理提供每起诉讼的详细情况(原告/被告、金额、进展),评估对财务的影响。",
"smart_recommendation": {
"primary_action": "提供诉讼清单(案由、金额、当前状态),说明对财务状况的影响评估",
"alternative_actions": [
"如为小额合同纠纷(<50万/起),提供说明后影响较小",
"如为债务纠纷,需评估败诉风险和财务影响",
"提供法律顾问意见书,说明诉讼风险可控"
],
"reference_case": {
"case_id": "CAS-2025-08-203",
"customer_name": "华泰建设工程",
"scenario": "2 起小额合同纠纷,总金额<100万",
"outcome": "确认为日常经营纠纷,不影响偿债能力,正常通过"
}
}
},
{
"level": "high",
"title": "对外担保金额较大",
"description": "远东新能源公交公司对外担保 3.5 亿元,为本次申请金额的 1.2 倍。",
"evidence": {
"rule_based": {
"source": "《融资租赁评审手册》第11条 - 担保风险",
"page": 11,
"extracted_by": "AI自动提取(已通过人工审核)",
"confidence": 0.88
},
"data_based": {
"sample_size": 18,
"time_range": "过去12个月",
"decision_stats": [
{"decision": "要求提供担保详情", "percentage": 66.7, "count": 12, "avg_processing_days": 5.8},
{"decision": "降额通过", "percentage": 22.2, "count": 4, "avg_processing_days": 7.2},
{"decision": "附加条件通过", "percentage": 11.1, "count": 2, "avg_processing_days": 6.5}
],
"similar_cases": [
{
"case_id": "CAS-2025-07-234",
"customer_name": "华信集团",
"decision": "附加条件通过",
"mitigation": "提供被担保方财务报告,设置代偿预警机制"
},
{
"case_id": "CAS-2025-09-045",
"customer_name": "宏达投资公司",
"decision": "降额通过(降20%)",
"mitigation": "降低授信以控制担保风险敞口"
}
]
}
},
"suggestion": "建议业务经理提供对外担保明细(被担保方、金额、期限),评估代偿风险。",
"smart_recommendation": {
"primary_action": "提供对外担保明细清单,包括被担保方资质、财务状况、代偿可能性评估",
"alternative_actions": [
"说明被担保方为关联公司/优质客户,代偿风险低",
"提供反担保措施说明(如抵押物、股权质押)",
"考虑降低授信额度至 2.4 亿元,降低风险敞口"
],
"reference_case": {
"case_id": "CAS-2025-07-234",
"customer_name": "华信集团",
"scenario": "对外担保为申请额度的 1.2 倍",
"outcome": "提供被担保方财务报告,设置代偿预警,附加条件通过"
}
}
},
{
"level": "medium",
"title": "新能源行业政策依赖性",
"description": "远东新能源公交公司属于新能源行业,收入可能依赖政府补贴,需关注补贴到位情况。",
"evidence": {
"rule_based": {
"source": "《行业风险指引》第3条 - 新能源行业",
"page": 3,
"extracted_by": "AI自动提取(已通过人工审核)",
"confidence": 0.90
},
"data_based": {
"sample_size": 31,
"time_range": "过去12个月",
"decision_stats": [
{"decision": "正常通过", "percentage": 48.4, "count": 15, "avg_processing_days": 4.5},
{"decision": "要求补贴承诺函", "percentage": 38.7, "count": 12, "avg_processing_days": 6.2},
{"decision": "附加条件通过", "percentage": 12.9, "count": 4, "avg_processing_days": 7.0}
],
"similar_cases": [
{
"case_id": "CAS-2025-08-112",
"customer_name": "晨光新能源科技",
"decision": "要求补贴承诺函",
"mitigation": "地方政府出具补贴承诺函,补贴到位率>85%"
},
{
"case_id": "CAS-2025-09-067",
"customer_name": "绿动公交集团",
"decision": "正常通过",
"mitigation": "补贴延迟但现金流充足,运营收入稳定"
}
]
}
},
"suggestion": "建议业务经理提供政府补贴承诺函、历史补贴到位情况,说明现金流不完全依赖补贴。",
"smart_recommendation": {
"primary_action": "要求提供地方政府补贴承诺函,核实历史补贴到位率(建议≥80%)",
"alternative_actions": [
"提供运营收入构成,证明补贴占比<40%,现金流多元化",
"说明补贴延迟应对预案(如银行授信、应收账款保理)",
"提供同行业可比公司补贴情况,证明行业普遍性"
],
"reference_case": {
"case_id": "CAS-2025-09-067",
"customer_name": "绿动公交集团",
"scenario": "新能源公交运营,补贴延迟但运营收入稳定",
"outcome": "证明运营收入占比65%,补贴延迟不影响还款,正常通过"
}
}
}
],
"summary": {
"high_count": 2,
"medium_count": 3,
"low_count": 0,
"focus_areas": ["财务杠杆", "信用状况", "法律风险", "对外担保", "行业风险"],
"overall_confidence": 0.86,
"data_coverage": "99 个案例"
}
}
@router.get("/health")
async def health_check():
return {"status": "ok", "service": "风险研判助手(双轨驱动版)"}
-145
View File
@@ -1,145 +0,0 @@
"""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
+372
View File
@@ -0,0 +1,372 @@
"""
兴邦金租智能体系统 API 路由
基于2026-08-18会议纪要真实痛点设计
"""
from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from typing import Optional, Dict, Any
import json
import asyncio
from pathlib import Path
from graph.agent import agent_manager
router = APIRouter(prefix="/api/xingbang", tags=["xingbang"])
async def _xingbang_llm(user_prompt: str, system_prompt: str) -> str:
"""调用 mini-OpenClaw 核心已初始化的 DeepSeek LLM,构建带差异化 system prompt 的临时 agent。
兼容核心 AgentManager:只复用其 LLM,不依赖不存在的 get_response 等方法。
启用后可替换为本地模型(Qwen)以支持"数据不出域"。
"""
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage
if agent_manager._llm is None:
from graph.agent import agent_manager as _am
_am.initialize(Path(__file__).resolve().parent.parent)
agent = create_agent(
model=agent_manager._llm,
tools=[], # 兴邦各 Agent 为纯分析角色,暂不绑定核心工具
system_prompt=system_prompt,
)
try:
result = await agent.ainvoke({"messages": [HumanMessage(content=user_prompt)]})
msgs = result.get("messages", [])
for m in reversed(msgs):
if hasattr(m, "content") and m.type == "ai" and m.content:
return m.content
return str(result.get("output", ""))
except Exception as e: # noqa
return f"[Agent 调用失败] {e}"
# ==================== 请求/响应模型 ====================
class WorkflowRequest(BaseModel):
"""完整五Agent流水线请求"""
customer_name: str
project_type: str = "对公新能源" # 对公新能源/零售/其他
amount: float # 申请金额(亿元)
class AgentRequest(BaseModel):
"""单个Agent执行请求"""
agent_type: str # data_gather / risk_screen / case_search / approval_advice / risk_monitor
input_data: Dict[str, Any]
# ==================== 五大Agent System Prompts ====================
AGENT_PROMPTS = {
"data_gather": """你是【数据汇聚Agent】,负责融资租赁项目的多源数据整理与情报卡生成。
**职责:**
1. 从多源异构数据(财报PDF、工商信息、征信、中诚信评级模板、租赁物估值等)整理生成统一的"客户情报卡"
2. 进行数据校验:交叉核对工商信息、财报数据、征信记录的一致性
3. 标注风险标签:现金流压力、关联交易、对外担保、涉诉情况
4. 标明"人工待核实项":数据缺失、矛盾、需现场尽调确认的项
**输出格式(Markdown):**
# 客户情报卡 - {客户名称}
## 基本信息
- 注册资本、成立时间、法人、股东结构
- 主营业务、所属行业
## 财务概况
- 近三年营收/净利润/现金流
- 资产负债率、流动比率
- 数据来源:{财报年份}
## 征信情况
- 对外担保金额、涉诉记录
- 信用评级(中诚信模板结果)
## 租赁物信息
- 租赁物类型、估值、覆盖率
## 风险标签
- 🔴 高风险:{具体事项}
- 🟡 中风险:{具体事项}
## 人工待核实项
- [ ] {事项1}
- [ ] {事项2}
**原则:只汇聚和标注,不做结论性判断。**
""",
"risk_screen": """你是【预审参谋Agent】,负责生成风险筛查清单。
**核心原则(对齐会议要求):**
- **给风险点,不给结论**:列出所有潜在风险项及制度依据,但不给"通过/不通过"的结论性建议
- **降低业务评审对立**:前置风险筛查,让业务部门提前知晓评审会关注的点
- **标注置信度**:每个风险点标注置信度(高/中/低),基于数据完整性和历史经验
**职责:**
1. 基于"客户情报卡",按公司评审手册逐项筛查风险点
2. 引用制度条款和历史案例作为依据
3. 提出"建议核实方向"(不是建议结论)
**输出格式(Markdown):**
# 风险筛查清单 - {客户名称}
## 风险点1:{风险事项}
- **置信度**:⚠️ 高
- **制度依据**:《评审手册》第X条 - {具体条款}
- **现象描述**:{客户情报卡中的具体数据/情况}
- **建议核实方向**:需现场核实{具体方向},或要求客户补充{资料}
## 风险点2:...
## 汇总
- 高风险项:{数量}
- 中风险项:{数量}
- 建议重点关注领域:{现金流/担保/租赁物合规/...}
**不要输出"建议通过/不通过"等结论性意见。**
""",
"case_search": """你是【经验检索Agent】,负责从历史案例库中检索相似案例。
**职责:**
1. 基于客户行业、规模、风险特征检索历史案例(成功+失败)
2. 提取案例的关键决策依据和风险缓释措施
3. 帮助"拉平评审标准差异":展示同类风险在历史上的不同评审员如何判断
**输出格式(Markdown):**
# 相似案例检索 - {客户名称}
## 案例1:{历史客户名称}({通过/被否})
- **相似度**:85%
- **项目概况**:{行业、金额、时间}
- **关键风险点**:{当时识别的风险}
- **评审决策**:{通过/被否/附加条件}
- **后续表现**:{是否逾期、结清情况}
- **经验提示**:{对当前项目的参考价值}
## 案例2:...
## 经验总结
- 同行业历史不良率:{%}
- 关键风控措施:{担保增信/租赁物覆盖率/定期检查}
**目标:让评审员看到"过去怎么做的",作为当前决策参考。**
""",
"approval_advice": """你是【审批意见Agent】,负责生成标准化审批参考意见。
**职责:**
1. 汇总前三个Agent(数据汇聚、风险筛查、经验检索)的产出
2. 生成标准化审批参考文档,包含:建议结论、证据引用、风险缓释建议
3. **明确标注"AI参考意见,最终决策权在评审员"**
**输出格式(Markdown):**
# 审批参考意见 - {客户名称}
> ⚠️ **重要提示**:本意见由AI生成,仅供评审员参考,最终决策权在评审员及审批委员会。评审员可一票否决。
## 一、项目概要
- 客户:{名称}
- 申请金额:{金额}亿元
- 租赁物:{类型}
- 期限:{年限}
## 二、综合评估
- **数据完整性**:✅ 完整 / ⚠️ 部分缺失
- **主要风险**:{列出Top 3风险点及来源}
- **历史案例参考**:{相似案例结论}
## 三、AI参考建议
- **倾向性意见**:🟢 建议通过 / 🟡 建议附加条件通过 / 🔴 建议审慎评估
- **建议条件**(如通过):
1. {风险缓释措施1,如追加担保}
2. {风险缓释措施2,如提高租赁物覆盖率}
- **重点关注事项**:{评审员需额外核实的点}
## 四、证据链
- 数据来源:{财报/征信/工商/中诚信}
- 风险依据:{制度条款引用}
- 历史案例:{案例编号}
**评审员有权基于独立判断修改或否决本意见。**
""",
"risk_monitor": """你是【风险监控Agent】,负责审批空窗期与租后动态监控。
**职责:**
1. 审批空窗期(立项→上会,约17工作日):动态监控客户征信、涉诉、舆情变化
2. 租后阶段:定期检查还款记录、保险覆盖、租赁物状态
3. 预警触发:发现风险信号时立即推送给评审员/租后管理岗
**输出格式(Markdown):**
# 风险监控报告 - {客户名称}
## 监控周期
- 起始:{立项日期}
- 当前:{今日日期}
- 距上会:{剩余天数}工作日
## 动态风险扫描
### 征信变化
- ✅ 无新增逾期记录
- 🔴 新增涉诉:{案件概要} [需立即关注]
### 舆情监控
- 🟡 负面舆情:{媒体报道/社交媒体}
### 保险覆盖(租后)
- 保单状态:✅ 有效 / ⚠️ 即将到期
- 覆盖率:{保额/租赁物价值} = {%}
- 条款检查:{是否足额覆盖风险,如泡水、盗抢等}
## 预警触发
- 🚨 高优先级:{需立即处理的风险}
- ⚠️ 中优先级:{需关注的异常}
## 建议行动
- [ ] 要求客户说明{具体事项}
- [ ] 现场核查{租赁物/经营状况}
- [ ] 补充{担保/保险}
**对齐会议痛点:"17天动态风险无预警" → 现在有了实时监控。**
"""
}
# ==================== 单Agent执行 ====================
async def execute_single_agent(agent_type: str, input_data: Dict[str, Any]) -> Dict[str, Any]:
"""执行单个Agent"""
if agent_type not in AGENT_PROMPTS:
raise HTTPException(400, f"Unknown agent type: {agent_type}")
system_prompt = AGENT_PROMPTS[agent_type]
user_prompt = f"""请基于以下输入数据执行任务:
{json.dumps(input_data, ensure_ascii=False, indent=2)}
严格按照你的角色职责和输出格式生成报告。"""
# 调用核心Agent(复用 mini-OpenClaw 的 agent_manager 及其 LLM)
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
# 复用 mini-OpenClaw 核心已初始化的 DeepSeek LLM,构建带差异化 system prompt 的临时 agent
response = await _xingbang_llm(user_prompt, system_prompt)
return {
"agent_type": agent_type,
"output": response,
"status": "completed"
}
# ==================== 完整流水线(SSE流式) ====================
async def run_workflow_stream(customer_name: str, project_type: str, amount: float):
"""五Agent流水线 SSE流式执行"""
# 模拟数据包(实际应从数据库/文件读取)
mock_data = {
"customer_name": customer_name,
"project_type": project_type,
"amount": amount,
"financial_data": {
"revenue_2025": 15.2,
"revenue_2024": 13.8,
"revenue_2023": 12.1,
"net_profit_2025": 1.8,
"cash_flow_2025": 2.3,
"debt_ratio": 0.62
},
"credit_info": {
"external_guarantee": 3.5,
"litigation_count": 2,
"credit_rating": "AA-"
},
"lease_asset": {
"type": "新能源公交车50台",
"valuation": 4.2,
"coverage_ratio": 1.4
}
}
# Agent执行顺序
agents = [
("data_gather", "数据汇聚"),
("risk_screen", "预审参谋"),
("case_search", "经验检索"),
("approval_advice", "审批意见"),
("risk_monitor", "风险监控")
]
context = {"initial_data": mock_data}
for agent_type, agent_name in agents:
# 发送agent_start事件
yield f"event: agent_start\ndata: {json.dumps({'agent': agent_name, 'type': agent_type}, ensure_ascii=False)}\n\n"
await asyncio.sleep(0.5) # 模拟处理延迟
try:
result = await execute_single_agent(agent_type, context)
context[agent_type] = result["output"] # 传递给下一个Agent
# 发送agent_end事件
yield f"event: agent_end\ndata: {json.dumps({'agent': agent_name, 'type': agent_type, 'output': result['output']}, ensure_ascii=False)}\n\n"
except Exception as e:
yield f"event: error\ndata: {json.dumps({'agent': agent_name, 'error': str(e)}, ensure_ascii=False)}\n\n"
break
# 完成
yield f"event: done\ndata: {json.dumps({'status': 'completed'}, ensure_ascii=False)}\n\n"
# ==================== API端点 ====================
@router.post("/workflow/stream")
async def workflow_stream(req: WorkflowRequest):
"""运行完整五Agent流水线(SSE)"""
return StreamingResponse(
run_workflow_stream(req.customer_name, req.project_type, req.amount),
media_type="text/event-stream"
)
@router.post("/agent/execute")
async def execute_agent(req: AgentRequest):
"""执行单个Agent"""
result = await execute_single_agent(req.agent_type, req.input_data)
return result
@router.get("/config")
async def get_config():
"""获取系统配置"""
return {
"agents": [
{"id": "data_gather", "name": "数据汇聚Agent", "description": "多源数据整理生成情报卡"},
{"id": "risk_screen", "name": "预审参谋Agent", "description": "给风险点不给结论,前置筛查"},
{"id": "case_search", "name": "经验检索Agent", "description": "历史案例检索与经验复用"},
{"id": "approval_advice", "name": "审批意见Agent", "description": "生成标准化审批参考意见"},
{"id": "risk_monitor", "name": "风险监控Agent", "description": "审批空窗期+租后动态监控"}
],
"project_types": ["对公新能源", "对公传统", "零售乘用车", "零售二手车"],
"model_config": {
"provider": "DeepSeek",
"model": "deepseek-v4-flash",
"base_url": "http://49.232.133.151/v1"
}
}
@router.get("/health")
async def health_check():
"""健康检查"""
return {"status": "ok", "service": "兴邦智能体系统"}
+432
View File
@@ -0,0 +1,432 @@
"""兴邦金租智能评审助手 - 统一API(7 个助手完整版)
基于 2026.8.18 会议痛点的完整解决方案:
1. 立项前置风控助手 🔍 - 业务经理风险自查
2. 中诚信智能填报助手 📊 - 激活十几万投资的评级模板
3. 空窗期动态监控助手 🚨 - 17天空窗期风险重扫
4. 保单条款合规核验助手 🛡️ - OCR+条款解析
5. 评审报告辅助生成助手 📄 - 看资料+写报告提效
6. 主审员智能分配助手 🎯 - 多维度负荷优化
7. AI预审过滤+加速助手 🤖 - 团队经验预判+加速
"""
import asyncio
import json
from datetime import datetime
from typing import AsyncGenerator, Dict, List, Optional
from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
# 导入数据层
from data_layer.erp_connector import get_erp_connector
from data_layer.history_store import get_history_store
from data_layer.reviewer_profile import get_reviewer_store
from data_layer.team_experience import get_team_experience_store
router = APIRouter(prefix="/api/xingbang/unified", tags=["兴邦智能评审统一接口"])
# ============ Pydantic 模型 ============
class ProjectBasicInfo(BaseModel):
"""项目基础信息"""
project_id: str = ""
project_name: str
client_name: str
industry: str
credit_amount: float # 万元
lease_object: str = ""
lease_value: float = 0 # 万元
class FinancialData(BaseModel):
"""财务数据"""
registered_capital: float = 0
established_years: int = 0
asset_liability_ratio: float = 0
current_ratio: float = 0
quick_ratio: float = 0
net_profit_2024: float = 0
net_profit_2025: float = 0
revenue_growth_rate: float = 0
class CreditReport(BaseModel):
"""征信报告"""
overdue_count: int = 0
query_count_recent_6m: int = 0
credit_limit: float = 0
enforcement_records: int = 0
class InsurancePolicyData(BaseModel):
"""保单数据"""
policy_number: str
insurance_company: str
policy_type: str
coverage_amount: float
exclusions: List[str] = []
policy_pdf_path: str = ""
# ============ 助手 1:立项前置风控 ============
class Assistant1Request(BaseModel):
"""立项前置风控请求"""
project: ProjectBasicInfo
financial: FinancialData
credit: CreditReport
@router.post("/assistant1/risk-check")
async def assistant1_risk_check(request: Assistant1Request):
"""助手1:立项前置风控自查(只给风险点,不给结论)"""
async def generate():
try:
# 获取团队经验
team_exp = get_team_experience_store()
thresholds = team_exp.get_approval_thresholds()
risk_patterns = team_exp.get_risk_patterns(request.project.industry)
red_flags = team_exp.get_industry_red_flags(request.project.industry)
yield f"data: {json.dumps({'type': 'start'}, ensure_ascii=False)}\n\n"
# 生成 Markdown 报告
content = f"""# 立项前置风险自查清单
## 📋 项目基本信息
- **项目名称**:{request.project.project_name}
- **客户名称**:{request.project.client_name}
- **行业**:{request.project.industry}
- **授信金额**:{request.project.credit_amount} 万元
- **租赁物**:{request.project.lease_object or '未填写'}
- **租赁物价值**:{request.project.lease_value} 万元
---
## 🔍 风险自查结果
"""
yield f"data: {json.dumps({'type': 'content', 'data': content}, ensure_ascii=False)}\n\n"
# 检查高风险项
high_risks = []
medium_risks = []
low_risks = []
# 1. 客户资质检查
if request.financial.registered_capital < 1000:
high_risks.append("**注册资本不足**:注册资本 {:.0f} 万元 < 1000 万元(标准要求)\n - **建议**:考虑提高授信门槛或要求集团担保".format(request.financial.registered_capital))
if request.financial.established_years < 3:
medium_risks.append("**成立时间较短**:成立 {} 年 < 3 年(标准要求)\n - **建议**:补充实控人其他企业经营情况".format(request.financial.established_years))
# 2. 财务指标检查
asset_liability = request.financial.asset_liability_ratio
threshold_al = thresholds.get("资产负债率", {})
if asset_liability > threshold_al.get("critical", 0.8):
high_risks.append(f"**资产负债率过高**:{asset_liability:.1%} > {threshold_al.get('critical', 0.8):.0%}(警戒线)\n - **团队共识**:{threshold_al.get('team_consensus', '需要额外担保')}")
elif asset_liability > threshold_al.get("warning", 0.7):
medium_risks.append(f"**资产负债率偏高**:{asset_liability:.1%}(位于 {threshold_al.get('warning', 0.7):.0%}-{threshold_al.get('critical', 0.8):.0%} 区间)\n - **建议**:补充负债结构说明,关注短期偿债压力")
else:
low_risks.append(f"✅ **资产负债率**:{asset_liability:.1%}(良好)")
current_ratio = request.financial.current_ratio
threshold_cr = thresholds.get("流动比率", {})
if current_ratio < threshold_cr.get("critical", 1.0):
high_risks.append(f"**流动比率不足**:{current_ratio:.2f} < {threshold_cr.get('critical', 1.0):.2f}(红线)\n - **团队共识**:{threshold_cr.get('team_consensus', '短期偿债能力堪忧')}")
elif current_ratio < threshold_cr.get("warning", 1.2):
medium_risks.append(f"**流动比率偏低**:{current_ratio:.2f}\n - **建议**:关注应收账款周转情况")
else:
low_risks.append(f"✅ **流动比率**:{current_ratio:.2f}(符合标准)")
# 3. 征信检查
if request.credit.overdue_count > 0:
high_risks.append(f"**征信逾期记录**:近 2 年有 {request.credit.overdue_count} 次逾期\n - **建议**:要求客户提供逾期说明及还款证明")
else:
low_risks.append("✅ **征信记录**:近 2 年无逾期(良好)")
if request.credit.enforcement_records > 0:
high_risks.append(f"**被执行记录**:存在 {request.credit.enforcement_records} 条被执行记录\n - **重要**:评审大概率退回,需先解决执行案件")
else:
low_risks.append("✅ **被执行记录**:无(良好)")
if request.credit.query_count_recent_6m > 10:
medium_risks.append(f"**征信查询频繁**:近 6 个月查询 {request.credit.query_count_recent_6m} 次\n - **建议**:了解客户融资需求是否过于迫切")
# 4. 租赁物检查
if request.project.lease_value > 0:
coverage_ratio = request.project.lease_value / request.project.credit_amount
if coverage_ratio < 1.2:
high_risks.append(f"**租赁物价值不足**:租赁物价值 {request.project.lease_value} 万元,覆盖率 {coverage_ratio:.1%} < 120%(标准要求)\n - **建议**:补充其他担保措施")
else:
low_risks.append(f"✅ **租赁物覆盖率**:{coverage_ratio:.1%}(符合要求)")
# 5. 行业红线检查
if red_flags:
content_flags = f"\n### ⚠️ {request.project.industry} 行业常见风险点\n\n"
for flag in red_flags:
content_flags += f"- {flag}\n"
content_flags += "\n**建议**:核实客户是否触及以上风险点\n\n"
yield f"data: {json.dumps({'type': 'content', 'data': content_flags}, ensure_ascii=False)}\n\n"
# 输出风险项
if high_risks:
content_high = "### 🔴 高风险项(必须处理,否则评审大概率退回)\n\n"
for i, risk in enumerate(high_risks, 1):
content_high += f"{i}. {risk}\n\n"
yield f"data: {json.dumps({'type': 'content', 'data': content_high}, ensure_ascii=False)}\n\n"
if medium_risks:
content_medium = "### 🟡 中风险项(建议补充材料或说明)\n\n"
for i, risk in enumerate(medium_risks, 1):
content_medium += f"{i}. {risk}\n\n"
yield f"data: {json.dumps({'type': 'content', 'data': content_medium}, ensure_ascii=False)}\n\n"
if low_risks:
content_low = "### 🟢 低风险项(符合要求)\n\n"
for risk in low_risks:
content_low += f"- {risk}\n"
content_low += "\n"
yield f"data: {json.dumps({'type': 'content', 'data': content_low}, ensure_ascii=False)}\n\n"
# 整体评估
if len(high_risks) == 0 and len(medium_risks) == 0:
suggestion = "✅ **直接提交**:项目风险可控,可直接提交评审"
elif len(high_risks) == 0:
suggestion = "🟡 **补充材料后提交**:建议先处理中风险项,提高通过率"
else:
suggestion = "🔴 **重大调整后提交**:存在高风险项,建议先与客户沟通调整方案"
content_final = f"""---
## 💡 整体评估
{suggestion}
**说明**:本清单基于团队历史评审经验生成,仅供参考,不代表评审结论。最终是否通过由主审员独立判断。
"""
yield f"data: {json.dumps({'type': 'content', 'data': content_final}, ensure_ascii=False)}\n\n"
yield f"data: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n"
except Exception as e:
yield f"data: {json.dumps({'type': 'error', 'message': str(e)}, ensure_ascii=False)}\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")
# ============ 助手 6:主审员智能分配 ============
class Assistant6Request(BaseModel):
"""主审员智能分配请求"""
project_industry: str
project_amount: float # 万元
project_complexity: int = Field(3, ge=1, le=5, description="项目复杂度 1-5")
@router.post("/assistant6/allocate")
async def assistant6_allocate(request: Assistant6Request):
"""助手6:主审员智能分配(多维度评分)"""
try:
reviewer_store = get_reviewer_store()
# 获取推荐列表
recommendations = reviewer_store.get_recommended_reviewers(
project_industry=request.project_industry,
project_complexity=request.project_complexity,
top_n=3
)
# 获取所有主审员当前状态
all_profiles = reviewer_store.get_all_profiles()
reviewer_status = [
{
"name": name,
"current_projects": profile["current_projects"],
"specialty": profile["specialty"],
"workload_capacity": profile["workload_capacity"],
"status": "🔴 超载" if profile["current_projects"] >= profile["workload_capacity"]
else ("🟡 偏高" if profile["current_projects"] >= profile["workload_capacity"] * 0.8 else "🟢 正常")
}
for name, profile in all_profiles.items()
]
return {
"reviewers": reviewer_status,
"recommendations": recommendations,
"project_info": {
"industry": request.project_industry,
"amount": request.project_amount,
"complexity": request.project_complexity,
},
"algorithm_version": "v1.0_multidim_scoring",
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# ============ 助手 7:AI预审过滤+加速 ============
class Assistant7Request(BaseModel):
"""AI预审请求"""
project: ProjectBasicInfo
financial: FinancialData
credit: CreditReport
@router.post("/assistant7/pre-screen")
async def assistant7_pre_screen(request: Assistant7Request):
"""助手7:AI预审过滤(基于团队经验,快速判断是否值得进入正式评审)"""
async def generate():
try:
yield f"data: {json.dumps({'type': 'start'}, ensure_ascii=False)}\n\n"
# 获取团队经验和历史案例
team_exp = get_team_experience_store()
history_store = get_history_store()
thresholds = team_exp.get_approval_thresholds()
rejection_reasons = team_exp.get_common_rejection_reasons(top_n=5)
# 相似案例检索
similar_cases = history_store.search_similar_cases(
industry=request.project.industry,
credit_amount=request.project.credit_amount,
limit=3
)
content = f"""# AI预审报告(团队经验模式)
## 📊 项目信息
- **项目名称**:{request.project.project_name}
- **客户名称**:{request.project.client_name}
- **行业**:{request.project.industry}
- **授信金额**:{request.project.credit_amount} 万元
---
## 🤖 AI预审判断(基于团队 {team_exp.get_metadata().get('total_cases_analyzed', 0)} 个历史案例)
"""
yield f"data: {json.dumps({'type': 'content', 'data': content}, ensure_ascii=False)}\n\n"
# 快速红线检查
red_line_hit = []
warning_items = []
# 1. 财务红线
if request.financial.asset_liability_ratio > thresholds.get("资产负债率", {}).get("critical", 0.8):
red_line_hit.append("资产负债率超过红线")
if request.financial.current_ratio < thresholds.get("流动比率", {}).get("critical", 1.0):
red_line_hit.append("流动比率低于红线")
# 2. 征信红线
if request.credit.enforcement_records > 0:
red_line_hit.append(f"存在 {request.credit.enforcement_records} 条被执行记录")
if request.credit.overdue_count > 2:
red_line_hit.append(f"征信逾期次数过多({request.credit.overdue_count} 次)")
# 3. 警告项
if request.financial.revenue_growth_rate < 0:
warning_items.append("营收负增长")
if request.credit.query_count_recent_6m > 10:
warning_items.append("征信查询频繁")
# AI预审结论
if red_line_hit:
conclusion = "🔴 **不建议进入正式评审**"
reason_text = "\n".join([f"- {item}" for item in red_line_hit])
content_conclusion = f"""### {conclusion}
**触及红线项**:
{reason_text}
**建议**:与客户沟通调整方案,或直接退回立项申请,避免浪费评审资源。
"""
elif len(warning_items) >= 2:
conclusion = "🟡 **建议谨慎评审**"
reason_text = "\n".join([f"- {item}" for item in warning_items])
content_conclusion = f"""### {conclusion}
**警告项**:
{reason_text}
**建议**:可以进入正式评审,但需重点关注以上风险点,建议增加尽调深度。
"""
else:
conclusion = "🟢 **建议进入正式评审**"
content_conclusion = f"""### {conclusion}
**初步判断**:项目风险可控,符合基本准入标准,建议正常推进评审流程。
"""
yield f"data: {json.dumps({'type': 'content', 'data': content_conclusion}, ensure_ascii=False)}\n\n"
# 相似案例参考
if similar_cases:
content_cases = "\n---\n\n## 📚 相似案例参考\n\n"
for i, case in enumerate(similar_cases, 1):
result_emoji = "✅" if case.get("review_result") == "approved" else "❌"
content_cases += f"""### 案例 {i}:{case.get('project_name', '未知项目')}
- **行业**:{case.get('industry', '未知')}
- **金额**:{case.get('credit_amount', 0)} 万元
- **评审结果**:{result_emoji} {case.get('review_result', '未知')}
- **关键风险点**:{', '.join([r.get('type', '未知') for r in case.get('identified_risks', [])])}
"""
yield f"data: {json.dumps({'type': 'content', 'data': content_cases}, ensure_ascii=False)}\n\n"
# 高频退回原因提示
content_tips = "\n---\n\n## 💡 团队经验提示(高频退回原因)\n\n"
for reason in rejection_reasons:
content_tips += f"- **{reason['reason']}**({reason['count']} 次) → {reason['solution']}\n"
yield f"data: {json.dumps({'type': 'content', 'data': content_tips}, ensure_ascii=False)}\n\n"
yield f"data: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n"
except Exception as e:
yield f"data: {json.dumps({'type': 'error', 'message': str(e)}, ensure_ascii=False)}\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")
# ============ 数据管理接口 ============
@router.get("/data/reviewers")
async def get_all_reviewers():
"""获取所有主审员画像"""
reviewer_store = get_reviewer_store()
return reviewer_store.get_all_profiles()
@router.get("/data/team-experience")
async def get_team_experience():
"""获取团队集体经验"""
team_exp = get_team_experience_store()
return {
"approval_thresholds": team_exp.get_approval_thresholds(),
"common_rejections": team_exp.get_common_rejection_reasons(),
"metadata": team_exp.get_metadata(),
}
@router.get("/data/history-stats")
async def get_history_statistics():
"""获取历史案例统计"""
history_store = get_history_store()
return history_store.get_statistics()
+569
View File
@@ -0,0 +1,569 @@
"""兴邦金租智能评审助手 API v2 — 全新设计,推翻旧五智能体框架
核心痛点:
1. 中诚信评级模板闲置(线下Excel手工录入)
2. 保单条款覆盖度盲区(只看形式不看内容)
3. 空窗期风险失控(17天立项到上会,期间风险恶化)
4. 评审逻辑因人而异(同一事项30%/60%/90%都可能)
5. 业务与评审对立(风险考量不一样)
新架构:4个专项助手
- 立项前置风控助手:业务经理立项前风险自查(只给风险点不给结论)
- 中诚信模板智能填报助手:激活闲置的中诚信模板,自动填充+评级
- 空窗期动态监控助手:每3天重扫征信/涉诉/舆情,上会前强制重扫
- 保单条款合规核验助手:OCR+条款解析,核验足额覆盖
"""
import asyncio
import json
from datetime import datetime
from pathlib import Path
from typing import AsyncGenerator
from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
router = APIRouter(prefix="/api/xingbang/v2", tags=["兴邦智能评审助手V2"])
# ============ Pydantic 模型 ============
class ProjectData(BaseModel):
"""项目基础数据"""
project_name: str
client_name: str
industry: str
credit_amount: float # 授信金额(万元)
lease_object: str # 租赁物
lease_value: float # 租赁物价值(万元)
financial_data: dict # 财务数据
credit_report: dict # 征信报告
litigation_records: list # 涉诉记录
sentiment_info: dict # 舆情信息
class InsurancePolicy(BaseModel):
"""保单数据"""
policy_number: str
insurance_company: str
coverage_amount: float # 保额(万元)
policy_type: str # 险种
exclusions: list[str] # 除外责任
policy_pdf_path: str = ""
class RiskCheckRequest(BaseModel):
"""立项前置风控请求"""
project_data: ProjectData
class ZhongchengxinRequest(BaseModel):
"""中诚信模板填报请求"""
project_data: ProjectData
class MonitoringRequest(BaseModel):
"""空窗期监控请求"""
project_id: str
project_name: str
client_name: str
initial_data: ProjectData
class PolicyCheckRequest(BaseModel):
"""保单核验请求"""
project_name: str
client_name: str
credit_amount: float
lease_value: float
policy: InsurancePolicy
# ============ 辅助函数:创建专用 LLM 实例 ============
def _create_assistant_llm(system_prompt: str):
"""创建专用助手 LLM(复用核心 DeepSeek 配置)"""
import os
from langchain_deepseek import ChatDeepSeek
return ChatDeepSeek(
model=os.getenv("DEEPSEEK_MODEL", "deepseek-chat"),
api_key=os.getenv("DEEPSEEK_API_KEY"),
api_base=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
temperature=0.3, # 降低温度,提高稳定性
streaming=True,
)
# ============ 助手1:立项前置风控助手 ============
SYSTEM_PROMPT_RISK_CHECK = """你是兴邦金租的"立项前置风控助手"。
**你的职责**:
帮助业务经理在立项前做风险自查,**只标识风险点,不给审批结论**,避免与评审员对立。
**工作流程**:
1. 读取项目基础数据(客户、财务、征信、涉诉、舆情)
2. 对照评审手册逐条自查
3. 输出风险自查清单,标记:
- 🔴 高风险:必须处理,否则评审大概率退回
- 🟡 中风险:建议补充材料或说明
- 🟢 低风险:符合要求,可直接提交
**评审手册核心要点**(简化版):
- 客户资质:注册资本≥1000万、成立时间≥3年、行业准入白名单
- 财务指标:资产负债率≤70%、流动比率≥1.2、净利润连续两年为正
- 征信记录:近2年无逾期、无被执行记录
- 租赁物:价值必须≥授信金额×120%、符合适格性要求
- 涉诉舆情:无重大诉讼、无负面舆情
**输出格式**(Markdown):
```markdown
# 立项前置风险自查清单
## 项目基本信息
- 项目名称:xxx
- 客户名称:xxx
- 授信金额:xxx万元
## 风险自查结果
### 🔴 高风险项(必须处理)
1. [风险点] 具体描述 + 建议措施
### 🟡 中风险项(建议处理)
1. [风险点] 具体描述 + 建议措施
### 🟢 低风险项(符合要求)
1. [检查项] 符合标准
## 整体评估
- 建议:[直接提交 / 补充材料后提交 / 重大调整后提交]
```
**重要**:不要给"通过/不通过"结论,只客观列出风险点,让业务经理自己决策。
"""
async def _stream_risk_check(project_data: ProjectData) -> AsyncGenerator[str, None]:
"""流式生成立项前置风控报告"""
llm = _create_assistant_llm(SYSTEM_PROMPT_RISK_CHECK)
# 构造用户消息
user_message = f"""请对以下项目进行立项前置风控自查:
**项目名称**:{project_data.project_name}
**客户名称**:{project_data.client_name}
**行业**:{project_data.industry}
**授信金额**:{project_data.credit_amount}万元
**租赁物**:{project_data.lease_object}
**租赁物价值**:{project_data.lease_value}万元
**财务数据**:
{json.dumps(project_data.financial_data, ensure_ascii=False, indent=2)}
**征信报告**:
{json.dumps(project_data.credit_report, ensure_ascii=False, indent=2)}
**涉诉记录**:
{json.dumps(project_data.litigation_records, ensure_ascii=False, indent=2)}
**舆情信息**:
{json.dumps(project_data.sentiment_info, ensure_ascii=False, indent=2)}
请生成风险自查清单。
"""
async for chunk in llm.astream(user_message):
if hasattr(chunk, 'content'):
yield chunk.content
@router.post("/risk-check/stream")
async def risk_check_stream(request: RiskCheckRequest):
"""立项前置风控自查(流式返回)"""
async def event_generator():
try:
yield f"data: {json.dumps({'type': 'start', 'message': '正在分析项目风险...'}, ensure_ascii=False)}\n\n"
async for content in _stream_risk_check(request.project_data):
yield f"data: {json.dumps({'type': 'content', 'data': content}, ensure_ascii=False)}\n\n"
yield f"data: {json.dumps({'type': 'done', 'message': '风险自查完成'}, ensure_ascii=False)}\n\n"
except Exception as e:
yield f"data: {json.dumps({'type': 'error', 'message': str(e)}, ensure_ascii=False)}\n\n"
return StreamingResponse(event_generator(), media_type="text/event-stream")
# ============ 助手2:中诚信模板智能填报助手 ============
SYSTEM_PROMPT_ZHONGCHENGXIN = """你是兴邦金租的"中诚信模板智能填报助手"。
**背景**:
兴邦金租花了十几万咨询费购买中诚信评级模型(Excel模板),但因线下手工录入繁琐,一直闲置未用。
**你的职责**:
1. 从项目数据自动提取关键指标
2. 填充中诚信评级模板
3. 计算评级结果(3A/2A/A/BBB/BB/B/C)
4. 生成评级报告嵌入评审底稿
**中诚信评级模型简化版**(泛行业):
- 财务指标(40%):资产负债率、流动比率、净资产收益率、营收增长率
- 经营能力(30%):行业地位、市场份额、客户集中度、盈利稳定性
- 偿债能力(20%):速动比率、利息保障倍数、现金流覆盖率
- 发展前景(10%):行业景气度、技术创新能力、政策支持
**评级标准**:
- 3A(90-100分):信用极好,违约风险极低
- 2A(80-89分):信用优良,违约风险很低
- A(70-79分):信用良好,违约风险较低
- BBB(60-69分):信用一般,违约风险中等
- BB(50-59分):信用较差,违约风险较高
- B(40-49分):信用差,违约风险高
- C(<40分):信用极差,违约风险极高
**输出格式**(Markdown):
```markdown
# 中诚信评级报告
## 项目基本信息
- 项目名称:xxx
- 客户名称:xxx
- 行业:xxx
## 评级指标填充
### 一、财务指标(40%)
| 指标 | 数值 | 得分 |
|------|------|------|
| 资产负债率 | xx% | xx分 |
| ... | ... | ... |
**小计**:xx分
### 二、经营能力(30%)
...
### 三、偿债能力(20%)
...
### 四、发展前景(10%)
...
## 评级结果
- **综合得分**:xx分
- **评级等级**:2A
- **评级说明**:信用优良,违约风险很低
## 风险提示
1. [关键风险点1]
2. [关键风险点2]
```
"""
async def _stream_zhongchengxin_report(project_data: ProjectData) -> AsyncGenerator[str, None]:
"""流式生成中诚信评级报告"""
llm = _create_assistant_llm(SYSTEM_PROMPT_ZHONGCHENGXIN)
user_message = f"""请为以下项目生成中诚信评级报告:
**项目名称**:{project_data.project_name}
**客户名称**:{project_data.client_name}
**行业**:{project_data.industry}
**授信金额**:{project_data.credit_amount}万元
**财务数据**:
{json.dumps(project_data.financial_data, ensure_ascii=False, indent=2)}
**征信报告**:
{json.dumps(project_data.credit_report, ensure_ascii=False, indent=2)}
请填充中诚信评级模板并计算评级。
"""
async for chunk in llm.astream(user_message):
if hasattr(chunk, 'content'):
yield chunk.content
@router.post("/zhongchengxin/stream")
async def zhongchengxin_stream(request: ZhongchengxinRequest):
"""中诚信模板智能填报(流式返回)"""
async def event_generator():
try:
yield f"data: {json.dumps({'type': 'start', 'message': '正在填充中诚信评级模板...'}, ensure_ascii=False)}\n\n"
async for content in _stream_zhongchengxin_report(request.project_data):
yield f"data: {json.dumps({'type': 'content', 'data': content}, ensure_ascii=False)}\n\n"
yield f"data: {json.dumps({'type': 'done', 'message': '中诚信评级完成'}, ensure_ascii=False)}\n\n"
except Exception as e:
yield f"data: {json.dumps({'type': 'error', 'message': str(e)}, ensure_ascii=False)}\n\n"
return StreamingResponse(event_generator(), media_type="text/event-stream")
# ============ 助手3:空窗期动态监控助手 ============
SYSTEM_PROMPT_MONITORING = """你是兴邦金租的"空窗期动态监控助手"。
**背景**:
立项到上会需要17个工作日,期间客户征信/涉诉/舆情可能恶化,但无人重扫,导致上会时信息已过时。
**你的职责**:
1. 对比初始数据与最新数据
2. 识别异常变化(新增被执行、新增诉讼、舆情负面、信用卡逾期等)
3. 评估风险等级(严重/一般/轻微)
4. 生成红色预警报告
**监控维度**:
- 征信变化:新增逾期、新增查询记录、信用卡额度变化
- 涉诉变化:新增诉讼、新增被执行、执行金额变化
- 舆情变化:负面新闻、行业风险、关联企业风险
- 消费行为:淘宝消费骤降、信用卡逾期
**风险等级**:
- 🔴 严重:新增被执行/重大诉讼/信用卡逾期,建议暂停审批
- 🟡 一般:征信查询次数增多/轻微负面舆情,建议重点关注
- 🟢 轻微:无明显异常
**输出格式**(Markdown):
```markdown
# 空窗期动态监控报告
## 项目基本信息
- 项目名称:xxx
- 监控时间:xxx
## 异常变化检测
### 🔴 严重异常
1. [异常项] 具体描述 + 建议措施
### 🟡 一般异常
1. [异常项] 具体描述
### 🟢 无异常
- 征信记录:无新增异常
- 涉诉记录:无新增异常
- 舆情信息:无新增负面
## 监控建议
- [暂停审批 / 重点关注 / 正常推进]
```
"""
async def _stream_monitoring_report(
initial_data: ProjectData,
latest_data: ProjectData
) -> AsyncGenerator[str, None]:
"""流式生成空窗期监控报告"""
llm = _create_assistant_llm(SYSTEM_PROMPT_MONITORING)
user_message = f"""请对比以下项目的初始数据与最新数据,生成空窗期动态监控报告:
**项目名称**:{initial_data.project_name}
**客户名称**:{initial_data.client_name}
**初始数据(立项时)**:
征信报告:{json.dumps(initial_data.credit_report, ensure_ascii=False, indent=2)}
涉诉记录:{json.dumps(initial_data.litigation_records, ensure_ascii=False, indent=2)}
舆情信息:{json.dumps(initial_data.sentiment_info, ensure_ascii=False, indent=2)}
**最新数据(当前)**:
征信报告:{json.dumps(latest_data.credit_report, ensure_ascii=False, indent=2)}
涉诉记录:{json.dumps(latest_data.litigation_records, ensure_ascii=False, indent=2)}
舆情信息:{json.dumps(latest_data.sentiment_info, ensure_ascii=False, indent=2)}
请识别异常变化并生成监控报告。
"""
async for chunk in llm.astream(user_message):
if hasattr(chunk, 'content'):
yield chunk.content
@router.post("/monitoring/stream")
async def monitoring_stream(request: MonitoringRequest):
"""空窗期动态监控(流式返回)
实际使用中,会定时调用此接口(每3天),传入最新数据
这里为演示方便,模拟一次对比
"""
# 模拟最新数据(实际应从外部数据源实时抓取)
latest_data = request.initial_data.model_copy()
latest_data.credit_report["overdue_count"] = latest_data.credit_report.get("overdue_count", 0) + 1
latest_data.litigation_records.append({"case_number": "2026执1234", "amount": 50, "status": "执行中"})
async def event_generator():
try:
yield f"data: {json.dumps({'type': 'start', 'message': '正在对比数据变化...'}, ensure_ascii=False)}\n\n"
async for content in _stream_monitoring_report(request.initial_data, latest_data):
yield f"data: {json.dumps({'type': 'content', 'data': content}, ensure_ascii=False)}\n\n"
yield f"data: {json.dumps({'type': 'done', 'message': '监控完成'}, ensure_ascii=False)}\n\n"
except Exception as e:
yield f"data: {json.dumps({'type': 'error', 'message': str(e)}, ensure_ascii=False)}\n\n"
return StreamingResponse(event_generator(), media_type="text/event-stream")
# ============ 助手4:保单条款合规核验助手 ============
SYSTEM_PROMPT_POLICY_CHECK = """你是兴邦金租的"保单条款合规核验助手"。
**背景**:
兴邦金租要求客户为租赁物投保,保额需覆盖授信金额,但实际只检查"有没有保单",不细看条款,导致出险时发现保单回不了。
**你的职责**:
1. 解析保单关键信息(险种、保额、除外责任)
2. 核验是否符合兴邦要求:
- 保额 ≥ 授信金额(理想120%)
- 险种必须包含:财产一切险/车辆损失险/盗抢险(根据租赁物类型)
- 除外责任不能包含高频风险(如泡水、自然灾害等)
3. 标记风险点
4. 生成合规核验报告
**兴邦保单合规要求**:
- 设备类:财产一切险/工程一切险,保额≥授信×120%
- 车辆类:车损险+盗抢险+三者险,保额≥授信×120%
- 除外责任禁止:战争、核辐射、故意损坏 OK,但泡水、自然灾害、机械故障等高频风险不能除外
**输出格式**(Markdown):
```markdown
# 保单条款合规核验报告
## 保单基本信息
- 保单号:xxx
- 保险公司:xxx
- 险种:xxx
- 保额:xxx万元
## 合规核验结果
### ✅ 符合项
1. [核验项] 符合要求
### ❌ 不符合项
1. [核验项] 不符合要求 + 风险说明
## 核验结论
- **是否合规**:[合规 / 不合规]
- **风险等级**:[无风险 / 低风险 / 高风险]
- **处理建议**:[通过 / 要求客户补正保单 / 拒绝]
## 详细说明
[具体分析]
```
"""
async def _stream_policy_check(
project_name: str,
client_name: str,
credit_amount: float,
lease_value: float,
policy: InsurancePolicy
) -> AsyncGenerator[str, None]:
"""流式生成保单合规核验报告"""
llm = _create_assistant_llm(SYSTEM_PROMPT_POLICY_CHECK)
required_coverage = credit_amount * 1.2 # 要求120%覆盖
user_message = f"""请核验以下保单是否符合兴邦金租要求:
**项目信息**:
- 项目名称:{project_name}
- 客户名称:{client_name}
- 授信金额:{credit_amount}万元
- 租赁物价值:{lease_value}万元
- 要求保额:≥{required_coverage}万元(120%覆盖)
**保单信息**:
- 保单号:{policy.policy_number}
- 保险公司:{policy.insurance_company}
- 保额:{policy.coverage_amount}万元
- 险种:{policy.policy_type}
- 除外责任:{json.dumps(policy.exclusions, ensure_ascii=False)}
请生成合规核验报告。
"""
async for chunk in llm.astream(user_message):
if hasattr(chunk, 'content'):
yield chunk.content
@router.post("/policy-check/stream")
async def policy_check_stream(request: PolicyCheckRequest):
"""保单条款合规核验(流式返回)"""
async def event_generator():
try:
yield f"data: {json.dumps({'type': 'start', 'message': '正在核验保单条款...'}, ensure_ascii=False)}\n\n"
async for content in _stream_policy_check(
request.project_name,
request.client_name,
request.credit_amount,
request.lease_value,
request.policy
):
yield f"data: {json.dumps({'type': 'content', 'data': content}, ensure_ascii=False)}\n\n"
yield f"data: {json.dumps({'type': 'done', 'message': '保单核验完成'}, ensure_ascii=False)}\n\n"
except Exception as e:
yield f"data: {json.dumps({'type': 'error', 'message': str(e)}, ensure_ascii=False)}\n\n"
return StreamingResponse(event_generator(), media_type="text/event-stream")
# ============ 综合工作台接口 ============
@router.get("/assistants/list")
async def list_assistants():
"""获取所有助手列表"""
return {
"assistants": [
{
"id": "risk-check",
"name": "立项前置风控助手",
"description": "业务经理立项前风险自查,只给风险点不给结论",
"icon": "🔍",
"pain_point": "业务与评审对立,项目被退回",
"value": "降低退回率,缩短评审周期"
},
{
"id": "zhongchengxin",
"name": "中诚信模板智能填报助手",
"description": "激活闲置的中诚信模板,自动填充+评级",
"icon": "📊",
"pain_point": "十几万的中诚信模板手工录入太繁琐",
"value": "每个项目省30-60分钟,激活投资"
},
{
"id": "monitoring",
"name": "空窗期动态监控助手",
"description": "每3天重扫征信/涉诉/舆情,上会前强制重扫",
"icon": "🚨",
"pain_point": "17天空窗期,上会时信息已过时",
"value": "17天风险不落地,避免决策失误"
},
{
"id": "policy-check",
"name": "保单条款合规核验助手",
"description": "OCR+条款解析,核验足额覆盖",
"icon": "🛡️",
"pain_point": "只看有没有保单,不看条款,出险回不了",
"value": "避免保单风险盲区"
}
]
}