很多团队把 LLM 当成一个「更聪明的字符串函数」直接塞进生产代码:拼个 prompt、调一次接口、把返回值当真值用。Demo 跑得很爽,上线后开始被各种诡异问题反噬——超时、幻觉、成本失控、偶发格式崩坏。本文从工程视角拆解「把 LLM 用对」的几个核心范式,以及与之对应的反模式。

直觉:LLM 是一个高方差、不可靠的远程子系统

先校正心智模型。传统函数 f(x) 是确定的、低延迟的、几乎零成本的。LLM 调用恰好相反:

  • 非确定:相同输入、相同 temperature 也可能输出不同(采样 + 后端 batch 调度抖动)。
  • 高延迟:首 token(TTFT)通常几百毫秒,长输出按 token 串行生成,整体可达秒级。
  • 有成本:按 token 计费,输入输出分别计价,长上下文会线性甚至更陡地放大开销。
  • 会自信地错:幻觉不是 bug,是采样分布的固有产物。

正确的工程定位:把它当成一个有限可靠的外部服务,像对待一个会偶发 500、偶发返回脏数据的第三方 API 那样去做容错、限流、降级、校验。

机制:数据流与三个必须卡住的关口

一条生产级 LLM 调用的数据流大致是:

1
2
3
用户输入 → 输入净化/注入防护 → 上下文组装(检索/裁剪) → prompt 模板
→ LLM 调用(重试/超时/限流) → 输出结构化解析+校验 → 业务消费
↘ 校验失败 → 重试/降级

三个最容易被忽略、却决定系统稳定性的关口:

1. 输出结构化与校验

不要用正则去抠自由文本。让模型输出 JSON,并用 schema 严格校验。Anthropic 的 Claude、以及多数主流模型都支持 tool use / 工具调用,本质是让模型把输出约束进你定义的 JSON Schema 里——这比「请你一定输出 JSON」这种 prompt 祈祷靠谱得多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import json
from jsonschema import validate, ValidationError

SCHEMA = {
"type": "object",
"properties": {
"intent": {"type": "string", "enum": ["refund", "query", "other"]},
"confidence": {"type": "number"},
},
"required": ["intent", "confidence"],
}

def parse_and_validate(raw: str) -> dict:
obj = json.loads(raw) # 可能抛 JSONDecodeError
validate(obj, SCHEMA) # 可能抛 ValidationError
return obj

校验失败时的处理策略,按代价从低到高:直接重试 → 把错误信息回灌让模型自修复 → 降级到规则兜底。关键是:校验失败必须是一条正常代码路径,而不是异常崩溃。

2. 重试、超时与幂等

LLM 网关偶发限流(429)、超时是常态。需要带指数退避的重试,但要注意:生成类调用不是天然幂等的,重试会产生不同结果,也会重复计费。对「写库」「发消息」这类副作用操作,要在业务层加幂等键,而不是依赖模型层去重。

3. 上下文预算管理

上下文窗口是有限资源。设窗口为 CC,则约束是:

Tsys+Tctx+Tout_maxCT_{sys} + T_{ctx} + T_{out\_max} \le C

其中 TctxT_{ctx}(检索/历史拼进去的内容)往往是最大变量。一个常见反模式是「把所有历史和所有检索结果一股脑塞进去」,结果是:成本线性上涨、延迟变长,而且容易触发「lost in the middle」——模型对长上下文中间段落的利用率显著下降。正解是做预算分配 + 相关性裁剪:检索 top-k 后再 rerank,只保留真正相关的片段。

公式视角:成本与延迟怎么估

单次调用成本可粗略建模为:

Cost=ninpin+noutpout\text{Cost} = n_{in} \cdot p_{in} + n_{out} \cdot p_{out}

由于 poutp_{out} 通常是 pinp_{in} 的数倍,且输出是串行生成的,控制输出长度对成本和延迟的杠杆远大于压缩输入。让模型「只回 JSON、不要解释」既省钱又快。

延迟则近似:

LatencyTTFT+nouttper_token\text{Latency} \approx \text{TTFT} + n_{out} \cdot t_{per\_token}

这意味着流式输出(streaming)对体感延迟极有价值——用户在 TTFT 之后就能看到内容,而不是干等整段生成完。

工程权衡与反模式清单

  • 反模式:把 prompt 散落在代码各处。 prompt 是会迭代的「配置/资产」,应集中管理、版本化,最好能 A/B 和回归测试。
  • 反模式:没有评测就改 prompt。 改一句话可能让整体效果回退却没人发现。建一个哪怕只有几十条样例的离线评测集(含 LLM-as-judge 或规则断言),每次改动跑一遍。
  • 反模式:用昂贵大模型干所有活。 做模型分级(routing):简单分类/抽取用小模型,复杂推理才上大模型。可显著降本。
  • 反模式:把用户输入直接拼进 prompt。 这是 prompt injection 的温床。把不可信内容放进明确隔离的区块,并对工具调用结果做权限校验——尤其当模型能触发真实副作用(下单、删数据)时。
  • 权衡:缓存。 对稳定的长系统提示,启用 prompt caching 可大幅降低重复输入的成本与 TTFT;代价是需要把可变内容放在 prompt 末尾以保持前缀稳定。
  • 权衡:把链路拆细 vs. 一次搞定。 多步 agent 链路更可控、可观测,但每一步都有失败概率,端到端成功率是各步的乘积 ipi\prod_i p_i,链路越长越脆。能用一次结构化调用解决的,就别硬拆成五步。

小结

把 LLM 用进生产,难点几乎不在「写出聪明的 prompt」,而在把一个非确定、有成本、会出错的远程子系统用工程手段约束成可依赖的组件:结构化输出 + schema 校验、带退避与幂等的调用、上下文预算管理、模型分级、注入防护,以及最容易被跳过却最值钱的——离线评测集。先把这套「脚手架」搭好,再去打磨 prompt,顺序别反了。