<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>工程实践 on Chico's Tech Blog</title><link>https://realtime-ai.chat/tags/%E5%B7%A5%E7%A8%8B%E5%AE%9E%E8%B7%B5/</link><description>Recent content in 工程实践 on Chico's Tech Blog</description><image><title>Chico's Tech Blog</title><url>https://github.com/chicogong.png</url><link>https://github.com/chicogong.png</link></image><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Sat, 09 May 2026 11:00:00 +0800</lastBuildDate><atom:link href="https://realtime-ai.chat/tags/%E5%B7%A5%E7%A8%8B%E5%AE%9E%E8%B7%B5/index.xml" rel="self" type="application/rss+xml"/><item><title>AI 写的代码,谁来审</title><link>https://realtime-ai.chat/posts/coding-agent-review/</link><pubDate>Sat, 09 May 2026 11:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/coding-agent-review/</guid><description>AI 能批量产出代码后,工程瓶颈从「写」挪到了「审」。这篇讲清 AI 代码为什么更难 review、人审扛不住量怎么办、AI 审 AI 靠不靠谱,以及团队流程该怎么改。</description><content:encoded><![CDATA[<p>一个用 AI 写代码的开发者,现在一天能开五六个 PR。</p>
<p>而审你 PR 的那个人,一天还是只能审五六个。</p>
<p>这两个数字凑在一起,就是 2026 年大多数工程团队真正的麻烦。Faros AI 的数据里,高 AI 使用率的团队完成的任务多了 21%、合并的 PR 多了将近一倍,但 PR 的 review 时长涨了 91%——而且那些 PR 还更大。Opsera 一份覆盖 25 万开发者的报告说得更直白:在没有治理的情况下,AI 生成的 PR 在 review 队列里等的时间是普通 PR 的 4.6 倍,哪怕从写完到提 PR 的时间砍掉了一半多。</p>
<p>写代码这件事,被 AI 解决了一大半。审代码这件事,一点没变。瓶颈就这么从「写」挪到了「审」,而且大多数团队还没意识到。</p>
<h2 id="为什么-ai-写的代码审起来反而更难">为什么 AI 写的代码,审起来反而更难</h2>
<p>直觉上,AI 代码该更好审才对——它风格统一、不会忘加分号、命名规规矩矩。但真审过 AI 大批量产出的代码的人都知道,事情反过来了。AI 代码难审,难在三个地方。</p>
<p><strong>第一,它看着太合理了。</strong> 人写错代码,常常会留下「破绽」——变量名词不达意、缩进乱、注释和代码对不上,这些视觉信号会让 reviewer 警觉。AI 不会。AI 写的错误代码,长得和正确代码一模一样:命名得体、结构清晰、注释贴心。CodeRabbit 的研究说 AI 写的代码暴露的问题比人写的多 1.7 倍,而这些问题里相当一部分是逻辑错误——业界一个被反复引用的说法是,AI 代码的纯逻辑错误率比人高约 75%。一个长得人模人样的函数,你的大脑会默认它没问题,于是真正的 bug 藏在「看着合理」的外壳里溜过去了。</p>
<p><strong>第二,量。</strong> 一个 reviewer 以前一天面对的是三五个 PR、每个两三百行。现在是十几个 PR,而且单个更大。注意力是有总量的——审第一个 PR 时你逐行读,审到第八个时你已经在「扫」。Review 质量不是匀速下降的,是过了某个阈值之后断崖式塌掉的。量本身就是一种攻击。</p>
<p><strong>第三,意图断了。</strong> 人写代码,脑子里有一条没写下来的推理链:为什么用这个数据结构、为什么这里要加锁、为什么宁可慢一点也不并发。Reviewer 审的其实是这条链。AI 生成代码时,这条链在 prompt 和模型权重里,没留在 diff 里。你看到的是结论,看不到「为什么」。于是 review 退化成「这段代码自己自洽吗」,而不是「这段代码做的是不是我们真正想要的事」——后者才是 review 的价值所在。</p>
<pre class="mermaid">flowchart LR
  A[人写代码] --> B[diff 里有破绽<br/>能看出意图] --> C[reviewer 审意图]
  D[AI 写代码] --> E[diff 看着完美<br/>意图没留下] --> F[reviewer 只能审自洽性]
  style E fill:#fde7c2,stroke:#e8b23c
  style F fill:#f8d0d0,stroke:#d06666
</pre><p>把这三点合起来:更隐蔽的错误、更大的量、更稀薄的意图。这不是「AI 代码质量差」的问题,是 review 这个动作的前提被抽掉了。</p>
<h2 id="人审扛不住量第一反应往往是错的">人审扛不住量,第一反应往往是错的</h2>
<p>发现 reviewer 堵住了,团队的第一反应通常是这几个,而它们大多没用,甚至有害。</p>
<p><strong>「招更多 reviewer」</strong> ——慢、贵,而且高级工程师本来就稀缺。更要命的是,AI 产代码的速度还在涨(有统计说人均产出 2025 到 2026 一年涨了约 60%),你招人的速度永远追不上模型迭代的速度。这是一场你结构性赢不了的军备竞赛。</p>
<p><strong>「review 快一点」</strong> ——把每个 PR 的 review 时间压缩,等于直接降低 review 质量。前面说过,AI 代码的错误本来就更隐蔽,你越快扫,漏得越多。这是用「看起来通畅」换「实际上失控」。</p>
<p><strong>「先合了再说,出问题再修」</strong> ——在 AI 时代尤其危险。以前一个有问题的模式进了主干,影响范围有限。现在 AI 会以那段代码为样本,在下一次生成时把同样的错误复制到十个地方。坏模式的传播速度,跟生成速度一样快。</p>
<p>真正有用的方向只有一个:<strong>别让所有代码都用同一种强度去审。</strong> Review 是稀缺资源,稀缺资源必须分配,不能平摊。</p>
<p>把进来的变更分层。一个能落地的分法是这样:</p>
<table>
  <thead>
      <tr>
          <th>变更类型</th>
          <th>例子</th>
          <th>审法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>低风险机械改动</td>
          <td>改文案、加日志、补测试、依赖小版本升级</td>
          <td>AI 审 + CI 把关,人抽查</td>
      </tr>
      <tr>
          <td>中风险常规功能</td>
          <td>已有模块里加一个接口、改一个表单</td>
          <td>AI 先过一遍,人审「意图」那一层</td>
      </tr>
      <tr>
          <td>高风险核心改动</td>
          <td>鉴权、计费、并发、数据迁移、对外 API</td>
          <td>人逐行审,且必须是熟悉该模块的人</td>
      </tr>
  </tbody>
</table>
<p>关键不是这张表本身,是承认一件事:<strong>不是所有代码都值得人来审。</strong> 让你最贵的工程师去逐行看一个改 UI 文案的 PR,是对稀缺资源的浪费,也是在透支他审计费系统时该有的注意力。把人的注意力,留给真正需要人类判断的地方。</p>
<h2 id="ai-审-ai-的代码到底靠不靠谱">AI 审 AI 的代码,到底靠不靠谱</h2>
<p>既然量是 AI 带来的,那让 AI 来审,逻辑上很顺。现在 AI code review 工具已经是个成熟品类,几十个产品,有真实的 ROI 数据。但「靠不靠谱」要分两面说。</p>
<p><strong>靠谱的那一面:</strong> AI reviewer 在「机械层」上是真的好用,而且能做到人做不到的事。它能记住整个依赖树里哪些包有已知 CVE、哪些 license 不合规、哪些 API 已经废弃;能在跨文件的几十个改动里发现「这个函数签名变了但那边三个调用没改」。有评测说,能看全代码库的工具,比只看单个 diff 的工具多抓出 40%~60% 的跨文件问题。这类活,人本来就做不好——人记不住那么多东西。</p>
<p><strong>不靠谱的那一面,要说透。</strong> AI 审 AI,有一个结构性的盲区:<strong>它们共享同一套训练分布。</strong> 2026 年初有研究指出,「生成 Agent + 审查 Agent」的流水线里,第一个 Agent 的错误经常被第二个 Agent 直接放行——因为它们出自同一个分布,缺乏「对抗性的差异」,自审最后退化成把原来的错误又确认了一遍。还有一篇 2026 年 2 月的论文更狠:就算是异构的多 Agent 团队,整体表现也稳定地打不过其中最强的那个单一成员,失败机制是「为了达成共识而牺牲专业判断」。</p>
<p>翻译成大白话:<strong>AI 会和你一起犯同一个错,而且犯得很自信。</strong> 你写 prompt 时漏掉的那个约束,生成的 Agent 不知道,审查的 Agent 同样不知道——它没法审出一个它自己也意识不到的需求。这正是 review 最该抓的那种错:不是「代码写错了」,是「代码做的不是我们要的事」。</p>
<p>另一个具体限制:AI reviewer 一旦喂的代码太多就垮。一个上千行的 diff 会撑爆上下文窗口,模型开始丢失连贯性,看不到改动之间的关联。它在小而聚焦的 PR 上表现最好——这反过来也说明,PR 该小,本来就该小。</p>
<p>所以我的判断是:<strong>AI 审机械层,人审意图层,两者不是替代关系,是分工。</strong> 把 AI 当成那个不知疲倦、记性超好、但永远不会质疑需求本身的初级 reviewer。它清场,人做判断。指望 AI 把人这一层也包了,是把它最不擅长的事派给了它。</p>
<pre class="mermaid">flowchart TB
  PR[一个 PR 进来] --> AI[AI reviewer]
  AI --> M[机械层:CVE、废弃 API<br/>跨文件一致性、风格、空指针]
  AI --> H{还需要人吗?}
  H -->|高风险或涉及意图| HUM[人 reviewer<br/>只审:这是不是我们想要的]
  H -->|低风险且 CI 全绿| MERGE[合并]
  HUM --> MERGE
  style HUM fill:#fde7c2,stroke:#e8b23c
</pre><h2 id="测试和类型系统从保险变成地基">测试和类型系统,从「保险」变成「地基」</h2>
<p>以前我们说测试和类型系统是「锦上添花的工程素养」。在 AI 时代,它们的角色被彻底放大了——它们成了你<strong>唯一能规模化、不靠人盯的那道防线</strong>。</p>
<p>道理很简单。人 review 不可规模化,AI review 有共享盲区,但一个类型检查、一个测试用例,是确定性的:它对一行代码和对一百万行代码,执行的是同一套标准,不会因为今天是第八个 PR 就走神。AI 能把生产代码的速度提十倍,但它没法提高你 review 的速度——能跟上这个速度的,只有自动化的、确定性的校验。</p>
<p>具体几条,都是能马上做的:</p>
<ul>
<li><strong>类型系统要严。</strong> 能上严格模式就上严格模式。AI 很爱写「看着对、类型上其实有洞」的代码,一个严格的类型检查器会在 review 之前就把一类错误挡掉,而且零成本、零情绪。</li>
<li><strong>测试覆盖的是意图,不是行数。</strong> 别用覆盖率数字考核——AI 生成高覆盖率但什么都没验证的测试,毫不费力。要写的是「断言业务规则」的测试:这笔订单在这个条件下必须被拒。这种测试,AI 之后改坏了逻辑,它会立刻红。</li>
<li><strong>测试本身也得审,而且要人审。</strong> AI 写功能、又顺手把测试写了,这是个陷阱:它可能写一个「永远会过」的测试来配合一段错的实现。审 AI 代码时,测试那部分要单独、用人的眼睛看。</li>
<li><strong>把校验往左移。</strong> 类型检查、静态扫描、安全规则、测试,全塞进 CI 的硬门禁。能在代码进 review 队列之前就拦下的问题,就别留给那个已经在审第八个 PR 的人。</li>
</ul>
<p>一句话:<strong>人和 AI 都会累、会走神、会有盲区,确定性的检查不会。</strong> 在代码量爆炸的时代,把质量赌在「有人会仔细看」上,是不可持续的;把它建在自动化门禁上,才扛得住量。</p>
<h2 id="团队流程得跟着改">团队流程,得跟着改</h2>
<p>工具和分工都到位了,最后是流程。不改流程,前面都白搭。几条具体的:</p>
<p><strong>PR 必须小,而且单一意图。</strong> 这条从「最佳实践」升级成了「硬约束」。reviewer 的带宽是整个系统的瓶颈,而瓶颈必须被主动管理。一个改了八件事的千行 PR,人审不动,AI 也审不动(上下文撑爆)。规矩要立死:一个 PR 只干一件事。AI 让你写得快,但不该让你把八件事打包成一个 diff 甩给别人。</p>
<p><strong>PR 描述要写「为什么」,这是在补回断掉的意图链。</strong> 2026 年一个合格的 PR 描述应该回答四件事:这个改动想干什么、为什么这么干(一两句);怎么证明它能跑(测试 + 手动验证步骤);风险多大、哪些部分是 AI 生成的;以及——明确点出 1~2 个「请人重点看这里」的地方。最后这点最关键:让提 PR 的人(他脑子里有意图)主动告诉 reviewer 该把注意力放哪。diff 该读起来像一份「意图说明书」,而不是一堆代码行的堆砌。</p>
<p><strong>「谁提的 PR,谁对它负责」要写进规矩。</strong> 用 AI 生成的代码,不等于责任也外包给了 AI。「这是 AI 写的」永远不能成为甩锅的理由。你按了那个生成键,你提了这个 PR,这段代码就是你的。这条不立清楚,所有人都会松懈,因为没人觉得自己该为一段「不是我写的」代码负责。</p>
<p><strong>测 review 这个环节本身。</strong> 既然瓶颈在这,就得给它装仪表盘:PR 在队列里平均等多久、单个 PR 多大、合并后多久出回滚或 hotfix。这些数字会告诉你 review 是不是正在悄悄塌掉——大多数团队是数字已经很难看了才后知后觉。</p>
<h2 id="写在最后">写在最后</h2>
<p>AI 没有消灭 review,它把 review 推到了聚光灯下。</p>
<p>以前 review 是写代码顺带的一环,藏在流程里不显眼。现在写代码便宜到几乎免费,「这段代码到底对不对、是不是我们要的」这个判断,就成了整个交付流程里最贵、最不可替代的一步。</p>
<p>所以别再盯着「怎么让 AI 写得更多」了——那个问题基本解决了。真正该花力气的是另一边:<strong>把 review 重新设计一遍。</strong> 让确定性的检查去扛量,让 AI 去扛机械层,把人最稀缺的判断力,集中投到「这段代码是不是在做我们真正想做的事」上。</p>
<p>AI 写的代码谁来审?机械层交给 AI,量交给自动化门禁,意图——只能是人。而且得是清醒的、注意力没被第八个 PR 耗光的人。把流程改成能保住这份清醒的样子,这件事,没有任何模型能替你做。</p>
<hr>
<p>参考资料:</p>
<ul>
<li><a href="https://dev.to/code-board/code-review-is-the-real-bottleneck-of-2026-and-most-teams-dont-see-it-5eed">Code Review Is the Real Bottleneck of 2026 — And Most Teams Don&rsquo;t See It</a></li>
<li><a href="https://levelup.gitconnected.com/the-ai-code-review-bottleneck-is-already-here-most-teams-havent-noticed-1b75e96e6781">The AI Code Review Bottleneck Is Already Here. Most Teams Haven&rsquo;t Noticed.</a></li>
<li><a href="https://addyo.substack.com/p/code-review-in-the-age-of-ai">Code Review in the Age of AI — Addy Osmani</a></li>
<li><a href="https://arxiv.org/pdf/2603.25773">The Specification as Quality Gate: Three Hypotheses on AI-Assisted Code Review (arXiv)</a></li>
<li><a href="https://www.codeant.ai/blogs/ai-code-review-accuracy">How Accurate Is AI Code Review in 2026? — CodeAnt</a></li>
<li><a href="https://engineering.salesforce.com/scaling-code-reviews-adapting-to-a-surge-in-ai-generated-code/">Scaling Code Reviews: Adapting to a Surge in AI-Generated Code — Salesforce Engineering</a></li>
<li><a href="https://www.qodo.ai/blog/5-ai-code-review-pattern-predictions-in-2026/">5 AI Code Review Pattern Predictions in 2026 — Qodo</a></li>
</ul>
]]></content:encoded></item><item><title>LLM 网关:多模型怎么统一接入和路由</title><link>https://realtime-ai.chat/posts/llm-gateway/</link><pubDate>Sun, 03 May 2026 11:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/llm-gateway/</guid><description>应用接入第二个模型那天起,就该有一层 LLM 网关。讲清它解决的统一 API、密钥、故障转移、限流、成本与缓存,自建与现成怎么选,路由策略怎么定,以及多一跳的代价。</description><content:encoded><![CDATA[<p>先说一个反常识的事:<strong>大多数团队的&quot;第一个 LLM 网关&quot;不是装出来的,是不知不觉写出来的。</strong></p>
<p>最初你的代码里只有一句 <code>openai.chat.completions.create()</code>。后来 OpenAI 半夜抽风,你在外面包了个 <code>try/except</code>,失败就调 Anthropic。再后来财务问&quot;这个月十几万的 token 花在哪了&quot;,你又加了一段记账逻辑。再后来某个客户的流量把你的限额打爆,你又写了个令牌桶。</p>
<p>这些 <code>if/else</code> 散在三个仓库、五个文件里,没人敢动。这就是网关——一个<strong>没有名字、没有人维护、谁碰谁倒霉</strong>的网关。</p>
<p>所以问题从来不是&quot;要不要 LLM 网关&quot;,而是&quot;这层东西,是攒成一坨烂代码,还是收拢成一个能被维护的组件&quot;。这篇就讲清楚:它到底该管什么、自建还是用现成、路由策略怎么定,以及它本身会带来什么麻烦。</p>
<h2 id="网关到底替你扛了什么">网关到底替你扛了什么</h2>
<p>把它想成 LLM 调用的反向代理:你的应用只跟网关说话,网关再去跟一堆模型供应商打交道。它该扛七件事,但<strong>这七件事的优先级差得很远</strong>。</p>
<table>
  <thead>
      <tr>
          <th>能力</th>
          <th>解决什么</th>
          <th>优先级</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>统一 API</td>
          <td>一套 OpenAI 格式的接口调所有模型,换模型不改业务代码</td>
          <td>必须</td>
      </tr>
      <tr>
          <td>故障转移</td>
          <td>某家供应商挂了 / 限流了,自动切到备用</td>
          <td>必须</td>
      </tr>
      <tr>
          <td>密钥管理</td>
          <td>上游真 key 收在网关,业务侧只发&quot;虚拟 key&quot;</td>
          <td>必须</td>
      </tr>
      <tr>
          <td>限流配额</td>
          <td>按 key、按团队、按租户限 QPS 和预算</td>
          <td>高</td>
      </tr>
      <tr>
          <td>成本核算</td>
          <td>每次调用算钱,按业务线 / 用户出账单</td>
          <td>高</td>
      </tr>
      <tr>
          <td>可观测</td>
          <td>全量请求日志、延迟分位、错误率、token 用量</td>
          <td>高</td>
      </tr>
      <tr>
          <td>缓存</td>
          <td>命中过的请求直接返回,省钱省延迟</td>
          <td>看场景</td>
      </tr>
  </tbody>
</table>
<p>前三个是&quot;接了第二个模型就立刻需要&quot;的。中间三个是&quot;上了生产、有了多个调用方&quot;之后绕不开的。最后一个——缓存——别一上来就上,后面单独说。</p>
<p><strong>统一 API</strong> 是地基。2026 年的事实标准是 OpenAI 的 <code>/v1/chat/completions</code> 格式:几乎所有网关都对外讲这套协议,对内再翻译成 Anthropic、Gemini、Bedrock、火山引擎、通义各自的方言。好处很直接——你的业务代码里不该出现任何一家供应商的 SDK,只有一个 base_url 指向网关。换模型这件事,从&quot;改代码、过测试、发版&quot;变成&quot;网关上改一行配置&quot;。</p>
<p><strong>故障转移</strong>是第二个动机,也是最容易被低估的。单家供应商的可用性,你别指望它有四个九。OpenAI、Anthropic 在 2026 年都还会有区域性的限流和抖动。网关该做的是:一次请求失败(超时、5xx、429),先重试 N 次,还不行就<strong>降级到另一个模型组</strong>。注意是&quot;模型组&quot;不是&quot;模型&quot;——GPT-5 这一组里可以同时挂 OpenAI 直连、Azure OpenAI 两个部署,先在组内负载均衡,整组都不行了再跨组降级到 Claude。</p>
<p><strong>密钥管理</strong>是个安全问题。你不会希望真正的 OpenAI key 散落在二十个微服务的环境变量里——那意味着二十个泄漏点,而且轮换一次 key 要发二十次版。网关的做法是:真 key 只存网关一处,业务侧拿到的是网关签发的<strong>虚拟 key</strong>,每个虚拟 key 自带预算上限、限流和过期时间。哪个团队的 key 泄漏了,网关上吊销一个,不影响别人。</p>
<h2 id="一次请求在网关里走过的路">一次请求在网关里走过的路</h2>
<p>把这几件事串起来,一次调用大致是这样:</p>
<pre class="mermaid">flowchart TD
  A[业务应用<br/>带虚拟 key] --> B{鉴权 &<br/>配额检查}
  B -->|超预算/超限流| X[拒绝 429]
  B -->|放行| C{缓存查询}
  C -->|命中| R[直接返回]
  C -->|未命中| D[路由决策<br/>选模型]
  D --> E[调上游供应商]
  E -->|失败| F{重试 / 降级}
  F -->|换模型重试| E
  F -->|彻底失败| Y[返回错误]
  E -->|成功| G[记账 + 日志 + 写缓存]
  G --> R
</pre><p>这条链路里值得强调一点:<strong>鉴权和配额检查必须在最前面</strong>。如果你把它放在调用上游之后,那超预算的请求会先把钱花掉再被拒绝,限流形同虚设。先验票,再放行,再花钱。</p>
<p>另一点:记账、日志、写缓存都该在拿到响应<strong>之后异步做</strong>,不要让它们卡在用户的关键路径上。网关给业务请求加的延迟,越接近零越好。</p>
<h2 id="自建还是用现成的">自建还是用现成的</h2>
<p>这是大多数团队真正纠结的地方。先把选项摆清楚——下面这些都是真实在用的东西:</p>
<ul>
<li><strong>LiteLLM</strong>:开源,Python 写的代理,目前最主流的自建选择。统一 100+ 模型、虚拟 key、预算、降级、成本追踪、带管理界面,功能很全。</li>
<li><strong>OpenRouter</strong>:托管服务,一个 key 接几百个模型,接入最快。本质是个聚合层 + 计费层,适合快速起步和做模型选型实验。</li>
<li><strong>Portkey</strong>:托管为主,主打可观测——全量日志、链路追踪、护栏、预算,偏生产化运营。</li>
<li><strong>Cloudflare AI Gateway / Vercel AI Gateway</strong>:如果你的应用本来就跑在这两家的平台上,网关能力近乎&quot;顺手就有&quot;,分析、缓存、限流都集成好了。</li>
<li><strong>Bifrost</strong>:开源,用 Go 写的,主打性能——号称 5000 RPS 下额外开销只有约 11 微秒。如果你嫌 Python 网关那 10–50ms 的开销肉疼,这是个方向。</li>
</ul>
<p>我的判断,按团队阶段分:</p>
<p><strong>早期、还在做模型选型</strong>——直接用 OpenRouter。你现在最需要的是&quot;今天换 Claude、明天试 Gemini&quot;的灵活度,不值得为此先搭一套基础设施。</p>
<p><strong>上了生产、有合规要求</strong>——自建 LiteLLM 或 Bifrost。原因不是省钱(托管服务的抽成其实不高),而是<strong>数据</strong>:你所有的 prompt 和回复都流经这一层,这是公司最敏感的资产之一。把它放进自己的 VPC,审计、合规、数据驻留这些事才好交代。金融、医疗这类场景基本没得选,必须自建。</p>
<p><strong>不想养基础设施团队、但要生产级运营</strong>——Portkey 这类托管网关是合理的中间档,你拿可观测和护栏,代价是数据出门和按量付费。</p>
<p>一个常被忽略的点:自建不等于零成本。2026 年 3 月,LiteLLM 的 1.82.7、1.82.8 两个版本出过一次供应链投毒,受影响版本被下架、1.83.0 才修干净。<strong>你的网关是全公司 LLM 流量的咽喉</strong>,自建意味着这个咽喉的补丁、监控、值班都归你。别把&quot;自建&quot;想成一次性的活。</p>
<h2 id="路由策略网关最有意思也最容易过度设计的部分">路由策略:网关最有意思、也最容易过度设计的部分</h2>
<p>有了网关,&ldquo;一个请求该用哪个模型&quot;就成了可以动态决定的事。常见三种路由维度:</p>
<p>**按成本路由。**这是最实在的省钱手段。2026 年初的行情大致是:旗舰模型每百万 token 三五十美元,中端十来美元,轻量级一两美元,小模型一两毛。而你的请求里,可能七成是&quot;把这段话改通顺&quot;&ldquo;判断这条评论是不是投诉&quot;这种小活儿——这些活儿用小模型的质量和旗舰模型没有可感差距。把简单请求分流到便宜模型,平均成本能掉一大截,体感却不变。</p>
<p>**按能力路由。**反过来:涉及多步推理、长上下文、写代码的请求,送旗舰模型。难点在于&quot;怎么判断一个请求难不难&rdquo;——</p>
<ul>
<li>最笨但最稳:<strong>业务侧自己标</strong>。你比网关更清楚这个调用是&quot;客服闲聊&quot;还是&quot;合同审查&rdquo;,在请求里带个 <code>tier: complex</code> 字段,网关照着分流。我推荐先用这个。</li>
<li>进阶:<strong>语义路由</strong>,网关用一个小模型 / embedding 实时判断请求归哪一类再分流。它的代价是<strong>每个请求多 50–100ms</strong>,而且多了一个判断错了就全错的环节。语义路由真正划算,通常是你能清晰划出 3–10 类查询的时候;类别糊成一团时,它带来的麻烦比收益多。</li>
</ul>
<p>**按延迟路由。**实时语音、输入补全这种场景,延迟是硬指标,网关该把请求送给当前 TTFT 最低的部署,并把慢的部署临时摘掉。</p>
<p>我的态度很明确:<strong>先上&quot;按成本&quot;的粗路由,而且让业务侧自己打标签。</strong> 别一开始就上语义路由。我见过太多团队,路由逻辑本身比它要路由的业务还复杂,最后没人搞得清一个请求为什么走了那条线——省下的那点钱,全赔进排查时间里了。路由策略的复杂度,要配得上你真实的流量规模。</p>
<h2 id="缓存看着诱人但有刺">缓存:看着诱人,但有刺</h2>
<p>缓存值得单独拿出来说,因为它是这七项里<strong>最容易出事</strong>的一个。</p>
<p>精确缓存没什么争议:prompt 一字不差命中过,直接返回,省钱省延迟。问题在<strong>语义缓存</strong>——用 embedding 找&quot;意思相近&quot;的历史请求然后复用答案。它确实能提命中率,但两个坑很深:</p>
<p>一是<strong>相似不等于相同</strong>。&ldquo;北京今天天气&quot;和&quot;上海今天天气&rdquo; embedding 距离很近,答案却必须不同。语义缓存的相似度阈值卡松了,就会把张三的答案返给李四。</p>
<p>二是<strong>新鲜度</strong>。涉及实时信息、用户个性化、带时间语义的请求,根本不该走缓存。</p>
<p>所以缓存别全局开。务实的做法:先只对<strong>明确无状态、答案稳定</strong>的请求开精确缓存——比如固定的内容分类、把文档切块打标签这类批处理。语义缓存留到你有数据证明&quot;这一类请求确实高度重复&quot;时再说,而且阈值要往严了卡。</p>
<h2 id="它本身的代价多一跳和一个新的单点">它本身的代价:多一跳,和一个新的单点</h2>
<p>网关不是白拿的。两个代价必须摆上台面。</p>
<p>**多一跳延迟。**所有流量绕一道。如果网关跟你的应用同机房、同 VPC,这一跳也就个位数毫秒,相对 LLM 动辄数百毫秒到数秒的响应,可以忽略。但要是网关部署在另一个区域,或者用了托管服务而它的入口离你很远,这一跳可能加上几十毫秒甚至更多。<strong>实时语音这种掐着毫秒过日子的场景,尤其要量一量这一跳到底多长。</strong> 顺带一提,Python 写的网关进程本身会贡献 10–50ms 的处理开销,Go 写的(如 Bifrost)能压到微秒级——流量大、延迟敏感时,这个差别是真金白银。</p>
<p>**新的单点故障。**这是更要命的。你做网关的初衷之一是&quot;某个供应商挂了不至于全挂&quot;,可一旦所有流量都过网关,<strong>网关自己挂了,就是全挂</strong>——而且挂得比任何单一供应商出事都彻底。这不是不用网关的理由,是必须把网关本身做成高可用的理由:多副本、跨可用区、配置和状态外置,健康检查要快。说白了,你把可用性的赌注从&quot;分散在各家供应商&quot;换成了&quot;押在自己的网关上&quot;——那就得真把它当核心基础设施来运维,而不是当一个内部小工具。</p>
<h2 id="收个尾务实的上法">收个尾:务实的上法</h2>
<p>如果你正要把那坨散落的 <code>if/else</code> 收拢成一个正经网关,顺序建议这样:</p>
<ol>
<li><strong>先做统一 API + 故障转移 + 虚拟 key。</strong> 这三件立刻见效,而且不需要你想清楚任何路由策略。</li>
<li><strong>再补限流、配额、可观测。</strong> 等你有了多个调用方、财务开始问钱花哪了,这些自然就该上了。</li>
<li><strong>路由从最粗的&quot;按成本&quot;开始,标签让业务侧自己打。</strong> 跑一阵,拿真实数据说话,再决定要不要上语义路由。</li>
<li><strong>缓存最后,而且先只开精确缓存。</strong> 语义缓存等到有数据支撑再碰。</li>
<li><strong>从第一天起就把网关当核心基础设施</strong>——多副本、跨可用区、有人值班、补丁跟紧。它是你全公司 LLM 流量的咽喉。</li>
</ol>
<p>LLM 网关不神秘。它就是你迟早会写的那层代码——区别只在于,你是任由它烂在五个文件里,还是趁早把它收成一个有名字、有人管、能演进的东西。接第二个模型的那天,就是动手的那天。</p>
<hr>
<p><em>参考:<a href="https://dev.to/lightningdev123/best-ai-gateway-tools-in-2026-for-scalable-llm-applications-4dg">Best AI Gateway Tools in 2026</a>、<a href="https://www.edenai.co/post/best-llm-routers">Best LLM Routers in 2026</a>、<a href="https://docs.litellm.ai/docs/">LiteLLM 文档</a>、<a href="https://github.com/maximhq/bifrost">Bifrost(maximhq)</a>、<a href="https://www.mindstudio.ai/blog/what-is-ai-model-router-optimize-cost-llm-providers">What Is an AI Model Router</a>。</em></p>
]]></content:encoded></item><item><title>Agent 的 token 账单怎么管</title><link>https://realtime-ai.chat/posts/agent-token-cost/</link><pubDate>Thu, 30 Apr 2026 11:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/agent-token-cost/</guid><description>Agent 上线后 token 成本最容易失控:多轮、长上下文、工具结果会成倍放大开销。这篇讲清钱花在哪、怎么定位大头,以及 prompt caching、上下文压缩、模型路由、步数熔断等可落地手段。</description><content:encoded><![CDATA[<p>先说一个数字:同样问&quot;帮我查一下这个 bug&quot;,发给聊天机器人和发给 Agent,token 消耗能差 <strong>50 倍</strong>。</p>
<p>聊天机器人就一来一回:你发一段、它回一段,结束。Agent 不一样——它跑的是一个循环:看任务、调工具、读文件、改代码、再检查。<strong>循环里的每一步,都要把到目前为止积累的全部上下文,重新发给模型一次。</strong></p>
<p>这就是 Agent 账单的根源。2026 年有人审计了 30 个在生产环境跑 Agent 的工程团队,一个 20 人的团队,单月 API 账单能冲到 11 万美元;用 Claude Code 或 Cursor 这类编码 Agent 的开发者,人均每月 400 到 1500 美元,失控的案例几天就烧掉 4000 美元以上。</p>
<p>更要命的是,这笔钱不是匀速烧的。Demo 跑得好好的,一上量就爆——大部分企业的 Agent 项目,在大规模铺开后的头 90 天里,实际花销会超出试点预算 4 到 11 倍。所以做 Agent,成本不是上线之后再优化的事,是设计时就得算进去的一笔账。</p>
<p>这篇把这笔账拆开:钱花在哪、怎么找到大头、有哪些真能省的手段、怎么设预算和熔断、怎么监控。</p>
<h2 id="钱到底花在哪">钱到底花在哪</h2>
<p>先建立一个最反直觉的认知:<strong>Agent 跑一个任务的成本,主要不是输出,是输入。</strong></p>
<p>模型 API 按 token 收费,输入和输出分开计价。聊天场景里,大家盯着输出看。但 Agent 不一样,它的成本大头在<strong>输入侧的重复计费</strong>。</p>
<p>为什么?因为对话历史会&quot;滚雪球&quot;。Agent 每调一次工具,就要把整段对话历史连同工具返回的结果,一起再发一次。一段已经积累到 10 万 token 的上下文,在后续<strong>每一次</strong>调用里,都按 10 万输入 token 收费——不是只收新增的那部分。一个跑到第 20 步的编码 Agent,光是文件读取塞进来的内容,单步输入就能超过 5 万 token,按 Sonnet 4.6 的价(每百万输入 token 3 美元)算,<strong>每一步 0.15 美元</strong>,二十步就是 3 美元,而这还只是一个任务。</p>
<p>把烧钱的来源列清楚,主要是这四个:</p>
<table>
  <thead>
      <tr>
          <th>成本来源</th>
          <th>为什么烧钱</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>多轮累积</td>
          <td>历史每轮都重发,N 步任务的输入约为单步的 N 倍量级</td>
      </tr>
      <tr>
          <td>长上下文</td>
          <td>大 system prompt、塞满的 RAG 检索结果,每次调用都全额计费</td>
      </tr>
      <tr>
          <td>工具结果</td>
          <td>一次文件读取、一次数据库查询返回几千 token,且永久留在上下文里</td>
      </tr>
      <tr>
          <td>多 Agent</td>
          <td>主 Agent 派生子 Agent,每个子 Agent 自己又是一个完整的 token 循环</td>
      </tr>
  </tbody>
</table>
<p>这四项里,<strong>多轮累积和工具结果是最隐蔽的</strong>——它们不在你写的 prompt 里,是 Agent 自己在运行时长出来的。你 review 代码时看不到,只有看账单才发现。</p>
<h2 id="先定位大头再动手">先定位大头,再动手</h2>
<p>不要凭感觉优化。第一步永远是<strong>按维度把成本拆开看</strong>,否则你很可能花两天去抠一个只占 5% 的环节。</p>
<p>至少要能按这几个维度归因(attribution):</p>
<ul>
<li><strong>按 Agent / 任务类型</strong>:哪类任务最贵?是&quot;代码重构&quot;还是&quot;简单问答&quot;?</li>
<li><strong>按步骤</strong>:成本是均匀分布,还是集中在某几步(比如某个返回巨量结果的工具)?</li>
<li><strong>按输入/输出</strong>:再确认一次,是输入贵还是输出贵——多数 Agent 是输入。</li>
<li><strong>按用户 / 会话</strong>:是不是 5% 的重度用户烧掉了 80% 的钱?</li>
</ul>
<p>这里有个观测层的坑要提前知道:Agent 的一次&quot;请求&quot;,在 trace 里会炸开成 8 到 15 个 span——API 调用、token 流式输出、embedding 查询、向量库检索、prompt 拼装、guardrail 检查、结果解析……普通 API 接口才 2 到 3 个。如果你的监控是按&quot;请求数&quot;做采样的,Agent 会瞬间把你的可观测性预算也撑爆。所以 Agent 的成本监控,<strong>得按 token 和按美元来记,不能只按请求数</strong>。</p>
<p>拆完之后你大概率会看到一个二八分布:某一两类任务、某一两个工具,吃掉了大半账单。先打这些点。</p>
<h2 id="真能省钱的几个手段">真能省钱的几个手段</h2>
<p>定位完大头,下面是 2026 年实测有效的手段,按&quot;性价比&quot;从高到低排。</p>
<h3 id="prompt-caching第一个要上几乎免费">prompt caching:第一个要上,几乎免费</h3>
<p>这是投入产出比最高的一项,优先级最高。</p>
<p>原理很简单:Agent 的上下文里有一大块是<strong>固定不变的前缀</strong>——system prompt、工具定义、few-shot 示例。每一步调用都把这块重新做一遍前向计算(prefill),纯属浪费。prompt caching 就是把这段固定前缀缓存住,后续调用直接命中缓存。</p>
<p>2026 年的价格,缓存命中的输入 token 只按基础价的 <strong>0.1 倍</strong>收费,也就是 9 折优惠——Anthropic 是这个价,GPT-5.4 现在也对齐到了 90% 的缓存折扣。对一个多轮 Agent,固定前缀往往占输入的一大半,命中率拉高之后,输入成本砍掉 50% 到 90% 是常态。</p>
<p>要拿到这个收益,有个纪律:<strong>别让缓存失效</strong>。缓存命中的前提是前缀逐字节一致。所以要把&quot;不变的东西&quot;放前面、&ldquo;会变的东西&quot;放后面——system prompt 和工具定义放最前,动态的对话历史和检索结果放后面。一旦你在 system prompt 里塞了个当前时间戳,整个缓存就废了。</p>
<h3 id="上下文压缩对付滚雪球的正面手段">上下文压缩:对付&quot;滚雪球&quot;的正面手段</h3>
<p>prompt caching 省的是固定前缀;滚雪球的对话历史得靠压缩。</p>
<p>最直接的做法是<strong>定期把历史压成摘要</strong>。Agent 跑了 30 步,前 20 步的细节其实没必要逐字带着——把它们总结成一段&quot;已完成:确认了 bug 在 X 模块,排除了 Y 假设&rdquo;,用摘要替换原始对话。Anthropic 在 2026 年 2 月放出的 Compaction API(beta)就是把这件事自动化:让模型自动总结、压缩对话历史,实现近乎&quot;无限&quot;的对话长度,不用手动裁剪或重开会话。</p>
<p>工具结果也要管。一次文件读取返回 5000 token,但 Agent 真正需要的可能只是其中一个函数。可以做的:工具返回时就<strong>截断或摘要</strong>,只保留相关片段;旧的工具结果在后续轮次里<strong>替换成一句占位符</strong>(&quot;[此处曾读取 config.py,已处理]&quot;)。</p>
<h3 id="按难度选模型别用大炮打蚊子">按难度选模型:别用大炮打蚊子</h3>
<p>不是每一步都需要最强的模型。</p>
<p>2026 年 Claude 三档价差很大:Haiku 4.5 是每百万 token 1/5 美元(输入/输出),Sonnet 4.6 是 3/15,Opus 4.7 是 5/25。<strong>Haiku 比 Sonnet 便宜 5 倍,比 Opus 便宜 25 倍。</strong></p>
<p>一个 Agent 流程里,真正需要顶配模型做复杂推理的步骤可能只占两三成。剩下的——意图分类、格式整理、判断&quot;任务完成了没&quot;、简单的工具参数填充——交给 Haiku 完全够用。做法就是按步骤的难度做路由(routing):简单步骤走小模型,复杂推理才升到 Sonnet 或 Opus。一个 500 输入 / 100 输出 的 Haiku 分类调用,成本大约 0.001 美元,几乎可以忽略。</p>
<h3 id="限制步数和递归给失控的循环装个闸">限制步数和递归:给失控的循环装个闸</h3>
<p>前面说企业 Agent 超预算 4 到 11 倍,原因之一就是<strong>没有上限的工具调用递归</strong>。</p>
<p>Agent 卡在一个错误里出不来,会一遍遍重试同一个工具;主 Agent 派生子 Agent,子 Agent 再派生……如果没有硬上限,一个本该 10 步的任务能跑成 200 步。必须设硬限制:</p>
<ul>
<li><strong>单任务最大步数</strong>(比如 25 步,到了就强制收尾或交还给人)</li>
<li><strong>多 Agent 的递归深度上限</strong>(比如最多 2 层)</li>
<li><strong>同一项的重试次数上限</strong>——一个实战配置是:同一项每天最多重试 3 次,两次重试之间至少隔 2 小时,不可重试的错误直接跳过,别困在死循环里</li>
</ul>
<h3 id="缓存工具结果--batch能省就省">缓存工具结果 + batch:能省就省</h3>
<p>两个补充手段。</p>
<p><strong>缓存工具结果</strong>:很多工具调用是确定性的、可重复的——查同一个文档、跑同一个查询。给工具调用层加一个缓存,相同输入直接返回上次结果,连模型调用都省了。语义缓存(semantic cache)更进一步,语义相近的请求也能命中。</p>
<p><strong>batch(批处理)</strong>:如果你的任务不需要实时返回——离线评测、批量数据标注、夜间跑的报告——走 Batch API,输入输出都打五折。把 prompt caching 的 9 折和 batch 的 5 折叠加,极端情况能把成本压到原来的 5%。代价是异步,最长可能等 24 小时,所以只适合离线场景。</p>
<p>下面这张图是这些手段的处理顺序:</p>
<pre class="mermaid">flowchart TD
  A[Agent 收到一步请求] --> B{固定前缀?}
  B -->|是| C[命中 prompt cache<br/>输入按 0.1x 计费]
  B -->|否| D[正常计费]
  C --> E{这步难度?}
  D --> E
  E -->|简单| F[路由到 Haiku]
  E -->|复杂| G[路由到 Sonnet/Opus]
  F --> H{工具结果是否过大?}
  G --> H
  H -->|是| I[截断/摘要后入上下文]
  H -->|否| J[直接入上下文]
  I --> K{历史是否过长?}
  J --> K
  K -->|是| L[压缩成摘要]
  K -->|否| M[继续]
</pre><h2 id="给-agent-设预算和熔断">给 Agent 设预算和熔断</h2>
<p>省钱手段是&quot;开源节流&quot;里的节流。但 Agent 还需要一道<strong>硬性的财务闸门</strong>——再怎么优化,也得有个东西在它失控时直接把它停掉。</p>
<p>这就是成本熔断(cost circuit breaker):预设一个开销上限,Agent 触到上限就强制中断,而不是任由它把账单跑飞。</p>
<p>预算定多少?别拍脑袋。<strong>先量,再定。</strong> 取一个有代表性的两周样本,统计每个完整任务消耗 token 的 p50 和 p95,然后把上限设在 <strong>p95 的 1.5 倍</strong>。这个值能覆盖正常的波动,又能在真正异常时及时触发。</p>
<p>熔断要分层设,至少三层:</p>
<table>
  <thead>
      <tr>
          <th>层级</th>
          <th>触发条件</th>
          <th>动作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>单任务</td>
          <td>单个任务 token 超 1.5× p95</td>
          <td>中断该任务,记录,交还给人</td>
      </tr>
      <tr>
          <td>单用户/会话</td>
          <td>用户当日累计超额度</td>
          <td>拒绝新请求或降级到小模型</td>
      </tr>
      <tr>
          <td>全局</td>
          <td>全组织当日总花销超阈值</td>
          <td>告警 + 限流,保住核心业务</td>
      </tr>
  </tbody>
</table>
<p>关键点:熔断的动作必须是<strong>确定性的、自动执行的</strong>。运行时的预算治理需要两样东西——明确的限额,加上确定的补救动作。光设个数字、靠人看告警手动去关,等你看到消息,钱已经烧完了。</p>
<h2 id="监控该怎么做">监控该怎么做</h2>
<p>最后是监控。没有监控,前面所有的优化都是一次性的——这个月省下来,下个月一个新功能上线又涨回去,你还不知道。</p>
<p>Agent 的成本监控,要盯这几个指标:</p>
<ul>
<li><strong>每任务成本(cost per task)</strong>:最核心的北极星指标。优化做对了,这个数应该往下走。</li>
<li><strong>缓存命中率</strong>:prompt caching 的命中率。如果某次发布后它突然掉下来,八成是有人改了 system prompt 把缓存搞失效了。</li>
<li><strong>每任务平均步数</strong>:悄悄往上爬,通常意味着 Agent 开始绕路或卡循环。</li>
<li><strong>token 成本归因</strong>:持续按 Agent、按用户、按任务类型拆,二八分布的那个&quot;二&quot;要一直盯着。</li>
<li><strong>熔断触发次数</strong>:偶尔触发是正常的安全网;频繁触发说明预算设低了,或者真有 Agent 在失控。</li>
</ul>
<p>把这些接进你现有的可观测性系统,设好告警。一个务实的目标:认真做完一轮成本优化的团队,通常能在 30 天内把 Agent 成本降低 55% 到 75%。</p>
<h2 id="一份上线前的清单">一份上线前的清单</h2>
<p>把上面的东西收成一张可勾选的表,Agent 上线前过一遍:</p>
<ul>
<li><input disabled="" type="checkbox"> 成本能按 Agent、用户、任务类型、步骤拆开归因</li>
<li><input disabled="" type="checkbox"> 固定前缀(system prompt、工具定义)放在最前,且已开 prompt caching</li>
<li><input disabled="" type="checkbox"> system prompt 里没有时间戳之类会破坏缓存的动态内容</li>
<li><input disabled="" type="checkbox"> 对话历史有压缩/摘要机制,不会无限滚雪球</li>
<li><input disabled="" type="checkbox"> 工具返回结果会截断或摘要,旧结果会被占位符替换</li>
<li><input disabled="" type="checkbox"> 简单步骤路由到小模型,只有复杂推理才上顶配</li>
<li><input disabled="" type="checkbox"> 设了单任务最大步数、多 Agent 递归深度、重试次数上限</li>
<li><input disabled="" type="checkbox"> 确定性的工具调用结果有缓存</li>
<li><input disabled="" type="checkbox"> 离线、非实时的任务走了 Batch API</li>
<li><input disabled="" type="checkbox"> 三层熔断(单任务/单用户/全局)都已配置,且动作是自动执行的</li>
<li><input disabled="" type="checkbox"> 预算阈值是基于 p95 实测数据定的,不是拍脑袋</li>
<li><input disabled="" type="checkbox"> 每任务成本、缓存命中率、平均步数都在监控里,有告警</li>
</ul>
<p>最后说一句优先级。如果时间有限,先做三件事:<strong>开 prompt caching、设步数上限、配单任务熔断</strong>。这三样投入小、见效快,而且能挡住最致命的那种&quot;一夜之间烧掉几千美元&quot;的事故。上下文压缩、模型路由这些是细水长流的优化,可以上线之后慢慢调。</p>
<p>Agent 的账单不会自己变小。但只要你知道钱花在哪、装好了闸门,它至少不会变成一个你不敢看的数字。</p>
<hr>
<p>参考资料:</p>
<ul>
<li><a href="https://leanopstech.com/blog/agentic-ai-cost-runaway-token-budget-2026/">AI Agents Burn 50x More Tokens Than Chats — LeanOps</a></li>
<li><a href="https://fast.io/resources/ai-agent-token-cost-optimization/">AI Agent Token Cost Optimization: Complete Guide for 2026 — Fastio</a></li>
<li><a href="https://dev.to/waxell/ai-agent-context-window-cost-the-compounding-math-your-architecture-is-hiding-2227">AI Agent Context Window Cost: The Compounding Math — DEV Community</a></li>
<li><a href="https://www.obviousworks.ch/en/token-optimization-saves-up-to-80-percent-llm-costs/">Token optimization 2026: Saving up to 80% LLM costs — Obvious Works</a></li>
<li><a href="https://fountaincity.tech/resources/blog/ai-agent-cost-circuit-breaker/">The Cost Circuit Breaker: Financial Controls for Production AI Agents — Fountain City</a></li>
<li><a href="https://blogs.oracle.com/ai-and-datascience/runtime-budget-guardrails-agentic-ai">Runtime Budget Guardrails for Agentic AI — Oracle</a></li>
<li><a href="https://www.truefoundry.com/blog/llm-cost-attribution-agentic-cicd">Agentic Token Explosion: Attribute, Budget, and Control LLM Costs — TrueFoundry</a></li>
<li><a href="https://oneuptime.com/blog/post/2026-03-07-ai-agents-breaking-observability-budget/view">AI Agents Are Breaking Your Observability Budget — OneUptime</a></li>
<li><a href="https://platform.claude.com/docs/en/about-claude/pricing">Claude API Pricing — Anthropic Docs</a></li>
<li><a href="https://tokenmix.ai/blog/openai-batch-api-pricing">OpenAI Batch API 2026: 50% Off Every Model — TokenMix</a></li>
</ul>
]]></content:encoded></item><item><title>ASR 工程:语音识别落地的那些坑</title><link>https://realtime-ai.chat/posts/asr-engineering/</link><pubDate>Sat, 25 Apr 2026 11:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/asr-engineering/</guid><description>ASR demo 很美,落地很坑。流式与非流式怎么选、专有名词和中英混读怎么救、WER 为什么会骗你,这篇把语音识别工程化里真正难的部分讲清楚。</description><content:encoded><![CDATA[<p>你试用一个 ASR 模型,拿手机对着它念一段稿子,转写结果几乎一字不差,你心里想:这事成了。</p>
<p>然后你把它接进真实业务。客户在马路边打来电话,背景是车流声;他说到你公司的产品名,转写出来是三个完全不相干的字;他报了一串订单号&quot;一三幺零&quot;,屏幕上是&quot;一三妖灵&quot;。Demo 里的惊艳,到这里一点都不剩。</p>
<p>问题不在模型菜。开源的 Whisper、各家的流式 ASR,<strong>安静环境下的中文字错率早就压到了 3% 以下</strong>,这已经接近人工转写的水平。坑全在&quot;安静环境、念稿子&quot;这八个字之外——真实的语音是脏的,而工程的活,就是处理这些脏东西。这篇把落地之后才会碰到的坑,一个一个拆开。</p>
<h2 id="第一个岔路口流式还是非流式">第一个岔路口:流式还是非流式</h2>
<p>这是接 ASR 之前必须想清楚的第一件事,选错了后面全是返工。</p>
<p><strong>非流式(批量)</strong>:把一整段音频丢进去,等它整段识别完,返回结果。<strong>流式</strong>:音频一边录一边送,模型一边听一边吐字,你能拿到不断更新的&quot;中间结果&quot;。</p>
<p>差别不只是&quot;快一点慢一点&quot;。它们是两类不同的产品形态:</p>
<table>
  <thead>
      <tr>
          <th>维度</th>
          <th>流式 ASR</th>
          <th>非流式 ASR</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>典型延迟</td>
          <td>首字 200–300ms,说完即出</td>
          <td>等说完后再算,3 秒话约多等 0.5–2 秒</td>
      </tr>
      <tr>
          <td>准确率</td>
          <td>略低(只能看到左边的上下文)</td>
          <td>略高(能看到整句,甚至整段)</td>
      </tr>
      <tr>
          <td>成本</td>
          <td>贵(常见 $0.016/分钟级别)</td>
          <td>便宜(常见 $0.008/分钟级别)</td>
      </tr>
      <tr>
          <td>适用</td>
          <td>语音 Agent、实时字幕、点单</td>
          <td>会议录音转写、客服质检、播客字幕</td>
      </tr>
  </tbody>
</table>
<p>记住一个核心事实:<strong>流式之所以准确率低,是因为它在&quot;说完之前&quot;就得交答案</strong>。它做&quot;我想订一张去——&ldquo;的转写时,根本不知道后面是&quot;北京&quot;还是&quot;报告&rdquo;。非流式没这个包袱,它能等整句听完再回头修正,所以同样的模型,非流式版本的字错率往往低一两个百分点。</p>
<p>这里有个 2026 年很常见、但容易踩反的折中:<strong>用 VAD 把长音频切成&quot;准句子&quot;,每段当成一个 mini-batch 走非流式模型</strong>。这套做法在&quot;实时字幕&quot;这种场景里很香——它不需要逐字滚动,用户能接受&quot;一句一句往外蹦&quot;,于是你既拿到了非流式的准确率,延迟又只是一句话的长度。但如果你做的是语音 Agent,用户在等 AI 回话,那这套就不行——你必须真流式,让 ASR 和用户说话并行跑,说完文字基本也就好了。</p>
<p><strong>别被&quot;我两个都要&quot;骗了。</strong> 一个产品里通常只有一条主链路。先确定用户是不是在&quot;等&quot;,再选。</p>
<h2 id="专有名词中英混读数字错得最离谱的三类">专有名词、中英混读、数字:错得最离谱的三类</h2>
<p>通用 ASR 在通用语料上训练,所以它对&quot;通用的话&quot;很强,对&quot;你的话&quot;很弱。落地后用户投诉最集中的,永远是这三类。</p>
<p><strong>专有名词。</strong> 你公司叫&quot;声析&quot;,模型从没见过这两个字的组合,它会按发音找一个最像的常见词——&ldquo;声息&quot;&ldquo;生气&quot;&ldquo;省吃&rdquo;。这不是模型蠢,是它在做概率上最合理的猜测。人名、产品名、内部黑话、行业术语,全是重灾区。</p>
<p><strong>中英混读。</strong> 中文工程师说话天然混英文:&ldquo;这个 bug 我提了个 PR&quot;&ldquo;把 latency 压下来&rdquo;。问题是很多 ASR 在&quot;语种判定&quot;上是僵的——它先猜你这句是中文还是英文,再用对应的解码路径。猜错了,&ldquo;PR&quot;变成&quot;批 R&rdquo;,&ldquo;latency&quot;变成一串看不懂的拼音。2026 年好一些的方案(比如 Gladia、Deepgram Nova-3)做了<strong>句内 code-switching</strong>,语种切换不需要整句重判;但便宜的、老的模型,中英混读仍然是硬伤。</p>
<p><strong>数字。</strong> 这是最隐蔽的坑。&ldquo;一千二百三十四&quot;应该写成 <code>1234</code> 还是&quot;一千二百三十四&rdquo;?电话号码、订单号、金额、日期,各有各的格式。这件事叫<strong>逆文本规整(ITN,Inverse Text Normalization)</strong>——把&quot;口语形式&quot;转成&quot;书面形式&rdquo;。ITN 做得糙,用户就会看到&quot;我要订 2026 年 4 月 25 号&quot;被写成&quot;二零二六年四月二十五号&rdquo;,或者反过来把门牌号&quot;幺零幺&quot;写成&quot;101&rdquo;——而它其实是房间名。</p>
<p>这三类问题的通用解药是<strong>热词 / 定制词表</strong>,下一节单讲。但要先有个心理预期:<strong>通用 ASR 在你的垂直场景里,开箱就是会错,而且错得很集中。</strong> 上线前必须拿你自己的真实音频跑一遍,把高频错词捞出来。</p>
<h2 id="噪声和远场训练集里没有的世界">噪声和远场:训练集里没有的世界</h2>
<p>录音棚的音频干净到不真实。真实音频里有空调声、键盘声、车流、回声、别人说话。</p>
<p>这里要分清两个不同的问题:</p>
<ul>
<li><strong>噪声(near-field + noise)</strong>:用户离麦克风很近,但环境吵。手机贴着脸打电话属于这类。现代 ASR 对平稳噪声(空调、风扇)已经相当稳,因为训练数据里加了大量噪声增强。难的是<strong>非平稳噪声</strong>——突然的关门声、背景音乐、旁边一句人声。</li>
<li><strong>远场(far-field)</strong>:用户离麦克风一两米,信号本身就弱,还叠加了房间混响。智能音箱、会议室麦克风阵列属于这类。远场的难点不是&quot;吵&rdquo;,是&quot;糊&quot;——混响把语音的时间结构抹掉了。</li>
</ul>
<p>工程上,<strong>别指望单靠 ASR 模型扛下这些</strong>。真正有效的是前面那一段链路:</p>
<pre class="mermaid">flowchart LR
  A[麦克风阵列] --> B[波束成形<br/>对准说话人]
  B --> C[降噪 / 去混响]
  C --> D[AGC<br/>自动增益]
  D --> E[VAD<br/>切掉静音段]
  E --> F[ASR]
  style B fill:#fde7c2,stroke:#e8b23c
  style C fill:#fde7c2,stroke:#e8b23c
</pre><p>橙色的两块——波束成形和降噪去混响——是远场场景里 ASR 准确率的真正杠杆。一个被忽略的细节是 <strong>VAD 的位置</strong>:它把长段静音和纯噪声段切掉,不送进 ASR。这既省钱,又能防止 ASR 在没人说话时&quot;幻听&quot;出几个字来。Whisper 这类模型在长静音上幻听是出了名的,VAD 是最便宜的止损。</p>
<p>还有一个反直觉的点:<strong>降噪不是越狠越好</strong>。激进的降噪会把语音本身的高频细节也削掉,ASR 反而更难听清。降噪是为人耳服务还是为 ASR 服务,目标不一样,参数也不该一样。</p>
<h2 id="标点顺滑热词让转写能用的后处理">标点、顺滑、热词:让转写&quot;能用&quot;的后处理</h2>
<p>原始的 ASR 输出是一长串没有标点、保留了所有口水词的文字流。它&quot;对&quot;,但不&quot;能用&quot;。中间隔着三层后处理。</p>
<p><strong>标点恢复。</strong> 给文字流加上逗号句号问号。没有标点的转写,人读着累,喂给下游 LLM 也容易断句错误。</p>
<p><strong>顺滑(disfluency removal)。</strong> 真实口语里全是&quot;嗯&quot;&ldquo;那个&quot;&ldquo;就是说&rdquo;,还有&quot;我要去北——啊不是,去南京&quot;这种自我修正。顺滑就是把这些口水词和修正痕迹删掉,留下干净的意思。这件事在&quot;会议纪要&quot;&ldquo;客服质检&quot;里是刚需——没人想看逐字的&quot;嗯嗯啊啊&rdquo;。但在另一些场景里<strong>你恰恰不能顺滑</strong>:比如做语言学研究、或者要分析用户的犹豫和情绪,口水词本身就是信号。所以顺滑要做成<strong>可开关的</strong>,别在管道里写死。</p>
<p>2026 年的趋势是把标点、ITN、大小写、顺滑这四件事<strong>用一个统一的标注模型一次做完</strong>,而不是串四个独立模块——后者每一层的误差会往下传。</p>
<p><strong>热词 / 定制词表</strong>,这是对付上一节那三类错误最直接的手段。原理是在解码时,对词表里的词做<strong>上下文偏置(contextual biasing)</strong>——让模型在&quot;声息&quot;和&quot;声析&quot;之间犹豫时,因为&quot;声析&quot;在你的热词表里,而把概率掰向它。</p>
<p>热词不是免费的,有几个坑:</p>
<ol>
<li><strong>热词表不能无限长。</strong> 表越大,偏置越稀,而且容易&quot;过纠&rdquo;——把本来对的常见词改成热词。几百到几千个词是合理区间,上万个就要换成&quot;按场景动态注入&quot;的检索式方案。</li>
<li><strong>热词要给对发音。</strong> &ldquo;GPU&quot;这个词,用户可能念&quot;G-P-U&rdquo;,也可能念&quot;鸡皮优&quot;。热词只配字面,模型对不上音,偏置就不生效。</li>
<li><strong>热词救不了&quot;完全没听清&quot;。</strong> 它只在模型已经有几个候选、正在犹豫时起作用。如果信号烂到模型根本没把你的词列进候选,热词无能为力——那是降噪和远场该解决的问题。</li>
</ol>
<h2 id="多人说话转写之外的另一道题">多人说话:转写之外的另一道题</h2>
<p>一对一的语音 Agent 不用操心这个。但只要场景里有两个以上的人——会议、多人客服、访谈——你就多了一道题:<strong>说话人分离(speaker diarization)</strong>,也就是&quot;谁在什么时候说了什么&quot;。</p>
<p>这是和 ASR 正交的另一个能力。ASR 回答&quot;说了什么&quot;,diarization 回答&quot;是谁说的&quot;。两件事各自都不简单,叠在一起更难,难点集中在:</p>
<ul>
<li><strong>重叠语音</strong>:两个人同时开口。这是 diarization 错得最多的地方,人耳都未必分得清。</li>
<li><strong>说话人数未知</strong>:你事先不知道这场会有几个人,模型得自己估。</li>
<li><strong>音色相近</strong>:两个声线接近的人,容易被归成同一个。</li>
</ul>
<p>2026 年一个值得关注的变化是<strong>联合建模</strong>:微软 1 月开源的 VibeVoice-ASR 把转写、说话人分离、时间戳放在一次前向里一起做,一遍处理 60 分钟音频。这条路线的好处是各个子任务能互相借力——知道&quot;说话人换了&quot;,有助于判断句子边界;反过来也成立。如果你的场景重度依赖 diarization,这类联合模型比&quot;ASR + 一个独立的 diarization 模块&quot;串起来要省心。</p>
<p>但提醒一句:<strong>diarization 的错误会污染下游</strong>。如果你拿带说话人标签的转写去做&quot;客服话术分析&quot;,标签错了,整份分析的归因就错了。先评估你的场景对说话人准确率的真实要求,再决定要不要上。</p>
<h2 id="wer-会骗你关于评测的真话">WER 会骗你:关于评测的真话</h2>
<p>聊到这里必须说说那个所有人都在用、但被严重高估的指标——<strong>WER(词错误率)</strong>。</p>
<p>WER 的算法是:把识别结果和标准答案对齐,数出替换、删除、插入三类错误,除以总词数。听起来很客观。但它有几个会让你做错决策的毛病:</p>
<p><strong>第一,WER 把所有错误当成一样重。</strong> &ldquo;我要订机票&quot;识别成&quot;我要定机票&rdquo;,和识别成&quot;我不要订机票&quot;,WER 给的惩罚可能差不多。但对你的业务,一个无所谓,一个是事故。<strong>关键信息(数字、人名、否定词、专有名词)错一个,比十个虚词错都严重。</strong></p>
<p><strong>第二,中文用 WER 本身就不对。</strong> 中文没有天然的词边界,&ldquo;语音识别&quot;算一个词还是两个词,取决于分词器。分词器一换,WER 就变。所以中文场景应该看 <strong>CER(字错误率)</strong>——按字算,跨分词器稳定。中英混读场景再进一步,业界用 <strong>MER(混合错误率)</strong>:中文按字、英文按词。你要是还在拿 WER 横向比中文 ASR,比出来的排名是不可信的。</p>
<p><strong>第三,WER 对&quot;语义等价&quot;零容忍。</strong> &ldquo;2026 年&quot;和&quot;二零二六年&rdquo;、&ldquo;OK&quot;和&quot;okay&rdquo;,意思完全一样,WER 照样判错。一个 ITN 风格不同的模型,WER 会无端偏高。</p>
<p>那该怎么评测自己场景的 ASR?给一份可以照着做的清单:</p>
<ol>
<li><strong>用你自己的真实音频建测试集</strong>,别用模型方给的 benchmark。至少几百条,覆盖你真实的口音、噪声、设备、语速分布。这一步最花时间,也最值钱。</li>
<li><strong>中文看 CER,中英混读看 MER</strong>,不要用 WER。</li>
<li><strong>单独算&quot;关键实体错误率&rdquo;</strong>:把数字、人名、产品名、否定词单拎出来统计准确率。这个数字比总体 CER 更能预测线上投诉量。</li>
<li><strong>分桶看,别只看一个平均数</strong>。按噪声等级、按远近场、按是否中英混读分别算 CER。平均数会把&quot;安静场景拉高、嘈杂场景拖垮&quot;这种结构性问题藏起来。</li>
<li><strong>测延迟,而且测的是首字延迟和稳定时间</strong>,不是整段耗时。流式 ASR 的中间结果会反复跳变,你要测的是&quot;结果多久不再变&quot;。</li>
<li><strong>做人工抽检</strong>。CER 降了 0.5%,但如果新错的全是关键词,体验是变差的。指标之外,定期让人看一眼真实转写。</li>
</ol>
<h2 id="最后把顺序理对">最后:把顺序理对</h2>
<p>如果你正要把 ASR 落地,优化的优先级应该是这样:</p>
<p><strong>先选对流式 / 非流式。</strong> 这是产品形态决策,选错了后面全白干。判断标准只有一个——用户是不是在等。</p>
<p><strong>再用真实音频建评测集。</strong> 没有这个,你后面所有的&quot;优化&quot;都是在拍脑袋。它还会直接告诉你高频错词是哪些。</p>
<p><strong>然后配热词、调 ITN、补降噪。</strong> 针对评测集暴露的具体问题对症下药——错词集中就上热词,数字老错就调 ITN,嘈杂场景拖后腿就补前端降噪。</p>
<p><strong>最后才考虑换模型或微调。</strong> 这是最贵、最慢的一步,而且很多时候,前面三步做扎实了,根本轮不到这一步。</p>
<p>ASR 落地真正难的,从来不是模型本身——模型早就够好了。难的是真实世界的语音很脏,而你的业务对&quot;哪种错误不可接受&quot;有自己的定义。把脏数据处理干净,把评测对齐到你的业务,模型才能真正帮上忙。</p>
<hr>
<p>参考与延伸阅读:</p>
<ul>
<li><a href="https://smallest.ai/blog/how-to-evaluate-asr-in-2026">How to Evaluate ASR in 2026: Accuracy, Latency and Cost — Smallest.ai</a></li>
<li><a href="https://www.gladia.io/blog/best-speech-to-text-apis">Best Speech-to-Text APIs in 2026 — Gladia</a></li>
<li><a href="https://www.assemblyai.com/blog/assemblyai-vs-deepgram-best-voice-agent-api">AssemblyAI vs Deepgram: Best Voice Agent API</a></li>
<li><a href="https://huggingface.co/microsoft/VibeVoice-ASR">microsoft/VibeVoice-ASR — Hugging Face</a></li>
<li><a href="https://arxiv.org/pdf/2512.21828">Contextual Biasing for LLM-Based ASR with Hotword Retrieval and Reinforcement Learning</a></li>
<li><a href="https://www.researchgate.net/publication/384811643_Advocating_Character_Error_Rate_for_Multilingual_ASR_Evaluation">Advocating Character Error Rate for Multilingual ASR Evaluation</a></li>
<li><a href="https://arxiv.org/pdf/2210.15063">Four-in-One: A Joint Approach to ITN, Punctuation, Capitalization, and Disfluency</a></li>
</ul>
]]></content:encoded></item><item><title>AI 应用的护栏:输入输出怎么管</title><link>https://realtime-ai.chat/posts/ai-guardrails/</link><pubDate>Mon, 20 Apr 2026 11:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/ai-guardrails/</guid><description>LLM 应用上线前,护栏决定它会不会闯祸。这篇拆解护栏管什么、怎么做、放哪一层、怎么测,以及过度护栏的反效果——给一份能直接对照的上线清单。</description><content:encoded><![CDATA[<p>先说一个数字:Guardrails AI 在 2025 年初做过一次基准测试,结论里有句话很刺眼——<strong>单个护栏哪怕准确率有 90%,串五个,误杀率就到 40%</strong>。</p>
<p>这句话基本能概括做护栏的全部难处。你不是在&quot;加保护&quot;,你是在一条已经够慢、够贵、够不确定的链路上,再叠一层会拖慢、会误判、还得自己维护的东西。问题不是&quot;要不要护栏&quot;——上线的 LLM 应用一定要有——问题是<strong>护栏管什么、做多厚、放哪一层</strong>。这三件事做错,护栏要么形同虚设,要么把好用户也一起赶跑了。</p>
<p>这篇不讲注入攻击的具体手法(那是另一篇的事),讲更宽的一件事:把用户的输入、模型的输出当成两道关口,这两道关口该怎么修。</p>
<h2 id="护栏到底在拦什么">护栏到底在拦什么</h2>
<p>很多团队上来就装个 moderation API,以为护栏就是&quot;过滤脏话&quot;。不是。护栏分两侧,两侧拦的东西完全不一样。</p>
<p><strong>输入侧</strong>,拦的是用户递进来的东西:</p>
<ul>
<li><strong>敏感信息</strong>。用户在对话里贴了身份证号、银行卡、内部工号、客户手机号。你不想把这些原样喂给第三方模型 API,更不想它们出现在日志里。</li>
<li><strong>越界请求</strong>。一个做企业财报问答的 Bot,用户问&quot;帮我写一首失恋的诗&quot;。这不危险,但它<strong>不是这个产品该干的事</strong>——回答了,就是在替竞品做免费体验。</li>
<li><strong>明显的恶意输入</strong>。攻击意图、自动化刷量、超长 prompt 灌爆上下文。</li>
</ul>
<p><strong>输出侧</strong>,拦的是模型吐出来的东西,这一侧更难,因为输出是模型生成的、不可预测的:</p>
<ul>
<li><strong>有害内容</strong>。暴力、仇恨、自残、违法信息。这是最经典的一类,也是 moderation API 唯一管得好的一类。</li>
<li><strong>幻觉</strong>。模型一本正经地编了一个不存在的退款政策、一个错误的药品剂量。在 RAG 场景里,这意味着输出和检索到的资料对不上。</li>
<li><strong>格式错误</strong>。你要的是一段能直接 <code>JSON.parse</code> 的结构化数据,模型给你前面加了句&quot;好的,这是您要的结果:&quot;。下游程序当场崩。</li>
<li><strong>品牌口径</strong>。模型说了&quot;我们的竞品确实更便宜&quot;,或者用了一个法务明令禁止的承诺词(&ldquo;保证收益&quot;&ldquo;绝对安全&rdquo;)。没毒,但能上财经新闻。</li>
</ul>
<p>注意最后两类——<strong>格式</strong>和<strong>品牌口径</strong>——很多人根本不把它当护栏。但它们恰恰是上线后最高频出事的地方。有害内容一年可能出一次,格式错误一天能出一百次。</p>
<h2 id="四种做法从便宜到贵">四种做法,从便宜到贵</h2>
<p>护栏不是一种技术,是一个工具箱。按&quot;成本/能力&quot;从低到高,有四档。</p>
<p><strong>第一档:规则与正则。</strong> 关键词黑名单、正则匹配身份证/手机号、长度限制、JSON schema 校验。便宜到几乎不要钱,延迟个位数毫秒,而且<strong>结果可解释</strong>——你能准确说出&quot;它是因为命中了第几条规则被拦的&rdquo;。缺点也明显:绕得过,且管不了语义。规则适合拦&quot;形状固定&quot;的东西:PII、超长输入、明确的禁用词。</p>
<p><strong>第二档:专门的分类模型。</strong> 这是 2026 年的主力。Meta 的 Llama Guard 是一个专门微调出来的输入输出安全模型,自带六大类不安全分类,还能自定义类目;OpenAI 的 <code>omni-moderation-latest</code> 免费、能同时分类文本和图像。它们的关键优势是<strong>快</strong>——一个专用 guard 模型跑一次大约 29 毫秒,而拿一个大模型当审查员要 5 到 11 秒。但要清楚它们的边界:OpenAI moderation 只做内容分类,<strong>不查注入、不查幻觉、不做 PII 脱敏</strong>。它是基线,不是全部。</p>
<p><strong>第三档:用 LLM 审查 LLM。</strong> 让另一个模型(或同一个模型换个 prompt)去判断&quot;这条输出有没有问题&quot;。最灵活,能处理&quot;品牌口径&quot;&ldquo;答非所问&quot;这种规则和分类器都搞不定的模糊判断。代价是它<strong>慢且贵</strong>——等于每个请求多一次完整的 LLM 调用,延迟翻倍,成本也翻倍。LLM 审查适合留给&quot;少量、高风险、规则写不出来&quot;的判断。</p>
<p><strong>第四档:约束生成(constrained decoding)。</strong> 这个和前三档不是一类——前三档是&quot;事后检查&rdquo;,约束生成是&quot;从源头让它不可能错&quot;。它在模型每一步采样时,直接把不符合 JSON schema 的 token 概率压成零,所以输出<strong>必然</strong>结构合法。Outlines、XGrammar 这些库,还有各家 API 的 structured output 都是这个原理。但记住它的边界:<strong>它保证结构对,不保证内容对</strong>。schema 合法的 JSON,字段值照样可以是幻觉。约束生成解决格式护栏,不解决事实护栏。</p>
<p>把这四档对一遍:</p>
<table>
  <thead>
      <tr>
          <th>做法</th>
          <th>典型延迟</th>
          <th>能解释</th>
          <th>管得了什么</th>
          <th>管不了什么</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>规则/正则</td>
          <td>&lt;10 ms</td>
          <td>强</td>
          <td>PII、长度、禁用词、JSON 格式</td>
          <td>任何需要理解语义的判断</td>
      </tr>
      <tr>
          <td>分类模型</td>
          <td>~30–90 ms</td>
          <td>中</td>
          <td>有害内容、注入信号、话题越界</td>
          <td>幻觉、品牌口径这种细活</td>
      </tr>
      <tr>
          <td>LLM 审查</td>
          <td>0.5–10 s</td>
          <td>弱</td>
          <td>品牌口径、答非所问、复杂合规</td>
          <td>它自己也会误判,且贵</td>
      </tr>
      <tr>
          <td>约束生成</td>
          <td>几乎为零</td>
          <td>强</td>
          <td>输出结构(JSON/枚举/格式)</td>
          <td>内容对不对、是不是幻觉</td>
      </tr>
  </tbody>
</table>
<p>没有哪一档能单独搞定所有事。真实的护栏系统是<strong>分层组合</strong>:规则挡掉一眼假的,分类模型处理大头,LLM 审查兜少量疑难,约束生成锁死格式。</p>
<h2 id="护栏放在哪一层">护栏放在哪一层</h2>
<p>做法选完了,还有个位置问题——同一个检查,放网关、放应用、放模型推理层,效果差很多。</p>
<pre class="mermaid">flowchart LR
  U[用户] --> G[网关层<br/>限流·PII脱敏·黑名单]
  G --> A[应用层<br/>话题边界·业务规则·上下文判断]
  A --> M[模型推理层<br/>约束生成·LLM审查]
  M --> A2[应用层<br/>幻觉/格式/品牌口径校验]
  A2 --> G2[网关层<br/>统一兜底·日志脱敏]
  G2 --> U
  style A fill:#fde7c2,stroke:#e8b23c
  style A2 fill:#fde7c2,stroke:#e8b23c
</pre><p>我的分配原则是这样:</p>
<p><strong>网关层放&quot;无状态、与业务无关&quot;的检查。</strong> PII 脱敏、限流、IP 黑名单、超长输入截断。这些东西不需要懂业务,放在最外层,挡掉的请求根本不会消耗下游算力。日志脱敏也必须在这层做死——一旦敏感信息进了应用日志,再清就是事故。</p>
<p><strong>应用层放&quot;要懂业务&quot;的检查。</strong> 话题边界(财报 Bot 该不该回答写诗)、业务规则(这个用户的权限能不能问这个数据)、需要对话上下文才能判断的东西。这一层是护栏的主战场,因为只有它同时知道&quot;用户是谁&quot;&ldquo;产品边界在哪&quot;&ldquo;检索到了什么资料&rdquo;。</p>
<p><strong>模型推理层放约束生成。</strong> structured output 必须贴着推理走,事后再校验格式纯属浪费——既然能从源头保证,就别留给下游收拾。</p>
<p>橙色那两块——<strong>输出进应用层之后、回给用户之前</strong>的校验——是最容易被漏掉、又最关键的。幻觉检查、品牌口径、答非所问,只能在这里做,因为只有这里能拿到完整的&quot;模型说了什么 + 它本该基于什么&rdquo;。</p>
<p>一个反复出现的错误是<strong>把所有护栏都堆在网关</strong>。图省事,接一个统一的安全中间件。结果就是网关根本不知道业务边界,要么放过一切要么乱拦一气,而最该管的幻觉和口径,它压根没有上下文去管。</p>
<h2 id="护栏的成本延迟和误杀">护栏的成本:延迟和误杀</h2>
<p>回到开头那个 40%。护栏不是免费的,它的账单写在两个地方。</p>
<p><strong>一笔是延迟。</strong> 输入侧的护栏串在用户请求的关键路径上,它慢,用户就等。分类模型几十毫秒还能接受,但你要是图省事拿大模型当审查员,5 到 11 秒——用户早走了。输出侧更麻烦:如果你的应用是流式输出(打字机效果),而输出护栏要等<strong>整段生成完</strong>才能检查,那流式就白做了,用户得对着一个转圈等到最后。这是流式应用做输出护栏时最容易踩的坑。</p>
<p><strong>另一笔是误杀。</strong> 这笔账更隐蔽。护栏拦错了,代价不是 0,是一个被冤枉的真实用户——他什么也没干错,被弹了一句&quot;抱歉,我无法回答这个问题&quot;。医疗问答 Bot 因为出现&quot;症状&quot;两个字就拒答,编程助手把所有正则表达式请求都当成攻击拦掉,这种事天天发生。</p>
<p>这两笔账还得放在一起算。有个说法很到位:<strong>一张冒犯性输出的截图传到社交媒体上,造成的损失,可能比一整年误杀管理的成本都高。</strong> 风险是不对称的——所以高风险场景(面向公众、涉及未成年人、金融医疗)护栏宁可严一点;但低风险的内部工具,你把护栏调得跟法务一样谨慎,纯属自残。<strong>护栏的松紧,是个业务决策,不是技术默认值。</strong></p>
<h2 id="怎么测护栏本身">怎么测护栏本身</h2>
<p>护栏是代码,代码就会有 bug,而护栏的 bug 你平时根本看不见——它默默放过该拦的,或者默默拦掉不该拦的,没有报错、没有崩溃。所以护栏必须被单独测试,而且要测两个方向。</p>
<p>很多团队只测一个方向:准备一批&quot;坏样本&quot;,看护栏能拦住多少。这只测了一半。护栏有两类错误,得两类都测:</p>
<ul>
<li><strong>漏报(false negative)</strong>:坏的没拦住。用一批已知的有害/越界/幻觉样本测,看<strong>召回率</strong>。</li>
<li><strong>误杀(false positive)</strong>:好的被拦了。这个更容易被忽略,但杀伤力更大。你得专门准备一批<strong>长得像坏、其实没问题</strong>的样本——讨论自残话题的心理健康咨询、带&quot;炸弹&quot;二字的化学课提问、正常的退款投诉——看护栏会不会错拦。</li>
</ul>
<p>把这两批样本固定下来,做成一个回归集。每次调护栏的阈值、换分类模型、改 prompt,都跑一遍。盯两个数:<strong>召回率</strong>(漏了多少坏的)和<strong>误杀率</strong>(冤了多少好的)。这两个数永远在拉扯——调严了召回上去、误杀也上去。你要找的不是某个完美点,是一条<strong>和业务风险匹配的取舍线</strong>。</p>
<pre class="mermaid">flowchart TD
  C[改护栏:换模型/调阈值/改 prompt] --> R[跑回归集]
  R --> N[坏样本集<br/>看召回率]
  R --> P[似坏实好样本集<br/>看误杀率]
  N --> D{两个数都可接受?}
  P --> D
  D -->|否| C
  D -->|是| L[上线 + 线上抽样复核]
</pre><p>上线之后还没完。线上要对<strong>被护栏拦掉的真实请求</strong>做抽样人工复核——这是发现误杀的唯一现实途径,因为被冤枉的用户通常不会投诉,他直接走了,你从指标上只看到一个安静下降的留存。</p>
<h2 id="过度护栏管得越多反而越糟">过度护栏:管得越多,反而越糟</h2>
<p>最后这条,是我最想说的。</p>
<p>护栏给人一种虚假的安全感:多加一层,总没坏处吧?有坏处。回到那个数学事实——五个 90% 准确率的护栏串起来,40% 的请求会被误杀。你以为加的是保险,实际加的是故障率。</p>
<p>过度护栏的几个典型反效果:</p>
<ul>
<li><strong>产品变得没用。</strong> 该回答的不回答。一个谨慎到拒绝讨论任何症状的医疗 Bot,对用户来说和不存在没区别。用户要的是帮助,不是一个不停说&quot;抱歉我无法&quot;的复读机。</li>
<li><strong>延迟堆到不可用。</strong> 每层护栏都加几十上百毫秒,叠四五层,本来 1 秒能出的结果变成 3 秒。安全是安全了,没人用了。</li>
<li><strong>维护成本失控。</strong> 每个护栏都是要喂数据、要调阈值、要跟着业务变的活物。堆五个,就是五份持续的维护负债。</li>
<li><strong>掩盖真问题。</strong> 团队拿&quot;我们有七层护栏&quot;当心理安慰,反而不去做真正该做的事——把 system prompt 写清楚、给模型接上权威知识库、把产品边界设计明白。<strong>护栏是补丁,不是地基。</strong> 一个本身就容易跑偏的应用,糊再多护栏也救不回来。</li>
</ul>
<p>学术界已经在认真对待&quot;过度拒绝&quot;这件事了——2025、2026 年有不少论文专门研究怎么在不牺牲安全的前提下,降低模型对正常请求的误拒。这从侧面说明:<strong>过度护栏不是小毛病,它是和&quot;护栏不够&quot;同等量级的失败模式。</strong></p>
<p>正确的心态是:护栏要<strong>少而准</strong>。每加一层之前,先问三个问题——这个风险真的会发生吗?发生了真的严重吗?现有的层挡不住吗?三个都是&quot;是&quot;,才加。</p>
<h2 id="一份上线清单">一份上线清单</h2>
<p>把上面的东西收成一张可以直接对照的清单:</p>
<p><strong>输入侧</strong></p>
<ul>
<li><input disabled="" type="checkbox"> PII(身份证/手机号/卡号)在进模型前已脱敏,且日志里也脱敏了</li>
<li><input disabled="" type="checkbox"> 有输入长度上限,挡掉超长 prompt</li>
<li><input disabled="" type="checkbox"> 话题边界明确——产品该回答什么、不该回答什么,有规则</li>
<li><input disabled="" type="checkbox"> 限流和黑名单放在网关层</li>
</ul>
<p><strong>输出侧</strong></p>
<ul>
<li><input disabled="" type="checkbox"> 有害内容过滤(分类模型,如 Llama Guard / moderation API)</li>
<li><input disabled="" type="checkbox"> 结构化输出用约束生成从源头保证格式,不靠事后修</li>
<li><input disabled="" type="checkbox"> RAG 场景有幻觉/事实一致性检查(输出对得上检索资料)</li>
<li><input disabled="" type="checkbox"> 品牌口径和法务禁用词有校验(高风险时用 LLM 审查)</li>
</ul>
<p><strong>工程</strong></p>
<ul>
<li><input disabled="" type="checkbox"> 每个护栏分了层:无状态的进网关,懂业务的进应用,格式的进推理层</li>
<li><input disabled="" type="checkbox"> 流式输出的护栏不会逼用户等整段生成完</li>
<li><input disabled="" type="checkbox"> 有护栏回归测试集,<strong>召回和误杀两个方向都测</strong></li>
<li><input disabled="" type="checkbox"> 线上对被拦请求做抽样人工复核</li>
<li><input disabled="" type="checkbox"> 算过总延迟账:所有护栏叠加后,响应时间还能接受</li>
<li><input disabled="" type="checkbox"> 每一层护栏都问过&quot;这个风险真的值得这层成本吗&quot;</li>
</ul>
<p>最后一句:护栏的目标不是&quot;零事故&quot;,是&quot;在可接受的延迟和误杀成本下,把高风险事件压到足够低&quot;。把它当成一个<strong>有取舍的工程问题</strong>来做,而不是一个&quot;装得越多越安心&quot;的合规动作——这是做好护栏和做砸护栏的分界线。</p>
<hr>
<p>参考:<a href="https://github.com/NVIDIA-NeMo/Guardrails">NVIDIA NeMo Guardrails</a> · <a href="https://pypi.org/project/guardrails-ai/">Guardrails AI Hub</a> · <a href="https://guardrails.openai.com/">OpenAI Guardrails</a> · <a href="https://generalanalysis.com/guides/best-ai-guardrails">Guardrails Index 基准(2025)</a> · <a href="https://modelmetry.com/blog/latency-of-llm-guardrails">LLM 护栏延迟实测</a></p>
]]></content:encoded></item></channel></rss>