大模型应用从 Demo 到生产,中间缺了什么
从一次「Demo 翻车」说起
去年我给一个电商客户做客服机器人。演示当天,我用三条精心挑选的问题跑通了全流程——意图识别、知识检索、回答生成,一条链路下来 3 秒出结果,客户很满意。
两周后接入了真实流量。第一天就出了问题:有用户发了一整屏的退货政策截图,有用户用方言拼音输入,有用户连续发了 12 条消息催单。模型要么返回超长截断,要么把前一条的回答重复了一遍,要么直接超时。客户截图发到群里,配文「这就是你们说的人工智能?」。
这件事让我意识到,Demo 和生产之间不是「加个接口」的距离,而是一整套工程能力的缺失。这篇文章就是我对这个 gap 的系统梳理。
为什么 Demo 能跑通,生产跑不通
先拆一下 Demo 和生产在本质上有什么不同。
Demo 跑的是「理想路径」:用户输入干净、意图明确、上下文简短、模型正常响应。生产面对的是「全集」:输入不完整、格式混乱、敏感信息混杂、模型偶发偏离、接口超时、成本波动。更要命的是,Demo 阶段没有人会去关心「如果模型答错了,谁来兜底」这个问题。
Chip Huyen 在她那篇被广泛引用的文章里提过一个判断:「LLM 应用的难点不在模型本身,而在模型周围的工程。」1 我深以为然。模型是确定的商品——你调用 API 就能拿到结果。但把结果变成一个可靠的、可维护的、成本可控的产品功能,中间需要填的东西远比想象中多。
Martin Fowler 团队在总结 LLM 应用工程实践时也指出:「测试能令人信服地证明 bug 的存在,但永远无法证明 bug 的不存在。」2 这句话放在大模型场景下尤其刺眼——传统软件的测试可以覆盖确定性逻辑,但大模型的输出空间是开放的、概率性的。你不能靠 20 条单元测试就说系统没问题。
三个真实案例,三种典型翻车
案例一:知识检索的「看似相关」陷阱
某内容平台用 RAG 做内部文档问答。Demo 阶段用了 50 篇文档测试,检索准确率 92%。上线后接入了 3000+ 篇文档,问题变成了「跨文档推理」——用户问的是一个流程,涉及三篇不同文档的章节。向量检索按单篇相似度打分,返回的片段各自相关但拼在一起互相矛盾。
模型没有幻觉,它忠实地根据检索到的内容生成了回答——只是检索到的内容本身就是冲突的。
这个问题的根因不在模型,而在检索策略。他们后来改成了两阶段检索:先按文档类型过滤,再在限定范围内做语义搜索,最后加了一个「冲突检测」步骤,如果返回的片段之间有矛盾,就让模型显式说明。
案例二:Eval 缺失导致的「静默退化」
一个代码审查工具,最初用 GPT-4 写 Prompt,效果不错。三个月后团队把底层模型换成了某国产模型以降低成本。替换后跑了一遍手动测试,感觉「差不多」。但上线两周后,开发团队反馈「审查意见变水了」——具体指标是:指出实际 bug 的比例从 35% 降到 18%,但格式和语气看起来没变。
因为没有 Eval 系统,这个退化发生了两周才被人注意到。如果有一组黄金样本和自动回归,模型切换后的质量下降应该在发布前就被发现。
案例三:没有降级策略的「雪崩」
某金融应用的智能摘要功能,直接调用 Claude API。某天 Anthropic 的 API 出现间歇性延迟(从正常的 2 秒飙到 30 秒),前端用户以为卡了,反复点击重试。短时间内积压了大量请求,触发了 API 的速率限制。等到 API 恢复,又有一批重试请求涌入,再次触发限流。
整个过程持续了 40 分钟。期间没有降级——哪怕返回一个「摘要暂时不可用,请稍后查看原文」的模板,也比让用户干等 30 秒好得多。
这三个案例对应三种不同的工程缺失:检索策略粗糙、Eval 体系空白、降级策略缺失。它们都不是模型能力问题,而是生产化工程没跟上。
生产化全景:从 Demo 到上线的完整链路
把一个大模型功能从 Demo 推到生产,需要补齐的能力可以用一张图来理解:
这条链路里有几个关键节点,我逐个说明。
输入治理
Demo 阶段不会去想「用户输入了什么」,生产必须记录。输入来源、长度、语言、是否包含敏感字段、用户意图是否明确——这些信息出了问题都要回溯。
一个实际的做法是在入口层加一个中间件,记录每次请求的原始输入、清洗后的输入、以及被过滤掉的敏感信息(做脱敏处理)。这不是为了优化模型,而是为了在出问题时能复现。
上下文管理
大模型应用的上下文通常由多个部分组成:系统规则、用户资料、检索到的材料、对话历史、当前查询。Demo 阶段可能把所有内容拼成一个字符串塞给模型,生产需要分层管理。
分层的好处是:系统规则可以独立版本控制,用户资料可以缓存,检索材料可以按相关性动态裁剪,对话历史可以按 Token 预算截断。任何一层出问题,其他层不受影响。
Prompt 版本管理
这是最容易被忽视的一环。很多团队的 Prompt 散落在代码各处,改一次要搜遍整个仓库。生产系统需要把 Prompt 当作一等公民来管理:独立存储、版本控制、A/B 测试、灰度发布。
Vellum 的团队在他们的「四大支柱」框架中强调:应该让非技术人员也能在不改代码的情况下调整 Prompt,直到它通过评测集。3 这个思路我认同——Prompt 的迭代频率远高于代码,把它锁在代码里会拖慢整个流程。
可观测性
LLM 应用的可观测性和传统服务有交集但也有本质不同。传统服务监控的是延迟、错误率、吞吐量;LLM 应用还需要监控「语义层面的健康度」——回答是否相关、是否有幻觉、是否偏离了预期风格。
Galileo AI 的分析把这两层区分得很清楚:监控(monitoring)追踪的是系统是否在运行,可观测性(observability)解释的是为什么出了问题。4 对于 RAG 或 Agent 这类多步骤架构,只有监控是不够的——你需要能追踪每一步的输入、输出和决策依据。
降级策略
当模型不可用、响应太慢或成本超预算时,系统不能干等。降级策略可以是:返回预设模板、切换到更小的模型、让人工接管、或者暂时关闭该功能并告知用户。
关键是降级策略要在架构设计阶段就规划好,而不是等第一次事故后才想起来加。
Demo 与生产的对比:五个维度
把 Demo 和生产的差异系统化,可以从五个维度来看:
| 维度 | Demo 阶段 | 生产阶段 | 差距本质 |
|---|---|---|---|
| 输入 | 干净、受控、意图明确 | 混乱、超长、含敏感信息、意图模糊 | 输入治理缺失 |
| 上下文 | 单文档或短对话 | 多文档、长历史、动态检索 | 上下文分层管理缺失 |
| 模型调用 | 固定版本、单次调用 | 多版本、多模型、需要重试和降级 | 调用链工程缺失 |
| 输出 | 人工目测「看起来对」 | 需要自动评测、需要置信度和兜底 | 评测体系缺失 |
| 运维 | 无所谓,出问题重跑 | 需要日志、追踪、告警、成本监控 | 可观测性缺失 |
再看工程实践的具体差异:
| 工程环节 | Demo 做法 | 生产做法 |
|---|---|---|
| Prompt 管理 | 硬编码在代码中 | 独立存储 + 版本控制 + 灰度发布 |
| 测试 | 手动试几条 | 黄金样本集 + 自动回归 + 对抗测试 |
| 错误处理 | 打印错误日志 | 结构化错误分类 + 自动降级 + 人工兜底 |
| 成本 | 不计较 | Token 预算 + 模型路由 + 缓存策略 |
| 数据安全 | 明文传输 | 敏感字段脱敏 + 权限隔离 + 审计日志 |
代码对比:Demo 思维 vs 生产思维
1. 输入处理
Demo 思维:直接拿用户输入就调模型。
# Demo:直接使用
user_input = request.json["message"]
response = llm.chat(user_input)生产思维:验证、清洗、记录、脱敏。
# 生产:输入治理
user_input = request.json["message"]
# 长度限制
if len(user_input) > MAX_INPUT_LENGTH:
user_input = user_input[:MAX_INPUT_LENGTH] + "[已截断]"
# 敏感信息脱敏
user_input = mask_sensitive_fields(user_input)
# 记录原始输入(脱敏后)用于回溯
logger.info("llm_input", extra={
"user_id": current_user.id,
"input_length": len(user_input),
"input_preview": user_input[:200],
})
response = llm.chat(user_input)2. 模型调用
Demo 思维:调一次,等结果。
# Demo:单次调用
result = llm.generate(prompt, model="gpt-4")
return result生产思维:重试、降级、超时控制、成本路由。
# 生产:带降级和路由的调用链
async def generate_with_fallback(prompt, complexity="high"):
# 根据复杂度路由到不同模型
model = "gpt-4" if complexity == "high" else "gpt-4-mini"
for attempt in range(MAX_RETRIES):
try:
result = await asyncio.wait_for(
llm.generate(prompt, model=model),
timeout=REQUEST_TIMEOUT,
)
# 输出校验
if validate_output(result):
return result
except (TimeoutError, RateLimitError) as e:
logger.warning(f"attempt {attempt} failed: {e}")
if attempt == MAX_RETRIES - 1:
# 降级:返回模板或人工队列
return await fallback_response(prompt)
return await fallback_response(prompt)3. 上下文组装
Demo 思维:全部拼在一起。
# Demo:简单拼接
context = system_prompt + "\n" + user_data + "\n" + retrieved_docs + "\n" + query
response = llm.chat(context)生产思维:分层管理、Token 预算控制、来源标注。
# 生产:分层上下文管理
def build_context(query, user, retrieved_docs):
layers = {
"system": load_system_prompt(version=CURRENT_PROMPT_VERSION),
"user_profile": format_user_context(user),
"retrieved": [],
"query": query,
}
# Token 预算分配
budget = MAX_CONTEXT_TOKENS
budget -= count_tokens(layers["system"])
budget -= count_tokens(layers["user_profile"])
budget -= count_tokens(layers["query"])
# 按相关性排序,在预算内填充
for doc in sorted(retrieved_docs, key=lambda d: d.score, reverse=True):
doc_tokens = count_tokens(doc.content)
if doc_tokens <= budget:
layers["retrieved"].append(doc)
budget -= doc_tokens
return render_context(layers)4. 评测
Demo 思维:自己看几条,觉得不错就行。
# Demo:手动测试
test_cases = ["问题1", "问题2", "问题3"]
for case in test_cases:
print(llm.chat(case))
input("看起来对吗?按回车继续...")生产思维:黄金样本集 + 自动评测 + 回归检测。
# 生产:自动化 Eval 框架
def run_eval_suite(eval_dataset, prompt_version):
results = []
for sample in eval_dataset:
response = llm.chat(
sample.input,
prompt_version=prompt_version,
)
results.append({
"input": sample.input,
"expected": sample.expected,
"actual": response,
"relevance_score": evaluate_relevance(response, sample.expected),
"faithfulness_score": evaluate_faithfulness(response, sample.context),
"latency_ms": response.latency,
})
# 与上一次基线对比
baseline = load_baseline(eval_dataset.name)
regression = detect_regression(results, baseline)
if regression.severity > THRESHOLD:
raise RegressionError(
f"质量回归: {regression.summary}",
details=regression.details,
)
return results上线前检查清单
把上面的讨论落地成一份可执行的清单。每次大模型功能上线前,我会逐项过一遍:
输入与数据
- 输入长度有上限,超长输入有截断或拒绝策略
- 敏感信息(手机号、身份证、银行卡)在发给模型前做了脱敏
- 输入来源、用户 ID、时间戳有日志记录
- 对异常输入(空值、纯符号、极端长度)有防御处理
上下文与 Prompt
- Prompt 独立管理,有版本号,不硬编码在业务逻辑中
- 上下文分层(系统、用户、检索、查询),各层可独立更新
- Token 预算有上限,超长上下文有裁剪策略
- 检索结果有来源标注,模型输出可追溯到具体材料
模型调用与降级
- 模型调用有超时控制,不会无限等待
- 有重试策略,但重试次数有限(避免雪崩)
- 有降级方案:模型不可用时返回模板、切小模型或人工兜底
- 有模型路由:简单问题不用大模型,控制成本
评测与回归
- 有一组黄金样本(至少 50 条),覆盖典型场景和边界情况
- 每次 Prompt 或模型变更都跑自动回归
- 有明确的评测指标:相关性、忠实度、拒答率、延迟
- 评测结果与上一次基线对比,回归超过阈值则阻断发布
可观测性与运维
- 每次请求记录:模型版本、Prompt 版本、Token 用量、延迟、状态
- 有告警规则:错误率、延迟、成本超过阈值时通知到人
- 能通过请求 ID 回溯完整链路:输入 → 上下文 → Prompt → 模型响应 → 输出
- 有成本监控:按功能、按用户维度能看到 Token 消耗趋势
安全与合规
- 模型输出有基本的内容安全检查(避免泄露系统 Prompt 或敏感信息)
- 涉及用户数据的功能有权限控制,不同用户看到不同范围的结果
- 对外发布的内容有人工审核环节(至少在早期)
- 有数据保留和清理策略,符合隐私合规要求
适用边界:不必一次做完
上面列了很多东西,但不意味着每个项目都要一次性全部补齐。我的经验是按风险等级来分配工程投入。
轻量上线(写作辅助、内部工具、低频场景):保留最小闭环——请求日志、Prompt 版本、50 条黄金样本、人工反馈入口。出了问题能查到,能回退,就够了。
中等投入(客服、推荐、内容生成):加上自动 Eval、降级策略、成本监控和基本的可观测性。这些功能面向外部用户或影响业务流程,需要更严格的保障。
重度投入(自动审批、金融决策、医疗辅助):完整的评测平台、自动回归、细粒度监控、审计日志、人工兜底。这些场景模型出错直接影响用户权益,工程投入不能省。
生产化的目标不是让模型永远正确——这在当前技术条件下做不到。目标是让系统在模型不稳定时仍然可观察、可解释、可恢复。用户能理解为什么出了错,团队能快速定位问题,系统能自动降级而不是雪崩。
这才是 Demo 和生产之间真正缺的东西。
参考资料
Footnotes
-
Chip Huyen. Building LLM Applications for Production. 2023-04. ↩
-
Martin Fowler. Engineering Practices for LLM Application Development. 2024-02. ↩
-
Vellum. The Four Pillars of Building a Production-Grade AI Application. 2024-01. ↩
-
Galileo AI. LLM Monitoring vs Observability: Understanding the Key Differences. 2026-04. ↩