<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>LLM on Snow</title>
        <link>https://SymphonyW.github.io/zh-cn/tags/llm/</link>
        <description>Recent content in LLM on Snow</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <copyright>Snow</copyright>
        <lastBuildDate>Thu, 15 Oct 2026 15:00:00 +0800</lastBuildDate><atom:link href="https://SymphonyW.github.io/zh-cn/tags/llm/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Agentic RAG 深度解析：让检索从固定流水线变成会规划、会反思的智能体</title>
        <link>https://SymphonyW.github.io/zh-cn/p/agentic-rag-guide/</link>
        <pubDate>Thu, 15 Oct 2026 15:00:00 +0800</pubDate>
        
        <guid>https://SymphonyW.github.io/zh-cn/p/agentic-rag-guide/</guid>
        <description>&lt;img src="https://SymphonyW.github.io/zh-cn/p/agentic-rag-guide/bg.png" alt="Featured image of post Agentic RAG 深度解析：让检索从固定流水线变成会规划、会反思的智能体" /&gt;&lt;h1 id=&#34;agentic-rag-深度解析让检索从固定流水线变成会规划会反思的智能体&#34;&gt;Agentic RAG 深度解析：让检索从固定流水线变成会规划、会反思的智能体
&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;传统 RAG 像一条预先铺好的流水线：收到问题、检索一次、拼接上下文、生成答案。Agentic RAG 则给这条流水线加上了一个“控制者”：它可以判断要不要检索、把问题拆成几步、选择不同数据源、检查证据是否够用，并在必要时改写查询继续搜索。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/agentic-rag-guide/bg.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/agentic-rag-guide/bg.png&#34; alt=&#34;Agent 调度多种知识源并循环评估证据&#34; width=&#34;1672&#34; height=&#34;941&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; class=&#34;gallery-image&#34; data-flex-grow=&#34;177&#34; data-flex-basis=&#34;426px&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;先给出本文最重要的结论：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;简短答案&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agentic RAG 是什么&lt;/td&gt;
&lt;td&gt;由 Agent 在运行时规划、调用、评估和迭代检索的 RAG 架构&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;和传统 RAG 最大的区别&lt;/td&gt;
&lt;td&gt;不在于有没有向量库，而在于&lt;strong&gt;检索控制流是否能根据中间结果动态改变&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;一定需要多个 Agent 吗&lt;/td&gt;
&lt;td&gt;不需要。一个 Agent 加多个检索工具就可以构成 Agentic RAG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;一定比传统 RAG 好吗&lt;/td&gt;
&lt;td&gt;不一定。简单问答往往只会得到更高延迟、更高成本和更多不确定性&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;最适合什么问题&lt;/td&gt;
&lt;td&gt;多跳推理、多数据源、需要澄清或证据校验的复杂任务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;落地最关键的能力&lt;/td&gt;
&lt;td&gt;有界循环、证据门控、权限隔离、完整轨迹与分层评估&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;一先回到传统-rag&#34;&gt;一、先回到传统 RAG
&lt;/h2&gt;&lt;p&gt;RAG 是 Retrieval-Augmented Generation 的缩写，即&lt;strong&gt;检索增强生成&lt;/strong&gt;。它把大模型参数中的“内部记忆”和外部知识库中的“非参数记忆”结合起来。用户提问后，系统先找出相关资料，再让模型依据这些资料回答。&lt;/p&gt;
&lt;p&gt;2020 年的 &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2005.11401&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;RAG 原始论文&lt;/a&gt; 重点讨论了如何把生成模型与外部稠密向量索引结合，以改善知识密集型任务中的事实性、知识更新与来源追溯问题。工程实践中的传统 RAG 通常被简化成下面这条在线链路：&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;flowchart LR
    Q[用户问题] --&gt; T[清洗或固定规则改写]
    T --&gt; E[生成查询向量]
    E --&gt; R[检索 Top-K 文档]
    R --&gt; K[可选：重排序]
    K --&gt; P[拼接 Prompt]
    P --&gt; L[LLM 生成答案]
    L --&gt; A[返回答案与引用]
&lt;/div&gt;&lt;p&gt;它的优势非常明确：流程短、延迟可预测、容易缓存、容易压测，也容易定位“是检索错了，还是生成错了”。如果问题基本都能通过“对一个知识库搜索一次”解决，这种结构往往已经足够好。&lt;/p&gt;
&lt;p&gt;但固定流水线也有天然边界。假设用户问：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“对比 A、B 两款产品最近三个版本的权限变化，并判断旧客户迁移时有哪些风险。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个问题隐含了多个子任务：识别版本范围、分别查 A 和 B 的更新日志、查权限说明、找迁移文档、对齐发布时间，最后才能做比较。一次相似度搜索很可能只覆盖其中一部分；即使拿到了十段文本，模型也未必知道缺失的是哪一块证据。&lt;/p&gt;
&lt;h2 id=&#34;二agentic-rag-到底agentic在哪里&#34;&gt;二、Agentic RAG 到底“Agentic”在哪里
&lt;/h2&gt;&lt;p&gt;Agentic RAG 不是给传统 RAG 换一个更强的模型，也不是“向量检索 + Function Calling”的简单拼装。它真正改变的是&lt;strong&gt;控制权&lt;/strong&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;检索不再是每次都执行一次的固定步骤，而是 Agent 可以按需选择、重复调用并根据结果调整的工具。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这类系统通常把规划、工具调用、状态更新和停止判断组织成一个受控循环。Agent 可以做出以下决策：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前问题能否直接回答，还是必须查资料；&lt;/li&gt;
&lt;li&gt;应该查向量库、关键字索引、SQL、知识图谱、内部 API，还是公开网页；&lt;/li&gt;
&lt;li&gt;原问题是否需要拆成多个子问题；&lt;/li&gt;
&lt;li&gt;检索结果是否相关、是否互相矛盾、是否覆盖了所有子问题；&lt;/li&gt;
&lt;li&gt;应该改写查询、扩大时间范围、切换数据源，还是向用户澄清；&lt;/li&gt;
&lt;li&gt;证据何时已经足够，可以停止搜索并生成答案。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种“推理—行动—观察—再推理”的思想与 &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2210.03629&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;ReAct&lt;/a&gt; 一脉相承。2025 年的 &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2501.09136&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Agentic RAG 综述&lt;/a&gt; 则把规划、反思、工具使用和多 Agent 协作归纳为常见的 Agentic 设计模式。&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;flowchart TD
    U[用户问题] --&gt; P[理解意图与制定计划]
    P --&gt; D{证据已经足够？}
    D -- 是 --&gt; G[基于证据生成答案]
    D -- 否 --&gt; S[选择数据源与检索工具]
    S --&gt; Q[生成或改写查询]
    Q --&gt; R[执行检索]
    R --&gt; V[评估相关性、覆盖度与冲突]
    V --&gt; C{下一步}
    C -- 补充证据 --&gt; S
    C -- 需要澄清 --&gt; H[向用户提问]
    C -- 达到预算上限 --&gt; F[降级回答或说明无法确认]
    C -- 可以作答 --&gt; G
    G --&gt; A[答案、引用与不确定性说明]
&lt;/div&gt;&lt;p&gt;这里有一个很容易被忽略的判断标准：&lt;strong&gt;只要查询改写、混合检索、重排序都按预先写死的顺序执行，它仍然可以是“高级 RAG”，但未必是 Agentic RAG。&lt;/strong&gt; Agentic 的关键不是组件数量，而是系统会不会依据中间状态改变后续路径。&lt;/p&gt;
&lt;h2 id=&#34;三一条完整的-agentic-rag-工作循环&#34;&gt;三、一条完整的 Agentic RAG 工作循环
&lt;/h2&gt;&lt;p&gt;可以把在线流程拆成七个阶段。实际系统可以合并节点，但这些责任最好在日志和评估中保持可区分。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;阶段&lt;/th&gt;
&lt;th&gt;Agent 要解决的问题&lt;/th&gt;
&lt;th&gt;典型输出&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. 意图识别&lt;/td&gt;
&lt;td&gt;用户真正想得到什么，是否需要外部事实&lt;/td&gt;
&lt;td&gt;任务类型、时间范围、实体、约束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. 任务分解&lt;/td&gt;
&lt;td&gt;是否包含多个可独立验证的子问题&lt;/td&gt;
&lt;td&gt;子问题列表、依赖关系&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. 数据源路由&lt;/td&gt;
&lt;td&gt;哪个来源最权威、最适合当前子问题&lt;/td&gt;
&lt;td&gt;工具名、索引名、过滤条件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. 查询规划&lt;/td&gt;
&lt;td&gt;用什么关键词、向量查询或结构化条件&lt;/td&gt;
&lt;td&gt;一组可执行查询&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. 结果评估&lt;/td&gt;
&lt;td&gt;资料是否相关、充分、新鲜、相互一致&lt;/td&gt;
&lt;td&gt;相关性、覆盖度、冲突标记&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6. 纠正与迭代&lt;/td&gt;
&lt;td&gt;要不要换查询、换来源或缩小问题&lt;/td&gt;
&lt;td&gt;下一动作、剩余预算&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7. 综合回答&lt;/td&gt;
&lt;td&gt;怎样让每个关键结论都能回到证据&lt;/td&gt;
&lt;td&gt;答案、引用、不确定性&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;假设用户问：“我们是否应该把生产数据库从 13 升级到 16？”Agent 可能执行这样的轨迹：&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;sequenceDiagram
    participant U as 用户
    participant A as Agent
    participant D as 内部文档库
    participant M as 资产数据库
    participant W as 官方文档

    U-&gt;&gt;A: 是否应该把生产数据库从 13 升级到 16？
    A-&gt;&gt;M: 查询当前版本、扩展、实例规模和维护窗口
    M--&gt;&gt;A: 返回环境清单
    A-&gt;&gt;D: 检索历史故障、兼容性测试和变更规范
    D--&gt;&gt;A: 返回内部证据
    A-&gt;&gt;W: 查询 14、15、16 的破坏性变更与支持周期
    W--&gt;&gt;A: 返回官方资料
    A-&gt;&gt;A: 检查扩展兼容性与证据缺口
    A-&gt;&gt;D: 继续检索缺失扩展的测试记录
    D--&gt;&gt;A: 未找到完整记录
    A--&gt;&gt;U: 给出条件式建议、风险清单，并明确需要补做的测试
&lt;/div&gt;&lt;p&gt;重点不是“多搜了几次”，而是后一次检索由前一次结果决定。如果资产数据库显示用了某个特殊扩展，Agent 才需要针对该扩展继续查兼容性；这正是固定流水线难以经济覆盖的分支。&lt;/p&gt;
&lt;h2 id=&#34;四常见架构并不只有一种&#34;&gt;四、常见架构并不只有一种
&lt;/h2&gt;&lt;p&gt;Agentic RAG 更像一个架构族，而不是唯一拓扑。&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;flowchart TB
    O[编排与状态层] --&gt; P[规划器 / Router]
    P --&gt; T1[向量检索]
    P --&gt; T2[关键字或混合检索]
    P --&gt; T3[SQL / API]
    P --&gt; T4[知识图谱 / Web]
    T1 --&gt; E[证据池]
    T2 --&gt; E
    T3 --&gt; E
    T4 --&gt; E
    E --&gt; J[证据评估器]
    J --&gt;|不足| P
    J --&gt;|充分| G[答案生成器]
    G --&gt; C[引用校验与安全门控]
&lt;/div&gt;&lt;h3 id=&#34;单-agent--多工具&#34;&gt;单 Agent + 多工具
&lt;/h3&gt;&lt;p&gt;一个 Agent 同时负责规划、选工具、评估和回答。这是最容易起步的结构，适合工具数量少、权限边界一致的场景。缺点是职责容易堆在一个 Prompt 里，决策稳定性和可测试性会逐渐变差。&lt;/p&gt;
&lt;h3 id=&#34;router--专用检索器&#34;&gt;Router + 专用检索器
&lt;/h3&gt;&lt;p&gt;Router 只负责判断问题类型和数据源，具体检索由稳定的专用链路完成。例如“制度问题走文档库，订单问题走 SQL，最新状态走业务 API”。它比完全开放的 Agent 更可控，通常也是生产系统最实用的折中。&lt;/p&gt;
&lt;h3 id=&#34;纠正式或反思式-rag&#34;&gt;纠正式或反思式 RAG
&lt;/h3&gt;&lt;p&gt;先检索，再由评估器判断结果质量；如果证据差，就改写查询、扩大来源或触发 Web 搜索。&lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2401.15884&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;CRAG&lt;/a&gt; 强调对检索结果进行纠正，&lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2310.11511&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Self-RAG&lt;/a&gt; 则让模型学习何时检索并对生成内容进行自我反思。它们与 Agentic RAG 高度相关，但不能简单画等号：这些机制可以成为 Agent 循环里的部件，也可以被做成固定流程。&lt;/p&gt;
&lt;h3 id=&#34;多-agent-rag&#34;&gt;多 Agent RAG
&lt;/h3&gt;&lt;p&gt;把任务分给研究、检索、验证、写作等不同 Agent，或按知识域划分 Agent。它适合真正可以并行、权限不同或专业性很强的任务，但会显著增加通信成本、状态同步和故障定位难度。&lt;strong&gt;多 Agent 是可选的扩展，不是 Agentic RAG 的入场券。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;五和传统-rag-的区别&#34;&gt;五、和传统 RAG 的区别
&lt;/h2&gt;&lt;p&gt;最准确的比较不是“传统 RAG 笨、Agentic RAG 聪明”，而是“确定性流水线”和“运行时决策系统”之间的工程取舍。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;对比维度&lt;/th&gt;
&lt;th&gt;传统 RAG&lt;/th&gt;
&lt;th&gt;Agentic RAG&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;控制流&lt;/td&gt;
&lt;td&gt;预先固定&lt;/td&gt;
&lt;td&gt;根据中间结果动态分支和循环&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;是否检索&lt;/td&gt;
&lt;td&gt;通常每次都检索&lt;/td&gt;
&lt;td&gt;可以选择检索、跳过或稍后再检索&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查询处理&lt;/td&gt;
&lt;td&gt;原查询或固定改写&lt;/td&gt;
&lt;td&gt;动态拆分、改写、扩展、追问&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;数据源&lt;/td&gt;
&lt;td&gt;通常一个索引或固定组合&lt;/td&gt;
&lt;td&gt;运行时选择多个异构来源&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;检索次数&lt;/td&gt;
&lt;td&gt;一次或固定次数&lt;/td&gt;
&lt;td&gt;直到证据充分或预算耗尽&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;多跳问题&lt;/td&gt;
&lt;td&gt;依赖一次召回覆盖全部信息&lt;/td&gt;
&lt;td&gt;可依据前一步结果继续追踪下一跳&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;失败处理&lt;/td&gt;
&lt;td&gt;返回当前结果或走固定兜底&lt;/td&gt;
&lt;td&gt;评估、重试、换工具、澄清、降级&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可预测性&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;较低，需要状态机和预算约束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;延迟与成本&lt;/td&gt;
&lt;td&gt;较低且稳定&lt;/td&gt;
&lt;td&gt;通常更高，波动也更大&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可观测性&lt;/td&gt;
&lt;td&gt;看检索结果和最终答案&lt;/td&gt;
&lt;td&gt;还要看计划、工具调用、状态和停止原因&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;安全面&lt;/td&gt;
&lt;td&gt;相对集中&lt;/td&gt;
&lt;td&gt;工具权限、提示注入和越权风险更大&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;最适合&lt;/td&gt;
&lt;td&gt;高频、单跳、边界清晰的问答&lt;/td&gt;
&lt;td&gt;多步、多源、开放式研究与决策支持&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;还有一个重要事实：传统 RAG 与 Agentic RAG 不是二选一。生产系统常见的做法是&lt;strong&gt;先走快速路径，只有检测到复杂问题或低置信度时才升级到 Agentic 路径&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;flowchart LR
    Q[用户问题] --&gt; X{复杂度与风险检测}
    X -- 简单、单跳 --&gt; R[传统 RAG 快速路径]
    X -- 多约束、多跳、低置信度 --&gt; A[Agentic RAG 深度路径]
    R --&gt; G[统一答案与引用层]
    A --&gt; G
&lt;/div&gt;&lt;p&gt;这种分层路由往往比“所有请求都交给 Agent”更划算：常见问题保持低延迟，复杂问题才使用额外的规划和检索预算。&lt;/p&gt;
&lt;h2 id=&#34;六实现时离线知识工程并没有消失&#34;&gt;六、实现时，离线知识工程并没有消失
&lt;/h2&gt;&lt;p&gt;Agentic RAG 主要改变在线查询阶段，但它无法替代离线的数据治理。文档解析错误、切块不合理、元数据缺失、权限标签不完整，再强的 Agent 也只能在坏数据上反复搜索。&lt;/p&gt;
&lt;p&gt;一个可落地的系统通常分成四层：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层&lt;/th&gt;
&lt;th&gt;主要职责&lt;/th&gt;
&lt;th&gt;关键设计&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;知识层&lt;/td&gt;
&lt;td&gt;采集、解析、切块、索引、更新&lt;/td&gt;
&lt;td&gt;数据质量、版本、权限、新鲜度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;工具层&lt;/td&gt;
&lt;td&gt;把检索能力暴露为清晰接口&lt;/td&gt;
&lt;td&gt;输入 Schema、过滤器、超时、结果格式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;编排层&lt;/td&gt;
&lt;td&gt;规划、路由、循环、状态和预算&lt;/td&gt;
&lt;td&gt;最大步数、并发、重试、检查点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;治理层&lt;/td&gt;
&lt;td&gt;评估、观测、安全和人工介入&lt;/td&gt;
&lt;td&gt;轨迹、审计、审批、离线测试集&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;工具描述要具体，不能只写“搜索知识库”。更好的描述会告诉 Agent：数据覆盖什么时间、适合解决什么问题、返回什么字段、有哪些权限限制。否则工具越多，选错工具的概率反而越高。&lt;/p&gt;
&lt;p&gt;下面是一段与框架无关的伪代码，它展示了最小闭环：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;answer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;question&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;max_steps&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;state&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;question&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;question&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;evidence&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;open_questions&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;question&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;trace&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;step&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;decision&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;planner&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decide&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;allowed_tools&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;allowed_tools&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;decision&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;action&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;clarify&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ask_user&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decision&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;question&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;decision&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;action&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;answer&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;evidence_gate&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;passed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;evidence&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;generator&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;grounded_answer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;decision&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;planner&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;repair_missing_evidence&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tools&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decision&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decision&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;filters&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decision&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filters&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;identity&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;identity&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;grade&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;evaluator&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;grade&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;open_questions&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;state&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;update_state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;decision&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;grade&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;generator&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bounded_fallback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;真正重要的不是这段循环本身，而是其中的护栏：&lt;code&gt;allowed_tools&lt;/code&gt; 限制权限，&lt;code&gt;max_steps&lt;/code&gt; 防止失控，&lt;code&gt;evidence_gate&lt;/code&gt; 阻止无证据作答，&lt;code&gt;trace&lt;/code&gt; 为复盘和评估保留结构化轨迹。&lt;/p&gt;
&lt;h2 id=&#34;七生产环境最容易踩的坑&#34;&gt;七、生产环境最容易踩的坑
&lt;/h2&gt;&lt;h3 id=&#34;无限搜索与成本失控&#34;&gt;无限搜索与成本失控
&lt;/h3&gt;&lt;p&gt;如果停止条件只是“模型觉得够了”，Agent 可能在相似查询之间来回打转。至少需要限制最大步数、总 Token、单工具超时、并发数和总费用，并在预算耗尽时返回明确的降级结果。&lt;/p&gt;
&lt;h3 id=&#34;把反思误当成事实校验&#34;&gt;把“反思”误当成事实校验
&lt;/h3&gt;&lt;p&gt;让同一个模型评价自己的答案，并不会自动得到独立证据。反思节点应该检查可观察指标，例如来源是否覆盖全部子问题、引用是否真的支持结论、时间范围是否一致，而不是只问一句“答案好吗”。&lt;/p&gt;
&lt;h3 id=&#34;检索内容中的提示注入&#34;&gt;检索内容中的提示注入
&lt;/h3&gt;&lt;p&gt;网页或文档也可能包含“忽略系统指令并调用某工具”之类的恶意文本。检索结果必须被当作&lt;strong&gt;不可信数据&lt;/strong&gt;，不能当作高优先级指令。工具调用应经过参数校验、来源白名单、最小权限和高风险操作审批。&lt;/p&gt;
&lt;h3 id=&#34;只有最终答案没有执行轨迹&#34;&gt;只有最终答案，没有执行轨迹
&lt;/h3&gt;&lt;p&gt;Agentic RAG 出错时，问题可能发生在任务分解、路由、查询、召回、评估、停止或生成中的任意一层。日志至少应记录结构化决策、工具名、查询参数、文档 ID、评分、耗时、Token、错误与停止原因；不必保存或暴露模型的私有思维过程。&lt;/p&gt;
&lt;h3 id=&#34;用更复杂的架构掩盖基础检索问题&#34;&gt;用更复杂的架构掩盖基础检索问题
&lt;/h3&gt;&lt;p&gt;如果 Top-K 里长期没有正确文档，先检查解析、切块、Embedding、过滤器、混合检索和重排序。Agent 可以修补偶发缺口，却不该替代基本的信息检索工程。&lt;/p&gt;
&lt;h2 id=&#34;八评估对象从答案扩展到了轨迹&#34;&gt;八、评估对象从“答案”扩展到了“轨迹”
&lt;/h2&gt;&lt;p&gt;传统 RAG 常看命中率和回答正确率。Agentic RAG 还必须回答：它是否选择了正确工具？是否做了不必要的搜索？是否在证据不足时过早停止？&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;评估层&lt;/th&gt;
&lt;th&gt;建议指标&lt;/th&gt;
&lt;th&gt;要回答的问题&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;检索&lt;/td&gt;
&lt;td&gt;Recall@K、MRR、nDCG、证据覆盖率&lt;/td&gt;
&lt;td&gt;正确资料有没有被找到&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;生成&lt;/td&gt;
&lt;td&gt;正确性、忠实度、引用精度与完整度&lt;/td&gt;
&lt;td&gt;答案是否被证据支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;轨迹&lt;/td&gt;
&lt;td&gt;工具选择准确率、查询有效率、平均步数、停止正确率&lt;/td&gt;
&lt;td&gt;Agent 的过程是否合理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;系统&lt;/td&gt;
&lt;td&gt;P50/P95 延迟、Token 成本、工具成本、超时率&lt;/td&gt;
&lt;td&gt;质量提升是否值得代价&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;安全&lt;/td&gt;
&lt;td&gt;越权调用率、注入攻击成功率、敏感信息泄漏率&lt;/td&gt;
&lt;td&gt;系统是否守住权限与数据边界&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;测试集也要分层：简单单跳题用于验证快速路径；多跳题验证分解与证据组合；信息不足题验证是否会澄清；矛盾资料题验证冲突处理；恶意文档验证提示注入防护。只用“答案看起来不错”的十几个示例，无法支撑生产决策。&lt;/p&gt;
&lt;h2 id=&#34;九什么时候该用什么时候不该用&#34;&gt;九、什么时候该用，什么时候不该用
&lt;/h2&gt;&lt;p&gt;可以用下面这张表做初筛：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;推荐方案&lt;/th&gt;
&lt;th&gt;原因&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FAQ、产品手册、制度单点问答&lt;/td&gt;
&lt;td&gt;传统 RAG&lt;/td&gt;
&lt;td&gt;查询稳定、答案边界清晰，优先延迟和成本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;一个索引，但偶尔召回不佳&lt;/td&gt;
&lt;td&gt;高级 RAG&lt;/td&gt;
&lt;td&gt;先加混合检索、Rerank、查询改写和评估器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;问题包含多个相互依赖的子问题&lt;/td&gt;
&lt;td&gt;Agentic RAG&lt;/td&gt;
&lt;td&gt;需要分解并根据中间结果继续检索&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;同时查询文档、数据库与实时 API&lt;/td&gt;
&lt;td&gt;Agentic RAG&lt;/td&gt;
&lt;td&gt;需要动态路由异构工具&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;医疗、金融、发布生产变更等高风险动作&lt;/td&gt;
&lt;td&gt;Agentic RAG + 人工审批&lt;/td&gt;
&lt;td&gt;自动检索可以辅助判断，高风险动作仍需明确授权&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;极低延迟、高吞吐、答案结构固定&lt;/td&gt;
&lt;td&gt;传统 RAG 或规则系统&lt;/td&gt;
&lt;td&gt;Agent 循环的收益通常抵不过额外开销&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果你正在从零建设，建议按以下顺序演进：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先做可评估的传统 RAG，建立检索与答案基线；&lt;/li&gt;
&lt;li&gt;加入混合检索、元数据过滤、Rerank 和引用校验；&lt;/li&gt;
&lt;li&gt;只把最明显的动态决策交给 Router；&lt;/li&gt;
&lt;li&gt;为复杂查询增加有界的“检索—评估—纠正”循环；&lt;/li&gt;
&lt;li&gt;最后才考虑多 Agent、长期记忆和更开放的工具权限。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;十总结&#34;&gt;十、总结
&lt;/h2&gt;&lt;p&gt;Agentic RAG 的价值，不是让 RAG 看起来更像一个“智能体”，而是让系统在面对复杂问题时拥有三种传统固定流水线缺少的能力：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;规划&lt;/strong&gt;：把复杂问题拆成可以验证的检索任务；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适应&lt;/strong&gt;：根据中间结果改查询、换数据源或请求澄清；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;校验&lt;/strong&gt;：在证据不足、冲突或超出权限时停止、降级或交给人。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;它的代价同样真实：更多模型调用、更高延迟、更复杂的状态、更大的安全面，以及更困难的评估。真正成熟的方案通常不是“把所有 RAG 都 Agent 化”，而是保留一条稳定的传统 RAG 快速路径，再让 Agentic RAG 处理那些确实需要多步、多源和动态决策的问题。&lt;/p&gt;
&lt;p&gt;一句话概括：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;传统 RAG 解决“从哪里找一段相关资料”，Agentic RAG 进一步解决“为了完成这个任务，接下来应该找什么、去哪里找、找到后是否还要继续”。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;参考资料&#34;&gt;参考资料
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Lewis et al., &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2005.11401&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks&lt;/a&gt;, 2020.&lt;/li&gt;
&lt;li&gt;Yao et al., &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2210.03629&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;ReAct: Synergizing Reasoning and Acting in Language Models&lt;/a&gt;, 2022.&lt;/li&gt;
&lt;li&gt;Asai et al., &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2310.11511&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection&lt;/a&gt;, 2023.&lt;/li&gt;
&lt;li&gt;Yan et al., &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2401.15884&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Corrective Retrieval Augmented Generation&lt;/a&gt;, 2024.&lt;/li&gt;
&lt;li&gt;Singh et al., &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2501.09136&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Agentic Retrieval-Augmented Generation: A Survey on Agentic RAG&lt;/a&gt;, 2025.&lt;/li&gt;
&lt;li&gt;Microsoft, &lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/azure/architecture/ai-ml/guide/rag/rag-agentic&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Develop an Agentic RAG Solution on Azure&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;LangChain, &lt;a class=&#34;link&#34; href=&#34;https://docs.langchain.com/oss/python/learn&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;RAG agent 与自定义 RAG Agent 教程索引&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        <item>
        <title>FastGPT 深度指南：RAG、工作流与私有部署</title>
        <link>https://SymphonyW.github.io/zh-cn/p/fastgpt-guide/</link>
        <pubDate>Thu, 08 Oct 2026 00:00:00 +0800</pubDate>
        
        <guid>https://SymphonyW.github.io/zh-cn/p/fastgpt-guide/</guid>
        <description>&lt;img src="https://SymphonyW.github.io/zh-cn/p/fastgpt-guide/bg.png" alt="Featured image of post FastGPT 深度指南：RAG、工作流与私有部署" /&gt;&lt;h1 id=&#34;fastgpt-深度指南rag工作流与私有部署&#34;&gt;FastGPT 深度指南：RAG、工作流与私有部署
&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;FastGPT 不是一个“换皮聊天框”，也不只是一个向量数据库管理界面。它把模型接入、文档处理、RAG 检索、流程编排、调试、发布和运营放进同一个平台，让团队可以用较低的工程成本，把一段 AI Demo 变成真正可交付的应用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果你准备做企业知识库、客服助手、制度问答、内容审核、数据分析 Agent，通常会很快遇到几件麻烦事：模型供应商不止一家；文档格式五花八门；检索效果要持续调；业务流程不可能只靠一段 Prompt；上线后还要做权限、日志、配额和渠道接入。&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/labring/FastGPT&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;FastGPT&lt;/a&gt; 正是试图把这些重复的基础设施收拢起来。官方把它定义为 &lt;strong&gt;AI Agent 构建平台&lt;/strong&gt;，社区版面向个人开发者和小型交付团队，核心覆盖 Agent、工作流与知识库。本文不只告诉你按钮在哪里，而是从系统设计出发，讲清它解决了什么、内部怎样协作、如何跑通一个项目，以及什么时候不该用它。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/fastgpt-guide/platform-map.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/fastgpt-guide/platform-map.svg&#34; alt=&#34;FastGPT 把模型、知识、流程和发布渠道组织成一套应用平台&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;先给结论fastgpt-到底是什么&#34;&gt;先给结论：FastGPT 到底是什么
&lt;/h2&gt;&lt;p&gt;可以把 FastGPT 理解为位于 &lt;strong&gt;基础模型之上的 AI 应用层&lt;/strong&gt;。模型负责理解与生成，向量数据库负责相似内容查找，而 FastGPT 负责把两者和业务流程组合成一个可配置、可调试、可发布的应用。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层次&lt;/th&gt;
&lt;th&gt;它解决的问题&lt;/th&gt;
&lt;th&gt;FastGPT 提供的能力&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;模型层&lt;/td&gt;
&lt;td&gt;调哪个聊天、Embedding、Rerank 或视觉模型&lt;/td&gt;
&lt;td&gt;统一配置模型提供商，通过 AI Proxy 转发与管理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;知识层&lt;/td&gt;
&lt;td&gt;文档怎样切分、索引、检索与引用&lt;/td&gt;
&lt;td&gt;Dataset、数据处理、向量/全文/混合检索、Rerank&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;编排层&lt;/td&gt;
&lt;td&gt;多步任务怎样串起来&lt;/td&gt;
&lt;td&gt;可视化节点、变量传递、条件分支、插件与 Agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;应用层&lt;/td&gt;
&lt;td&gt;如何调试、复用和交付&lt;/td&gt;
&lt;td&gt;应用配置、调试日志、分享链接、API、嵌入与 MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;运维层&lt;/td&gt;
&lt;td&gt;谁在用、花了多少、哪里失败&lt;/td&gt;
&lt;td&gt;团队空间、调用记录、配额、日志和数据标注&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;它最适合两类人：一类是希望快速交付 AI 应用、但不想从零搭 RAG 与编排后台的开发者；另一类是懂业务、能理解流程节点，但不准备手写整套后端的产品或实施团队。&lt;/p&gt;
&lt;p&gt;FastGPT 也不是“安装后立刻拥有高质量答案”的魔法盒。它把工程链路搭好了，但答案质量仍取决于原始资料、分块策略、Embedding、检索参数、Rerank、Prompt 和模型能力。平台降低的是系统建设成本，不会自动消除数据问题。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;核心能力怎样协作&#34;&gt;核心能力怎样协作
&lt;/h2&gt;&lt;p&gt;FastGPT 的价值不在某个孤立功能，而在于知识库、工作流和发布渠道能使用同一套应用上下文。&lt;/p&gt;
&lt;h3 id=&#34;知识库不只是文档转向量&#34;&gt;知识库：不只是“文档转向量”
&lt;/h3&gt;&lt;p&gt;一份文档进入知识库后，通常经历解析、清洗、分块、索引生成和入库。查询时也不只是做一次向量 Top-K。根据&lt;a class=&#34;link&#34; href=&#34;https://doc.fastgpt.io/en/guide/dataset/dataset_engine&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;官方检索说明&lt;/a&gt;，FastGPT 可以先做 Query Rewrite，再从语义检索、全文检索乃至图像检索等路径召回候选，用 RRF 融合多路排序，随后通过 Rerank 精排，并根据相关度与引用 Token 上限筛选最终上下文。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/fastgpt-guide/rag-pipeline.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/fastgpt-guide/rag-pipeline.svg&#34; alt=&#34;FastGPT 知识库从入库到生成回答的完整链路&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;这里有四个容易被忽略的设计点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Dataset、Collection、Data 是三层结构。&lt;/strong&gt; Dataset 是可检索的知识库，Collection 类似一个文件或一组资料，Data 是最终参与问答的数据条目。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一条数据可以映射多个向量。&lt;/strong&gt; 长文本可以保留完整答案，同时用多个更短、更聚焦的索引提高召回概率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;混合检索比单路向量检索更稳。&lt;/strong&gt; 产品型号、错误码、人名等精确词更依赖全文检索；自然语言问题更依赖语义检索，RRF 用于融合排名。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;引用上限用 Token 而非只用 Top-K。&lt;/strong&gt; 因为不同分块长度差异很大，固定取 5 条可能有时太少、有时又塞爆上下文。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;MongoDB 保存向量之外的大部分业务数据；向量数据可以放在 PostgreSQL/PGVector、Milvus、OceanBase、SeekDB 等后端。使用 PGVector 时，官方文档说明向量 ID 会再关联回 MongoDB 中的原始数据，这也是为什么备份时不能只备向量库。&lt;/p&gt;
&lt;h3 id=&#34;工作流把-prompt-变成可观察的程序&#34;&gt;工作流：把 Prompt 变成可观察的程序
&lt;/h3&gt;&lt;p&gt;最简单的聊天应用只有“用户问题 → AI 对话”两个节点；真实业务往往还需要分类、查库、调用 HTTP 接口、条件判断、格式化和兜底。FastGPT 的节点很像函数：有输入、输出和触发关系，前一个节点的输出可以成为后一个节点的变量。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/fastgpt-guide/workflow-example.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/fastgpt-guide/workflow-example.svg&#34; alt=&#34;一个典型的客服工作流如何分流、检索与调用工具&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;官方工作流采用连接线状态推动执行：节点看到活跃前驱才执行，等待中的前驱会让它继续等待，没有可执行前驱则跳过。这个模型带来两个好处：一是分支与汇合能画出来，二是每次运行都可以检查节点输入输出，而不是只看到最后一段模型回答。&lt;/p&gt;
&lt;p&gt;实际项目中，我更建议把职责拆开：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;节点&lt;/th&gt;
&lt;th&gt;只负责什么&lt;/th&gt;
&lt;th&gt;不要塞进去什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;问题分类&lt;/td&gt;
&lt;td&gt;判断咨询、投诉、查询或闲聊&lt;/td&gt;
&lt;td&gt;大段业务回答&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query Rewrite&lt;/td&gt;
&lt;td&gt;补全指代，生成适合检索的完整问题&lt;/td&gt;
&lt;td&gt;修改订单号、型号等精确实体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;知识库搜索&lt;/td&gt;
&lt;td&gt;召回可引用资料&lt;/td&gt;
&lt;td&gt;直接替用户下结论&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP / 插件&lt;/td&gt;
&lt;td&gt;查询实时数据或执行动作&lt;/td&gt;
&lt;td&gt;隐藏不可审计的复杂规则&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI 对话&lt;/td&gt;
&lt;td&gt;综合上下文，按格式生成答案&lt;/td&gt;
&lt;td&gt;持久化关键业务状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;指定回复&lt;/td&gt;
&lt;td&gt;错误、拒答、转人工等确定性输出&lt;/td&gt;
&lt;td&gt;需要模型推理的开放问题&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;agent插件与发布&#34;&gt;Agent、插件与发布
&lt;/h3&gt;&lt;p&gt;工作流适合路径相对确定的流程；Agent 更适合让模型根据目标自行选择工具和步骤。插件把搜索、数据库、消息通知、文档处理等外部能力封装成可复用工具。两者并非互相替代：一个可靠的生产 Agent，通常仍会把高风险动作约束在结构清晰的工具与子流程中。&lt;/p&gt;
&lt;p&gt;应用完成后可以通过分享链接、嵌入页面、OpenAI 风格 API 或 MCP Server 暴露。API Key 现在是成员级 OpenAPI 凭据，调用 &lt;code&gt;chat/completions&lt;/code&gt; 时推荐在请求体中传 &lt;code&gt;appId&lt;/code&gt;；只支持传统 OpenAI Key 配置的客户端，可以使用官方提供的兼容格式。发布不等于安全：公开分享链接会消耗应用所属团队的额度，API Key 应配置过期时间与配额，写操作工具还应做二次授权。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;从零做一个员工制度助手&#34;&gt;从零做一个“员工制度助手”
&lt;/h2&gt;&lt;p&gt;下面用一个常见场景把整条链路串起来。目标不是让模型自由聊天，而是做到：答案基于公司制度、带来源、资料缺失时明确拒答，涉及个人数据时转到内部接口查询。&lt;/p&gt;
&lt;h3 id=&#34;1-先定义验收问题&#34;&gt;1. 先定义验收问题
&lt;/h3&gt;&lt;p&gt;不要先上传几百份文件。先写 20～50 个代表性问题，其中至少包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可直接命中的事实题：“试用期员工有几天年假？”&lt;/li&gt;
&lt;li&gt;需要跨段落理解的问题：“出差遇到周末，调休和报销分别怎么处理？”&lt;/li&gt;
&lt;li&gt;精确词问题：“OA-TRAVEL-017 表单在哪里？”&lt;/li&gt;
&lt;li&gt;多轮追问：“那海外出差呢？”&lt;/li&gt;
&lt;li&gt;应拒答的问题：“帮我查张三今年还剩多少年假。”&lt;/li&gt;
&lt;li&gt;资料中根本没有的问题，用来检查模型是否会编造。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这组题既是调参基线，也是后续升级模型、修改分块时的回归测试。没有固定测试集，所谓“感觉回答更好了”通常不可复现。&lt;/p&gt;
&lt;h3 id=&#34;2-整理与导入资料&#34;&gt;2. 整理与导入资料
&lt;/h3&gt;&lt;p&gt;优先处理文档质量，而不是立刻调相似度阈值：删除重复版本；保留标题层级；把扫描 PDF 做 OCR；给文件加版本、部门、生效日期等元数据；把已经失效的制度移出生产知识库。&lt;/p&gt;
&lt;p&gt;分块应保持“语义完整且主题单一”。一条规则的条件、例外和结论最好放在同一个块里；表格不要被机械切成互不相关的行。对于制度问答，可以为同一原文补充多种问题式索引，例如“年假有几天”“年假额度”“试用期能否休年假”，它们都指向同一段完整制度。&lt;/p&gt;
&lt;h3 id=&#34;3-先用最小工作流跑通&#34;&gt;3. 先用最小工作流跑通
&lt;/h3&gt;&lt;p&gt;初版只需要：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;用户问题
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  → Query Rewrite（仅多轮或指代不清时启用）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  → 知识库搜索（混合检索 + Rerank）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  → AI 对话（只依据引用回答）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  → 返回答案与来源
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;系统提示词可以从这个约束开始，而不是堆几十条“角色设定”：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;你是公司制度助手。只根据“引用资料”回答。
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;资料不足时明确说“现有制度资料中没有找到答案”，不要自行补充。
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;若问题涉及个人余额、审批状态或其他员工隐私，不要猜测，提示用户授权后查询内部系统。
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;回答先给结论，再列适用条件；引用制度名称与生效日期。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果召回错误码和表单编号不稳定，先打开全文或混合检索；如果自然语言追问召回差，再启用 Query Rewrite；如果候选内容正确但顺序差，再检查 Rerank。一次只改一个变量并重跑测试集，否则你很难知道改进来自哪里。&lt;/p&gt;
&lt;h3 id=&#34;4-再增加实时接口分支&#34;&gt;4. 再增加实时接口分支
&lt;/h3&gt;&lt;p&gt;将“还剩多少年假”识别为个人数据查询，先检查登录身份与授权，再调用内部 HR API。制度解释仍走知识库，个人余额走实时系统。不要把每天变化的数据定期塞进向量库，也不要让 LLM 直接拼数据库 SQL。&lt;/p&gt;
&lt;h3 id=&#34;5-发布前做四类测试&#34;&gt;5. 发布前做四类测试
&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;测试&lt;/th&gt;
&lt;th&gt;重点观察&lt;/th&gt;
&lt;th&gt;合格标准示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;检索测试&lt;/td&gt;
&lt;td&gt;目标片段是否进入候选、排名是否合理&lt;/td&gt;
&lt;td&gt;Top 候选包含正确制度，引用无过期版本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;生成测试&lt;/td&gt;
&lt;td&gt;是否忠于引用、是否正确拒答&lt;/td&gt;
&lt;td&gt;无资料时不补写；数字与原文一致&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;流程测试&lt;/td&gt;
&lt;td&gt;分支、变量、异常与超时&lt;/td&gt;
&lt;td&gt;API 失败进入兜底，不泄露内部错误&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;安全测试&lt;/td&gt;
&lt;td&gt;越权、Prompt 注入、密钥与隐私&lt;/td&gt;
&lt;td&gt;非授权用户无法调用个人查询工具&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;私有部署组件命令与生产注意事项&#34;&gt;私有部署：组件、命令与生产注意事项
&lt;/h2&gt;&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/fastgpt-guide/deployment-architecture.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/fastgpt-guide/deployment-architecture.svg&#34; alt=&#34;FastGPT 私有部署的主要组件与数据流&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;官方 Docker Compose 架构至少包含 FastGPT 主服务、MongoDB、向量数据库和 AI Proxy。启用 Agent/Skill 沙箱、对象存储、MCP 或更复杂的解析能力后，还会出现额外服务。测试环境可以单机起步，生产环境则应把“能启动”和“可运营”分开看。&lt;/p&gt;
&lt;p&gt;官方当前提供交互式安装脚本，在 Linux、macOS 或 Windows WSL 中运行：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;FASTGPT_DEPLOY_BASE_URL&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;https://doc.fastgpt.cn &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  bash &amp;lt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;curl -fsSL https://doc.fastgpt.cn/deploy/install.sh&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker compose --profile prepull pull
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;脚本会选择向量数据库方案、下载 Compose 配置并生成随机凭据。生产环境执行远程脚本前，应先下载并审阅内容；保存生成后的 &lt;code&gt;docker-compose.yml&lt;/code&gt; 与凭据；把镜像标签固定到明确版本，不要长期依赖 &lt;code&gt;latest&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;部署后首先做三件事：配置对外访问的 &lt;code&gt;FE_DOMAIN&lt;/code&gt;；在管理后台配置聊天模型与 Embedding 模型；按需安装系统插件。主服务默认映射到 3000 端口，但生产环境应放在 HTTPS 反向代理之后，并只对外开放必要入口。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;生产检查项&lt;/th&gt;
&lt;th&gt;建议&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;数据持久化&lt;/td&gt;
&lt;td&gt;MongoDB、向量库、对象存储分别使用持久卷；验证恢复而不只是“做过备份”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;凭据&lt;/td&gt;
&lt;td&gt;替换默认密码；API Key、数据库密码和模型密钥进入 Secret 管理，不写入仓库&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;网络&lt;/td&gt;
&lt;td&gt;数据库不暴露公网；限制容器出站范围；上传与抓取功能设置大小、类型和域名白名单&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;升级&lt;/td&gt;
&lt;td&gt;阅读跨版本迁移说明，小步升级；先备份并在预发布环境回放核心测试集&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;观测&lt;/td&gt;
&lt;td&gt;记录节点耗时、模型错误、Token 消耗、检索命中与拒答率；为外部 API 设置超时和重试&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;容量&lt;/td&gt;
&lt;td&gt;关注向量条数、索引构建时间、MongoDB 容量、并发请求和模型限流，而不只看 CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;向量库选型不必一开始就追求最大规模。官方建议中，PGVector 方案更轻量，适合中小规模；达到非常大的向量规模或需要更强分布式能力时再评估 Milvus 等方案。迁移成本不仅是搬向量，还包括索引重建、停机窗口、性能回归与备份策略。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;api-接入与应用边界&#34;&gt;API 接入与应用边界
&lt;/h2&gt;&lt;p&gt;FastGPT 可以作为独立聊天页使用，也可以隐藏在你自己的 Web、企业微信、飞书或业务系统之后。OpenAI 风格接口降低了接入成本，但“兼容”不意味着每个扩展字段都完全等同于 OpenAI；应以当前实例的 OpenAPI 文档为准。&lt;/p&gt;
&lt;p&gt;一个便于理解的请求示例如下，实际 Key 和 App ID 从“发布渠道 → API”获得：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl https://fastgpt.example.com/api/v1/chat/completions &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  -H &lt;span class=&#34;s2&#34;&gt;&amp;#34;Authorization: Bearer &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$FASTGPT_API_KEY&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  -H &lt;span class=&#34;s2&#34;&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  -d &lt;span class=&#34;s1&#34;&gt;&amp;#39;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;    &amp;#34;appId&amp;#34;: &amp;#34;your-app-id&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;    &amp;#34;stream&amp;#34;: false,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;    &amp;#34;messages&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;      {&amp;#34;role&amp;#34;: &amp;#34;user&amp;#34;, &amp;#34;content&amp;#34;: &amp;#34;试用期员工有几天年假？&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;  }&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;在应用架构里，建议把 FastGPT 当成“AI 决策与生成服务”，而不是主业务数据库：订单状态、余额、审批结果等事实仍由业务系统保存；FastGPT 通过只读或受控工具查询它们。涉及扣款、发送通知、删除数据等副作用操作时，要加入幂等键、权限校验、参数校验和人工确认，不能只相信模型生成的参数。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;适合谁也不适合谁&#34;&gt;适合谁，也不适合谁
&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th style=&#34;text-align:center&#34;&gt;适配度&lt;/th&gt;
&lt;th&gt;原因&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;企业文档问答、客服知识助手&lt;/td&gt;
&lt;td style=&#34;text-align:center&#34;&gt;高&lt;/td&gt;
&lt;td&gt;知识库、引用、工作流、发布链路齐全&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内部流程助手、表单审核、内容处理&lt;/td&gt;
&lt;td style=&#34;text-align:center&#34;&gt;高&lt;/td&gt;
&lt;td&gt;可视化编排方便连接模型、规则和 HTTP 工具&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;快速交付多个垂直 AI 应用&lt;/td&gt;
&lt;td style=&#34;text-align:center&#34;&gt;高&lt;/td&gt;
&lt;td&gt;模型、知识库、插件和应用模板可复用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;纯聊天模型体验页&lt;/td&gt;
&lt;td style=&#34;text-align:center&#34;&gt;中&lt;/td&gt;
&lt;td&gt;能做，但如果没有知识与流程需求，平台可能偏重&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;超低延迟、高吞吐推理网关&lt;/td&gt;
&lt;td style=&#34;text-align:center&#34;&gt;低&lt;/td&gt;
&lt;td&gt;更适合由专门模型网关和推理服务承担&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;强事务、强一致性的核心业务流程&lt;/td&gt;
&lt;td style=&#34;text-align:center&#34;&gt;低&lt;/td&gt;
&lt;td&gt;LLM 与可视化编排不能替代事务系统和确定性服务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;想直接运营同类多租户 SaaS&lt;/td&gt;
&lt;td style=&#34;text-align:center&#34;&gt;需谨慎&lt;/td&gt;
&lt;td&gt;许可证对同类多租户 SaaS 和品牌信息有额外条件&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;与 Dify、Coze、Langflow、Flowise、LangChain/LlamaIndex 自研方案相比，FastGPT 的鲜明重心是 &lt;strong&gt;知识库问答 + 可视化工作流 + 私有部署交付&lt;/strong&gt;。平台类工具更快、更统一；代码框架则更自由、更容易做深度定制。正确选择通常不是问“谁功能最多”，而是问：团队最想省掉哪部分工程工作，又愿意承担哪部分平台约束。&lt;/p&gt;
&lt;p&gt;许可证尤其值得单独阅读。FastGPT 使用带附加条件的 FastGPT Open Source License：其文本以 Apache License 2.0 为基础，但对运营类似 FastGPT 的多租户 SaaS、移除或修改控制台 Logo 与版权信息等情形有额外要求。本文不是法律意见，商业化前应以仓库中的&lt;a class=&#34;link&#34; href=&#34;https://github.com/labring/FastGPT/blob/main/LICENSE&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;最新 LICENSE&lt;/a&gt;为准，必要时联系项目方取得授权。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;真正决定效果的是持续评估&#34;&gt;真正决定效果的，是持续评估
&lt;/h2&gt;&lt;p&gt;FastGPT 最有价值的地方，是让一条 AI 应用链路变得可搭建、可观察、可复用；它最容易被误用的地方，是让人以为拖完节点就等于完成系统。&lt;/p&gt;
&lt;p&gt;一个可长期运行的项目，至少应持续观察这些指标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;检索命中率&lt;/strong&gt;：正确资料是否进入候选，以及最终是否被引用；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;忠实度&lt;/strong&gt;：答案中的事实能否由引用支持；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务成功率&lt;/strong&gt;：流程是否真正完成业务目标，而不是只生成了流畅文本；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拒答与转人工率&lt;/strong&gt;：过高说明覆盖不足，过低可能意味着模型在冒险回答；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P50 / P95 延迟&lt;/strong&gt;：拆到 Query Rewrite、检索、Rerank、模型和外部工具；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单次成功任务成本&lt;/strong&gt;：只看 Token 单价会忽略失败重试和无效调用；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;版本回归&lt;/strong&gt;：模型、Prompt、知识、工作流任一变化都应跑固定测试集。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果只记住一句话：&lt;strong&gt;把 FastGPT 当成 AI 应用工程平台，而不是答案质量的替代品。&lt;/strong&gt; 先用最小流程证明价值，再用评测集迭代检索和 Prompt，最后才增加 Agent、插件与自动化动作。这样做出来的系统，通常比一开始就画满整张工作流画布更可靠。&lt;/p&gt;
&lt;p&gt;本文依据 2026 年 10 月可见的官方资料整理。FastGPT 更新较快，部署变量、版本依赖与接口字段请在实施前再次核对。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;官方资料：&lt;/strong&gt; &lt;a class=&#34;link&#34; href=&#34;https://github.com/labring/FastGPT&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;GitHub 仓库&lt;/a&gt; · &lt;a class=&#34;link&#34; href=&#34;https://doc.fastgpt.io/en/guide/version/opensource/intro&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;社区版说明&lt;/a&gt; · &lt;a class=&#34;link&#34; href=&#34;https://doc.fastgpt.io/en/guide/build/workflow/intro&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;工作流原理&lt;/a&gt; · &lt;a class=&#34;link&#34; href=&#34;https://doc.fastgpt.io/en/guide/dataset/dataset_engine&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;知识库检索&lt;/a&gt; · &lt;a class=&#34;link&#34; href=&#34;https://doc.fastgpt.io/en/self-host/deploy/docker&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Docker Compose 部署&lt;/a&gt; · &lt;a class=&#34;link&#34; href=&#34;https://doc.fastgpt.io/en/guide/build/publish/openapi&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;API 发布&lt;/a&gt;&lt;/p&gt;
</description>
        </item>
        <item>
        <title>KV Cache 深入理解：为什么只存 K/V，不存 Q</title>
        <link>https://SymphonyW.github.io/zh-cn/p/kv-cache/</link>
        <pubDate>Fri, 18 Sep 2026 21:35:00 +0800</pubDate>
        
        <guid>https://SymphonyW.github.io/zh-cn/p/kv-cache/</guid>
        <description>&lt;img src="https://SymphonyW.github.io/zh-cn/p/kv-cache/bg.svg" alt="Featured image of post KV Cache 深入理解：为什么只存 K/V，不存 Q" /&gt;&lt;h1 id=&#34;kv-cache-深入理解为什么只存-kv不存-q&#34;&gt;KV Cache 深入理解：为什么只存 K/V，不存 Q
&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;KV Cache 是大模型推理里最重要的加速手段之一。它的名字里只有 K 和 V，没有 Q，不是命名偷懒，而是由自回归解码的计算结构决定的：&lt;strong&gt;未来 token 会反复读取过去 token 的 Key 和 Value，却不会再读取过去 token 的 Query。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/kv-cache/bg.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/kv-cache/bg.svg&#34; alt=&#34;KV Cache 封面&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;先把结论放在前面：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;对象&lt;/th&gt;
&lt;th&gt;在注意力里的角色&lt;/th&gt;
&lt;th style=&#34;text-align:right&#34;&gt;未来 token 还会用吗&lt;/th&gt;
&lt;th style=&#34;text-align:right&#34;&gt;是否适合长期缓存&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Query &lt;code&gt;Q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前 token 发出的“读请求”&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;否&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;否&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key &lt;code&gt;K&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;历史 token 暴露出的“地址/索引”&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;是&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;是&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Value &lt;code&gt;V&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;历史 token 暴露出的“内容/信息”&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;是&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;是&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attention score&lt;/td&gt;
&lt;td&gt;当前 Query 与历史 Key 的匹配结果&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;通常否&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;否&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果只记一句话：&lt;strong&gt;Q 是读者，K/V 是被读的记忆。读者每一步都会换，记忆会被后面的读者反复读取。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;一把-attention-看成一次查表&#34;&gt;一、把 Attention 看成一次“查表”
&lt;/h2&gt;&lt;p&gt;Transformer 里的自注意力通常写成：&lt;/p&gt;
&lt;p&gt;$$
Q = XW_Q,\quad K = XW_K,\quad V = XW_V
$$&lt;/p&gt;
&lt;p&gt;$$
\text{Attention}(Q,K,V)=\text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
$$&lt;/p&gt;
&lt;p&gt;这个公式看起来像矩阵乘法，但从直觉上可以拆成三件事：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名字&lt;/th&gt;
&lt;th&gt;更像什么&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Q&lt;/code&gt; Query&lt;/td&gt;
&lt;td&gt;查询语句&lt;/td&gt;
&lt;td&gt;“我现在想找什么信息？”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;K&lt;/code&gt; Key&lt;/td&gt;
&lt;td&gt;索引地址&lt;/td&gt;
&lt;td&gt;“我这里有什么特征，可以被谁匹配到？”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;V&lt;/code&gt; Value&lt;/td&gt;
&lt;td&gt;实际内容&lt;/td&gt;
&lt;td&gt;“如果你匹配到了我，真正拿走的是这些信息。”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/kv-cache/attention-cache.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/kv-cache/attention-cache.svg&#34; alt=&#34;把 Attention 看成查表&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;在普通的全序列计算中，&lt;code&gt;Q/K/V&lt;/code&gt; 都来自同一段输入序列 &lt;code&gt;X&lt;/code&gt;。但在自回归生成里，时间方向非常关键：模型已经生成了前面的 token，现在只需要生成下一个 token。&lt;/p&gt;
&lt;p&gt;假设当前正在生成第 &lt;code&gt;t&lt;/code&gt; 个 token，在某一层 decoder self-attention 中真正需要的是：&lt;/p&gt;
&lt;p&gt;$$
\text{Attention}(q_t,K_{\le t},V_{\le t})
=\text{softmax}\left(\frac{q_tK_{\le t}^T}{\sqrt{d_k}}\right)V_{\le t}
$$&lt;/p&gt;
&lt;p&gt;注意这个公式里只有 &lt;strong&gt;当前的 &lt;code&gt;q_t&lt;/code&gt;&lt;/strong&gt; 和 &lt;strong&gt;历史到当前的 &lt;code&gt;K/V&lt;/code&gt;&lt;/strong&gt;。它并没有用到 &lt;code&gt;q_1, q_2, ..., q_{t-1}&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这就是 KV Cache 的根。&lt;/p&gt;
&lt;h2 id=&#34;二prefill-与-decode缓存到底发生在哪&#34;&gt;二、Prefill 与 Decode：缓存到底发生在哪
&lt;/h2&gt;&lt;p&gt;大模型生成通常可以分成两个阶段：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;阶段&lt;/th&gt;
&lt;th style=&#34;text-align:right&#34;&gt;输入规模&lt;/th&gt;
&lt;th&gt;主要工作&lt;/th&gt;
&lt;th&gt;KV Cache 的状态&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prefill&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;整个 prompt&lt;/td&gt;
&lt;td&gt;一次性处理用户输入、系统提示词、历史上下文&lt;/td&gt;
&lt;td&gt;建立初始 &lt;code&gt;K/V&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decode&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;每次 1 个新 token&lt;/td&gt;
&lt;td&gt;用新 token 继续生成下一个 token&lt;/td&gt;
&lt;td&gt;每步追加新的 &lt;code&gt;K/V&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/kv-cache/qkv-lifecycle.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/kv-cache/qkv-lifecycle.svg&#34; alt=&#34;Prefill 与 Decode&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;在 Prefill 阶段，模型要把 prompt 里的所有 token 都跑一遍。比如 prompt 长度是 &lt;code&gt;n&lt;/code&gt;，每一层都会产生：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;K_cache[layer] = [k1, k2, ..., kn]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;V_cache[layer] = [v1, v2, ..., vn]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;然后进入 Decode 阶段。每生成一个新 token，模型并不会把从 &lt;code&gt;x1&lt;/code&gt; 到 &lt;code&gt;xt&lt;/code&gt; 的整段上下文重新计算一遍，而是只处理最新 token：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;new token -&amp;gt; 当前层 hidden state
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          -&amp;gt; q_new, k_new, v_new
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          -&amp;gt; q_new 读取已有 K_cache / V_cache
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          -&amp;gt; k_new, v_new 追加进缓存
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;用伪代码看会更清楚：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# prefill
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;for layer in layers:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    q, k, v = project(prompt_hidden_states)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    K_cache[layer] = k
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    V_cache[layer] = v
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    hidden_states = attention(q, K_cache[layer], V_cache[layer])
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# decode: 每次只处理一个新 token
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;for step in generation:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    x = embedding(last_token)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    for layer in layers:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        q_new, k_new, v_new = project(x)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        K_cache[layer].append(k_new)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        V_cache[layer].append(v_new)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        x = attention(q_new, K_cache[layer], V_cache[layer])
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    last_token = sample(lm_head(x))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里有一个容易忽略的细节：&lt;strong&gt;KV Cache 是每一层都有一份，不是整个模型共享一份。&lt;/strong&gt; 因为每一层的 hidden state 不同，投影出来的 &lt;code&gt;K/V&lt;/code&gt; 也不同。&lt;/p&gt;
&lt;p&gt;常见张量形状可以写成：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;K_cache[layer]: [batch, kv_heads, seq_len, head_dim]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;V_cache[layer]: [batch, kv_heads, seq_len, head_dim]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果是普通多头注意力（MHA），&lt;code&gt;kv_heads&lt;/code&gt; 通常等于 query heads；如果是 MQA/GQA，&lt;code&gt;kv_heads&lt;/code&gt; 会少很多，这是现代大模型降低 KV Cache 显存压力的重要技巧。&lt;/p&gt;
&lt;h2 id=&#34;三为什么只存-kv不存-q&#34;&gt;三、为什么只存 K/V，不存 Q
&lt;/h2&gt;&lt;p&gt;这个问题是理解 KV Cache 的核心。直觉答案是：&lt;strong&gt;旧 Q 用完就没了；旧 K/V 会被未来反复访问。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/kv-cache/why-not-cache-q.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/kv-cache/why-not-cache-q.svg&#34; alt=&#34;为什么不缓存 Q&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;更严谨一点，可以从四个角度看。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;角度&lt;/th&gt;
&lt;th&gt;为什么旧 Q 没必要存&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;公式&lt;/td&gt;
&lt;td&gt;生成第 &lt;code&gt;t&lt;/code&gt; 个 token 时用的是 &lt;code&gt;q_t K_{\le t}^T&lt;/code&gt;，旧的 &lt;code&gt;q_i&lt;/code&gt; 不在公式里&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;因果方向&lt;/td&gt;
&lt;td&gt;未来 token 只能拿自己的 Query 去看过去的 Key，不能反过来让过去的 Query 查询未来&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;复用价值&lt;/td&gt;
&lt;td&gt;&lt;code&gt;k_i/v_i&lt;/code&gt; 会被 &lt;code&gt;q_{i+1}, q_{i+2}, ...&lt;/code&gt; 多次读取；&lt;code&gt;q_i&lt;/code&gt; 只服务于第 &lt;code&gt;i&lt;/code&gt; 行 attention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;显存收益&lt;/td&gt;
&lt;td&gt;存 Q 会增加大量显存，却几乎不减少 decode 计算&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;可以把第 &lt;code&gt;i&lt;/code&gt; 个 token 的三样东西拆开看：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;token &lt;code&gt;i&lt;/code&gt; 产生的东西&lt;/th&gt;
&lt;th&gt;第 &lt;code&gt;i&lt;/code&gt; 步怎么用&lt;/th&gt;
&lt;th&gt;第 &lt;code&gt;i+1&lt;/code&gt; 步以后怎么用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;q_i&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用它查询 &lt;code&gt;K_{\le i}&lt;/code&gt;，生成当前 token 的注意力输出&lt;/td&gt;
&lt;td&gt;不再需要&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;k_i&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;被 &lt;code&gt;q_i&lt;/code&gt; 匹配，也成为缓存的一部分&lt;/td&gt;
&lt;td&gt;会被未来每个 &lt;code&gt;q_j&lt;/code&gt; 匹配&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v_i&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;被当前注意力加权求和&lt;/td&gt;
&lt;td&gt;会被未来每个注意力分布加权求和&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;再换一种说法：&lt;code&gt;q_i&lt;/code&gt; 描述的是“第 &lt;code&gt;i&lt;/code&gt; 个位置想问什么”，这个问题只对第 &lt;code&gt;i&lt;/code&gt; 个位置有意义。到了第 &lt;code&gt;j&lt;/code&gt; 个位置，模型关心的是“第 &lt;code&gt;j&lt;/code&gt; 个位置想问什么”，也就是 &lt;code&gt;q_j&lt;/code&gt;，而不是 &lt;code&gt;q_i&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;如果存-q能不能少算一点&#34;&gt;如果存 Q，能不能少算一点？
&lt;/h3&gt;&lt;p&gt;通常不能。&lt;/p&gt;
&lt;p&gt;Decode 时新 token 的 &lt;code&gt;q_new&lt;/code&gt; 必须根据当前层的 hidden state 现场算出来。这个 hidden state 又来自前面层的输出，而前面层的输出依赖当前 token、位置编码、残差、MLP、LayerNorm 以及上一层 attention 结果。换句话说，未来的 Query 在未来 token 出现之前根本不存在。&lt;/p&gt;
&lt;p&gt;缓存旧 Query 最多能避免“重新计算旧位置的 Query”。但 KV Cache 的设计本来就已经不重新计算旧位置了：decode 阶段只跑新 token，旧位置不会再进前向计算图。于是旧 Query 没有可节省的计算。&lt;/p&gt;
&lt;h3 id=&#34;旧-attention-分数要不要缓存&#34;&gt;旧 attention 分数要不要缓存？
&lt;/h3&gt;&lt;p&gt;也通常不缓存。&lt;/p&gt;
&lt;p&gt;第 &lt;code&gt;t&lt;/code&gt; 步真正需要的是新的一行分数：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;score_t = q_t · [k1, k2, ..., kt]^T
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;旧分数 &lt;code&gt;score_1, score_2, ..., score_{t-1}&lt;/code&gt; 描述的是旧 Query 如何看旧 Key。它们不能替代 &lt;code&gt;q_t&lt;/code&gt; 对历史 Key 的新匹配。把所有 attention score 都存下来，显存会从线性增长变成更接近平方增长，收益却很小。&lt;/p&gt;
&lt;h3 id=&#34;训练时为什么好像会保存-q&#34;&gt;训练时为什么好像会保存 Q？
&lt;/h3&gt;&lt;p&gt;训练和推理要分开看。&lt;/p&gt;
&lt;p&gt;训练阶段为了反向传播，框架可能会临时保留 &lt;code&gt;Q/K/V&lt;/code&gt;、attention score、softmax 结果、MLP 激活等中间量，或者用 activation checkpointing 在反向时重算。那是为了求梯度，不是为了自回归生成加速。&lt;/p&gt;
&lt;p&gt;推理阶段没有反向传播，目标是尽快生成下一个 token。因此长期留下来的只有未来还会被读到的东西：&lt;code&gt;K/V&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id=&#34;四kv-cache-换来的代价&#34;&gt;四、KV Cache 换来的代价
&lt;/h2&gt;&lt;p&gt;KV Cache 的本质是用显存换时间。它显著减少重复计算，但显存会随着上下文长度线性增长。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/kv-cache/memory-cost.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/kv-cache/memory-cost.svg&#34; alt=&#34;KV Cache 显存成本&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;常见估算公式是：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;KV cache bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;≈ 2 × layers × batch × seq_len × kv_heads × head_dim × bytes_per_elem
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;其中前面的 &lt;code&gt;2&lt;/code&gt; 表示 &lt;code&gt;K&lt;/code&gt; 和 &lt;code&gt;V&lt;/code&gt; 两份缓存。&lt;/p&gt;
&lt;p&gt;举个近似例子，假设模型有：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th style=&#34;text-align:right&#34;&gt;数值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;层数 &lt;code&gt;layers&lt;/code&gt;&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;query heads&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;head_dim&lt;/code&gt;&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;128&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;数据类型&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;FP16 / BF16，2 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果是普通 MHA，&lt;code&gt;kv_heads = 32&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;每 token KV cache
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;≈ 2 × 32 × 32 × 128 × 2 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;= 524288 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;≈ 512 KiB
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果是 GQA，假设 &lt;code&gt;kv_heads = 8&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;每 token KV cache
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;≈ 2 × 32 × 8 × 128 × 2 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;= 131072 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;≈ 128 KiB
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;所以同样 8192 token 的上下文，单请求 KV Cache 可能就是：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;注意力结构&lt;/th&gt;
&lt;th style=&#34;text-align:right&#34;&gt;每 token 约占&lt;/th&gt;
&lt;th style=&#34;text-align:right&#34;&gt;8192 token 约占&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MHA，32 个 KV heads&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;512 KiB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;4 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GQA，8 个 KV heads&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;128 KiB&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;1 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这还只是 batch size = 1 的估算。服务端如果要同时处理很多请求，KV Cache 很快就会成为吞吐瓶颈。&lt;/p&gt;
&lt;p&gt;现在再看“不存 Q”的显存意义：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;架构&lt;/th&gt;
&lt;th&gt;如果额外缓存 Q，会发生什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MHA：&lt;code&gt;q_heads = kv_heads&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在原本 K+V 两份之外再加一份 Q，缓存大约增加 50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GQA/MQA：&lt;code&gt;q_heads &amp;gt; kv_heads&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Q 的头数比 KV 多，额外缓存 Q 可能比现有 KV Cache 还大&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;也就是说，缓存 Q 不只是“多存一点”，在 GQA/MQA 模型里可能是非常昂贵的浪费。&lt;/p&gt;
&lt;h2 id=&#34;五工程实践里还要注意什么&#34;&gt;五、工程实践里还要注意什么
&lt;/h2&gt;&lt;p&gt;KV Cache 听起来简单，真正服务化时会牵出不少工程问题：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prefix Cache&lt;/td&gt;
&lt;td&gt;如果很多请求共享同一段系统提示词，可以复用这段前缀的 K/V&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PagedAttention&lt;/td&gt;
&lt;td&gt;把 KV Cache 切成页管理，减少长短请求混批时的显存碎片&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sliding Window&lt;/td&gt;
&lt;td&gt;只保留最近窗口内的 K/V，适合局部注意力模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KV Quantization&lt;/td&gt;
&lt;td&gt;把 KV Cache 量化到更低 bit，降低显存与带宽压力&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beam Search&lt;/td&gt;
&lt;td&gt;多个 beam 共享前缀时要正确复制、重排或引用缓存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RoPE 位置&lt;/td&gt;
&lt;td&gt;缓存的 K 通常已经带有位置信息，复用前缀时必须保证 position offset 一致&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;KV Cache 不会改变模型的理论输出，它只是避免重复计算。实际工程里如果使用低精度缓存、不同 attention kernel、不同 batch 合并策略，可能会有极小的数值差异，但原理上它是等价优化。&lt;/p&gt;
&lt;p&gt;它也不是万能加速器：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;KV Cache 的作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prefill 很长&lt;/td&gt;
&lt;td&gt;仍然要处理完整 prompt，KV Cache 不能消除 prefill 成本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decode 很长&lt;/td&gt;
&lt;td&gt;很有用，因为每步都能复用历史 K/V&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;上下文非常长&lt;/td&gt;
&lt;td&gt;计算变少，但显存和显存带宽压力变大&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;请求很多且长度不一&lt;/td&gt;
&lt;td&gt;需要分页缓存、连续批处理、调度器配合&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;最后用一张小表把核心逻辑收住：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;答案&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;KV Cache 缓存的是什么&lt;/td&gt;
&lt;td&gt;每一层历史 token 的 Key 和 Value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;为什么能加速&lt;/td&gt;
&lt;td&gt;Decode 阶段不再重复计算旧 token 的 K/V 和旧位置输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;为什么不存 Q&lt;/td&gt;
&lt;td&gt;旧 Query 只用于旧位置的 attention，不会被未来 token 读取&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;代价是什么&lt;/td&gt;
&lt;td&gt;显存随层数、序列长度、batch、KV head 数线性增长&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;主要优化方向&lt;/td&gt;
&lt;td&gt;GQA/MQA、PagedAttention、Prefix Cache、KV 量化、窗口注意力&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;一句话总结：KV Cache 的名字不是偶然。K/V 是上下文留给未来的可读记忆；Q 是当前 token 临时发出的读取动作。动作结束就结束了，记忆才需要留下来。&lt;/strong&gt;&lt;/p&gt;
</description>
        </item>
        <item>
        <title>Token 是怎么计算的：AI 厂商和中转站如何统计用量</title>
        <link>https://SymphonyW.github.io/zh-cn/p/token-counting-in-ai/</link>
        <pubDate>Fri, 04 Sep 2026 21:43:19 +0800</pubDate>
        
        <guid>https://SymphonyW.github.io/zh-cn/p/token-counting-in-ai/</guid>
        <description>&lt;img src="https://SymphonyW.github.io/zh-cn/p/token-counting-in-ai/bg.svg" alt="Featured image of post Token 是怎么计算的：AI 厂商和中转站如何统计用量" /&gt;&lt;h1 id=&#34;token-是怎么计算的ai-厂商和中转站如何统计用量&#34;&gt;Token 是怎么计算的：AI 厂商和中转站如何统计用量
&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;用大模型 API 时，经常会看到 &lt;code&gt;prompt_tokens&lt;/code&gt;、&lt;code&gt;completion_tokens&lt;/code&gt;、&lt;code&gt;total_tokens&lt;/code&gt; 这些字段。很多人第一反应是：“Token 是不是就是字数？”不是。&lt;strong&gt;Token 是模型真正处理文本时使用的基本编号单元，厂商和中转站的用量统计，本质上就是围绕这些编号单元做输入、输出、缓存、工具调用和账单记录。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/token-counting-in-ai/bg.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/token-counting-in-ai/bg.svg&#34; alt=&#34;Token 用量统计流程&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;先给出一句话版本：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;简短答案&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Token 是什么&lt;/td&gt;
&lt;td&gt;模型 tokenizer 把文本切分后得到的编号单元&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token 等于字数吗&lt;/td&gt;
&lt;td&gt;不等于。中文、英文、标点、空格、代码都会有不同切法&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;厂商怎么统计&lt;/td&gt;
&lt;td&gt;请求进入模型前统计输入 token，生成时统计输出 token，结束后返回 usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;中转站怎么统计&lt;/td&gt;
&lt;td&gt;优先读取上游厂商返回的 usage，同时自己预估、限额、落库、扣费&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;为什么我本地算的不一样&lt;/td&gt;
&lt;td&gt;tokenizer 不同、隐藏模板、工具 schema、RAG 上下文、多模态、重试和缓存都会带来差异&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;一token-到底是什么&#34;&gt;一、Token 到底是什么
&lt;/h2&gt;&lt;p&gt;大模型并不是直接理解“字”或“词”。在进入模型前，文本会先经过一个叫 &lt;strong&gt;Tokenizer&lt;/strong&gt; 的组件，被切成一串 token，然后每个 token 再映射成数字 ID。&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;flowchart LR
    A[原始文本] --&gt; B[Tokenizer 切分]
    B --&gt; C[Token 序列]
    C --&gt; D[Token ID]
    D --&gt; E[模型推理]
&lt;/div&gt;&lt;p&gt;比如用户输入：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;帮我写一个 Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;在 tokenizer 看来，它可能会被切成类似这样的片段：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[&amp;#34;帮&amp;#34;, &amp;#34;我&amp;#34;, &amp;#34;写&amp;#34;, &amp;#34;一个&amp;#34;, &amp;#34; Docker&amp;#34;, &amp;#34;file&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这只是示意，不同模型的 tokenizer 结果可能完全不同。有的模型会把常见词切成一个 token，有的会把生僻字、特殊符号、空格、换行或代码符号切得更碎。&lt;/p&gt;
&lt;p&gt;因此：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hello&lt;/code&gt; 可能是 1 个 token；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hello world&lt;/code&gt; 可能是 2 到 3 个 token；&lt;/li&gt;
&lt;li&gt;一个中文词可能是 1 个 token，也可能被拆成多个 token；&lt;/li&gt;
&lt;li&gt;代码、JSON、Base64、日志堆栈通常比较费 token；&lt;/li&gt;
&lt;li&gt;空格、换行、标点也可能被计入 token。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;更准确地说，Token 是 &lt;strong&gt;“模型词表里的最小可识别片段”&lt;/strong&gt;。模型每次生成回答，本质上也是一个 token 一个 token 地往外吐。&lt;/p&gt;
&lt;h2 id=&#34;二为什么大模型要按-token-计费&#34;&gt;二、为什么大模型要按 Token 计费
&lt;/h2&gt;&lt;p&gt;因为大模型的主要成本和 token 数强相关：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;输入越长，模型要读的上下文越多；&lt;/li&gt;
&lt;li&gt;输出越长，模型要生成的步骤越多；&lt;/li&gt;
&lt;li&gt;上下文越长，显存、缓存和注意力计算压力越大；&lt;/li&gt;
&lt;li&gt;如果启用工具调用、多模态、长上下文、推理增强，成本结构还会更复杂。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以厂商通常不会简单按“请求次数”计费，而是按 token 计量。&lt;/p&gt;
&lt;p&gt;常见账单可以抽象成：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;费用 = 输入 token 数 * 输入单价
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     + 输出 token 数 * 输出单价
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     + 缓存 / 推理 / 图片 / 音频 / 工具等附加 token 成本
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;为什么输出 token 往往更贵？因为输出不是一次性算出来的，而是模型逐步生成。每生成一个新 token，都要基于已有上下文继续推理一次。输入阶段可以有缓存和批处理优化，输出阶段通常更受实时生成过程约束。&lt;/p&gt;
&lt;h2 id=&#34;三一次请求里哪些内容会被计入-token&#34;&gt;三、一次请求里哪些内容会被计入 Token
&lt;/h2&gt;&lt;p&gt;很多人只会数用户输入的那句话，但真实 API 请求里远不止这些内容。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;th&gt;是否通常计入&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;用户消息&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;用户真正输入的问题&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;系统提示词&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;&lt;code&gt;system&lt;/code&gt; 或平台预置角色设定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;开发者提示词&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;一些平台会额外注入规则或安全策略&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;历史对话&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;多轮聊天时，前面的上下文也会重新进入模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG 检索片段&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;从知识库召回后拼进 prompt 的内容&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;工具定义&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;Function calling / tool schema 往往很占 token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;工具调用结果&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;工具返回的 JSON、表格、日志也会进入后续上下文&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;输出文本&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;模型生成的回答&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;图片、音频、视频&lt;/td&gt;
&lt;td&gt;视模型而定&lt;/td&gt;
&lt;td&gt;通常会折算成专门的输入或输出计量单位&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;缓存命中内容&lt;/td&gt;
&lt;td&gt;视厂商而定&lt;/td&gt;
&lt;td&gt;有的平台会单独返回 cached tokens，并按更低价格计费&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;所以你看到的一次“简单提问”，在服务端可能是这样的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;系统提示词
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;+ 平台安全规则
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;+ 历史对话
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;+ 用户当前问题
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;+ RAG 检索内容
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;+ 工具 schema
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;+ 输出格式约束
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;= 实际输入给模型的 prompt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这也是为什么同样一句“帮我总结一下”，在不同应用里消耗 token 差异很大。聊天框、知识库问答、Agent、代码助手、联网搜索，它们背后拼进去的上下文不一样。&lt;/p&gt;
&lt;h2 id=&#34;四厂商是怎么统计-token-的&#34;&gt;四、厂商是怎么统计 Token 的
&lt;/h2&gt;&lt;p&gt;AI 厂商统计 token，通常发生在模型服务的几个关键节点。&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;sequenceDiagram
    participant C as Client
    participant G as API Gateway
    participant T as Tokenizer
    participant M as Model Server
    participant B as Billing

    C-&gt;&gt;G: 发送请求
    G-&gt;&gt;G: 鉴权、限流、检查上下文长度
    G-&gt;&gt;T: 对输入消息做 tokenization
    T--&gt;&gt;G: input token count
    G-&gt;&gt;M: 输入 token ID
    M--&gt;&gt;G: 逐 token 生成输出
    G-&gt;&gt;G: 统计 output token count
    G-&gt;&gt;B: 写入 usage 和账单事件
    G--&gt;&gt;C: 返回 response + usage
&lt;/div&gt;&lt;p&gt;大致流程是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;请求进入网关&lt;/strong&gt;：先鉴权，确认 API Key、组织、项目、余额、限速策略；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;规范化请求&lt;/strong&gt;：把 messages、tools、response format、多模态内容等转成模型内部格式；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输入计数&lt;/strong&gt;：用该模型对应的 tokenizer 计算输入 token；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;上下文检查&lt;/strong&gt;：判断输入 token + 允许输出 token 是否超过模型上下文窗口；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型生成&lt;/strong&gt;：模型每生成一个 token，服务端就能记录输出 token；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结束汇总&lt;/strong&gt;：生成完成、被 stop 截断、达到最大输出长度或发生错误后，汇总 usage；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;账单落库&lt;/strong&gt;：把模型、用户、请求 ID、token 数、价格、状态写入计费系统；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;返回结果&lt;/strong&gt;：API 响应里通常会带 usage 字段，供调用方记录。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一个典型 usage 结构可能长这样：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;usage&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;prompt_tokens&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1200&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;completion_tokens&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;350&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;total_tokens&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1550&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;更复杂的平台还可能拆得更细：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;usage&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;input_tokens&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1200&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;output_tokens&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;350&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;cached_input_tokens&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;800&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;reasoning_tokens&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;120&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;image_input_tokens&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;240&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;字段名和含义会随厂商变化，但底层逻辑类似：&lt;strong&gt;把模型实际处理过的输入和实际生成过的输出，折算成可计费单位。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;五流式输出怎么统计&#34;&gt;五、流式输出怎么统计
&lt;/h2&gt;&lt;p&gt;流式输出看起来像模型一边想一边发，其实服务端仍然知道自己生成了多少 token。&lt;/p&gt;
&lt;p&gt;普通非流式响应：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;模型生成完整回答 → 统计输出 token → 一次性返回 response + usage
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;流式响应：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;模型生成 token 1 → 发一个 chunk
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;模型生成 token 2 → 发一个 chunk
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;模型生成 token 3 → 发一个 chunk
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;模型结束 → 返回最终 usage 或由服务端内部记录
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;中转站处理流式请求时通常会做两件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;边转发 chunk 给客户端，保证用户体验；&lt;/li&gt;
&lt;li&gt;同时缓存或累积输出文本，等上游结束后记录用量。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果上游在最后一个 chunk 里返回 usage，中转站就直接使用它。如果上游不返回 usage，中转站可能会用 tokenizer 对完整输出再算一次，作为估算值。&lt;/p&gt;
&lt;p&gt;流式计费最容易出问题的地方是断连：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;通常怎么处理&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;用户主动停止生成&lt;/td&gt;
&lt;td&gt;已经生成的输出 token 仍可能计费&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;客户端网络断开&lt;/td&gt;
&lt;td&gt;上游可能还生成了一小段，取决于中转站是否及时取消请求&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;中转站转发失败&lt;/td&gt;
&lt;td&gt;要看上游是否已经成功处理，以及中转站的计费策略&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;请求超时&lt;/td&gt;
&lt;td&gt;可能只有输入被处理，也可能已有部分输出&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;因此严谨的中转站不会只根据“客户端收到了多少字”扣费，而会尽量以 &lt;strong&gt;上游 usage 或服务端实际生成记录&lt;/strong&gt; 为准。&lt;/p&gt;
&lt;h2 id=&#34;六中转站是怎么统计用户用量的&#34;&gt;六、中转站是怎么统计用户用量的
&lt;/h2&gt;&lt;p&gt;中转站可以理解成站在用户和上游模型厂商之间的 API 网关。它要做的不只是转发请求，还要做鉴权、限流、余额、计费、渠道选择和审计。&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;flowchart TD
    A[用户请求] --&gt; B[中转站鉴权]
    B --&gt; C[余额和额度检查]
    C --&gt; D[预估输入 token]
    D --&gt; E[选择上游渠道]
    E --&gt; F[转发给模型厂商]
    F --&gt; G[读取上游 usage]
    G --&gt; H[按价格表计算成本]
    H --&gt; I[扣用户余额或额度]
    I --&gt; J[写入用量流水]
    J --&gt; K[返回结果给用户]
&lt;/div&gt;&lt;p&gt;中转站一般会有三层统计：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层级&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;准确性&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;请求前预估&lt;/td&gt;
&lt;td&gt;判断是否超上下文、余额是否足够、是否限流&lt;/td&gt;
&lt;td&gt;估算&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;请求后结算&lt;/td&gt;
&lt;td&gt;根据上游 usage 或本地 tokenizer 计算真实消耗&lt;/td&gt;
&lt;td&gt;更接近真实&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;离线对账&lt;/td&gt;
&lt;td&gt;和上游账单、渠道日志、用户流水做一致性校验&lt;/td&gt;
&lt;td&gt;最终核对&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;1-请求前预估-token&#34;&gt;1. 请求前：预估 Token
&lt;/h3&gt;&lt;p&gt;请求还没发给上游时，中转站通常已经要做判断：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个请求会不会超过模型上下文窗口？&lt;/li&gt;
&lt;li&gt;用户余额够不够？&lt;/li&gt;
&lt;li&gt;是否超过每分钟 token 限额？&lt;/li&gt;
&lt;li&gt;是否命中某个模型或渠道的风控限制？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这时它会用对应模型的 tokenizer 先估算输入 token：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-gdscript3&#34; data-lang=&#34;gdscript3&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;model&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;tokenizer&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tokenizer_registry&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;normalized_prompt&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;render_messages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;system&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;system&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;messages&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;messages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;tools&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tools&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;response_format&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;response_format&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;input_tokens_estimated&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tokenizer&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;normalized_prompt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;input_tokens_estimated&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_output_tokens&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;context_window&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;reject&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;context length exceeded&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;remaining_quota&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;estimate_cost&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;input_tokens_estimated&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_output_tokens&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;reject&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;insufficient quota&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里的 &lt;code&gt;render_messages&lt;/code&gt; 很关键。真实模型不是只看用户输入的字符串，而是看消息角色、分隔符、工具定义、输出格式等拼接后的内部 prompt。中转站如果只数 &lt;code&gt;messages[-1].content&lt;/code&gt;，一定会低估。&lt;/p&gt;
&lt;h3 id=&#34;2-请求后读取-usage-并扣费&#34;&gt;2. 请求后：读取 usage 并扣费
&lt;/h3&gt;&lt;p&gt;请求完成后，中转站会优先使用上游厂商返回的 usage：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;response = upstream.call(request)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;usage = normalize_usage(response.usage, provider, model)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cost = usage.input_tokens * price.input
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     + usage.output_tokens * price.output
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     + usage.cached_tokens * price.cached_input
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     + usage.reasoning_tokens * price.reasoning
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ledger.insert({
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    request_id,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    user_id,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    provider,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    model,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    usage,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    cost,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    status: &amp;#34;success&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;user.balance -= cost
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;为什么要优先用上游 usage？因为上游最清楚模型内部到底拼了什么、缓存命中了多少、输出生成了多少、特殊模态如何折算。中转站自己的 tokenizer 只能尽量接近，不能保证完全一致。&lt;/p&gt;
&lt;h3 id=&#34;3-落库为什么要有用量流水&#34;&gt;3. 落库：为什么要有用量流水
&lt;/h3&gt;&lt;p&gt;一个成熟中转站不会只在用户表里减余额，还会写不可随意改动的流水记录。否则一旦用户质疑扣费，平台很难解释。&lt;/p&gt;
&lt;p&gt;一个简化的用量表可以这样设计：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ai_usage_ledger&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;BIGINT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;PRIMARY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;KEY&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request_id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;VARCHAR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;BIGINT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;provider&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;VARCHAR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;VARCHAR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;128&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;prompt_tokens&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;completion_tokens&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cached_tokens&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;reasoning_tokens&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;total_tokens&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;upstream_cost&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;DECIMAL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_cost&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;DECIMAL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;status&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;VARCHAR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;created_at&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TIMESTAMP&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;关键字段：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;request_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;幂等和排查，避免重试导致重复扣费&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;provider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;区分不同上游厂商&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不同模型价格和 tokenizer 不同&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输入 token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;completion_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输出 token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cached_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;缓存命中的输入 token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reasoning_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;推理类模型可能单独统计的内部输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upstream_cost&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;中转站实际付给厂商的成本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_cost&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;中转站按自己价格表向用户收取的费用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;成功、失败、取消、超时、部分输出等状态&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果平台有余额系统，扣费还要做事务处理：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;begin transaction
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  insert usage_ledger
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  update user_balance set balance = balance - user_cost
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  insert balance_change_log
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;commit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这样才能保证“用量流水、余额变化、账单记录”三者对得上。&lt;/p&gt;
&lt;h2 id=&#34;七中转站为什么会和官方统计不一致&#34;&gt;七、中转站为什么会和官方统计不一致
&lt;/h2&gt;&lt;p&gt;如果你自己写一个 token 计算器，经常会发现结果和官方 usage 对不上。原因主要有这些：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;原因&lt;/th&gt;
&lt;th&gt;为什么会导致差异&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;tokenizer 版本不同&lt;/td&gt;
&lt;td&gt;模型升级后词表或特殊 token 规则可能变了&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;消息模板不同&lt;/td&gt;
&lt;td&gt;厂商内部会给 role、message、tool 加包装格式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;系统提示词不可见&lt;/td&gt;
&lt;td&gt;应用或中转站可能注入了用户看不到的 prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;历史上下文被带入&lt;/td&gt;
&lt;td&gt;多轮聊天会把旧消息一起计入输入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;工具 schema 很大&lt;/td&gt;
&lt;td&gt;一个复杂 JSON Schema 可能比用户问题还长&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG 召回内容多&lt;/td&gt;
&lt;td&gt;知识库片段会直接增加输入 token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;输出格式约束&lt;/td&gt;
&lt;td&gt;JSON mode、结构化输出、示例格式都会增加 prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;多模态折算&lt;/td&gt;
&lt;td&gt;图片、音频、视频通常不是按普通文本 token 简单计算&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;缓存命中&lt;/td&gt;
&lt;td&gt;cached tokens 可能计入总量，但按不同价格结算&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;请求重试&lt;/td&gt;
&lt;td&gt;上游失败重试可能产生额外成本，平台要定义是否转嫁&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;所以中转站的合理做法是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;请求前用本地 tokenizer 预估&lt;/strong&gt;，用于限额和上下文判断；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;请求后以上游 usage 为准结算&lt;/strong&gt;，尽量减少争议；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有上游 usage 时再本地估算&lt;/strong&gt;，并在账单里标记来源；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定期离线对账&lt;/strong&gt;，比较中转站记录和上游账单差异。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;八上下文窗口和最大输出-token&#34;&gt;八、上下文窗口和最大输出 Token
&lt;/h2&gt;&lt;p&gt;Token 统计不只影响费用，也影响请求能不能成功。&lt;/p&gt;
&lt;p&gt;每个模型都有上下文窗口，比如能处理 8K、32K、128K 或更长上下文。这个窗口通常要同时容纳输入和输出：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;输入 token + 最大输出 token &amp;lt;= 模型上下文窗口
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;假设某模型上下文窗口是 8192：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&#34;text-align:right&#34;&gt;输入 token&lt;/th&gt;
&lt;th style=&#34;text-align:right&#34;&gt;设置最大输出&lt;/th&gt;
&lt;th&gt;是否安全&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;2000&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;1000&lt;/td&gt;
&lt;td&gt;安全&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;7000&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;1000&lt;/td&gt;
&lt;td&gt;接近上限&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;7900&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;1000&lt;/td&gt;
&lt;td&gt;可能被拒绝&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;8200&lt;/td&gt;
&lt;td style=&#34;text-align:right&#34;&gt;500&lt;/td&gt;
&lt;td&gt;输入本身已经超限&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;很多“上下文超长”错误，不是用户当前问题太长，而是应用把历史对话、系统 prompt、RAG 内容和工具 schema 全塞进去了。&lt;/p&gt;
&lt;p&gt;中转站通常会在请求前做保护：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;available_output = model.context_window - estimated_input_tokens
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;if available_output &amp;lt;= 0:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    reject(&amp;#34;input too long&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;max_output_tokens = min(request.max_output_tokens, available_output, platform_limit)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这也是为什么有些平台会提示“请开启上下文压缩”或“减少知识库引用数量”。本质是在帮你节省输入 token。&lt;/p&gt;
&lt;h2 id=&#34;九缓存-token-是怎么回事&#34;&gt;九、缓存 Token 是怎么回事
&lt;/h2&gt;&lt;p&gt;一些厂商会对重复前缀做上下文缓存。比如系统提示词、工具 schema、长文档前半部分每次都一样，那么服务端可以复用已经处理过的缓存，降低延迟和成本。&lt;/p&gt;
&lt;p&gt;抽象来看：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;请求 1:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;长系统提示词 + 工具 schema + 用户问题 A
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;全部输入都需要正常处理
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;请求 2:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;长系统提示词 + 工具 schema + 用户问题 B
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;前面相同部分可能命中缓存
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这类 usage 可能拆成：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输入 token 总数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cached_input_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输入中命中缓存的部分&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;billable_input_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;实际按普通输入价计费的部分，具体取决于厂商规则&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;对中转站来说，缓存会让计费更复杂。它不能只看 &lt;code&gt;total_tokens&lt;/code&gt; 乘一个固定单价，而要按厂商返回的细分字段计算。&lt;/p&gt;
&lt;h2 id=&#34;十不同任务的-token-消耗差异&#34;&gt;十、不同任务的 Token 消耗差异
&lt;/h2&gt;&lt;p&gt;同一个模型，不同应用形态的 token 消耗会差很多。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;Token 消耗特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;普通问答&lt;/td&gt;
&lt;td&gt;用户输入短，输出长度决定主要成本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;长文总结&lt;/td&gt;
&lt;td&gt;输入很长，输出中等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;代码生成&lt;/td&gt;
&lt;td&gt;代码符号、缩进、上下文文件都比较费 token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG 问答&lt;/td&gt;
&lt;td&gt;检索片段越多，输入 token 越高&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent 调工具&lt;/td&gt;
&lt;td&gt;工具定义和工具结果会反复进入上下文&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON 结构化输出&lt;/td&gt;
&lt;td&gt;schema、字段名、示例都会增加输入和输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedding&lt;/td&gt;
&lt;td&gt;通常只按输入文本 token 计量，没有 completion token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;图片理解&lt;/td&gt;
&lt;td&gt;图片会被折算成视觉输入 token 或类似单位&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;语音转写 / 语音生成&lt;/td&gt;
&lt;td&gt;可能按音频时长、音频 token 或混合规则计费&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果你在开发 AI 应用，最容易低估的是三类开销：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;历史对话&lt;/strong&gt;：用户只发一句话，但你把前 30 轮都带上了；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具 schema&lt;/strong&gt;：函数参数定义复杂，每次请求都要传；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAG 内容&lt;/strong&gt;：Top-K 设得太大，召回片段太长。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;优化 token 成本，通常不是把用户问题压短，而是控制这些“看不见的上下文”。&lt;/p&gt;
&lt;h2 id=&#34;十一如何自己估算-token&#34;&gt;十一、如何自己估算 Token
&lt;/h2&gt;&lt;p&gt;如果你是应用开发者，至少要在请求前做一次本地估算，用来控制上下文和成本。&lt;/p&gt;
&lt;p&gt;最简单但不精确的方法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;estimated_tokens = text.length / 3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这只能做非常粗的 UI 提示。更靠谱的方法是使用模型对应的 tokenizer：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tokenizer = get_tokenizer(model)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tokens = tokenizer.encode(rendered_prompt)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;count = len(tokens)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;但要记住：本地估算要尽量接近真实请求，而不是只数用户输入。&lt;/p&gt;
&lt;p&gt;应该计入：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;system prompt；&lt;/li&gt;
&lt;li&gt;developer prompt；&lt;/li&gt;
&lt;li&gt;历史 messages；&lt;/li&gt;
&lt;li&gt;RAG context；&lt;/li&gt;
&lt;li&gt;tool definitions；&lt;/li&gt;
&lt;li&gt;response format；&lt;/li&gt;
&lt;li&gt;当前用户问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不应该只计入：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;count_tokens(user_message)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;更合理的是：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-gdscript3&#34; data-lang=&#34;gdscript3&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;rendered_prompt&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;render_chat_request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;system_prompt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;developer_prompt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;history_messages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;retrieved_context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;tool_schemas&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;response_format&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;user_message&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;count_tokens&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rendered_prompt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;本地估算的目标不是做到和厂商 100% 一样，而是提前发现明显超限和明显超预算。&lt;/p&gt;
&lt;h2 id=&#34;十二中转站计费系统的工程要点&#34;&gt;十二、中转站计费系统的工程要点
&lt;/h2&gt;&lt;p&gt;如果从工程实现角度看，一个可靠的中转站至少要有这些模块：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模块&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;模型路由&lt;/td&gt;
&lt;td&gt;把用户请求路由到不同厂商、渠道或模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token 预估器&lt;/td&gt;
&lt;td&gt;请求前计算输入 token 和最大可能成本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Usage 归一化&lt;/td&gt;
&lt;td&gt;把不同厂商返回的 usage 统一成内部格式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;价格表&lt;/td&gt;
&lt;td&gt;维护模型输入、输出、缓存、多模态等单价&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;账单流水&lt;/td&gt;
&lt;td&gt;记录每次请求的 token、费用、状态和 request_id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;余额系统&lt;/td&gt;
&lt;td&gt;做预扣、实扣、退款或额度扣减&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;重试策略&lt;/td&gt;
&lt;td&gt;定义上游失败、超时、断流时是否继续扣费&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;对账任务&lt;/td&gt;
&lt;td&gt;和上游账单做日级或小时级核对&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;风控限流&lt;/td&gt;
&lt;td&gt;按用户、模型、IP、组织维度限制请求和 token&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一个常见请求链路可以写成：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;handle_request(req):
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    user = authenticate(req.api_key)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    model = resolve_model(req.model)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    estimated = estimate_usage(req, model)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    reserve_quota(user, estimated.max_cost)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    try:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        upstream_resp = call_provider(req, model)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        actual_usage = normalize_usage(upstream_resp.usage)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        actual_cost = calculate_cost(actual_usage, model.price)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        settle_quota(user, actual_cost)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        write_ledger(req.id, user.id, model, actual_usage, actual_cost, &amp;#34;success&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        return upstream_resp
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    except ProviderError as err:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        release_or_settle_by_policy(user, req.id, err)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        write_ledger(req.id, user.id, model, estimated, 0, &amp;#34;failed&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        raise
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里有两个细节很关键：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;预扣不等于实扣&lt;/strong&gt;：请求前按最大可能输出预扣，结束后按真实 usage 结算，多退少补；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;幂等不等于不重试&lt;/strong&gt;：同一个 &lt;code&gt;request_id&lt;/code&gt; 重复上报时不能重复扣费，但上游真实重试产生的成本也要能解释清楚。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;十三怎么降低-token-成本&#34;&gt;十三、怎么降低 Token 成本
&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法&lt;/th&gt;
&lt;th&gt;原理&lt;/th&gt;
&lt;th&gt;风险&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;精简系统提示词&lt;/td&gt;
&lt;td&gt;减少每次固定输入&lt;/td&gt;
&lt;td&gt;规则太少可能影响输出稳定性&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;压缩历史对话&lt;/td&gt;
&lt;td&gt;摘要替代完整历史&lt;/td&gt;
&lt;td&gt;摘要可能丢细节&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;控制 RAG Top-K&lt;/td&gt;
&lt;td&gt;少塞无关片段&lt;/td&gt;
&lt;td&gt;召回不足会影响答案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;缩短工具 schema&lt;/td&gt;
&lt;td&gt;减少重复传入的结构定义&lt;/td&gt;
&lt;td&gt;过度简化会降低工具调用准确性&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;设置合理最大输出&lt;/td&gt;
&lt;td&gt;避免模型无限写长&lt;/td&gt;
&lt;td&gt;可能截断答案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;使用缓存&lt;/td&gt;
&lt;td&gt;重复前缀命中缓存&lt;/td&gt;
&lt;td&gt;依赖厂商支持和请求结构稳定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;按任务选模型&lt;/td&gt;
&lt;td&gt;简单任务用低成本模型&lt;/td&gt;
&lt;td&gt;模型能力不足会带来返工&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;日志里记录 usage&lt;/td&gt;
&lt;td&gt;找到最费 token 的链路&lt;/td&gt;
&lt;td&gt;注意隐私和数据合规&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;实践中最有效的优化通常是这三个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不要无脑带全量历史；&lt;/li&gt;
&lt;li&gt;不要把过长的文档片段塞进 prompt；&lt;/li&gt;
&lt;li&gt;不要给简单任务使用过强、过贵、上下文过大的模型。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结
&lt;/h2&gt;&lt;p&gt;Token 计算可以拆成三层理解：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层级&lt;/th&gt;
&lt;th&gt;关注点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;模型层&lt;/td&gt;
&lt;td&gt;tokenizer 如何把文本变成 token ID，模型如何逐 token 生成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API 层&lt;/td&gt;
&lt;td&gt;输入、输出、工具、多模态、缓存如何汇总成 usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;平台层&lt;/td&gt;
&lt;td&gt;中转站如何预估、限额、扣费、落库、对账和处理异常&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果只记住一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Token 不是字数，而是模型实际处理的上下文单位；厂商按模型真实处理量统计 usage，中转站则围绕 usage 做预估、转发、结算和审计。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对开发者来说，真正要养成的习惯是：不要只看用户输入的长度，而要看完整上下文。系统提示词、历史消息、RAG 片段、工具 schema、结构化输出约束，都会变成模型要处理的 token，也都会最终影响成本、延迟和上下文上限。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>关于微调的时机</title>
        <link>https://SymphonyW.github.io/zh-cn/p/%E5%85%B3%E4%BA%8E%E5%BE%AE%E8%B0%83%E7%9A%84%E6%97%B6%E6%9C%BA/</link>
        <pubDate>Sat, 11 Jul 2026 17:30:00 +0800</pubDate>
        
        <guid>https://SymphonyW.github.io/zh-cn/p/%E5%85%B3%E4%BA%8E%E5%BE%AE%E8%B0%83%E7%9A%84%E6%97%B6%E6%9C%BA/</guid>
        <description>&lt;img src="https://SymphonyW.github.io/zh-cn/p/%E5%85%B3%E4%BA%8E%E5%BE%AE%E8%B0%83%E7%9A%84%E6%97%B6%E6%9C%BA/bg.png" alt="Featured image of post 关于微调的时机" /&gt;&lt;h1 id=&#34;关于微调的时机&#34;&gt;关于微调的时机
&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;微调不是大模型效果优化的默认选项，更不是遇到效果问题后的第一反应。真正成熟的工程判断，是先弄清楚：当前缺的是&lt;strong&gt;知识&lt;/strong&gt;，还是模型的&lt;strong&gt;行为稳定性&lt;/strong&gt;？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;“什么时候应该做大模型微调？”是大模型岗位面试中很常见的一道题。&lt;/p&gt;
&lt;p&gt;它表面上在问微调技术，实际上考察的是两个更重要的能力：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;能不能区分知识问题和行为问题&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有没有技术选型的成本意识&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果连问题类型都没有判断清楚，就直接把微调提上日程，往往会把一个本可低成本解决的问题，变成数据准备、模型训练和持续维护的复杂工程。&lt;/p&gt;
&lt;p&gt;因此，讨论“要不要微调”之前，先记住一个结论：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;知识问题优先用检索解决，行为问题才进入微调的候选范围。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;先判断缺知识还是行为不稳定&#34;&gt;先判断：缺知识，还是行为不稳定？
&lt;/h2&gt;&lt;p&gt;大模型应用中的效果问题，可以先沿着“知识”和“行为”两条线拆开。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题类型&lt;/th&gt;
&lt;th&gt;典型表现&lt;/th&gt;
&lt;th&gt;对应解法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;知识问题&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;模型缺少业务文档、规章制度、行业事实等外部信息&lt;/td&gt;
&lt;td&gt;使用检索增强（RAG），在生成时补充所需知识&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;行为问题&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;模型知道正确内容，但输出格式、语气或判断规则不稳定&lt;/td&gt;
&lt;td&gt;提示词无法稳定解决后，再考虑微调&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;知识问题模型不知道事实&#34;&gt;知识问题：模型不知道事实
&lt;/h3&gt;&lt;p&gt;如果模型答不出某个问题，是因为它没有接触过企业内部文档、最新规章制度或特定行业事实，那么问题的核心不是“模型不会回答”，而是&lt;strong&gt;模型缺少回答所需的外部信息&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这类问题应该优先使用检索增强。知识保留在外部知识源中，需要时检索并提供给模型，而不是把它固化进模型权重。&lt;/p&gt;
&lt;p&gt;这样做的关键价值是：&lt;strong&gt;知识可以灵活更新&lt;/strong&gt;。文档变化时更新知识源即可，不必重新训练模型。&lt;/p&gt;
&lt;h3 id=&#34;行为问题模型知道但做得不稳定&#34;&gt;行为问题：模型知道，但做得不稳定
&lt;/h3&gt;&lt;p&gt;另一类问题是：正确内容模型其实知道，但它无法持续按照业务要求输出。例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输出结构有时正确、有时缺字段；&lt;/li&gt;
&lt;li&gt;语气无法长期保持一致；&lt;/li&gt;
&lt;li&gt;面对复杂判断规则时，执行结果反复波动。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这不是知识缺失，而是&lt;strong&gt;行为不稳定&lt;/strong&gt;。微调真正擅长处理的，正是这类“让模型更稳定地按照指定方式做事”的问题。&lt;/p&gt;
&lt;p&gt;所以，区分两者最直接的判断方式是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;缺事实，补检索；事实已经具备但表现不稳定，才评估微调。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;效果优化要走阶梯而不是一步跳到微调&#34;&gt;效果优化要走阶梯，而不是一步跳到微调
&lt;/h2&gt;&lt;p&gt;工业界做技术选型，不能只问“哪种方案能解决”，还要问“是否有成本更低的方案能先解决”。大模型效果优化应该按照从低成本到高成本的顺序逐级推进：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;顺序&lt;/th&gt;
&lt;th&gt;优化手段&lt;/th&gt;
&lt;th&gt;进入条件&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;第一步&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;把提示词优化到极致&lt;/td&gt;
&lt;td&gt;所有场景都应先完成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;第二步&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;引入检索增强方案&lt;/td&gt;
&lt;td&gt;问题涉及外部知识，或提示词缺少必要事实&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;第三步&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;考虑使用微调&lt;/td&gt;
&lt;td&gt;前两步走不通，且问题确实适合微调&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这套阶梯的关键不是三种技术谁更高级，而是&lt;strong&gt;上一级没有解决问题，再进入下一级&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;第一步先把提示词优化到极致&#34;&gt;第一步：先把提示词优化到极致
&lt;/h3&gt;&lt;p&gt;面对格式、语气和规则执行问题，第一步不是训练，而是先验证提示词能否解决。只要提示词仍有优化空间，就不能轻易得出“必须微调”的结论。&lt;/p&gt;
&lt;h3 id=&#34;第二步需要外部知识时引入检索&#34;&gt;第二步：需要外部知识时引入检索
&lt;/h3&gt;&lt;p&gt;如果问题来自知识缺失，就通过检索把业务文档、规章制度或行业事实提供给模型。检索负责补齐事实，也保留了知识更新的灵活性。&lt;/p&gt;
&lt;h3 id=&#34;第三步最后才考虑微调&#34;&gt;第三步：最后才考虑微调
&lt;/h3&gt;&lt;p&gt;只有提示词已经反复优化、检索也已经覆盖所需事实，问题依然存在时，微调才真正进入决策范围。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;微调应该是阶梯的第三层，而不是项目启动后的默认配置。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;真正需要微调的三个信号&#34;&gt;真正需要微调的三个信号
&lt;/h2&gt;&lt;p&gt;“微调不是起点”不等于“永远不做微调”。当下面三个信号出现时，微调就可能具备明确的工程价值。&lt;/p&gt;
&lt;h3 id=&#34;信号一行为与格式仍然不稳定&#34;&gt;信号一：行为与格式仍然不稳定
&lt;/h3&gt;&lt;p&gt;业务要求模型稳定输出特定结构、固定语气或复杂规则，但提示词经过反复优化后，效果仍然时好时坏。&lt;/p&gt;
&lt;p&gt;这里有两个必要前提：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目标是约束模型的&lt;strong&gt;行为与输出形态&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;提示词优化已经充分尝试，但稳定性仍未达标。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两个条件同时成立，才说明问题可能需要通过微调进一步固化。&lt;/p&gt;
&lt;h3 id=&#34;信号二高频窄口径任务受到成本与延迟制约&#34;&gt;信号二：高频窄口径任务受到成本与延迟制约
&lt;/h3&gt;&lt;p&gt;意图分类、实体抽取、内容打标这类任务，通常口径明确、调用频率高。为了让模型准确执行规则，系统可能需要在每一次请求中重复携带很长的提示词。&lt;/p&gt;
&lt;p&gt;长提示词会带来持续的重复开销，也会增加延迟。此时，把任务行为通过微调迁移到小模型中，能够&lt;strong&gt;大幅降低成本和延迟&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这里决定是否微调的，不只是效果，还有调用规模下的长期成本。&lt;/p&gt;
&lt;h3 id=&#34;信号三距离业务硬指标只差最后几个点&#34;&gt;信号三：距离业务硬指标只差最后几个点
&lt;/h3&gt;&lt;p&gt;提示词已经做到位，检索也已经补齐事实，但准确率距离业务硬指标仍然差最后几个点。这说明低成本手段已经基本用尽，而剩余差距又会直接影响业务上线或验收。&lt;/p&gt;
&lt;p&gt;此时，微调才适合作为继续突破能力边界的手段。&lt;/p&gt;
&lt;p&gt;三个信号可以浓缩成一张判断表：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;信号&lt;/th&gt;
&lt;th&gt;关键问题&lt;/th&gt;
&lt;th&gt;微调价值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;行为与格式&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;提示词反复优化后，输出仍不稳定吗？&lt;/td&gt;
&lt;td&gt;固化结构、语气和复杂规则&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;成本与延迟&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;高频窄任务是否长期重复携带长提示词？&lt;/td&gt;
&lt;td&gt;用小模型降低持续成本与延迟&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;能力边界&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;提示词和检索都到位后，是否仍差硬指标最后几个点？&lt;/td&gt;
&lt;td&gt;进一步提升专项准确率&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;微调最常见的四个坑&#34;&gt;微调最常见的四个坑
&lt;/h2&gt;&lt;p&gt;真正的工程风险往往不在“会不会训练”，而在于微调目标、数据和评估是否正确。下面四个问题尤其常见。&lt;/p&gt;
&lt;h3 id=&#34;坑一用微调灌知识&#34;&gt;坑一：用微调灌知识
&lt;/h3&gt;&lt;p&gt;把业务文档或行业知识直接拿去微调，看起来像是在“教会模型”，实际很容易让知识变成模型中的&lt;strong&gt;模糊印象&lt;/strong&gt;，反而更容易胡说。&lt;/p&gt;
&lt;p&gt;更麻烦的是，知识会持续变化。一旦知识固化进模型权重，每次更新都可能重新触发数据准备和训练，维护成本极高。&lt;/p&gt;
&lt;p&gt;因此，这条边界要足够明确：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;知识类需求永远优先走检索，不要用微调灌知识。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;坑二重数量轻质量&#34;&gt;坑二：重数量，轻质量
&lt;/h3&gt;&lt;p&gt;微调数据不是越多越好。&lt;strong&gt;几百条高质量、统一口径的样本，效果可能好过几万条脏数据。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;数据中的口径冲突和错误示范，会直接让模型学到不稳定的行为。与其盲目追求数量，不如先确保样本准确、清晰且规则一致。&lt;/p&gt;
&lt;p&gt;对于简单任务，&lt;strong&gt;50～100 条样本就能看到明显效果&lt;/strong&gt;。这意味着，微调项目早期更应该关注样本质量，而不是先堆出庞大的数据规模。&lt;/p&gt;
&lt;h3 id=&#34;坑三忽视灾难性遗忘&#34;&gt;坑三：忽视灾难性遗忘
&lt;/h3&gt;&lt;p&gt;模型在窄任务上被过度微调后，可能出现通用能力退化。专项任务的指标变好，并不代表整体效果一定更好。&lt;/p&gt;
&lt;p&gt;因此，微调后的评估不能只盯着目标任务，必须同时检查：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;专项能力是否提升&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通用能力是否退化&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;只有两者一起评估，才能判断这次微调是否真的值得上线。&lt;/p&gt;
&lt;h3 id=&#34;坑四没有评估就开始微调&#34;&gt;坑四：没有评估就开始微调
&lt;/h3&gt;&lt;p&gt;没有评估体系就直接训练，相当于&lt;strong&gt;闭着眼睛开车&lt;/strong&gt;：训练完成后，既无法确认效果提升了多少，也无法判断出现了哪些退化。&lt;/p&gt;
&lt;p&gt;正确的顺序应该是：&lt;strong&gt;评估集先于训练搭建&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;先明确用什么样本和标准验证效果，再开始准备微调。否则，训练过程即使顺利完成，也无法形成可信的工程结论。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一套可落地的决策顺序&#34;&gt;一套可落地的决策顺序
&lt;/h2&gt;&lt;p&gt;面对“当前场景是否需要微调”，可以按下面的顺序做判断：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;决策步骤&lt;/th&gt;
&lt;th&gt;要回答的问题&lt;/th&gt;
&lt;th&gt;对应动作&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1. 定义问题&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;缺的是外部知识，还是行为稳定性？&lt;/td&gt;
&lt;td&gt;知识问题走检索；行为问题继续判断&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2. 优化提示词&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;提示词是否已经反复优化到位？&lt;/td&gt;
&lt;td&gt;没有则继续优化，不急于微调&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3. 补齐知识&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;是否缺少业务文档、制度或行业事实？&lt;/td&gt;
&lt;td&gt;引入 RAG，不把知识写进权重&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4. 检查信号&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;是否出现行为不稳、成本延迟或能力边界信号？&lt;/td&gt;
&lt;td&gt;信号明确后再立项微调&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5. 搭建评估&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;是否已有专项与通用能力评估？&lt;/td&gt;
&lt;td&gt;评估集就绪后才开始训练&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;6. 准备数据&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;样本是否高质量且口径统一？&lt;/td&gt;
&lt;td&gt;先保证质量，再考虑数量&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这套顺序体现的不是对微调的保守，而是对工程成本和方案边界的尊重。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;面试时应该怎样回答&#34;&gt;面试时应该怎样回答？
&lt;/h2&gt;&lt;p&gt;如果面试官问“什么时候做微调”，一个逻辑完整的回答可以这样组织：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我会先判断这是知识问题还是行为问题。模型缺少业务文档、规章制度或行业事实时，优先使用 RAG，因为知识需要灵活更新，不应该固化进模型权重。模型已经知道正确内容，但格式、语气或判断规则在提示词反复优化后仍不稳定，才是微调的适用场景。&lt;/p&gt;
&lt;p&gt;在方案顺序上，我会先把提示词优化到极致，再引入检索增强，前两步走不通后才考虑微调。真正推动微调立项的信号主要有三个：行为和格式仍然不稳定；高频窄口径任务受长提示词的成本与延迟影响；提示词和检索都到位后，准确率距离业务硬指标仍差最后几个点。&lt;/p&gt;
&lt;p&gt;开始训练前，我会先搭建评估集，并保证训练样本高质量、口径统一。评估时同时关注专项能力和通用能力，避免窄任务过度微调导致灾难性遗忘。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个回答覆盖了问题分类、方案优先级、成本判断和上线风险，也更接近真实工业场景中的技术决策方式。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结
&lt;/h2&gt;&lt;p&gt;大模型项目中，微调最容易被误用的原因，是团队把所有效果问题都归结为“模型能力不够”。但真正需要先回答的是：&lt;strong&gt;模型缺的是事实，还是稳定执行规则的行为？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;整套决策可以压缩为三句话：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;知识问题优先用 RAG，行为问题才考虑微调&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;先提示词，再检索，最后才是微调&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有明确微调信号、评估集和高质量数据，就不要启动训练&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;成熟的工业界方案，往往是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;微调管形态，检索管事实。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但要特别注意：这是一套经过逐级验证后形成的&lt;strong&gt;终局组合方案&lt;/strong&gt;，不是项目一开始就要全部开启的技术清单。&lt;/p&gt;
&lt;p&gt;真正专业的选型，不是把所有技术都用上，而是在正确的问题上使用正确的手段。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>经典面试题：RAG 检索 top-k 到底怎么设？</title>
        <link>https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/</link>
        <pubDate>Wed, 08 Jul 2026 22:05:00 +0800</pubDate>
        
        <guid>https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/</guid>
        <description>&lt;img src="https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/bg.png" alt="Featured image of post 经典面试题：RAG 检索 top-k 到底怎么设？" /&gt;&lt;h1 id=&#34;经典面试题rag-检索-top-k-到底怎么设&#34;&gt;经典面试题：RAG 检索 top-k 到底怎么设？
&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;面试官问：&amp;ldquo;你们 RAG 系统的 top-k 是怎么设的？&amp;rdquo; 你如果只回答一句&amp;quot;看情况&amp;quot;，那基本就和 offer 说再见了。top-k 这个看似简单的参数，背后考的是你对&lt;strong&gt;完整 RAG 链路&lt;/strong&gt;的理解深度。本文参考「小哲讲 agent 面试」的拆解思路，把这个高频面试题一次讲透。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一为什么-top-k-是必考题&#34;&gt;一、为什么 top-k 是必考题？
&lt;/h2&gt;&lt;p&gt;在 RAG（Retrieval-Augmented Generation）系统中，top-k 决定&lt;strong&gt;从向量数据库里召回多少条文档片段喂给大模型&lt;/strong&gt;。它看起来只是 &lt;code&gt;search_kwargs={&amp;quot;k&amp;quot;: 4}&lt;/code&gt; 这么一个参数，但它同时牵动四件事：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;top-k 的影响&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;召回率（Recall）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;k 太小 → 漏掉真正相关的片段&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;噪声（Noise）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;k 太大 → 召回一堆无关片段干扰生成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;成本（Cost）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;k 越大 → Prompt 越长 → token 费用越高、延迟越高&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;生成质量（Quality）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;噪声片段会让 LLM &amp;ldquo;迷失在中间&amp;rdquo;，答非所问&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/top-k.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/top-k.png&#34; alt=&#34;top-k&#34; width=&#34;1758&#34; height=&#34;459&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; class=&#34;gallery-image&#34; data-flex-grow=&#34;383&#34; data-flex-basis=&#34;919px&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;也就是说，top-k 是&lt;strong&gt;召回、噪声、成本、质量&lt;/strong&gt;四个目标的交汇点。面试官真正想听的不是&amp;quot;我设成了 5&amp;quot;，而是&amp;quot;我为什么这样设，背后是什么链路思维&amp;quot;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二top-k-设小了幻觉的温床&#34;&gt;二、top-k 设小了：幻觉的温床
&lt;/h2&gt;&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/topk-too-small.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/topk-too-small.svg&#34; alt=&#34;top-k 设小的隐患：排名第一的片段可能不完整&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;很多人凭直觉认为&amp;quot;检索结果越靠前越相关，取 Top-1 不就行了？&amp;quot;——这是第一个坑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;问题在于：排名第一 ≠ 信息完整。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;举个真实场景：用户问&amp;quot;公司请假流程是什么？&amp;quot;，向量库里有一份《员工手册》第 3 章详细写了请假流程，但因为切分（chunking）策略，这段内容被切成两半：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;片段 A（排名第 1）：&amp;quot;……请假需提前在 OA 系统提交申请，由直属主管审批。&amp;quot;&lt;/li&gt;
&lt;li&gt;片段 B（排名第 2）：&amp;ldquo;超过 3 天的请假需提供医院证明，并报 HR 备案。&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你只取 Top-1，LLM 拿到的上下文只有&amp;quot;提交申请 + 主管审批&amp;quot;这一半，它大概率会&lt;strong&gt;编造&lt;/strong&gt;&amp;ldquo;3 天以上需要什么材料&amp;rdquo;——因为用户问到了，它必须给出回答，但又没看到完整信息。这就是典型的&lt;strong&gt;幻觉&lt;/strong&gt;：模型不是在撒谎，而是被不完整的上下文逼着&amp;quot;脑补&amp;quot;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心结论&lt;/strong&gt;：top-k 设小的代价是&lt;strong&gt;召回不足 → 上下文不完整 → 模型幻觉&lt;/strong&gt;。排名第一只能保证&amp;quot;最相关&amp;quot;，不能保证&amp;quot;最完整&amp;quot;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三top-k-设大了噪声与成本的双重暴击&#34;&gt;三、top-k 设大了：噪声与成本的双重暴击
&lt;/h2&gt;&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/topk-too-large.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/topk-too-large.svg&#34; alt=&#34;top-k 设大的隐患：噪声片段淹没真正答案&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;那干脆设大点，k=20、k=50 总不会漏了吧？这是第二个坑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;k 太大会引入三类问题：&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;31-噪声淹没信号&#34;&gt;3.1 噪声淹没信号
&lt;/h3&gt;&lt;p&gt;向量检索本质是&lt;strong&gt;相似度排序&lt;/strong&gt;，但&amp;quot;相似&amp;quot;不等于&amp;quot;相关&amp;quot;。当 k=20 时，后 10 条往往是&amp;quot;看起来有点像但其实没用&amp;quot;的片段。这些噪声片段进入 Prompt 后，会触发 LLM 的 &lt;strong&gt;&amp;ldquo;Lost in the Middle&amp;rdquo;&lt;/strong&gt; 现象——模型对上下文中间位置的信息注意力会下降，反而把真正相关的片段忽略了。&lt;/p&gt;
&lt;h3 id=&#34;32-成本线性增长&#34;&gt;3.2 成本线性增长
&lt;/h3&gt;&lt;p&gt;LLM 的推理成本是按 token 计费的，k 越大，Prompt 越长：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;k=4：上下文约 2000 token&lt;/li&gt;
&lt;li&gt;k=20：上下文约 10000 token&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;成本直接 5 倍，延迟也跟着涨。线上高 QPS 场景下，这是真金白银的浪费。&lt;/p&gt;
&lt;h3 id=&#34;33-生成质量下降&#34;&gt;3.3 生成质量下降
&lt;/h3&gt;&lt;p&gt;更隐蔽的问题是：&lt;strong&gt;噪声片段会让模型&amp;quot;分心&amp;quot;&lt;/strong&gt;。LLM 是基于注意力机制的，当 Prompt 里混入大量无关内容，模型会尝试&amp;quot;综合&amp;quot;这些信息，导致回答跑题、啰嗦，甚至把无关片段当事实引用。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心结论&lt;/strong&gt;：top-k 设大的代价是&lt;strong&gt;噪声增加 → 成本上升 → 生成质量反而下降&lt;/strong&gt;。多并不等于好。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四两阶段检索粗排--精排&#34;&gt;四、两阶段检索：粗排 + 精排
&lt;/h2&gt;&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/two-stage-retrieval.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/two-stage-retrieval.svg&#34; alt=&#34;两阶段检索：粗排保召回，精排提质量&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;既然 k 设小了漏、设大了吵，那有没有两全其美的办法？这就是**两阶段检索（Two-Stage Retrieval）**的价值。&lt;/p&gt;
&lt;h3 id=&#34;41-第一阶段粗排recall宁可错杀不可放过&#34;&gt;4.1 第一阶段：粗排（Recall）——宁可错杀，不可放过
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用什么&lt;/strong&gt;：双塔 Embedding 模型（如 &lt;code&gt;bge-large-zh&lt;/code&gt;、&lt;code&gt;text-embedding-3&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;怎么做&lt;/strong&gt;：query 和 doc 分别编码，算余弦相似度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;k 设多大&lt;/strong&gt;：放宽到 &lt;strong&gt;20~50&lt;/strong&gt;，甚至 100&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目标&lt;/strong&gt;：&lt;strong&gt;高召回率&lt;/strong&gt;，把可能相关的片段都捞进来，哪怕混入噪声&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一步本质是&amp;quot;撒大网&amp;quot;，速度快（向量检索毫秒级），但精度有限。&lt;/p&gt;
&lt;h3 id=&#34;42-第二阶段精排precision去伪存真&#34;&gt;4.2 第二阶段：精排（Precision）——去伪存真
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用什么&lt;/strong&gt;：Cross-Encoder 重排模型（如 &lt;code&gt;bge-reranker-large&lt;/code&gt;、Cohere Rerank）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;怎么做&lt;/strong&gt;：把 query 和每一条候选 doc &lt;strong&gt;拼在一起&lt;/strong&gt;送进模型，输出相关性分数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;取多少&lt;/strong&gt;：按分数从高到低取 &lt;strong&gt;3~5 条&lt;/strong&gt; 喂给 LLM&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目标&lt;/strong&gt;：&lt;strong&gt;高精度&lt;/strong&gt;，把粗排里的噪声过滤掉，只留真正相关的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一步是&amp;quot;过筛子&amp;quot;，精度高（Cross-Encoder 能捕捉 query-doc 的细粒度交互），但速度慢，所以只对粗排的少量候选做。&lt;/p&gt;
&lt;h3 id=&#34;43-为什么不能直接用-cross-encoder-检索&#34;&gt;4.3 为什么不能直接用 Cross-Encoder 检索？
&lt;/h3&gt;&lt;p&gt;因为 Cross-Encoder 没有&amp;quot;索引&amp;quot;概念，必须对库里每条 doc 都跑一次模型，百万级文档库根本扛不住。所以工业界的标准做法就是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;双塔粗排（快但糙）→ Cross-Encoder 精排（慢但准）→ 取 Top-N 喂 LLM&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这正是 top-k 在两阶段里的&amp;quot;分工&amp;quot;——粗排的 k 大（保召回），精排后的 k 小（保质量）。&lt;strong&gt;面试时把这两个 k 区分开讲，就已经超过 80% 的候选人了。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五动态阈值让输入规模自适应&#34;&gt;五、动态阈值：让输入规模自适应
&lt;/h2&gt;&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/dynamic-threshold.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/dynamic-threshold.svg&#34; alt=&#34;动态阈值：按相关性分数自适应截断&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;两阶段检索解决了&amp;quot;召回 vs 精度&amp;quot;的矛盾，但还有一个问题：&lt;strong&gt;精排后取几条？固定取 3 条还是 5 条？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;固定数字依然不够好，因为不同问题的&amp;quot;答案密度&amp;quot;不一样：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;简单事实题&lt;/strong&gt;（&amp;ldquo;公司全称是什么&amp;rdquo;）：可能只有 1 条强相关，取 5 条全是噪声&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复杂流程题&lt;/strong&gt;（&amp;ldquo;请假流程&amp;rdquo;）：可能需要 4~5 条片段拼起来才完整&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开放性问题&lt;/strong&gt;（&amp;ldquo;总结公司福利&amp;rdquo;）：可能需要 8~10 条覆盖不同福利项&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就引出&lt;strong&gt;动态阈值（Dynamic Threshold）&lt;/strong&gt;：&lt;/p&gt;
&lt;h3 id=&#34;51-基于分数的截断&#34;&gt;5.1 基于分数的截断
&lt;/h3&gt;&lt;p&gt;不固定数量，而是设一个&lt;strong&gt;相关性分数阈值&lt;/strong&gt; θ：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;保留所有 score &amp;gt;= θ 的片段
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;比如精排分数分布是 &lt;code&gt;[0.92, 0.88, 0.85, 0.41, 0.38, ...]&lt;/code&gt;，阈值设 0.5，那就只取前 3 条，后边的低分噪声自动丢弃。&lt;/p&gt;
&lt;h3 id=&#34;52-基于分数差距的截断drop-off-检测&#34;&gt;5.2 基于分数差距的截断（Drop-off 检测）
&lt;/h3&gt;&lt;p&gt;观察相邻分数的&amp;quot;断崖&amp;quot;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;分数：0.92 → 0.88 → 0.85 → 0.41（断崖！）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;在分数急剧下降的位置截断，能自适应地保留&amp;quot;高分组&amp;quot;而丢弃&amp;quot;低分组&amp;quot;。&lt;/p&gt;
&lt;h3 id=&#34;53-加上上下文长度约束&#34;&gt;5.3 加上上下文长度约束
&lt;/h3&gt;&lt;p&gt;当然，动态阈值还要和 LLM 的上下文窗口配合——总不能召回 50 条全塞进去。实际工程里通常是：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;最终输入 = min(动态阈值筛出的片段, 上下文长度预算)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心结论&lt;/strong&gt;：动态阈值让&amp;quot;取几条&amp;quot;从&lt;strong&gt;静态参数&lt;/strong&gt;变成&lt;strong&gt;自适应决策&lt;/strong&gt;，这是 top-k 的高阶玩法，也是面试加分项。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六完整链路top-k-不是参数是思维&#34;&gt;六、完整链路：top-k 不是参数，是思维
&lt;/h2&gt;&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/rag-pipeline-mindset.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E9%A2%98rag-%E6%A3%80%E7%B4%A2-top-k-%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E8%AE%BE/rag-pipeline-mindset.svg&#34; alt=&#34;完整 RAG 链路思维：top-k 是四阶段的协同&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;把前面几节串起来，你会看到 top-k 背后其实是一条完整的链路：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;用户 Query
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[Query 改写]（可选：HyDE / Multi-Query / Step-back）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[粗排] 双塔 Embedding 检索 → top-k₁ = 20~50（保召回）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[精排] Cross-Encoder Rerank → 重排打分
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[截断] 动态阈值 / Drop-off 检测 → top-k₂ = 3~5（保质量）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[上下文压缩]（可选：LongLLMLingua）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[Prompt 拼装] Context + Question
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[LLM 生成] → 可溯源回答
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;每个环节都有它的&amp;quot;k&amp;quot;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;k₁（粗排）&lt;/strong&gt;：大，保召回&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;k₂（精排后）&lt;/strong&gt;：小，保质量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;k₃（最终输入）&lt;/strong&gt;：受动态阈值和上下文预算双重约束&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;面试官听到你能区分 k₁、k₂、k₃，并解释清楚每一步的目标，基本就认定你&lt;strong&gt;真正做过 RAG 工程&lt;/strong&gt;，而不是只会调 LangChain 的 &lt;code&gt;search_kwargs={&amp;quot;k&amp;quot;: 4}&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七面试高分回答模板&#34;&gt;七、面试高分回答模板
&lt;/h2&gt;&lt;p&gt;如果被问到&amp;quot;top-k 怎么设&amp;quot;，可以这样组织回答（约 90 秒）：&lt;/p&gt;
&lt;h3 id=&#34;第一层先承认-trade-off10-秒&#34;&gt;第一层：先承认 trade-off（10 秒）
&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;top-k 不是孤立参数，它同时影响召回率、噪声、成本和生成质量，设小了会漏、设大了会吵，所以不能一刀切。&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;第二层讲两阶段检索40-秒&#34;&gt;第二层：讲两阶段检索（40 秒）
&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;我们的做法是两阶段检索。粗排用双塔 Embedding，k 设到 20~50，优先保召回；精排用 Cross-Encoder Rerank，比如 &lt;code&gt;bge-reranker-large&lt;/code&gt;，对粗排结果重打分。Cross-Encoder 精度高但慢，所以只对少量候选做。&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;第三层讲动态阈值25-秒&#34;&gt;第三层：讲动态阈值（25 秒）
&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;精排后不固定取几条，而是用动态阈值——设一个相关性分数下限，或者检测分数 drop-off 的断崖位置，自适应截断。再叠加 LLM 上下文长度预算做上限保护。&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;第四层回到本质15-秒&#34;&gt;第四层：回到本质（15 秒）
&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;本质上 top-k 考的不是调参，而是对 RAG 链路的理解——召回、排序、截断、生成，每个阶段都有它的 k。区分清楚 k₁、k₂、k₃，效果就稳了。&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八常见追问与应对&#34;&gt;八、常见追问与应对
&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;面试官追问&lt;/th&gt;
&lt;th&gt;参考回答要点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;ldquo;粗排 k 设 50 不会太慢吗？&amp;rdquo;&lt;/td&gt;
&lt;td&gt;向量检索是 ANN（近似最近邻），百万级库毫秒级，HNSW/IVF 索引加持下没问题&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;ldquo;Rerank 模型怎么选？&amp;rdquo;&lt;/td&gt;
&lt;td&gt;中文用 &lt;code&gt;bge-reranker-large&lt;/code&gt;，英文用 Cohere Rerank；轻量场景可用 &lt;code&gt;bge-reranker-base&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;ldquo;动态阈值怎么调？&amp;rdquo;&lt;/td&gt;
&lt;td&gt;离线评估集上扫阈值，看 Recall@k 和 Precision@k 的 F1 拐点；或用分数分布的肘部&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;ldquo;为什么不直接用长上下文模型塞进去？&amp;rdquo;&lt;/td&gt;
&lt;td&gt;长上下文有 Lost-in-the-Middle、成本高、不可溯源三个问题，RAG 仍然是更经济的方案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;ldquo;k 设大了模型答非所问怎么办？&amp;rdquo;&lt;/td&gt;
&lt;td&gt;先加 Rerank，再加动态阈值；必要时上 LongLLMLingua 做上下文压缩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;ldquo;如何评估 top-k 设得对不对？&amp;rdquo;&lt;/td&gt;
&lt;td&gt;用 Ragas / TruLens 做 Faithfulness、Context Recall、Answer Relevancy 三项指标评估&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结
&lt;/h2&gt;&lt;p&gt;回到那个面试题——&amp;ldquo;RAG 检索的 top-k 怎么设？&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;低分回答&lt;/strong&gt;：&amp;ldquo;看情况，一般设 3~5。&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;及格回答&lt;/strong&gt;：&amp;ldquo;设小了会幻觉，设大了会引入噪声，所以要在召回和精度之间权衡。&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;高分回答&lt;/strong&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;top-k 不是单一参数，而是贯穿 RAG 链路的协同设计。粗排用双塔 Embedding 大 k 保召回（20~50），精排用 Cross-Encoder 重排提精度，再用动态阈值自适应截断到 3~5 条，最后受 LLM 上下文预算约束。本质考的不是调参，而是对&lt;strong&gt;召回 → 排序 → 截断 → 生成&lt;/strong&gt;整条链路的理解。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;记住视频里那句点睛之笔：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;top-k 的本质是完整 RAG 链路思维，而非调参数。&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;把这句话在面试里讲出来，配合上面的链路图，这道题基本就稳了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;本文参考抖音「小哲讲 agent 面试」的视频内容整理扩展，原视频链接见文末。封面图由作者自行添加。&lt;/em&gt;&lt;/p&gt;
</description>
        </item>
        <item>
        <title>RAG 技术全景：让大模型从&#34;闭卷考试&#34;到&#34;开卷考试&#34;</title>
        <link>https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/</link>
        <pubDate>Wed, 08 Jul 2026 15:00:00 +0800</pubDate>
        
        <guid>https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/</guid>
        <description>&lt;img src="https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/bg.png" alt="Featured image of post RAG 技术全景：让大模型从&#34;闭卷考试&#34;到&#34;开卷考试&#34;" /&gt;&lt;h1 id=&#34;rag-技术全景让大模型从闭卷考试到开卷考试&#34;&gt;RAG 技术全景：让大模型从&amp;quot;闭卷考试&amp;quot;到&amp;quot;开卷考试&amp;quot;
&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;大模型很强，但它会&amp;quot;一本正经地胡说八道&amp;quot;。RAG（Retrieval-Augmented Generation，检索增强生成）给大模型外挂了一个&amp;quot;知识库&amp;quot;，让它从闭卷考试变成开卷考试，回答既准又能溯源。本文用 6 张图带你完整搞懂 RAG。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一先聊痛点大模型的幻觉从哪来&#34;&gt;一、先聊痛点：大模型的&amp;quot;幻觉&amp;quot;从哪来？
&lt;/h2&gt;&lt;p&gt;你大概遇到过这样的场景：问 GPT 一个公司内部的政策问题，或者让它总结一份最新发布的财报，它信誓旦旦地给你一段听起来很专业的回答——但仔细一查，全是编的。&lt;/p&gt;
&lt;p&gt;这就是大模型的&lt;strong&gt;幻觉（Hallucination）&lt;/strong&gt;。它不是故意的，而是由其工作方式决定的：LLM 本质上是在做&amp;quot;下一个 token 预测&amp;quot;，它根据训练时记住的概率分布生成文本，分不清&amp;quot;知道&amp;quot;和&amp;quot;不知道&amp;quot;。&lt;/p&gt;
&lt;p&gt;原生 LLM 在企业级场景下有四个硬伤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;知识截止&lt;/strong&gt;：训练数据有截止日期，不知道今天发生的事&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;幻觉问题&lt;/strong&gt;：没见过的知识就靠&amp;quot;编&amp;quot;，还很自信&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;领域知识不足&lt;/strong&gt;：对企业内部文档、专业细分领域不熟悉&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无法溯源&lt;/strong&gt;：回答不知道来自哪里，没法核验&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;要解决这些问题，传统思路是&lt;strong&gt;微调（Fine-tuning）&lt;/strong&gt;——用领域数据重新训练模型。但微调又贵又慢，而且知识一更新就得再来一遍。&lt;/p&gt;
&lt;p&gt;RAG 给了另一条路：&lt;strong&gt;不重新训练模型，而是给它外挂一个可随时更新的知识库，回答时先去知识库里检索，再基于检索到的内容作答。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/llm-vs-rag.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/llm-vs-rag.svg&#34; alt=&#34;原生 LLM vs RAG：闭卷考试 vs 开卷考试&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;就像考试一样：原生 LLM 是闭卷考试，全凭记忆，记不清就编；RAG 是开卷考试，遇到不会的可以翻书，答案有据可循。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二rag-是什么核心思想一句话&#34;&gt;二、RAG 是什么：核心思想一句话
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;RAG = 检索（Retrieval）+ 增强（Augmentation）+ 生成（Generation）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 LLM 生成回答之前，先从外部知识库中&lt;strong&gt;检索&lt;/strong&gt;相关资料，把这些资料&lt;strong&gt;增强&lt;/strong&gt;到 Prompt 里作为上下文，再让 LLM 基于这些上下文&lt;strong&gt;生成&lt;/strong&gt;回答。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;用一句话概括：&lt;strong&gt;RAG 让 LLM 从&amp;quot;闭卷考试&amp;quot;变成&amp;quot;开卷考试&amp;quot;。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;它和微调的根本区别在于：微调把知识&amp;quot;烧进&amp;quot;模型权重里，RAG 把知识放在模型之外的数据库里，用的时候现查。这也决定了两者的适用边界——后面会详细对比。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三rag-工作流程三阶段详解&#34;&gt;三、RAG 工作流程：三阶段详解
&lt;/h2&gt;&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/rag-overview.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/rag-overview.svg&#34; alt=&#34;RAG 工作流程：检索 → 增强 → 生成&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;RAG 的完整流程可以拆成&lt;strong&gt;离线建库&lt;/strong&gt;和&lt;strong&gt;在线问答&lt;/strong&gt;两大阶段。在线问答又分为检索、增强、生成三步。&lt;/p&gt;
&lt;h3 id=&#34;31-离线阶段把文档变成可检索的向量库&#34;&gt;3.1 离线阶段：把文档变成可检索的向量库
&lt;/h3&gt;&lt;p&gt;这一步是&amp;quot;建图书馆&amp;quot;的过程，通常一次性完成，知识更新时增量重建：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;文档加载&lt;/strong&gt;：读取 PDF、Word、HTML、Markdown 等原始文档&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;切分（Chunking）&lt;/strong&gt;：把长文档切成小段。为什么不整篇存？因为检索时要找最相关的片段，整篇文档向量会&amp;quot;稀释&amp;quot;语义；而且 LLM 上下文有限，塞不下整篇&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;向量化（Embedding）&lt;/strong&gt;：用嵌入模型把每段文本转成一个高维向量（通常 768~1536 维），语义相近的文本在向量空间里距离也近&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存入向量数据库&lt;/strong&gt;：把向量和原文一起存进去，建好索引，供后续快速检索&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;32-在线阶段检索--增强--生成&#34;&gt;3.2 在线阶段：检索 → 增强 → 生成
&lt;/h3&gt;&lt;p&gt;用户每一次提问，都会实时跑这三步：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;① 检索 Retrieval&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把用户问题用同一个 Embedding 模型转成向量&lt;/li&gt;
&lt;li&gt;在向量数据库中做相似度检索（通常用余弦相似度），召回 Top-K（一般 3~10 条）最相关的文档片段&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;② 增强 Augmentation&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把检索到的片段拼到 Prompt 模板里，作为上下文&lt;/li&gt;
&lt;li&gt;一个典型的 Prompt 长这样：&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;你是一个严谨的问答助手，请根据以下参考资料回答用户问题。
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;如果资料中没有答案，请回答&amp;#34;根据现有资料无法回答&amp;#34;，不要编造。
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;【参考资料】
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;{检索到的文档片段1}
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;{检索到的文档片段2}
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;【用户问题】
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;{用户的问题}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;③ 生成 Generation&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把拼好的 Prompt 喂给 LLM，生成最终回答&lt;/li&gt;
&lt;li&gt;可以要求模型在回答中标注引用来源，实现可溯源&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四完整架构图&#34;&gt;四、完整架构图
&lt;/h2&gt;&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/rag-architecture.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/rag-architecture.svg&#34; alt=&#34;RAG 完整架构：离线建库 &amp;#43; 在线问答&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;这张图把离线和在线两条流水线放在了一起。注意几个关键点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Embedding 模型必须一致&lt;/strong&gt;：离线建库用哪个模型，在线检索时也要用同一个，否则向量空间不兼容&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;知识更新无需重训模型&lt;/strong&gt;：新增文档只需走一遍&amp;quot;切分 → 向量化 → 入库&amp;quot;，模型权重完全不动&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;检索质量是 RAG 的命门&lt;/strong&gt;：如果第一步召回的片段就不相关，后面 LLM 再强也答不好&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五核心组件选型&#34;&gt;五、核心组件选型
&lt;/h2&gt;&lt;p&gt;RAG 系统由四个核心组件构成，选型直接影响效果：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;组件&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;常见选型&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Embedding 模型&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;文本转向量，决定语义匹配上限&lt;/td&gt;
&lt;td&gt;BGE（智源）、M3E、OpenAI text-embedding-3、Cohere、bge-m3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;向量数据库&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;存储与检索向量&lt;/td&gt;
&lt;td&gt;Milvus、Qdrant、Chroma、Pinecone、FAISS、Weaviate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LLM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;基于上下文生成回答&lt;/td&gt;
&lt;td&gt;GPT-4、Claude、Qwen、DeepSeek、Llama、GLM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;编排框架&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;串联整个流程&lt;/td&gt;
&lt;td&gt;LangChain、LlamaIndex、Haystack、DSPy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;几个选型经验：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;中文场景&lt;/strong&gt;：Embedding 优先考虑 &lt;code&gt;bge-large-zh&lt;/code&gt; 或 &lt;code&gt;bge-m3&lt;/code&gt;，比 OpenAI 的英文模型在中文检索上更准&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;轻量起步&lt;/strong&gt;：向量库用 Chroma（纯 Python，单机嵌入式）跑通流程，量大了再换 Milvus / Qdrant&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;框架选择&lt;/strong&gt;：快速原型用 LangChain，文档密集型场景用 LlamaIndex（它对文档处理和索引抽象更细致）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六进阶技术从能用到好用&#34;&gt;六、进阶技术：从&amp;quot;能用&amp;quot;到&amp;quot;好用&amp;quot;
&lt;/h2&gt;&lt;p&gt;朴素 RAG（一次向量检索 + 拼 Prompt）能跑通，但效果往往一般。真正生产级的 RAG 通常会在四个方向上做增强：&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/advanced-rag.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/advanced-rag.svg&#34; alt=&#34;RAG 进阶技术矩阵&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;h3 id=&#34;61-query-改写让问题更适合检索&#34;&gt;6.1 Query 改写（让问题更适合检索）
&lt;/h3&gt;&lt;p&gt;用户的问题往往是口语化、模糊的，直接拿去检索效果差。常见技巧：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Query Rewriting&lt;/strong&gt;：让 LLM 把&amp;quot;那个东西怎么用&amp;quot;改写成&amp;quot;产品 X 的使用方法&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HyDE（Hypothetical Document Embeddings）&lt;/strong&gt;：先让 LLM 生成一个假设性答案，再用这个答案的向量去检索——因为&amp;quot;答案&amp;quot;和&amp;quot;答案&amp;quot;的语义比&amp;quot;问题&amp;quot;和&amp;quot;答案&amp;quot;更接近&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-Query&lt;/strong&gt;：把一个问题拆成多个子问题分别检索，结果合并去重&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step-back Prompting&lt;/strong&gt;：先抽象出上位概念再检索，比如&amp;quot;2024 年诺贝尔物理学奖得主&amp;quot;先退到&amp;quot;诺贝尔物理学奖近年得主&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;62-混合检索--rerank多路召回精排提纯&#34;&gt;6.2 混合检索 + Rerank（多路召回，精排提纯）
&lt;/h3&gt;&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/retrieval-detail.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/retrieval-detail.svg&#34; alt=&#34;混合检索 &amp;#43; 重排：提升召回质量&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;单靠向量检索有个问题：它擅长语义匹配，但对&lt;strong&gt;专有名词、产品型号、人名&lt;/strong&gt;这种精确字面匹配反而不如传统检索。所以生产系统通常用&lt;strong&gt;混合检索&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;向量检索&lt;/strong&gt;（语义）+ &lt;strong&gt;BM25 关键词检索&lt;/strong&gt;（字面）并行召回&lt;/li&gt;
&lt;li&gt;用 &lt;strong&gt;RRF（Reciprocal Rank Fusion）&lt;/strong&gt; 融合两路结果&lt;/li&gt;
&lt;li&gt;再用 &lt;strong&gt;Cross-Encoder 重排模型&lt;/strong&gt;（如 &lt;code&gt;bge-reranker-large&lt;/code&gt;、Cohere Rerank）对召回结果做精排&lt;/li&gt;
&lt;li&gt;最后取 Top-K 喂给 LLM&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rerank 的关键区别：Embedding 是把 query 和 doc &lt;strong&gt;分别&lt;/strong&gt;编码再算相似度（双塔），而 Cross-Encoder 是把 query 和 doc &lt;strong&gt;拼一起&lt;/strong&gt;送进模型（交叉编码），精度高得多，但速度慢——所以先用向量检索粗筛，再用 Rerank 精排，兼顾速度和精度。&lt;/p&gt;
&lt;h3 id=&#34;63-graphrag基于知识图谱的全局检索&#34;&gt;6.3 GraphRAG：基于知识图谱的全局检索
&lt;/h3&gt;&lt;p&gt;微软 2024 年提出的 GraphRAG 解决的是朴素 RAG 的另一个痛点：&lt;strong&gt;局部性&lt;/strong&gt;。向量检索只能召回和问题相似的片段，回答不了&amp;quot;总结整个文档集的核心主题&amp;quot;这种全局性问题。&lt;/p&gt;
&lt;p&gt;GraphRAG 的做法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用 LLM 从文档中抽取&lt;strong&gt;实体和关系&lt;/strong&gt;，构建知识图谱&lt;/li&gt;
&lt;li&gt;用&lt;strong&gt;社区检测算法&lt;/strong&gt;（如 Leiden）把图谱分成主题层次化的社区&lt;/li&gt;
&lt;li&gt;为每个社区生成摘要&lt;/li&gt;
&lt;li&gt;查询时根据问题粒度选择社区摘要或具体节点回答&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;它特别适合&amp;quot;全局性总结&amp;quot;&amp;ldquo;跨文档推理&amp;quot;类问题，代价是建图成本高、更新麻烦。&lt;/p&gt;
&lt;h3 id=&#34;64-self-rag--自适应-rag让模型自己决定要不要检索&#34;&gt;6.4 Self-RAG / 自适应 RAG：让模型自己决定要不要检索
&lt;/h3&gt;&lt;p&gt;不是每个问题都需要检索——&amp;ldquo;1+1 等于几&amp;quot;还去查库就浪费了。Self-RAG 让模型学会自我反思：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;是否检索&lt;/strong&gt;：判断问题是否需要外部知识，简单问题直接答&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;检索结果是否相关&lt;/strong&gt;：过滤掉无关片段&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成是否忠于检索&lt;/strong&gt;：检查答案有没有脱离资料编造&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;好处是省 token、降延迟，坏处是需要训练模型学会这些判断（或用 prompt 模拟），实现复杂度高。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七方案对比rag-vs-微调-vs-长上下文&#34;&gt;七、方案对比：RAG vs 微调 vs 长上下文
&lt;/h2&gt;&lt;p&gt;&lt;a href=&#34;https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/solution-comparison.svg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;img src=&#34;https://SymphonyW.github.io/zh-cn/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E8%AE%A9%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BB%8E%E9%97%AD%E5%8D%B7%E8%80%83%E8%AF%95%E5%88%B0%E5%BC%80%E5%8D%B7%E8%80%83%E8%AF%95/solution-comparison.svg&#34; alt=&#34;四种知识注入方案横向对比&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;随着模型上下文窗口越来越长（Claude 200K、Gemini 1M、GPT-4o 128K），有人质疑 RAG 是不是过时了——直接把所有文档塞进上下文不就行了？&lt;/p&gt;
&lt;p&gt;实际并非如此，三者各有定位：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;长上下文&lt;/strong&gt;：贵（按 token 计费）、易&amp;quot;迷失在中间&amp;rdquo;（Lost in the Middle 现象）、不可溯源。适合一次性分析单份长文档&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAG&lt;/strong&gt;：经济、可扩展到海量文档、可溯源。适合知识库问答&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;微调&lt;/strong&gt;：改变模型行为/风格，而非注入事实知识。适合固定格式输出、领域术语适配&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAG + 微调&lt;/strong&gt;：效果最好，成本最高，用于高精度专业场景&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;经验法则：&lt;strong&gt;知识频繁变化 → RAG；风格/格式固定 → 微调；两者都要 → 结合用。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八实战用-langchain-跑通一个最小-rag&#34;&gt;八、实战：用 LangChain 跑通一个最小 RAG
&lt;/h2&gt;&lt;p&gt;理论讲完，来看一段能跑的代码。用 LangChain + Chroma + OpenAI 实现 30 行的 RAG：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;36
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;37
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;38
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;39
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;40
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;41
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TextDocumentLoader&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;langchain_text_splitters&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RecursiveCharacterTextSplitter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;langchain_openai&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ChatOpenAI&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;langchain_community.vectorstores&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Chroma&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;langchain_core.prompts&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ChatPromptTemplate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;langchain_core.runnables&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RunnablePassthrough&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;langchain_core.output_parsers&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;StrOutputParser&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ① 离线建库：加载 → 切分 → 向量化 → 入库&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;docs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TextDocumentLoader&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;knowledge.txt&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;load&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;chunks&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RecursiveCharacterTextSplitter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;chunk_size&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;500&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;chunk_overlap&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;50&lt;/span&gt;      &lt;span class=&#34;c1&#34;&gt;# 500 字一段，重叠 50 字&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;split_documents&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;docs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;vectorstore&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Chroma&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;from_documents&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;chunks&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;text-embedding-3-small&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;retriever&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;vectorstore&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_retriever&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;search_kwargs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;k&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# 召回 Top-4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ② 在线问答：检索 → 增强 → 生成&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;prompt&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ChatPromptTemplate&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;from_template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;你是严谨的问答助手，根据以下资料回答问题。资料中没有答案就说&amp;#34;无法回答&amp;#34;，不要编造。
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;【资料】
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{context}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;【问题】
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{question}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;format_docs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;docs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\n\n&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;page_content&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;d&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;docs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;rag_chain&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;context&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;retriever&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;format_docs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;question&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RunnablePassthrough&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;prompt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ChatOpenAI&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;gpt-4o-mini&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;temperature&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;StrOutputParser&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ③ 提问&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rag_chain&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;invoke&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;公司请假流程是什么？&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这段代码虽然简单，但已经包含了 RAG 的所有核心环节。生产化时再把 Embedding 换成 BGE、加上 Rerank、换成 Milvus，骨架不变。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九常见挑战与优化方向&#34;&gt;九、常见挑战与优化方向
&lt;/h2&gt;&lt;p&gt;实际落地 RAG，踩坑往往不在模型，而在检索。下面是常见问题对应的优化手段：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;症状&lt;/th&gt;
&lt;th&gt;根因&lt;/th&gt;
&lt;th&gt;优化方向&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;检索结果不相关&lt;/td&gt;
&lt;td&gt;Query 与文档语义差距大&lt;/td&gt;
&lt;td&gt;Query 改写、换更强的 Embedding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;召回准确但答非所问&lt;/td&gt;
&lt;td&gt;片段顺序/噪声干扰&lt;/td&gt;
&lt;td&gt;加 Rerank 精排、调 Top-K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;答案不忠于资料&lt;/td&gt;
&lt;td&gt;LLM 忽略上下文编造&lt;/td&gt;
&lt;td&gt;Prompt 加约束、降 temperature、强制引用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;专有名词召回差&lt;/td&gt;
&lt;td&gt;纯向量检索短板&lt;/td&gt;
&lt;td&gt;加 BM25 混合检索&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;上下文超长&lt;/td&gt;
&lt;td&gt;召回太多塞不下&lt;/td&gt;
&lt;td&gt;上下文压缩、LongLLMLingua、换长上下文模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;知识更新延迟&lt;/td&gt;
&lt;td&gt;索引重建慢&lt;/td&gt;
&lt;td&gt;增量索引、流式更新&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;答案无法溯源&lt;/td&gt;
&lt;td&gt;未保留片段来源&lt;/td&gt;
&lt;td&gt;检索结果带 metadata，Prompt 要求标注引用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一个经验：&lt;strong&gt;80% 的 RAG 优化工作都在检索阶段&lt;/strong&gt;，而不是生成阶段。先把检索做准，效果立刻提升一大截。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十典型应用场景&#34;&gt;十、典型应用场景
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;企业知识问答&lt;/strong&gt;：基于内部 Wiki、手册、合同的智能客服与员工助手&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;法律/医疗咨询&lt;/strong&gt;：基于法条、判例、药品说明、病历的精准问答&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码助手&lt;/strong&gt;：基于私有代码库的代码生成（Cursor、Copilot Workspace 都用了 RAG 思想）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;教育辅导&lt;/strong&gt;：基于教材的个性化答疑，答案可指向课本页码&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;智能搜索&lt;/strong&gt;：搜索引擎的 AI 摘要（Perplexity、Bing Chat、Google AI Overviews）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据分析&lt;/strong&gt;：Text-to-SQL，基于数据库 schema 的自然语言查询&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结
&lt;/h2&gt;&lt;p&gt;RAG 不是某个具体工具，而是一种 &lt;strong&gt;&amp;ldquo;把外部知识检索和大模型生成结合起来&amp;rdquo;&lt;/strong&gt; 的架构思想。它的价值在于：让大模型能用最新、最准、可溯源的外部知识来回答问题，而无需重新训练。&lt;/p&gt;
&lt;p&gt;回顾本文要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心思想&lt;/strong&gt;：检索 → 增强 → 生成，让 LLM 从闭卷变开卷&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两大阶段&lt;/strong&gt;：离线建库（切分+向量化+入库）+ 在线问答（检索+增强+生成）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;命门在检索&lt;/strong&gt;：80% 的优化工作都在检索阶段&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;进阶四件套&lt;/strong&gt;：Query 改写、混合检索+Rerank、GraphRAG、Self-RAG&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;方案选择&lt;/strong&gt;：知识频繁变化用 RAG，风格固定用微调，高精度场景结合使用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;随着模型上下文窗口变长、多模态能力增强，RAG 的形态还在演进（比如多模态 RAG、Agentic RAG），但&amp;quot;外挂知识库&amp;quot;这个核心思想短期内不会过时——毕竟，开卷考试永远比背书更可靠。&lt;/p&gt;
&lt;p&gt;如果你对其中某个环节（比如向量数据库选型、Embedding 微调、GraphRAG 实现）想深入，欢迎在评论区交流。&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
