Files
group_xinghuo_jinrong/docs/course/jinrong-overview/index.html
T
zhanghongyu_0626 647c07062e feat(trade): Enhance trade processing and context handling in customer service
- Introduced `pending_trade` handling in the chat API to manage trade requests more effectively.
- Updated the `submit_trade_api` to allow advisors to access customer trades based on assigned roles.
- Added new methods in `GatewayRepository` for managing core holdings during trade subscriptions and redemptions.
- Implemented context-aware trade dialogue management in the customer service layer to improve user experience during multi-turn interactions.
- Enhanced the tool service to support trade actions and suitability checks, ensuring accurate processing of user requests.

This update significantly improves the trade interaction flow, providing a more robust and user-friendly experience for customers engaging in trading activities.
2026-09-13 17:45:04 +08:00

803 lines
48 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>JinRong 项目现状 · 交互导览</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: #2A7B9B;
--color-accent-hover: #1F6280;
--color-accent-light: #E4F2F7;
--color-accent-muted: #5A9DB8;
}
</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">JinRong 项目现状</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="四个角色" role="tab" aria-label="模块 2: 四个角色"></button>
<button class="nav-dot" data-target="module-3" data-tooltip="进门钥匙" role="tab" aria-label="模块 3: 进门钥匙"></button>
<button class="nav-dot" data-target="module-4" data-tooltip="数据从哪来" role="tab" aria-label="模块 4: 数据从哪来"></button>
<button class="nav-dot" data-target="module-5" data-tooltip="现在做到哪" role="tab" aria-label="模块 5: 现在做到哪"></button>
<button class="nav-dot" data-target="module-6" data-tooltip="硬阀门" role="tab" aria-label="模块 6: 硬阀门"></button>
<button class="nav-dot" data-target="module-7" data-tooltip="未实现对照" role="tab" aria-label="模块 7: 未实现对照"></button>
<button class="nav-dot" data-target="module-8" data-tooltip="答辩动线" role="tab" aria-label="模块 8: 答辩动线"></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">JinRong 是什么?<br>一次「问 AI」背后发生了什么</h1>
<p class="module-lead animate-in">
这是一个<strong>金融智能管家 Demo</strong>:四类人用四个不同的 AI 助手(客户 / 理财师 / 分析员 / 风控),
共用同一套数据和合规规则。没有真实银行 Core,用本地模拟库代替。
</p>
<div class="screen animate-in">
<h2>你已经在用的能力</h2>
<ul>
<li>登录后看<strong>持仓、流水、产品行情</strong>(平台只读 API)</li>
<li>和 AI <strong>对话</strong>(同步或流式 SSE)</li>
<li>分析员<strong>问数</strong>(自然语言 → SQL → 表格)</li>
<li>风控看<strong>预警台账</strong>、做<strong>模拟交易</strong>造演示数据</li>
</ul>
<div class="callout callout-accent">
<strong>为什么要懂这些?</strong> 你指挥 AI 改功能时,得知道「这是对话线还是平台 API」「要不要带 X-Agent-Type」——否则改完浏览器 401,AI 也会绕圈。
</div>
</div>
<div class="screen animate-in">
<h2>想象:客户打开「客户助手」发一句话</h2>
<p>下面是一次真实链路的简化版(2026-09 现状:客户线已走<span class="term" data-definition="SSE = Server-Sent Events,服务器像打字一样一段段把 AI 回复推给浏览器,不用等整段话生成完。">SSE 流式</span>)。</p>
<div class="flow-animation" data-steps='[
{"highlight":"flow-actor-1","label":"浏览器:登录拿到 JWT,打开 ChatPanel"},
{"highlight":"flow-actor-2","label":"POST /api/chat/stream + X-Agent-Type: customer","packet":true,"from":"1","to":"2"},
{"highlight":"flow-actor-3","label":"四 Agent 对话 HTTP 入口(chat.py)分流 → 登录客户 14 节点编排(customer_service.py)跑完 LangGraph","packet":true,"from":"2","to":"3"},
{"highlight":"flow-actor-4","label":"查 Core 模拟库 / Milvus 知识库,再拼回复","packet":true,"from":"3","to":"4"},
{"highlight":"flow-actor-1","label":"SSE 分块返回 + MySQL 落会话历史","packet":true,"from":"4","to":"1"}
]'>
<div class="flow-actors">
<div class="flow-actor" id="flow-actor-1"><span class="flow-actor-icon">🖥</span><span>浏览器 web/</span></div>
<div class="flow-actor" id="flow-actor-2"><span class="flow-actor-icon">🚪</span><span>四 Agent 对话 HTTP 入口(chat.py)四 Agent 对话 HTTP 入口</span></div>
<div class="flow-actor" id="flow-actor-3"><span class="flow-actor-icon">🤖</span><span>登录客户 14 节点编排(customer_service.py)登录客户 14 节点编排</span></div>
<div class="flow-actor" id="flow-actor-4"><span class="flow-actor-icon">🗄</span><span>Core + Milvus</span></div>
</div>
<p class="flow-step-label">点击「下一步」看数据怎么流</p>
<div class="flow-controls">
<button class="btn flow-next-btn">下一步</button>
<button class="btn flow-reset-btn">重来</button>
</div>
</div>
</div>
<div class="screen animate-in">
<div class="translation-block">
<div class="translation-code">
<span class="translation-label">入口</span>
<pre><code><span class="code-line"><span class="code-comment"># web/src/api/chat.ts</span></span>
<span class="code-line"><span class="code-string">'X-Agent-Type'</span>: agentType, <span class="code-comment">// customer | risk | …</span></span>
<span class="code-line">Authorization: <span class="code-string">`Bearer ${token}`</span></span></code></pre>
</div>
<div class="translation-english">
<span class="translation-label">白话</span>
<div class="translation-lines">
<p class="tl">前端每次调「对话 API」都要带两样东西:登录令牌 + 你正在用哪条 Agent 线。</p>
<p class="tl">后端据此决定走客服 LangGraph 还是通用 顾问通用 tool→llm→guard 编排(agent_service.py),以及能不能过门禁。</p>
</div>
</div>
</div>
<div class="quiz-container" id="quiz-module1">
<div class="quiz-question-block"
data-correct="option-b"
data-explanation-right="对。问数走 /api/analyst/*,用平台鉴权,不要 X-Agent-Type。"
data-explanation-wrong="对话线才要 X-Agent-Type。问数和读客户档案一样,只带 JWT 即可。">
<h3 class="quiz-question">分析员「问数工作台」调 API 时,要不要带 X-Agent-Type?</h3>
<div class="quiz-options">
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>要,填 analyst</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>不要,只带 Bearer JWT</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-module1')">检查答案</button>
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-module1')">重做</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">四个 Agent,<br>各干各的活</h1>
<p class="module-lead animate-in">
它们<strong>不互相打电话</strong>(不调彼此的 LLM)。要共享信息,走数据库里的画像层、预警表、平台 API。
像一栋楼里的四个窗口,共用同一套档案室。
</p>
<div class="screen animate-in">
<h2>四个窗口分别是谁</h2>
<div class="pattern-cards">
<div class="pattern-card">
<h3>客户财富 Agent</h3>
<p>服务登录客户本人:查持仓/流水、产品规则 RAG、合规话术。代码:登录客户 14 节点编排(<code>customer_service.py</code>)登录客户 14 节点编排</p>
</div>
<div class="pattern-card">
<h3>代理人助手</h3>
<p>服务理财师:聊名下客户、公开知识库。代码:顾问通用 tool→llm→guard 编排(<code>agent_service.py</code>)顾问通用 tool→llm→guard 编排 + advisor 分支</p>
</div>
<div class="pattern-card">
<h3>数据分析 Agent</h3>
<p>NL→SQL 问数、看板 metrics、口径资产。独立路由 <code>/api/analyst/*</code></p>
</div>
<div class="pattern-card">
<h3>风控监测 Agent</h3>
<p>预警台账、适当性、AML、模拟交易触发规则。代码:<code>app/service/risk/*</code></p>
</div>
</div>
</div>
<div class="screen animate-in">
<h2>群聊:一次请求里谁跟谁说话</h2>
<p>理财师问「客户 CUST-9527 持仓如何」——简化对话:</p>
<div class="chat-window" id="chat-module2">
<div class="chat-messages">
<div class="chat-message" data-msg="0" data-sender="browser" style="display:none">
<div class="chat-avatar" style="background:#2A7B9B">前</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#2A7B9B">浏览器</span>
<p>POST /api/chat/stream · X-Agent-Type: advisor</p>
</div>
</div>
<div class="chat-message" data-msg="1" data-sender="chat" style="display:none">
<div class="chat-avatar" style="background:#5A9DB8">API</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#5A9DB8">四 Agent 对话 HTTP 入口(chat.py)</span>
<p>验 JWT + 矩阵准入 → 建/续 session</p>
</div>
</div>
<div class="chat-message" data-msg="2" data-sender="tool" style="display:none">
<div class="chat-avatar" style="background:#1F6280">Tool</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#1F6280">对话 Tool 编排(tool_service.py)</span>
<p>关键词命中「持仓」→ query_holdings → 查 core_ro</p>
</div>
</div>
<div class="chat-message" data-msg="3" data-sender="llm" style="display:none">
<div class="chat-avatar" style="background:#D4A843">AI</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#D4A843">DeepSeek</span>
<p>把 Tool 结果组织成自然语言,SSE 推回前端</p>
</div>
</div>
</div>
<div class="chat-typing" id="chat-module2-typing" style="display:none">
<div class="chat-avatar" id="chat-module2-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">
<h2>代码地图(改功能前先找门)</h2>
<div class="file-tree animate-in">
<div class="tree-item tree-folder">app/</div>
<div class="tree-item tree-indent">FastAPI 应用入口(main.py)FastAPI 应用入口 — 挂载所有路由</div>
<div class="tree-item tree-indent">四 Agent 对话 HTTP 入口(chat.py)四 Agent 对话 HTTP 入口 — app/api/chat.py</div>
<div class="tree-item tree-indent">问数 REST 路由(analyst.py)问数 REST 路由 — app/api/analyst.py</div>
<div class="tree-item tree-indent">风控 REST(risk.py)风控 REST — app/api/risk.py · 模拟交易 HTTP 入口(simulate.py)模拟交易 HTTP 入口 — app/api/simulate.py</div>
<div class="tree-item tree-indent">登录客户 14 节点编排(customer_service.py)登录客户 14 节点编排 — app/service/customer_service.py</div>
<div class="tree-item tree-indent">顾问通用 tool→llm→guard 编排(agent_service.py)顾问通用 tool→llm→guard 编排 — app/service/agent_service.py</div>
<div class="tree-item tree-folder">web/</div>
<div class="tree-item tree-indent">src/pages/* — 四角色页面</div>
<div class="tree-item tree-indent">src/api/*.ts — 调后端(注意鉴权头差异)</div>
</div>
<div class="quiz-container" id="quiz-module2">
<div class="quiz-question-block"
data-correct="option-c"
data-explanation-right="客户线已独立:同步 POST /api/chat 和 SSE stream 都走 登录客户 14 节点编排(customer_service.py),不是 顾问通用 tool→llm→guard 编排(agent_service.py)那条通用图。"
data-explanation-wrong="customer 在 四 Agent 对话 HTTP 入口(chat.py)里有专门 if 分支,不会和 advisor 共用同一张 LangGraph。">
<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>顾问通用 tool→llm→guard 编排(agent_service.py)通用图</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>analyst_agent 问数</span>
</button>
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>登录客户 14 节点编排(customer_service.py)LangGraph</span>
</button>
</div>
<div class="quiz-feedback"></div>
</div>
<button class="quiz-check-btn" onclick="checkQuiz('quiz-module2')">检查答案</button>
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-module2')">重做</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">JWT 与 X-Agent-Type:<br>两套门禁别混</h1>
<p class="module-lead animate-in">
<span class="term" data-definition="JWT 像一张带防伪的工牌:服务器验签后知道你是谁(客户还是员工)、有什么角色。">JWT</span>
证明「你是谁」;<span class="term" data-definition="HTTP 请求头里的一行额外信息,告诉后端这次请求走哪条 Agent 业务线。">X-Agent-Type</span>
证明「你正在敲哪扇窗」。本项目里<strong>有两套验法</strong>,混用会 401。
</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>X-Agent-Type</th></tr></thead>
<tbody>
<tr><td>对话 / 风控 REST</td><td><code>get_auth_context</code></td><td><strong>必须</strong>(customer/advisor/risk/analyst)</td></tr>
<tr><td>平台只读 / 问数</td><td><code>get_platform_auth_context</code></td><td><strong>不要</strong></td></tr>
<tr><td>模拟交易</td><td><code>get_auth_context</code></td><td>要;且角色需 risk_demo 或客户本人</td></tr>
</tbody>
</table>
<div class="callout callout-warning">
<strong>真实 bug 已修:</strong> 风控台账 <code>risk.ts</code> 曾缺 <code>X-Agent-Type: risk</code>,JWT 正确也会 401。指挥 AI 接新 REST 时记得对照这条表。
</div>
</div>
<div class="screen animate-in">
<div class="translation-block">
<div class="translation-code">
<span class="translation-label">CODE · 模块鉴权工厂(deps.py)模块鉴权工厂</span>
<pre><code><span class="code-line">agent_type = request.headers.get(<span class="code-string">"X-Agent-Type"</span>)</span>
<span class="code-line"><span class="code-keyword">if not</span> agent_type:</span>
<span class="code-line"> raise ApiError(<span class="code-number">401</span>, <span class="code-string">"AUTH_401_MISSING_AGENT_TYPE"</span>)</span>
<span class="code-line">assert_agent_access(auth, agent_type, ...)</span></code></pre>
</div>
<div class="translation-english">
<span class="translation-label">白话</span>
<div class="translation-lines">
<p class="tl">读请求头:你声明自己是 customer 还是 risk?</p>
<p class="tl">没声明 → 直接 401,连业务代码都进不去。</p>
<p class="tl">再查矩阵:STAFF-30001 能不能走 risk 线?客户 token 能不能走 advisor 线?</p>
</div>
</div>
</div>
</div>
<div class="screen animate-in">
<h2>Demo 账号(本地登录页一键用)</h2>
<div class="badge-list">
<div class="badge-item"><span class="badge-code">CUST-9527</span><span class="badge-desc">客户</span></div>
<div class="badge-item"><span class="badge-code">STAFF-10086</span><span class="badge-desc">理财师</span></div>
<div class="badge-item"><span class="badge-code">STAFF-20001</span><span class="badge-desc">分析员</span></div>
<div class="badge-item"><span class="badge-code">STAFF-30001</span><span class="badge-desc">风控 + risk_demo</span></div>
</div>
<p>改 宿主 JWT 签发/验签(<code>jwt_service.py</code>)宿主 JWT 签发/验签 里角色后,<strong>必须重新登录</strong> 才会拿到新 JWT。</p>
<div class="quiz-container" id="quiz-module3">
<div class="quiz-question-block"
data-correct="option-a"
data-explanation-right="GET /api/customers/... 走 platform 鉴权,前端 apiFetch 只带 Bearer。"
data-explanation-wrong="平台读 API 故意不要 X-Agent-Type,和 chat 线不同。">
<h3 class="quiz-question">客户「我的持仓」页调 /api/customers/.../holdings,要带 X-Agent-Type 吗?</h3>
<div class="quiz-options">
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>不要</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>要,填 customer</span>
</button>
</div>
<div class="quiz-feedback"></div>
</div>
<button class="quiz-check-btn" onclick="checkQuiz('quiz-module3')">检查答案</button>
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-module3')">重做</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">双 MySQL、Redis、<br>模拟 Core 与风控引擎</h1>
<p class="module-lead animate-in">
没有真实银行 Core。L0 事实(客户、持仓、流水)在 <code>jinrong_core</code> 模拟库;
Agent 会话、预警、审计在 <code>jinrong_agent</code>。像<strong>前台展示用的假金库 + 后台自己的笔记本</strong>。
</p>
<div class="screen animate-in">
<h2>分层速记</h2>
<ul>
<li><strong>L0</strong> — <code>jinrong_core</code> 只读(Tool / 问数 SQL _guard 白名单)</li>
<li><strong>L1/L2/L3 画像</strong> — agent 库 MySQL 表(客户/顾问/风控 enrich)</li>
<li><strong>会话窗口</strong> — Redis(TTL 2h,挂了会降级回 MySQL)</li>
<li><strong>RAG</strong> — Milvus 向量库 + <code>data/kb/</code> 产品手册</li>
</ul>
</div>
<div class="screen animate-in">
<h2>风控演示:模拟交易 → 出预警</h2>
<div class="flow-animation" data-steps='[
{"highlight":"flow-actor-1","label":"风控 Demo 提交 POST /api/simulate/trade"},
{"highlight":"flow-actor-2","label":"trade_gateway 写 core_trade(唯一允许 INSERT Core 的路径)","packet":true,"from":"1","to":"2"},
{"highlight":"flow-actor-3","label":"risk 规则引擎扫描 → 聚合写入 risk_alert","packet":true,"from":"2","to":"3"},
{"highlight":"flow-actor-4","label":"Redis Pub/Sub 可选推送;台账页 GET /api/risk/alerts 可见","packet":true,"from":"3","to":"4"}
]'>
<div class="flow-actors">
<div class="flow-actor" id="flow-actor-1"><span class="flow-actor-icon">🧪</span><span>模拟交易 UI</span></div>
<div class="flow-actor" id="flow-actor-2"><span class="flow-actor-icon">⚙</span><span>trade_gateway</span></div>
<div class="flow-actor" id="flow-actor-3"><span class="flow-actor-icon">🛡</span><span>risk_engine</span></div>
<div class="flow-actor" id="flow-actor-4"><span class="flow-actor-icon">📋</span><span>risk_alert 表</span></div>
</div>
<p class="flow-step-label">A-3 预设:CUST-3001 · 50 万 · 大额预警</p>
<div class="flow-controls">
<button class="btn flow-next-btn">下一步</button>
<button class="btn flow-reset-btn">重来</button>
</div>
</div>
<div class="translation-block">
<div class="translation-code">
<span class="translation-label">问数 · sql_guard</span>
<pre><code><span class="code-line"><span class="code-comment"># 分析 Agent 只读 SELECT,customer 域强制 self</span></span>
<span class="code-line">assert_analyst_query_access(auth)</span>
<span class="code-line"><span class="code-comment"># 客户问数:仅趋势/统计,尾部「AI 分析有风险」</span></span></code></pre>
</div>
<div class="translation-english">
<span class="translation-label">白话</span>
<div class="translation-lines">
<p class="tl">问数不是随便跑 SQL:先过权限域,再过只读与白名单。</p>
<p class="tl">客户角色不能查别人的数据,也不能给投资建议(产品规则写死在 guardrail)。</p>
</div>
</div>
</div>
</div>
<div class="screen animate-in">
<div class="quiz-container" id="quiz-module4">
<div class="quiz-question-block"
data-correct="option-b"
data-explanation-right="Core 正式账 C1~C5 禁止被 Agent 画像覆盖;只有 gateway 模拟 INSERT 交易。"
data-explanation-wrong="画像写 L1/L2/L3 可以,但不能动 Core 里的正式客户等级等 C 类字段。">
<h3 class="quiz-question">项目硬规则:Agent 能不能直接改 Core 里客户的「正式风险等级」?</h3>
<div class="quiz-options">
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>可以,方便演示</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>不可以,Core 只读(模拟交易除外)</span>
</button>
</div>
<div class="quiz-feedback"></div>
</div>
<button class="quiz-check-btn" onclick="checkQuiz('quiz-module4')">检查答案</button>
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-module4')">重做</button>
</div>
</div>
</div>
</section>
<section class="module" id="module-5">
<div class="module-inner">
<p class="eyebrow animate-in">模块 5 · 现状与下一步</p>
<h1 class="module-title animate-in">现在做到哪?<br>怎么本地跑起来</h1>
<p class="module-lead animate-in">
分支 <code>merger</code> · 测试基线 <strong>825 pytest</strong> · 前端 <strong>22 Vitest</strong> · Redis Docker <strong>6380</strong>。
文档入口:<code>docs/memory/MEMORY.md</code> §0 · 答辩提纲 <code>docs/答辩/答辩知识点清单.md</code> · 滚动版见<strong>模块 8</strong>。
</p>
<div class="screen animate-in">
<h2>已完成(2026-09-10 快照)</h2>
<div class="pattern-cards">
<div class="pattern-card"><h3>✓ 平台 API v0.1</h3><p>客户/产品/顾问/合规只读 REST</p></div>
<div class="pattern-card"><h3>✓ 四 Agent 对话 + SSE</h3><p>客服 Wave3 · 游客 1B · customer 14 节点</p></div>
<div class="pattern-card"><h3>✓ 问数 S3 + D-06</h3><p>模板填参 · 结果缓存 + 写侧 bump · 问数 UI 标签</p></div>
<div class="pattern-card"><h3>✓ 风控 + 前端 P0</h3><p>台账/模拟/AML 页 · <code>prepare_all.ps1</code> 灌库</p></div>
</div>
</div>
<div class="screen animate-in">
<h2>仍开放(指挥 AI 前先心里有数)</h2>
<ul>
<li>接口契约发群(稿在 <code>docs/项目管理/接口契约发群-2026-09-09.md</code>)</li>
<li>风控演示种子 / cron 脚本(运维向,见 TODO)</li>
<li>画像 Redis L1/L2 热缓存未全接</li>
<li>口径字典种子 SQL 需本机执行一次</li>
</ul>
</div>
<div class="screen animate-in">
<h2>本地启动(最小路径)</h2>
<div class="step-cards">
<div class="step-card"><span class="step-num">1</span><p><code>pip install -r requirements.txt</code></p></div>
<div class="step-card"><span class="step-num">2</span><p><code>.\scripts\demo\prepare_all.ps1</code> 或 <code>.\scripts\core\reset.ps1</code></p></div>
<div class="step-card"><span class="step-num">3</span><p><code>.\scripts\dev\start-redis.ps1</code> → 6380</p></div>
<div class="step-card"><span class="step-num">4</span><p><code>uvicorn app.main:app --reload</code> :8000</p></div>
<div class="step-card"><span class="step-num">5</span><p><code>cd web && npm run dev</code> → :5173 代理 API</p></div>
<div class="step-card"><span class="step-num">6</span><p><code>python -m pytest</code> 验收 825 绿</p></div>
</div>
<div class="callout callout-accent">
<strong>改后端后:</strong> 若浏览器问数 404,多半是 :8000 的 uvicorn 没重启,OpenAPI 里还缺新路由。
</div>
</div>
<div class="screen animate-in">
<h2>情景题:AI 说「问数和 Chat 用同一套鉴权」</h2>
<div class="quiz-container" id="quiz-module5">
<div class="quiz-question-block"
data-correct="option-c"
data-explanation-right="应纠正 AI:问数走 get_platform_auth_context,Chat 走 get_auth_context + X-Agent-Type。两套不能混。"
data-explanation-wrong="这是本项目的核心接缝设计,写错会导致 401 或越权。">
<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>好的,都加 X-Agent-Type: analyst</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>都去掉 JWT,用 debug 头</span>
</button>
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>不对,对照 MEMORY/发群稿里两套鉴权表</span>
</button>
</div>
<div class="quiz-feedback"></div>
</div>
<button class="quiz-check-btn" onclick="checkQuiz('quiz-module5')">检查答案</button>
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-module5')">重做</button>
</div>
<div class="callout">
<strong>想继续深入?</strong> 模块 8 有<strong>答辩 5~8 分钟动线</strong>;或打开 <a href="../index.html" style="color:var(--accent)">课程中心</a> 按角色深潜。
</div>
<p class="module-footer-note">课程更新 2026-09-10 · 对照 <code>docs/memory/MEMORY.md</code> 与 <code>docs/答辩/答辩知识点清单.md</code></p>
</div>
</div>
</section>
<section class="module" id="module-6">
<div class="module-inner">
<p class="eyebrow animate-in">模块 6 · 硬阀门</p>
<h1 class="module-title animate-in">改代码前先念五条<br>踩了就是合规事故</h1>
<p class="module-lead animate-in">
四 Agent 可以各自演进,但有几条是<strong>全项目硬阀门</strong>——不是「最好遵守」,是写进合规文档、测试也会卡住的规则。
指挥 AI 加功能前,先用这张表判断「能不能做」。
</p>
<div class="screen animate-in">
<h2>五条硬阀门(2026-09 快照)</h2>
<div class="pattern-cards">
<div class="pattern-card">
<h3>① Core 正式 C1~C5 不可被画像覆盖</h3>
<p>L0 风评在 <code>jinrong_core</code> 是权威;L1 画像 enrich 只能辅助,不能改正式等级。</p>
</div>
<div class="pattern-card">
<h3>② 审计表只 INSERT</h3>
<p><code>audit_log</code> / <code>input_guard_log</code> 等只追加、不 UPDATE/DELETE——留痕不可篡改。</p>
</div>
<div class="pattern-card">
<h3>③ 仅 R-02 可阻断交易</h3>
<p>适当性不匹配可在网关拦单;R-01/R-03 只生成预警,<strong>不自动冻户</strong>。</p>
</div>
<div class="pattern-card">
<h3>④ 代理人草稿不外发</h3>
<p>顾问 Agent 产出是草稿;没有「一键发给客户」API,须人工复核后才能外用。</p>
</div>
<div class="pattern-card">
<h3>⑤ 四 Agent 不互调 LLM</h3>
<p>跨角色协作走 L1/L2/L3 画像表与 <code>risk_alert</code>,禁止 A 调 B 的 LangGraph。</p>
</div>
</div>
<div class="callout callout-warning">
<strong>还有技术硬阀门:</strong> Core 模拟库除模拟交易网关外<strong>只读</strong>;Streamlit 禁止引入(MEMORY 第 3 节)。
</div>
</div>
<div class="screen animate-in">
<h2>数据流:谁有权改什么</h2>
<div class="flow-animation" data-steps='[
{"highlight":"flow-h-1","label":"客户问持仓 → Core 只读 Tool(core_ro_tool.py)只读 SELECT"},
{"highlight":"flow-h-2","label":"模拟下单 → 仅 gateway_repository 可 INSERT core_trade","packet":true,"from":"1","to":"2"},
{"highlight":"flow-h-3","label":"风控预警 → risk_alert 待人工 pending_review,不自动改账户状态","packet":true,"from":"2","to":"3"},
{"highlight":"flow-h-4","label":"audit_log → 只 INSERT,任何 UPDATE 都是违规","packet":true,"from":"3","to":"4"}
]'>
<div class="flow-actors">
<div class="flow-actor" id="flow-h-1"><span class="flow-actor-icon">📖</span><span>Core 只读</span></div>
<div class="flow-actor" id="flow-h-2"><span class="flow-actor-icon">💳</span><span>模拟网关</span></div>
<div class="flow-actor" id="flow-h-3"><span class="flow-actor-icon">⚠</span><span>预警台账</span></div>
<div class="flow-actor" id="flow-h-4"><span class="flow-actor-icon">📋</span><span>审计总账</span></div>
</div>
<p class="flow-step-label">点击「下一步」看写入边界</p>
<div class="flow-controls">
<button class="btn flow-next-btn">下一步</button>
<button class="btn flow-reset-btn">重来</button>
</div>
</div>
</div>
<div class="screen animate-in">
<div class="quiz-container" id="quiz-overview-m6">
<div class="quiz-question-block"
data-correct="option-b"
data-explanation-right="对。R-01 大额等只生成 pending_review 预警,冻户必须风控专员人工处置,Agent 不能自动冻。"
data-explanation-wrong="只有 R-02 适当性可在交易网关阻断;其他预警不自动冻户。">
<h3 class="quiz-question">AI 建议「大额预警命中就自动冻结账户」,你怎么回复?</h3>
<div class="quiz-options">
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>好,在 risk_engine 里加 freeze 分支</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>不行,只生成预警单,处置走 POST /api/risk/alerts/{id}/handle</span>
</button>
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>可以,让客服 Agent 调风控 LLM 冻户</span>
</button>
</div>
<div class="quiz-feedback"></div>
</div>
<button class="quiz-check-btn" onclick="checkQuiz('quiz-overview-m6')">检查答案</button>
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-overview-m6')">重做</button>
</div>
<p class="module-footer-note">深潜各模块的硬阀门细节见对应课程 · 下一模块:未实现对照表</p>
</div>
</div>
</section>
<section class="module" id="module-7">
<div class="module-inner">
<p class="eyebrow animate-in">模块 7 · 未实现对照</p>
<h1 class="module-title animate-in">文档写了 ≠ 代码有<br>2026-09 快照表</h1>
<p class="module-lead animate-in">
指挥 AI 时最容易把「需求规格里的 P0」当成「仓库已经做完」。
下表是<strong>课程专用快照</strong>(不写入项目记忆文件),对照 <code>merger</code> 分支实际代码。
</p>
<div class="screen animate-in">
<h2>已实现(别重复造轮子)</h2>
<div class="badge-list">
<div class="badge-item"><span class="badge-code">✓</span><span class="badge-desc">平台 API v0.1 · 四 Agent 对话 + customer SSE</span></div>
<div class="badge-item"><span class="badge-code">✓</span><span class="badge-desc">问数 S3 + D-06 模板/结果缓存 · 风控台账 + 模拟交易 + 前端四页</span></div>
<div class="badge-item"><span class="badge-code">✓</span><span class="badge-desc">客服 14 节点 + 游客 9 节点 · R-02 网关阻断</span></div>
</div>
</div>
<div class="screen animate-in">
<h2>未实现 / 演示停在某层(指挥 AI 前先查)</h2>
<div class="pattern-cards">
<div class="pattern-card">
<h3>空壳 API</h3>
<p>知识库空壳 API(<code>knowledge.py</code>)· 管理空壳 API(<code>admin.py</code>)空壳 API — T-21 拍板一期只做脚本入库,无上传/重建端点。</p>
</div>
<div class="pattern-card">
<h3>T-20 顾问工作台</h3>
<p>A-03 话术草稿复核 · A-05 合规巡检台 · A-06 跟进草稿入库 — prompt 有约束,无前端/表流程。</p>
</div>
<div class="pattern-card">
<h3>前端仍简版</h3>
<p>风控 P1 已接(筛选/适当性/AML)· 看板<strong>无钻取</strong> · 分析 Chat 仍占位 · 平台适当性按钮部分未接。</p>
</div>
<div class="pattern-card">
<h3>运维 cron 无 UI</h3>
<p>FR-9 超期升级 cron(<code>escalation_scan.py</code>)FR-9 cron · FR-10 行为扫描 cron(<code>agent_behavior_scan.py</code>)FR-10 cron — 引擎在,须手动跑脚本。</p>
</div>
<div class="pattern-card">
<h3>演示边界</h3>
<p>转人工 = API 字段 + 橙色 Tag · debug 头仅 dev · 脱敏默认关 · 无真呼叫中心排队。</p>
</div>
<div class="pattern-card">
<h3>画像热缓存</h3>
<p>L1 Redis 全量热缓存 · L2 Repository — 客服线部分已接,宿主/顾问未全接。</p>
</div>
</div>
<div class="callout callout-accent">
<strong>注意:</strong> <code>REQUIREMENTS.md</code> 部分 Wave 条目仍标「未做」,与代码不同步——以本表 + 各模块深潜课为准。
</div>
</div>
<div class="screen animate-in">
<h2>鉴权三线速查(跨课总表)</h2>
<div class="translation-block">
<div class="translation-code">
<span class="translation-label">三条入口</span>
<pre><code><span class="code-line">对话 Chat → get_auth_context + X-Agent-Type</span>
<span class="code-line">问数/平台 → get_platform_auth_context(无 Agent 头)</span>
<span class="code-line">风控 REST → get_auth_context + X-Agent-Type: risk</span></code></pre>
</div>
<div class="translation-english">
<span class="translation-label">白话</span>
<div class="translation-lines">
<p class="tl">401 <code>MISSING_AGENT_TYPE</code>:Chat 忘了带头,不是 JWT 坏了。</p>
<p class="tl">问数页加 <code>X-Agent-Type: analyst</code> 是<strong>错方向</strong>——会走错鉴权栈。</p>
<p class="tl">持仓页用平台鉴权,不要 Agent 头——和 Chat 不是一套。</p>
</div>
</div>
</div>
<div class="quiz-container" id="quiz-overview-m7">
<div class="quiz-question-block"
data-correct="option-c"
data-explanation-right="对。T-21 一期知识库走 知识库脚本入库(scripts/kb/build_kb.py)脚本入库,空壳 knowledge API 是刻意的。"
data-explanation-wrong="知识库空壳 API(knowledge.py)空壳是拍板结果,不是「忘了做」——别指挥 AI 直接补上传 API 而不对契约。">
<h3 class="quiz-question">AI 说「补全 knowledge 上传 API 就能用 RAG」,你怎么回?</h3>
<div class="quiz-options">
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>好,在 知识库空壳 API(knowledge.py)加 POST /upload</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>好,让前端直连 Milvus</span>
</button>
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>先查 T-21 拍板:一期脚本入库,上传端点不做</span>
</button>
</div>
<div class="quiz-feedback"></div>
</div>
<button class="quiz-check-btn" onclick="checkQuiz('quiz-overview-m7')">检查答案</button>
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-overview-m7')">重做</button>
</div>
<p class="module-footer-note">快照日期 2026-09-10 · 仅用于交互课程 · 深潜见 <a href="../index.html" style="color:var(--accent)">课程中心</a></p>
</div>
</div>
</section>
<section class="module" id="module-8">
<div class="module-inner">
<p class="eyebrow animate-in">模块 8 · 答辩动线</p>
<h1 class="module-title animate-in">5~8 分钟 Demo<br>+ 必背铁律</h1>
<p class="module-lead animate-in">
对照 <code>docs/答辩/答辩稿.md</code> 与 <code>DEMO-SOP-答辩全流程.md</code>:先讲清<strong>四角色不互调 LLM</strong>与合规铁律,再按动线<strong>展示业务</strong>,收口讲<strong>四角色价值</strong>(不口播缺陷与测试数)。
手册 <code>docs/答辩/README.md</code> · 幻灯片 · 答辩稿 · 功能测试(内部)。
</p>
<div class="screen animate-in">
<h2>30 秒总架构(开口即说)</h2>
<div class="pattern-cards">
<div class="pattern-card">
<h3>分层</h3>
<p>浏览器 → FastAPI → 编排 service → tool/repository → 双库 MySQL + Redis + Milvus</p>
</div>
<div class="pattern-card">
<h3>四条入口</h3>
<p>Chat · 客服 14 节点 · 问数 + analyze · <strong>/api/advisor-agent</strong> 顾问 REST(2026-09-12 合入)</p>
</div>
<div class="pattern-card">
<h3>铁律</h3>
<p>L0 画像不可覆盖 · 仅 R-02 可阻断交易 · 风控不自动冻户 · 审计只 INSERT</p>
</div>
</div>
</div>
<div class="screen animate-in">
<h2>推荐演示顺序(动画)</h2>
<div class="flow-animation" data-steps='[
{"highlight":"flow-actor-1","label":"① 登录 → Dashboard(平台只读 API 从 Core 来)"},
{"highlight":"flow-actor-2","label":"② 客户:持仓 / Chat SSE 或阈值提醒(Core + 1B sanitize)","packet":true,"from":"1","to":"2"},
{"highlight":"flow-actor-3","label":"③ 风控:模拟交易 → 台账 pending → 人工处置(事件线闭环)","packet":true,"from":"2","to":"3"},
{"highlight":"flow-actor-4","label":"④ 问数:「客户总数」→ 看模板命中标签 + SQL 折叠(D-06 + 护栏)","packet":true,"from":"3","to":"4"},
{"highlight":"flow-actor-1","label":"⑤ 可选:风控 Chat 只读查预警(强调不能改状态)","packet":true,"from":"4","to":"1"}
]'>
<div class="flow-actors">
<div class="flow-actor" id="flow-actor-1"><span class="flow-actor-icon">🖥</span><span>登录/Dashboard</span></div>
<div class="flow-actor" id="flow-actor-2"><span class="flow-actor-icon">💬</span><span>客户线</span></div>
<div class="flow-actor" id="flow-actor-3"><span class="flow-actor-icon">🛡</span><span>风控线</span></div>
<div class="flow-actor" id="flow-actor-4"><span class="flow-actor-icon">📊</span><span>问数线</span></div>
</div>
<p class="flow-step-label">灌库:<code>prepare_all.ps1</code> + <code>seed_advisor_agent.ps1</code> · SOP + 幻灯片见 <code>docs/答辩/</code></p>
<div class="flow-controls">
<button class="btn flow-next-btn">下一步</button>
<button class="btn flow-reset-btn">重来</button>
</div>
</div>
</div>
<div class="screen animate-in">
<h2>群聊:评委问「四个 Agent 为什么不互相打电话?」</h2>
<div class="chat-window" id="chat-defense-m8">
<div class="chat-messages">
<div class="chat-message" data-msg="0" data-sender="j" style="display:none">
<div class="chat-avatar" style="background:#2A7B9B">评委</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#2A7B9B">评委</span>
<p>风控 Agent 能不能直接让客服 Agent 去安抚客户?</p>
</div>
</div>
<div class="chat-message" data-msg="1" data-sender="you" style="display:none">
<div class="chat-avatar" style="background:#5A9DB8">你</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#5A9DB8">答辩口径</span>
<p>不互调 LLM。跨线靠<strong>表</strong>(如 <code>risk_alert</code>)和平台只读 API;审计按 Agent 边界留痕,避免一条链路里混处置权限。</p>
</div>
</div>
<div class="chat-message" data-msg="2" data-sender="j" style="display:none">
<div class="chat-avatar" style="background:#2A7B9B">评委</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#2A7B9B">评委</span>
<p>那数据怎么同步?</p>
</div>
</div>
<div class="chat-message" data-msg="3" data-sender="you" style="display:none">
<div class="chat-avatar" style="background:#5A9DB8">你</div>
<div class="chat-bubble">
<span class="chat-sender" style="color:#5A9DB8">答辩口径</span>
<p>交易写 Core → 风控引擎出单进 agent 库;客服查持仓走 Core RO Tool,不读风控写侧。问数只统计,<strong>不能</strong>调处置 API。</p>
</div>
</div>
</div>
<button class="btn chat-play-btn" data-chat="chat-defense-m8">播放对话</button>
</div>
</div>
<div class="screen animate-in">
<h2>诚实边界(主动说加分)</h2>
<ul>
<li><strong>客户交易 2026-09-13</strong>:选购清单 · Modal · holding 同步(工作区未 commit)</li>
<li><strong>顾问 REST 已合入</strong> · T-20 / KYC D2 / D3 open</li>
<li>L0 是模拟 Core,不是真银行托管库</li>
<li>问数与 Chat 已拆分 · D-09 多轮 · D-12 钻取 open</li>
<li>看板无钻取 · 行情 Phase B 草案未接</li>
<li>知识库 T-21 脚本入库,上传 API 一期不做</li>
</ul>
<div class="quiz-container" id="quiz-defense-m8">
<div class="quiz-question-block"
data-correct="option-b"
data-explanation-right="对。StrictMode 开发态双 mount 会让两个并发请求竞态,旧响应覆盖新响应会「先闪错再正常」——已用 useAsyncSequence 按序号丢弃过期结果。"
data-explanation-wrong="这不是后端 500,多半是前端并发竞态;答辩可指向 web 课模块 4 与清单 §九。">
<h3 class="quiz-question">评委:「刷新 Dashboard 会先红一下再正常,是不是没做错误处理?」</h3>
<div class="quiz-options">
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>是,axios 没 catch</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>否,React StrictMode 双请求竞态,已用 useAsyncSequence</span>
</button>
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>是,JWT 过期</span>
</button>
</div>
<div class="quiz-feedback"></div>
</div>
<button class="quiz-check-btn" onclick="checkQuiz('quiz-defense-m8')">检查答案</button>
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-defense-m8')">重做</button>
</div>
<div class="callout">
<strong>全文提纲:</strong> 仓库内 <code>docs/答辩/答辩知识点清单.md</code> · 深潜按角色打开 <a href="../index.html" style="color:var(--accent)">课程中心</a>。
</div>
<p class="module-footer-note">模块 8 · 2026-09-10 · 与答辩清单同步</p>
</div>
</div>
</section>
</main>
</body>
</html>