Files
group_xinghuo_jinrong/docs/course/jinrong-module-advisor/index.html
T
zhanghongyu_0626 4b8e11c9bd feat(threshold): Implement customer loss threshold configuration and notification system
- Added `ThresholdRepository` for managing customer loss threshold configurations and notifications.
- Introduced `threshold_service` to handle loss threshold alerts based on customer portfolio performance.
- Enhanced `customer_prompts` to include new intent for querying product net values.
- Updated `customer_service` to integrate new threshold alert functionality into existing workflows.
- Implemented `sanitize_postprocess` for improved compliance handling in customer interactions.
- Enhanced course documentation to reflect updates in advisor training modules and interactive elements.

This update significantly improves the customer experience by providing proactive loss threshold notifications and enhancing the overall service framework.
2026-09-10 10:57:40 +08:00

484 lines
29 KiB
HTML
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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>代理人助手 Agent 深潜 · 交互导览</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,600;12..96,700;12..96,800&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400;1,9..40,500&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
<style>
:root {
--color-accent: #D4A843;
--color-accent-hover: #B8922F;
--color-accent-light: #FBF4E3;
--color-accent-muted: #E0C06A;
}
</style>
<script src="main.js" defer></script>
</head>
<body>
<nav class="nav" id="nav">
<div class="progress-bar" id="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
<div class="nav-inner">
<span class="nav-title">代理人助手 Agent 深潜</span>
<div class="nav-dots" id="nav-dots" role="tablist">
<button class="nav-dot" data-target="module-1" data-tooltip="理财师场景" role="tab" aria-label="模块 1: 理财师场景"></button>
<button class="nav-dot" data-target="module-2" data-tooltip="tool→llm→guard" role="tab" aria-label="模块 2: 编排群聊"></button>
<button class="nav-dot" data-target="module-3" data-tooltip="归属与 run_tool" role="tab" aria-label="模块 3: 归属与 run_tool"></button>
<button class="nav-dot" data-target="module-4" data-tooltip="T-20 与 Neo4j" role="tab" aria-label="模块 4: T-20 与 Neo4j"></button>
</div>
</div>
</nav>
<main id="main">
<section class="module" id="module-1">
<div class="module-inner">
<p class="eyebrow animate-in">模块 1 · 理财师视角</p>
<h1 class="module-title animate-in">代理人助手:<br>聊名下客户,不外发草稿</h1>
<p class="module-lead animate-in">
理财师(如 <code>STAFF-10086</code>)登录后打开「代理人助手」,通过
<span class="term" data-definition="X-Agent-Type 是 HTTP 请求头里的一行,告诉后端这次对话走哪条 Agent 业务线;理财师必须填 advisor。">X-Agent-Type: advisor</span>
进入顾问线。只能讨论<strong>名下已绑定</strong>的客户,生成的沟通草稿不会自动发给客户。
</p>
<div class="screen animate-in">
<h2>三条和 customer 线的硬区别</h2>
<div class="pattern-cards">
<div class="pattern-card">
<h3>编排入口</h3>
<p>走 顾问通用 tool→llm→guard 编排(<code>agent_service.py</code>)顾问通用 tool→llm→guard 编排,<strong>不是</strong> 登录客户 14 节点编排(<code>customer_service.py</code>)登录客户 14 节点编排。</p>
</div>
<div class="pattern-card">
<h3>客户范围</h3>
<p>会话可绑定某个 <code>customer_id</code>;查数据前过 G-01 归属——必须是 <code>customer_advisor_rel</code> 里 active 的名下客户。</p>
</div>
<div class="pattern-card">
<h3>能力边界</h3>
<p>System prompt 固化:可查持仓/流水/风评、搜产品知识库;<strong>草稿不外发</strong>、不做投资建议承诺。</p>
</div>
</div>
<div class="callout callout-accent">
<strong>Demo 账号:</strong> <code>STAFF-10086</code> 理财师 · 名下示例客户 <code>CUST-9527</code>。改功能时先确认会话里绑的是哪位客户,再测归属拒绝场景。
</div>
</div>
<div class="screen animate-in">
<h2>请求长什么样</h2>
<div class="translation-block">
<div class="translation-code">
<span class="translation-label">前端 · chat.ts</span>
<pre><code><span class="code-line">POST /api/chat/stream</span>
<span class="code-line">Authorization: Bearer &lt;jwt&gt;</span>
<span class="code-line"><span class="code-string">"X-Agent-Type"</span>: <span class="code-string">"advisor"</span></span>
<span class="code-line">{ <span class="code-string">"message"</span>: <span class="code-string">"客户持仓如何"</span>, <span class="code-string">"customer_id"</span>: <span class="code-string">"CUST-9527"</span> }</span></code></pre>
</div>
<div class="translation-english">
<span class="translation-label">白话</span>
<div class="translation-lines">
<p class="tl">和对话 API 一样要带 JWT,但 Agent 类型填 advisor,不是 customer。</p>
<p class="tl">body 里可指定要服务的客户号;后端会校验这位客户是否在该理财师名下。</p>
<p class="tl">缺 X-Agent-Type 直接 401,和业务代码无关。</p>
</div>
</div>
</div>
</div>
<div class="screen animate-in">
<h2>顾问线能调哪些 Tool(关键词意图)</h2>
<div class="badge-list">
<div class="badge-item"><span class="badge-code">query_holdings</span><span class="badge-desc">持仓 / 持有 / 仓位</span></div>
<div class="badge-item"><span class="badge-code">query_recent_trades</span><span class="badge-desc">流水 / 交易记录</span></div>
<div class="badge-item"><span class="badge-code">query_customer_profile</span><span class="badge-desc">风险测评 / 风险等级</span></div>
<div class="badge-item"><span class="badge-code">search_knowledge</span><span class="badge-desc">基金 / 申购 / 费率 / 产品手册</span></div>
</div>
<div class="quiz-container" id="quiz-advisor-m1">
<div class="quiz-question-block"
data-correct="option-b"
data-explanation-right="对。advisor 走 顾问通用 tool→llm→guard 编排(agent_service.py)的 tool→llm→guard;customer 才走 登录客户 14 节点编排(customer_service.py)14 节点。"
data-explanation-wrong="理财师线和客户线是 四 Agent 对话 HTTP 入口(chat.py)里两个 if 分支,编排文件不同。">
<h3 class="quiz-question">理财师打开「代理人助手」对话,编排跑在哪?</h3>
<div class="quiz-options">
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>登录客户 14 节点编排(customer_service.py)</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>顾问通用 tool→llm→guard 编排(agent_service.py)</span>
</button>
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>analyst_agent 问数</span>
</button>
</div>
<div class="quiz-feedback"></div>
</div>
<button class="quiz-check-btn" onclick="checkQuiz('quiz-advisor-m1')">检查答案</button>
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-advisor-m1')">重做</button>
</div>
</div>
</div>
</section>
<section class="module" id="module-2">
<div class="module-inner">
<p class="eyebrow animate-in">模块 2 · 三张牌编排</p>
<h1 class="module-title animate-in">tool → llm → guard<br>群聊走一遍</h1>
<p class="module-lead animate-in">
顾问线 LangGraph 只有三个业务节点(加 START/END):
<span class="term" data-definition="tool 节点 = 根据用户消息关键词匹配 Tool 名,调 对话 Tool 编排(tool_service.py)run_tool 查库并落 agent_tool_call 留痕。">tool</span>
→ llm → guard。比客户 14 节点图短,但<strong>归属校验在 对话 Tool 编排(tool_service.py)run_tool 里</strong>,和客户线铁律口径一致。
</p>
<div class="screen animate-in">
<h2>节点职责(背下来少踩坑)</h2>
<div class="pattern-cards">
<div class="pattern-card">
<h3>tool_node</h3>
<p><code>match_intent("advisor", message)</code> 命中关键词 → 对话 Tool 编排(<code>tool_service.py</code>)<code>run_tool</code> 查 Core / 知识库 → 结果写入 <code>tool_results</code>。</p>
</div>
<div class="pattern-card">
<h3>llm_node</h3>
<p>System prompt(顾问边界)+ Tool 摘要注入 + 历史窗口 + 本轮用户话 → DeepSeek 组织自然语言。</p>
</div>
<div class="pattern-card">
<h3>guard_node</h3>
<p>顾问线<strong>不附</strong>客户/风控那种尾部免责声明(<code>needs_disclaimer("advisor")</code> 为 false)。</p>
</div>
</div>
</div>
<div class="screen animate-in">
<h2>群聊:理财师问「客户 CUST-9527 持仓如何」</h2>
<p>简化播放 tool → llm → guard 全链路(前面 四 Agent 对话 HTTP 入口(chat.py)已验 JWT + G-01)。</p>
<div class="chat-window" id="chat-advisor-m2">
<div class="chat-messages">
<div class="chat-message" data-msg="0" data-sender="browser" style="display:none">
<div class="chat-avatar" style="background:#D4A843">前</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#D4A843">浏览器</span>
<p>POST /api/chat/stream · X-Agent-Type: advisor · customer_id: CUST-9527</p>
</div>
</div>
<div class="chat-message" data-msg="1" data-sender="chat" style="display:none">
<div class="chat-avatar" style="background:#B8922F">API</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#B8922F">四 Agent 对话 HTTP 入口(chat.py)</span>
<p>验 JWT → assert_customer_access(G-01:名下客户)→ 建/续 session</p>
</div>
</div>
<div class="chat-message" data-msg="2" data-sender="tool" style="display:none">
<div class="chat-avatar" style="background:#E0C06A">Tool</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#E0C06A">tool_node</span>
<p>关键词「持仓」→ <code>query_holdings</code> → 对话 Tool 编排(tool_service.py)run_tool</p>
</div>
</div>
<div class="chat-message" data-msg="3" data-sender="core" style="display:none">
<div class="chat-avatar" style="background:#2D8B55">DB</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#2D8B55">core_ro</span>
<p>只读 SQL 查持仓 → fact_text 摘要写入 tool_results;agent_tool_call 落库 success</p>
</div>
</div>
<div class="chat-message" data-msg="4" data-sender="llm" style="display:none">
<div class="chat-avatar" style="background:#7B6DAA">AI</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#7B6DAA">llm_node</span>
<p>Tool 摘要注入 system 上下文 → DeepSeek 生成顾问口吻回复(数据引用 fact_text)</p>
</div>
</div>
<div class="chat-message" data-msg="5" data-sender="guard" style="display:none">
<div class="chat-avatar" style="background:#2A7B9B">盾</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#2A7B9B">guard_node</span>
<p>advisor 不拼 CHAT_DISCLAIMER;reply 原样传出</p>
</div>
</div>
<div class="chat-message" data-msg="6" data-sender="sse" style="display:none">
<div class="chat-avatar" style="background:#D4A843">流</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#D4A843">stream_chat</span>
<p>Tool 同步跑完后逐块推 LLM 文本;收完 done 后 api 层统一落 MySQL 回合</p>
</div>
</div>
</div>
<div class="chat-typing" id="chat-advisor-m2-typing" style="display:none">
<div class="chat-avatar" id="chat-advisor-m2-typing-avatar">…</div>
<div class="chat-typing-dots"><span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span></div>
</div>
<div class="chat-controls">
<button class="btn chat-next-btn">下一条</button>
<button class="btn chat-all-btn">全部播放</button>
<button class="btn chat-reset-btn">重播</button>
<span class="chat-progress"></span>
</div>
</div>
</div>
<div class="screen animate-in">
<div class="translation-block">
<div class="translation-code">
<span class="translation-label">CODE · 顾问通用 tool→llm→guard 编排(agent_service.py)tool_node</span>
<pre><code><span class="code-line">tool_name = tool_service.match_intent(</span>
<span class="code-line"> state[<span class="code-string">"agent_type"</span>], state[<span class="code-string">"user_message"</span>]</span>
<span class="code-line">)</span>
<span class="code-line">record = tool_service.run_tool(</span>
<span class="code-line"> tool_name=tool_name,</span>
<span class="code-line"> agent_type=state[<span class="code-string">"agent_type"</span>],</span>
<span class="code-line"> customer_id=state.get(<span class="code-string">"customer_id"</span>) or <span class="code-string">""</span>,</span>
<span class="code-line"> ...</span>
<span class="code-line">)</span></code></pre>
</div>
<div class="translation-english">
<span class="translation-label">白话</span>
<div class="translation-lines">
<p class="tl">先靠关键词判断要不要查库;闲聊不命中就 tool_results 为空,LLM 纯聊。</p>
<p class="tl">customer_id 来自会话绑定(四 Agent 对话 HTTP 入口(chat.py)归属校验后的值),不是 LLM 生成的。</p>
<p class="tl">query_holdings 和 customer 线共用 Core 只读 Tool 定义(core_tools.py)定义,但跑在 顾问通用 tool→llm→guard 编排(agent_service.py)图里。</p>
</div>
</div>
</div>
<div class="quiz-container" id="quiz-advisor-m2">
<div class="quiz-question-block"
data-correct="option-a"
data-explanation-right="对。一期用关键词 match_intent;命中「持仓」等词才调 query_holdings。"
data-explanation-wrong="顾问线 Tool 意图是一期关键词规则,不是 LLM 自由选函数名。">
<h3 class="quiz-question">理财师问「这位客户仓位怎么样」,tool_node 如何选中 query_holdings?</h3>
<div class="quiz-options">
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>match_intent 关键词命中「仓位」</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>LLM 在回复里输出函数名</span>
</button>
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>前端在 body 里传 tool_name</span>
</button>
</div>
<div class="quiz-feedback"></div>
</div>
<button class="quiz-check-btn" onclick="checkQuiz('quiz-advisor-m2')">检查答案</button>
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-advisor-m2')">重做</button>
</div>
</div>
</div>
</section>
<section class="module" id="module-3">
<div class="module-inner">
<p class="eyebrow animate-in">模块 3 · 归属铁闸</p>
<h1 class="module-title animate-in">G-01 归属校验 +<br>run_tool 留痕</h1>
<p class="module-lead animate-in">
理财师能查谁的数据,由
<span class="term" data-definition="G-01 = 平台客户数据归属规则:customer 只能本人;advisor 只能名下 active 客户;risk_officer 全量只读。">G-01 归属校验</span>
决定。API 层越权抛 403;对话 Tool 层(对话 Tool 编排(tool_service.py))拒绝则落 <code>blocked</code> 行留痕,不抛异常挡整条链路。
</p>
<div class="screen animate-in">
<h2>两层校验,别混口径</h2>
<table style="width:100%; border-collapse:collapse; margin: 1.5rem 0; font-size: 0.95rem;">
<thead><tr><th>层级</th><th>位置</th><th>拒绝时</th></tr></thead>
<tbody>
<tr><td>API(四 Agent 对话 HTTP 入口(chat.py))</td><td><code>assert_customer_access</code></td><td>HTTP 403 + 鉴权审计</td></tr>
<tr><td>Tool(对话 Tool 编排(tool_service.py))</td><td><code>run_tool</code> 内归属检查</td><td><code>status='blocked'</code> + agent_tool_call 留痕</td></tr>
</tbody>
</table>
<div class="callout callout-accent">
<strong>advisor 规则:</strong> 角色含 advisor 时,<code>customer_advisor_rel</code> 里该客户与当前 <code>actor_id</code> 须为 active。查非名下客户 → API 403 或 Tool blocked(<code>AUTH_403_NOT_ASSIGNED</code>)。
</div>
</div>
<div class="screen animate-in">
<div class="translation-block">
<div class="translation-code">
<span class="translation-label">CODE · 对话 Tool 编排(tool_service.py)run_tool(节选)</span>
<pre><code><span class="code-line"><span class="code-keyword">def</span> run_tool(</span>
<span class="code-line"> tool_name, agent_type, actor, customer_id,</span>
<span class="code-line"> tool_input, session_id, trace_id=<span class="code-keyword">None</span></span>
<span class="code-line">) -> dict:</span>
<span class="code-line"> <span class="code-comment"># 1. 归属校验(对齐 模块鉴权工厂(deps.py)assert_customer_access)</span></span>
<span class="code-line"> denial = _check_access(agent_type, actor, customer_id)</span>
<span class="code-line"> <span class="code-keyword">if</span> denial:</span>
<span class="code-line"> <span class="code-keyword">return</span> _blocked_record(denial, ...)</span>
<span class="code-line"> <span class="code-comment"># 2. 执行 Core 只读 Tool 定义(core_tools.py)func</span></span>
<span class="code-line"> data = spec[<span class="code-string">"func"</span>](customer_id, ...)</span>
<span class="code-line"> <span class="code-comment"># 3. agent_tool_call 落库(失败降级 warning)</span></span>
<span class="code-line"> _persist_tool_call(status=<span class="code-string">"success"</span>, ...)</span></code></pre>
</div>
<div class="translation-english">
<span class="translation-label">白话</span>
<div class="translation-lines">
<p class="tl">run_tool 是顾问线查数据的唯一编排入口:先验你有没有资格看这个 customer_id。</p>
<p class="tl">blocked 不是 403——对话里会呈现「查询被拒绝」,同时 MySQL 留一行 tool 调用记录。</p>
<p class="tl">customer_id 参数由 四 Agent 对话 HTTP 入口(chat.py)会话注入;Tool 不信 LLM 或用户消息里随便写的客户号。</p>
</div>
</div>
</div>
</div>
<div class="screen animate-in">
<h2>相关文件</h2>
<div class="file-tree animate-in">
<div class="tree-item tree-folder">app/api/</div>
<div class="tree-item tree-indent">模块鉴权工厂(deps.py)模块鉴权工厂 — app/api/deps.py · assert_customer_access(G-01 权威口径)</div>
<div class="tree-item tree-indent">四 Agent 对话 HTTP 入口(chat.py)四 Agent 对话 HTTP 入口 — app/api/chat.py · 会话绑定 customer_id + 入口归属</div>
<div class="tree-item tree-folder">app/service/</div>
<div class="tree-item tree-indent">对话 Tool 编排(tool_service.py)Tool 编排 — match_intent + run_tool</div>
<div class="tree-item tree-indent">顾问通用 tool→llm→guard 编排(agent_service.py)顾问通用 tool→llm→guard 编排 — tool_node 调用 run_tool</div>
<div class="tree-item tree-folder">app/tool/</div>
<div class="tree-item tree-indent">Core 只读 Tool 定义(core_tools.py)Core 只读 Tool 定义 — query_holdings 等</div>
</div>
<div class="quiz-container" id="quiz-advisor-m3">
<div class="quiz-question-block"
data-correct="option-b"
data-explanation-right="对。Tool 层归属拒绝落 agent_tool_call status=blocked,对话继续;API 层才抛 403。"
data-explanation-wrong="run_tool 的 blocked 是留痕位,不是 HTTP 403;和 模块鉴权工厂(deps.py)抛异常是两层。">
<h3 class="quiz-question">理财师在对话里查非名下客户,对话 Tool 编排(tool_service.py)会怎么做?</h3>
<div class="quiz-options">
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>抛 PermissionDenied 整请求 403</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>返回 blocked 记录,LLM 可解释拒绝原因</span>
</button>
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>静默返回空数据当没查到</span>
</button>
</div>
<div class="quiz-feedback"></div>
</div>
<button class="quiz-check-btn" onclick="checkQuiz('quiz-advisor-m3')">检查答案</button>
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-advisor-m3')">重做</button>
</div>
</div>
</div>
</section>
<section class="module" id="module-4">
<div class="module-inner">
<p class="eyebrow animate-in">模块 4 · 未做与误区</p>
<h1 class="module-title animate-in">T-20 还没做<br>Neo4j 也不是对话 Tool</h1>
<p class="module-lead animate-in">
顾问线 chat 骨架已通,但<strong>草稿复核工作台、合规巡检、Neo4j 实时查询</strong>都还没进对话链路。
课程中心卡片写 Neo4j,容易让 AI 去改不存在的 Cypher 节点——这模块专门纠偏。
</p>
<div class="screen animate-in">
<h2>T-20 未实现清单(2026-09 快照)</h2>
<div class="pattern-cards">
<div class="pattern-card">
<h3>A-03 话术草稿</h3>
<p>无 <code>advisor_draft</code> 表流程 · 无复核 UI · prompt 里「草稿不外发」≠ 有工作台。</p>
</div>
<div class="pattern-card">
<h3>A-05 合规巡检</h3>
<p>合规专员抽检全量顾问会话 — 无前端/API。</p>
</div>
<div class="pattern-card">
<h3>A-06 跟进日志</h3>
<p>确认后回写 L2 — 对话线未入库跟进草稿。</p>
</div>
<div class="pattern-card">
<h3>已有</h3>
<p>顾问通用 tool→llm→guard 编排(<code>agent_service.py</code>)tool→llm→guard · G-01 归属 · <code>search_knowledge</code>(kb_product_rules)。</p>
</div>
</div>
<div class="callout callout-accent">
<strong>prompt 禁外发 ≠ 系统禁外发:</strong> 代码里没有「发给客户」API,但也没有强制 <code>review_status</code> 门禁——上线前必须补 T-20。
</div>
</div>
<div class="screen animate-in">
<h2>Neo4j:同步脚本,不是 LangGraph 节点</h2>
<div class="translation-block">
<div class="translation-code">
<span class="translation-label">实际存在 · Neo4j 关系图离线同步(sync_neo4j.py)/ 顾问归属离线同步(sync_advisor_rel.py)离线同步脚本</span>
<pre><code><span class="code-line">python scripts/sync/sync_neo4j.py</span>
<span class="code-line">python scripts/sync/sync_advisor_rel.py</span>
<span class="code-line"><span class="code-comment"># Neo4j 关系图离线同步(sync_neo4j.py)/ 顾问归属离线同步(sync_advisor_rel.py)离线同步脚本</span></span></code></pre>
</div>
<div class="translation-english">
<span class="translation-label">白话</span>
<div class="translation-lines">
<p class="tl">顾问对话<strong>不</strong>在运行时发 Cypher;名下客户靠 G-01 + Core 只读 Tool(<code>core_ro_tool.py</code>)。</p>
<p class="tl">指挥 AI「加 Neo4j Tool 查客户关系」= 新需求,不是修现有 bug。</p>
<p class="tl">Neo4j 数据来自 Neo4j 关系图离线同步(sync_neo4j.py)/ 顾问归属离线同步(sync_advisor_rel.py)batch sync,和 Chat 会话无实时联动。</p>
</div>
</div>
</div>
</div>
<div class="screen animate-in">
<h2>群聊:顾问想「一键发给客户」</h2>
<div class="chat-window" id="chat-advisor-m4">
<div class="chat-messages">
<div class="chat-message" data-msg="0" data-sender="adv" style="display:none">
<div class="chat-avatar" style="background:#8B6B4A">顾</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#8B6B4A">理财师</span>
<p>帮客户写一段持仓说明,我要发微信</p>
</div>
</div>
<div class="chat-message" data-msg="1" data-sender="llm" style="display:none">
<div class="chat-avatar" style="background:#6B5B4A">AI</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#6B5B4A">顾问通用 tool→llm→guard 编排(agent_service.py)</span>
<p>生成草稿文本(引用 fact_text)· 系统<strong>无</strong>外发通道</p>
</div>
</div>
<div class="chat-message" data-msg="2" data-sender="you" style="display:none">
<div class="chat-avatar" style="background:#2A7B9B">你</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#2A7B9B">正确指挥</span>
<p>先补 T-20 草稿表 + review_status,不要加 POST /send-to-customer</p>
</div>
</div>
</div>
<div class="chat-typing" id="chat-advisor-m4-typing" style="display:none">
<div class="chat-avatar" id="chat-advisor-m4-typing-avatar">…</div>
<div class="chat-typing-dots"><span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span></div>
</div>
<div class="chat-controls">
<button class="btn chat-next-btn">下一条</button>
<button class="btn chat-all-btn">全部播放</button>
<button class="btn chat-reset-btn">重播</button>
<span class="chat-progress"></span>
</div>
</div>
<div class="quiz-container" id="quiz-advisor-m4">
<div class="quiz-question-block"
data-correct="option-c"
data-explanation-right="对。Neo4j 是 Neo4j 关系图离线同步(sync_neo4j.py)/ 顾问归属离线同步(sync_advisor_rel.py)离线灌图;顾问 runtime 走 core_ro + G-01,不是 Cypher Tool。"
data-explanation-wrong="Neo4j 不在 顾问通用 tool→llm→guard 编排(agent_service.py)LangGraph 里,别按课程卡片字面去加 Cypher 节点。">
<h3 class="quiz-question">顾问 Chat 查「我名下有哪些客户」,代码走哪?</h3>
<div class="quiz-options">
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>Neo4j Cypher Tool</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>POST /api/chat/visitor</span>
</button>
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>顾问通用编排(agent_service.py)+ Core 只读 Tool(core_ro_tool.py)+ G-01 归属</span>
</button>
</div>
<div class="quiz-feedback"></div>
</div>
<button class="quiz-check-btn" onclick="checkQuiz('quiz-advisor-m4')">检查答案</button>
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-advisor-m4')">重做</button>
</div>
</div>
</div>
</section>
</main>
</body>
</html>