- 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.
91 lines
5.7 KiB
HTML
91 lines
5.7 KiB
HTML
<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 层拒绝则落 <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(chat.py)</td><td><code>assert_customer_access</code></td><td>HTTP 403 + 鉴权审计</td></tr>
|
||
<tr><td>Tool(tool_service)</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_service.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.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_tools.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 参数由 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 — assert_customer_access(G-01 权威口径)</div>
|
||
<div class="tree-item tree-indent">chat.py — 会话绑定 customer_id + 入口归属</div>
|
||
<div class="tree-item tree-folder">app/service/</div>
|
||
<div class="tree-item tree-indent">tool_service.py — match_intent + run_tool</div>
|
||
<div class="tree-item tree-indent">agent_service.py — tool_node 调用 run_tool</div>
|
||
<div class="tree-item tree-folder">app/tool/</div>
|
||
<div class="tree-item tree-indent">core_tools.py — query_holdings 等只读 Tool 定义</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 抛异常是两层。">
|
||
<h3 class="quiz-question">理财师在对话里查非名下客户,tool_service 会怎么做?</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>
|