Files
group_xinghuo_jinrong/docs/course/jinrong-module-advisor/modules/03-ownership.html
T
zhanghongyu_0626 6f222f1c56 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.
2026-09-09 23:22:59 +08:00

91 lines
5.7 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.
<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>