feat(course): Add course assembly script and module structure for advisor training

- Introduced `build_all.py` script to automate the assembly of course modules into a single `index.html` file.
- Created `index.html` for the main course overview, featuring a structured layout and navigation for various modules.
- Developed `_base.html` and `_footer.html` templates for the advisor module, ensuring consistent styling and structure.
- Added `build.sh` script for individual module assembly, enhancing modularity and ease of updates.
- Implemented multiple module HTML files detailing specific training scenarios and functionalities for advisors, including interactive elements and quizzes.

This update significantly enhances the course delivery framework, providing a comprehensive and interactive learning experience for advisors.
This commit is contained in:
2026-09-09 23:22:59 +08:00
parent 3ad244add0
commit 6f222f1c56
78 changed files with 20237 additions and 1 deletions
@@ -0,0 +1,86 @@
<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>走 <code>agent_service.py</code> 通用 LangGraph(tool → llm → guard),<strong>不是</strong> <code>customer_service</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 走 agent_service 的 tool→llm→guard;customer 才走 customer_service 14 节点。"
data-explanation-wrong="理财师线和客户线是 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>customer_service.py</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>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>