Files
group_xinghuo_jinrong/docs/course/jinrong-module-customer/modules/03-stream-rules.html
T

99 lines
6.6 KiB
HTML
Raw Normal View History

<section class="module" id="module-3">
<div class="module-inner">
<p class="eyebrow animate-in">模块 3 · 铁律与流式</p>
<h1 class="module-title animate-in">两条铁律 +<br>prepare_customer_stream</h1>
<p class="module-lead animate-in">
客户线能跑通,靠文件头写死的两条
<span class="term" data-definition="铁律 = 写进代码注释、测试会卡住的硬规则;违反会导致合规或数据错乱。">铁律</span>。
流式 SSE 看起来是「边生成边推」,实现上是<strong>先跑完整张 LangGraph,再切块推送</strong>。
</p>
<div class="screen animate-in">
<h2>铁律(指挥 AI 改代码时别碰)</h2>
<div class="pattern-cards">
<div class="pattern-card">
<h3>铁律 1 · 数值不经过 LLM 编造</h3>
<p>持仓、流水、风评等查询结果 100% 来自 Core 只读查询 Tool(<code>core_ro_tool.py</code>)的 <code>fact_text</code>。LLM 在 <code>interpret</code> / <code>generate</code> 里只做解读与组织语言,不能自己算数。</p>
</div>
<div class="pattern-card">
<h3>铁律 2 · customer_id 只来自 JWT</h3>
<p>四 Agent 对话 HTTP 入口(<code>chat.py</code>)在进图之前已从令牌解析客户号并做归属校验。图内 <code>state["customer_id"]</code> 不信用户消息里的「帮我查 CUST-xxx」。</p>
</div>
</div>
<div class="callout callout-warning">
<strong>和 advisor 线的差异:</strong> 理财师走顾问通用编排(<code>agent_service.py</code>)的 tool→llm→guard;客户走独立 14 节点图。别混用对话 Tool 编排(<code>tool_service.py</code>)关键词意图那套来改客户 RAG 分支。
</div>
</div>
<div class="screen animate-in">
<h2>流式真相:图跑完再切块</h2>
<p>方案 C 下 <code>prepare_customer_stream</code> 先调用 <code>run_customer_chat</code> 跑完整图,再用 <code>_chunk_reply_text</code> 切成 16 份左右推 SSE——Tool/RAG 仍是同步完成,不是 token 级流式。</p>
<div class="translation-block">
<div class="translation-code">
<span class="translation-label">CODE · prepare_customer_stream</span>
<pre><code><span class="code-line"><span class="code-keyword">def</span> prepare_customer_stream(</span>
<span class="code-line"> ctx, message, session_id, customer_id, end_session=<span class="code-keyword">False</span></span>
<span class="code-line">) -> dict[str, Any]:</span>
<span class="code-line"> reply, has_disclaimer, intent, transfer = run_customer_chat(</span>
<span class="code-line"> ctx, message, session_id, customer_id, end_session</span>
<span class="code-line"> )</span>
<span class="code-line"> <span class="code-keyword">return</span> {</span>
<span class="code-line"> <span class="code-string">"reply"</span>: reply,</span>
<span class="code-line"> <span class="code-string">"has_disclaimer"</span>: has_disclaimer,</span>
<span class="code-line"> <span class="code-string">"intent"</span>: intent,</span>
<span class="code-line"> <span class="code-string">"transfer_to_human"</span>: transfer,</span>
<span class="code-line"> <span class="code-string">"chunks"</span>: _chunk_reply_text(reply),</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">入参里的 customer_id 是四 Agent 对话 HTTP 入口(<code>chat.py</code>)验完 JWT 后塞进来的,函数内部不再解析用户文本。</p>
<p class="tl">先拿到完整 reply(含 intent、是否转人工、是否附免责),再切片给 SSE 层逐块写。</p>
<p class="tl">改「真流式」要先动 LangGraph 执行模型,不是只改前端 ChatPanel。</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/service/</div>
<div class="tree-item tree-indent">登录客户 14 节点 LangGraph 编排(customer_service.py)— prepare_customer_stream</div>
<div class="tree-item tree-indent">游客试聊 9 节点 LangGraph 编排(visitor_service.py)— 无 Tool 查持仓</div>
<div class="tree-item tree-indent">RAG 检索层(rag_service.py)— fin_faq / fin_product / fin_policy</div>
<div class="tree-item tree-folder">app/api/</div>
<div class="tree-item tree-indent">四 Agent 对话 HTTP 入口(chat.py)— customer 分支分流 + SSE 写帧</div>
<div class="tree-item tree-indent">宿主 AuthContext → 模块 AuthContext 适配(auth_adapter.py)— host_auth_for_customer_service</div>
<div class="tree-item tree-folder">app/tool/</div>
<div class="tree-item tree-indent">客户 Agent Core 只读查询工具(core_ro_tool.py)— query_holdings / query_trades 等</div>
</div>
<div class="quiz-container" id="quiz-customer-m3">
<div class="quiz-question-block"
data-correct="option-c"
data-explanation-right="对。prepare_customer_stream 先 run_customer_chat 整图 invoke,再 _chunk_reply_text 切块;不是 LLM token 级流。"
data-explanation-wrong="客户 SSE 是「整图跑完再切块」,Tool/RAG 在推流前已同步完成。">
<h3 class="quiz-question">客户 SSE 流式回复,LangGraph 什么时候跑完?</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>只跑 interpret,前面跳过</span>
</button>
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>推流前整图 invoke 完成</span>
</button>
</div>
<div class="quiz-feedback"></div>
</div>
<button class="quiz-check-btn" onclick="checkQuiz('quiz-customer-m3')">检查答案</button>
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-customer-m3')">重做</button>
</div>
</div>
</div>
</section>