# -*- coding: utf-8 -*- """ ⚖️ 多智能体辩论诊断页面 (Premium Financial-Grade Design) 选择企业 → 真实 Token 级打字机流式推演 → 三方交叉质证 → 综合裁决与核保看板 使用 Glassmorphism 玻璃拟态 + 暗黑金融科技 CSS 调色盘 """ import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import streamlit as st import plotly.graph_objects as go import time from collectors.financial_collector import get_all_companies, get_company_by_code from agents.debate_engine import DebateEngine st.set_page_config(page_title="多智能体辩论诊断", page_icon="⚖️", layout="wide") # ============================================================ # 自定义 UI 视觉增强样式 (CSS 注入) # ============================================================ st.markdown(""" """, unsafe_allow_html=True) # ============================================================ # 页面顶部 Banner # ============================================================ st.markdown("""
⚖️ 多智能体交叉验证辩论诊断
基于 Multi-Agent 辩论图谱 · 法务 / 技术 / 财务专家分布式研判 · 全过程打字机流式追溯
Phase 1: 独立穿透研判 Phase 2: 三方交叉质证 Phase 3: 委员会综合裁决 🛡️ 保险精算核保建议
""", unsafe_allow_html=True) from utils.session_helper import render_company_selector, render_sidebar_global_company_selector with st.sidebar: render_sidebar_global_company_selector() st.markdown("---") col_ctrl1, col_ctrl2 = st.columns([3, 1]) with col_ctrl1: company = render_company_selector("🏢 选择待诊断科创企业", key_suffix="debate_page") stock_code = company["stock_code"] if company else "688256" # 初始化 session_state if "debate_results" not in st.session_state: st.session_state["debate_results"] = None with col_ctrl2: st.markdown("
", unsafe_allow_html=True) start_btn = st.button("🚀 启动多智能体辩论", type="primary", use_container_width=True) # ============================================================ # 辩论触发与打字机流式渲染 # ============================================================ if start_btn: company = get_company_by_code(stock_code) if not company: st.error("未找到企业数据") st.stop() engine = DebateEngine() company_name = company.get("short_name", "未知") st.markdown("---") st.markdown("### 📡 智能体实时思考与流式推演控制台 (Live Streaming Terminal)") progress_bar = st.progress(0, text="▶️ 正在初始化分布式智能体网络...") # 动态渲染控制台 live_box = st.container(border=True) def run_agent_with_typewriter(agent, agent_title, start_progress, end_progress): progress_bar.progress(start_progress, text=f"{agent.role_icon} {agent_title} 正在穿透企业数据并与大模型通信...") with live_box: st.markdown(f"##### {agent.role_icon} {agent_title}") status_placeholder = st.empty() thinking_placeholder = st.empty() content_placeholder = st.empty() thinking_buf = [] content_buf = [] status_placeholder.info("🔗 建立 SSE 流式通信中...") def on_token(token_type: str, token_text: str): if token_type == "reasoning": thinking_buf.append(token_text) t_str = "".join(thinking_buf) status_placeholder.markdown("🧠 **[大模型深度思考中...]**") display_text = t_str[-350:] if len(t_str) > 350 else t_str thinking_placeholder.markdown( f"""
[THINKING] {display_text}▌
""", unsafe_allow_html=True ) elif token_type == "content": content_buf.append(token_text) c_str = "".join(content_buf) status_placeholder.markdown("📝 **[正在实时生成审查报告...]**") display_c = c_str[-280:] if len(c_str) > 280 else c_str content_placeholder.markdown( f"""
[REPORT OUTPUT] {display_c}▌
""", unsafe_allow_html=True ) agent.on_token_callback = on_token result = agent.evaluate(company if agent != engine.judge_agent else None) progress_bar.progress(end_progress, text=f"✅ {agent_title} 研判完毕") status_placeholder.success(f"✅ {agent_title} 完成风险评级") return result, list(agent.reasoning_trace) # 1. 法务 Agent law_result, law_trace = run_agent_with_typewriter(engine.law_agent, "法务风控节点", 10, 35) # 2. 技术 Agent tech_result, tech_trace = run_agent_with_typewriter(engine.tech_agent, "技术风控节点", 35, 60) # 3. 财务 Agent finance_result, finance_trace = run_agent_with_typewriter(engine.finance_agent, "财务风控节点", 60, 85) # 4. 交叉质证 progress_bar.progress(88, text="🔄 正在比对三方判定结论,进行交叉质证分析...") conflicts = engine._identify_conflicts(law_result, tech_result, finance_result) # 5. 综合裁决 Agent progress_bar.progress(92, text="⚖️ 综合裁决委员会进行权重复核...") with live_box: st.markdown("##### ⚖️ 综合裁决节点") j_status = st.empty() j_thinking = st.empty() j_content = st.empty() j_think_buf = [] j_cont_buf = [] j_status.info("🚀 综合裁决委员会正在消解分歧...") def judge_on_token(token_type: str, token_text: str): if token_type == "reasoning": j_think_buf.append(token_text) t_str = "".join(j_think_buf) j_status.markdown("🧠 **[裁决委员会讨论中...]**") display_t = t_str[-350:] if len(t_str) > 350 else t_str j_thinking.markdown( f"""
[COMMITTEE THOUGHTS] {display_t}▌
""", unsafe_allow_html=True ) elif token_type == "content": j_cont_buf.append(token_text) c_str = "".join(j_cont_buf) j_status.markdown("📝 **[生成核保决议中...]**") display_c = c_str[-280:] if len(c_str) > 280 else c_str j_content.markdown( f"""
[FINAL VERDICT] {display_c}▌
""", unsafe_allow_html=True ) engine.judge_agent.on_token_callback = judge_on_token judge_result = engine.judge_agent.evaluate(company, law_result, tech_result, finance_result) judge_trace = list(engine.judge_agent.reasoning_trace) j_status.success("✅ 综合裁决完成") progress_bar.progress(100, text="✅ 辩论诊断流程全量完成!") # 持久化结果 st.session_state["debate_results"] = { "company": company, "law_result": law_result, "tech_result": tech_result, "finance_result": finance_result, "law_trace": law_trace, "tech_trace": tech_trace, "finance_trace": finance_trace, "judge_trace": judge_trace, "conflicts": conflicts, "judge_result": judge_result, "debate_log": engine.debate_log, } # ============================================================ # 结果持久化渲染与面板展示 # ============================================================ def render_trace_expander(trace: list, title: str): """渲染研判结果对应的详细辩论链""" with st.expander(f"🔗 查看 【{title}】 详细辩论链与凭据 (共 {len(trace)} 步)", expanded=False): for step in trace: ts = step.get("timestamp", "") step_name = step.get("step", "") content = step.get("content", "") if "思维链" in step_name or "原始" in step_name or "Context" in step_name or len(content) > 150: st.markdown(f"**`[{ts}]` {step_name}**") st.code(content, language="text") else: st.markdown(f"- **`[{ts}]` {step_name}**: {content}") def render_risk_card(result: dict, keys: list, trace: list, agent_title: str, overall_key: str): """渲染三方风控精美卡片""" overall = result.get(overall_key, {}).get("score", 50) level = result.get(overall_key, {}).get("level", "中") pill_class = "pill-high" if level == "高" else ("pill-med" if level == "中" else "pill-low") st.markdown(f"""
{agent_title} {level}风险 {overall}分
""", unsafe_allow_html=True) # 细分指标渲染 for key in keys: item = result.get(key, {}) if isinstance(item, dict) and "detail" in item: score = item.get("score", 0) color = "#EF4444" if score >= 70 else ("#F59E0B" if score >= 40 else "#10B981") st.markdown( f"
" f"{score}分 — {item.get('detail', '')}
", unsafe_allow_html=True, ) # 关键发现 findings = result.get("key_findings", []) if findings: st.markdown("

📌 关键风险发现:

", unsafe_allow_html=True) for f in findings: st.markdown(f"• {f}", unsafe_allow_html=True) # 建议 recs = result.get("recommendations", []) if recs: st.markdown("

💡 专家处置建议:

", unsafe_allow_html=True) for r in recs: st.markdown(f"• {r}", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) render_trace_expander(trace, agent_title) res = st.session_state.get("debate_results") if res is not None: company = res["company"] law_result = res["law_result"] tech_result = res["tech_result"] finance_result = res["finance_result"] conflicts = res["conflicts"] judge_result = res["judge_result"] st.markdown("---") st.markdown(f"### 📊 多智能体交叉辩论诊断报告 — {company['short_name']} ({company['stock_code']})") # ---- Phase 1: 三方独立研判 ---- st.markdown("#### 📋 Phase 1: 三方专家节点穿透研判全景") col_law, col_tech, col_fin = st.columns(3) with col_law: render_risk_card( law_result, ["algo_compliance_risk", "geopolitical_risk", "data_compliance_risk", "ip_litigation_risk"], res.get("law_trace", []), "👩‍⚖️ 法务风控节点", "overall_law_risk", ) with col_tech: render_risk_card( tech_result, ["tech_disruption_risk", "talent_loss_risk", "patent_moat", "tech_iteration_pressure"], res.get("tech_trace", []), "👨‍🔬 技术风控节点", "overall_tech_risk", ) with col_fin: render_risk_card( finance_result, ["rd_capitalization_risk", "concentration_risk", "receivable_risk", "cashflow_risk"], res.get("finance_trace", []), "👔 财务风控节点", "overall_fin_risk", ) # ---- Phase 2: 交叉质证分析 ---- st.markdown("---") st.markdown("#### 🔄 Phase 2: 三方交叉质证与冲突判定") if conflicts: for conflict in conflicts: st.markdown(f"""
⚠️ 判定冲突警示:{conflict}
""", unsafe_allow_html=True) else: st.markdown("""
一致性确认:法务、技术、财务三方判定逻辑高度契合,无重大矛盾分歧。
""", unsafe_allow_html=True) # ---- Phase 3: 综合裁决与保险核保 ---- st.markdown("---") st.markdown("#### ⚖️ Phase 3: 风险委员会综合裁决与核保决策") comp_score = judge_result.get("comprehensive_score", 50) decision = judge_result.get("underwriting_decision", "标准承保") risk_level = judge_result.get("risk_level", "中") col_j1, col_j2 = st.columns([1, 1]) with col_j1: # Plotly 仪表盘 (暗黑高精质感) fig = go.Figure(go.Indicator( mode="gauge+number", value=comp_score, title={"text": "综合风险指数 (0-100)", "font": {"color": "#F8FAFC", "size": 16}}, number={"font": {"color": "#FFFFFF", "size": 48}}, gauge={ "axis": {"range": [0, 100], "tickcolor": "#94A3B8"}, "bar": {"color": "#e94560", "width": 0.3}, "bgcolor": "rgba(0,0,0,0)", "bordercolor": "rgba(255,255,255,0.1)", "steps": [ {"range": [0, 40], "color": "rgba(16, 185, 129, 0.25)"}, {"range": [40, 60], "color": "rgba(245, 158, 11, 0.25)"}, {"range": [60, 80], "color": "rgba(249, 115, 22, 0.25)"}, {"range": [80, 100], "color": "rgba(239, 68, 68, 0.25)"}, ], }, )) fig.update_layout( height=260, margin=dict(l=20, r=20, t=40, b=10), paper_bgcolor="rgba(0,0,0,0)", font=dict(color="white"), ) st.plotly_chart(fig, use_container_width=True) with col_j2: if "拒绝" in decision: v_color = "#EF4444" v_bg = "rgba(239, 68, 68, 0.15)" elif "附条件" in decision: v_color = "#F59E0B" v_bg = "rgba(245, 158, 11, 0.15)" elif "优先" in decision: v_color = "#10B981" v_bg = "rgba(16, 185, 129, 0.15)" else: v_color = "#3B82F6" v_bg = "rgba(59, 130, 246, 0.15)" st.markdown(f"""
核保决策: 【{decision}】

综合风险等级: {risk_level} | 加权裁决得分: {comp_score}分

""", unsafe_allow_html=True) st.markdown(f"**📝 裁决终审意见**: {judge_result.get('summary', '')}") conditions = judge_result.get("underwriting_conditions", []) if conditions: st.markdown("**📌 保险核保附加条件**:") for c in conditions: st.markdown(f"- {c}", unsafe_allow_html=True) # 挂载裁决节点的辩论链 st.markdown("
", unsafe_allow_html=True) render_trace_expander(res.get("judge_trace", []), "⚖️ 综合裁决委员会") else: # 默认引导说明 st.markdown("""

💡 多智能体辩论与风险识别流程说明

  1. Phase 1 - 三方穿透研判: 法务风控节点审查算法与出口管制、技术风控节点审查路线与人员、财务风控节点穿透资本化与集中度。
  2. Phase 2 - 交叉质证分析: 识别法务、技术、财务意见间的潜在分歧(如:研发投入大 vs 资本化美化利润)。
  3. Phase 3 - 委员会综合裁决: 基于【合规 > 技术 > 财务】优先级规则进行加权综合评分并输出精算核保决议。
""", unsafe_allow_html=True)