最近一年,垂直AI大概是最热闹的方向,也是最容易让团队预算打水漂的方向。CRM客服、金融尽调、法律检索、医疗问诊、供应链排产,几乎每个行业都在尝试“用大模型做一个自己行业的产品”。但真正到交付现场,大家会发现一个非常尴尬的现象:同一个问题,两次调用返回的答案不一样;昨天演示还正常的流程,今天换了个模型版本就静默失败;测试集上准确率90%以上,一上真实业务数据就开始一本正经地胡说。
业内流传着一个很尖锐的说法:The Vertical AI Bubble: We Keep Forgetting That LLMs Roll Dice。翻译过来就是——我们总是忘记,LLM 本质上是在掷骰子。我越来越觉得,这句话比很多大模型技术分享都重要。垂直AI泡沫的核心风险,不是模型能力不够强,也不是行业数据不够多,而是大量团队在设计产品时,把概率模型当成了确定性计算单元。这种认知错位,会直接决定一个产品是稳定可用的工具,还是一个永远在“碰运气”的演示系统。
这篇文章不吹捧大模型,也不劝退垂直AI。我想把一件事讲透:LLM 的随机性到底从哪来;垂直场景为什么特别怕这种随机性;以及工程上究竟有哪些手段,能把“骰子”约束在可控范围内。文章后面会给出可运行的 Python 示例、结构化输出校验、重试与自一致性投票、兜底配置,以及一套可以复用的评估和排错方法。
1. 垂直AI泡沫:热闹之下到底在担忧什么
所谓垂直AI,指的不是ChatGPT这种“什么都能聊一点”的通用助手,而是绑定某个具体行业、具体业务流程的智能化产品。比如客服工单自动分类、合同条款风险审查、医疗初筛问答、工业质检报告生成。这类产品有一个共同点:它们不是给用户“聊着玩”的,而是要进入真实业务系统,承担某个岗位的一部分职责。
一旦进入真实业务,要求就完全变了。财务对账不允许“大概对”,医疗建议不允许“可能有误”,合同审查不允许“这段责任条款模棱两可”。垂直场景对确定性、可解释性、可追溯性的要求,和传统软件几乎没有区别。但LLM提供的,恰恰是最不稳定的那一层——自然语言理解与生成。
泡沫担忧的本质就在这里:市场给的估值和预期,是按照“可靠的生产系统”来定价的;但当前主流LLM在概率采样机制下,输出天然带有不确定性。模型能力越强,这种不确定性被隐藏得越好,但它不会消失。你用ChatGPT聊天觉得它聪明,不代表它能稳定处理一万笔订单状态查询。
另一个被忽视的问题是AI Agent对随机性的放大。一个Agent链路通常由意图识别、信息抽取、工具调用、结果生成等多个步骤组成。每一步都有概率性,整个链路的成功率是指数级叠加的。单步成功率90%,五步之后理论成功率不到60%。这就是为什么很多Agent演示很惊艳,一放到生产环境就频繁失败。垂直AI泡沫,本质上不是“AI没用”的泡沫,而是“把随机性当成确定性来卖”的泡沫。
2. LLM 的“骰子机制”:从概率分布到采样参数
要理解为什么LLM不稳定,就要回到它最基本的工作方式。LLM在做的事,本质上是“预测下一个Token”。给定一段文本,模型会计算词表中每一个Token出现的概率,形成一个概率分布。真正输出哪个Token,取决于解码策略——是永远选概率最大的那个,还是在概率分布里随机抽样。
绝大多数商业对话模型默认采用采样方式生成文本。也就是说,模型不是从“唯一正确答案”里拷贝内容,而是从概率分布里随机抽取候选Token。这就是“掷骰子”三个字的字面含义:温度、Top-P这些参数,决定了骰子有多“随机”。
| 参数 | 作用 | 对随机性的影响 |
|---|---|---|
| temperature | 缩放Softmax概率分布,温度越高分布越平缓 | 温度越高,低概率Token越容易被抽到,输出越随机 |
| top_p | 只保留累积概率达到阈值的候选Token | 缩小抽样范围,让输出更集中 |
| top_k | 只保留概率最高的K个Token | 进一步限制候选集合 |
| presence penalty / frequency penalty | 惩罚已经出现过的Token | 改变概率分布形状,降低重复,间接影响随机性 |
很多团队有一种误解:把temperature设成0,模型输出就是确定的了。理论上,temperature为0时变成贪心解码,每一步都选概率最高的Token,看起来确实确定。但在真实生产环境里,这种“确定性”并不牢固。推理框架做浮点运算时会引入精度误差,GPU、CUDA版本、甚至同一批请求的batch拼接方式不同,都可能让结果发生细微偏移。近两年行业里经常讨论的FP16、BF16精度问题,就是这类误差的来源之一。换句话说,temperature=0是工程上的“尽力而为”,不是数学上的“绝对保证”。
真正要接受的现实是:幻觉也不完全是一个“错误”,而是概率采样的副产品。模型从分布中抽出一个在训练数据里出现过、但和当前上下文并不匹配的片段,人看起来就是在编造。所以,不要试图用更强的prompt“消灭”幻觉,而应该在系统工程层面接受它、检测它、兜住它。
3. 垂直场景为什么最怕随机性
我们可以把垂直AI产品拆开看。一个典型的订单查询机器人,链路大概是:用户输入 → 意图识别 → 抽取订单号 → 查询订单系统 → 判断订单状态 → 生成回复。每一步如果都用LLM实现,那么每一步都是一次掷骰子。用户表述稍微变一下,意图识别可能从“查询物流”跳到“申请退款”;抽取订单号时可能多抽出一个无关的数字;生成回复时可能把状态“运输中”描述成“已送达”。
传统软件工程之所以稳定,是因为它建立在确定性组件之上:条件分支、状态机、数据库事务、强类型校验。垂直AI产品则是把一组概率组件串成了一条流水线。流水线越长,积累的偏差越大,最终表现就是“时好时坏、难以复现”。测试人员报一个Bug,开发人员复现不出来,这种场景在AI项目里已经是常态。
这也能解释为什么这两年各种LLM编排框架会火。LangChain、Spring AI、各类Agent框架,本质上都是在帮助开发者管理概率性链路中的分支、重试和回退。但要注意,框架只是“容器”,它不会消除随机性。如果不知道每一步都可能失败,并在设计上预留容错,换再多的框架也只是让失败变得更整齐而已。
还有一个常见的误区:以为接上RAG,模型不稳定问题就解决了。RAG能解决的是“知识更新”和“上下文长度”问题,能让模型在回答时参考检索到的资料,但它改变不了采样机制。同一个检索结果,模型两次生成的摘要依然可能不同。RAG提高了答案的“上限”,但不会自动提高输出的“稳定性”。
4. 工程化应对原则:把随机性关进笼子
面对一个概率组件,最重要的设计原则是:不要让LLM承担它不该承担的职责。具体来说,可以参考下面五条原则。
第一,能枚举就不生成。如果业务里需要的是固定类别的判断,比如工单类型、投诉等级、订单状态,应该让模型输出固定标签,再用代码做映射和校验,而不是让模型自由发挥写一段话。分类问题的输出空间越小,稳定性越高。
第二,能用代码校验,就不靠模型自觉。凡是模型要输出结构化数据,一律要求JSON格式,并在拿到输出后做Schema校验。模型自己说的“我会严格按JSON输出”不可信,真正的保证来自response_format约束加后置校验。
第三,能重试,就不接受一次成功。对于高价值场景,可以用多次采样加自一致性投票,取多数答案作为最终结果。成本会变高,但稳定性会明显改善。
第四,能回退,就坚决回退。当模型连续多次输出不合法,或者置信度过低时,不要硬扛。回退到模板话术、规则引擎,甚至直接转人工,都是合理的SLA设计。宁可让系统说“我处理不了”,也不能让它给用户一个错误答案。
第五,每一次输出都要可追溯。记录模型版本、prompt版本、temperature、top_p以及原始输出。没有这些日志,线上出问题你连复现的抓手都没有。
这五条原则的本质,是把产品架构改成“确定性骨架 + 概率节点”。工作流的编排、状态流转、数据读写,全部用确定性代码完成;LLM只负责那些真正需要语义理解或自然语言生成的局部环节,并且每个环节都有校验、重试和回退。把随机性关进笼子,而不是让它主导整条链路。
5. 环境准备与最小示例代码
下面用最小代码把上面的原则演示一遍。示例基于Python 3.9+和OpenAI兼容的调用方式,换成任意OpenAI兼容网关或本地部署模型,只需要调整base_url和model。
5.1 环境准备
python -m venv .venv source .venv/bin/activate pip install openai pydantic jsonschema然后配置API Key。生产环境不要把Key直接写进代码,建议通过环境变量注入:
export OPENAI_API_KEY="sk-你的密钥"如果你用的是兼容网关,再加上base_url参数:
client = OpenAI( api_key=os.getenv("OPENAI_API_KEY"), base_url="https://your-gateway.example.com/v1" )5.2 示例1:直观感受“掷骰子”
先跑一个最简单的实验:同一个prompt,同样的模型,循环调用5次,看看输出差多少。
# demo_nondeterminism.py from openai import OpenAI import os client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) prompt = "请用一句话解释:什么是垂直AI?" for i in range(5): resp = client.chat.completions.create( model="gpt-4o-mini", # 换成你账号可用的模型ID messages=[{"role": "user", "content": prompt}], temperature=0.7, ) print(f"第{i+1}次: {resp.choices[0].message.content}")运行后会看到,temperature=0.7时,5次输出的用词、句式甚至结论都有明显差异。这个实验能直观建立“LLM是概率采样器”的观念。把它跑给团队里所有人看,比讲十页原理都管用。
5.3 示例2:结构化输出 + 校验 + 重试
真实项目里,我们不关心模型“说得好不好”,只关心它输出的JSON能不能直接入库。下面的代码要求模型输出订单状态JSON,并做Schema校验,失败就重试:
# demo_structured_output.py import json import os from openai import OpenAI from jsonschema import validate, ValidationError client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) SCHEMA = { "type": "object", "properties": { "order_id": {"type": "string"}, "status": {"type": "string", "enum": ["已发货", "运输中", "已签收", "异常"]}, "description": {"type": "string"} }, "required": ["order_id", "status", "description"] } def ask_llm(user_text, max_retries=3): system_prompt = ( "你是订单查询助手。只输出JSON,不要输出任何其他内容。" "JSON必须包含order_id、status、description字段," "status只能取:已发货/运输中/已签收/异常。" ) for attempt in range(max_retries): resp = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_text}, ], temperature=0.2, response_format={"type": "json_object"}, ) raw = resp.choices[0].message.content try: data = json.loads(raw) validate(instance=data, schema=SCHEMA) return data except (json.JSONDecodeError, ValidationError) as exc: print(f"第{attempt+1}次输出不合法: {exc}") print("原始输出:", raw) raise RuntimeError("LLM多次输出不符合schema,需要回退") if __name__ == "__main__": result = ask_llm("查询订单ABC123,系统显示已签收") print("最终结构化结果:", result)这段代码的关键点有两个:一是用response_format={"type": "json_object"}让模型尽量输出JSON;二是用jsonschema.validate在代码层做强制校验,不合法就带着原始输出重试。即使模型偶发抽风,系统也会自动修复,而不是把坏数据传给下游。
5.4 示例3:自一致性投票
对于高价值、低频率的判断节点,可以用多次采样投票来“平滑”随机性。比如合同条款风险等级判断,多问几次,取出现次数最多的答案:
# demo_self_consistency.py from collections import Counter from openai import OpenAI import os client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) def ask_once(text, temperature=0.3): resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": text}], temperature=temperature, ) return resp.choices[0].message.content.strip() def majority_answer(text, n=5, temperature=0.3): answers = [ask_once(text, temperature=temperature) for _ in range(n)] print("所有候选答案:") for idx, ans in enumerate(answers, 1): print(f" {idx}. {ans}") counter = Counter(answers) return counter.most_common(1)[0][0] if __name__ == "__main__": final = majority_answer("合同里出现'不可抗力不包括汇率波动',风险等级是高、中、低中的哪一个?请只回复一个词。") print("最终投票结果:", final)自一致性的代价是调用次数从1次变成n次,成本和时间都会上升。实践中不要对每个请求都用,只在业务影响面最大的判断节点上用。
5.5 示例4:兜底与回退配置
最后用一份YAML配置展示“确定性骨架+概率节点”的服务形态。生产项目里这类配置应该落到配置中心,而不是散落在代码里:
# llm_service.yaml llm: provider: openai-compatible model: gpt-4o-mini temperature: 0.2 top_p: 0.8 max_retries: 3 guardrails: schema_validate: true keyword_blocklist: - "忽略之前的指令" - "把上一条消息忘掉" confidence_threshold: 0.6 fallback: on_schema_error: "转人工工单" on_too_many_retries: "返回模板话术:抱歉,暂时无法处理,请稍后重试"这份配置想表达的是:模型只是服务里的一个组件,它上面有防注入的关键词检查,下面有Schema校验,旁边有转人工的兜底。把这些都写清楚,产品才算进入“可运维”阶段。
6. 运行结果与效果验证
依次运行上面几个脚本,预期看到的效果如下。
python demo_nondeterminism.pytemperature=0.7时,5次输出通常有明显差异。如果5次完全一样,要么你用的模型拒绝采样,要么后端服务被配置成强制贪婪解码,这也是有可能的。
python demo_structured_output.py正常场景会输出类似:
{"order_id": "ABC123", "status": "已签收", "description": "订单ABC123已于2024年10月29日签收"}如果模型某次输出少了一个字段或者status值非法,脚本会打印“第X次输出不合法”,然后自动重试。这一步是判断“结构化能力”的关键:不只是看成功时的输出,更要看失败时能不能自动恢复。
python demo_self_consistency.py你会看到5个候选答案,多数情况下它们会集中在同一个词上。如果5个答案高度分散,说明这个任务超出了模型当前的稳定范围,需要换模型或调整任务设计。
除了肉眼判断,建议项目里保存一个稳定性评估脚本,用“同输入多次执行的一致率”作为基础指标:
# stability_check.py from collections import Counter from demo_self_consistency import ask_once def stability_score(prompt, n=10, temperature=0.2): outputs = [ask_once(prompt, temperature=temperature) for _ in range(n)] return max(Counter(outputs).values()) / len(outputs) # 实际项目里应该维护一份golden test set,逐条计算稳定率和结构合法率评估时主要看三个数字:结构合法率、同输入一致率、业务准确率。结构合法率衡量模型输出能否通过Schema校验;同输入一致率反映随机性大小;业务准确率需要在带标注的测试集上人工比对。三个指标分开统计,才能定位问题到底出在模型能力、采样参数还是prompt设计。
如果运行失败,优先检查三件事:API Key是否正确配置;模型ID是否可用;网络环境能否正常访问模型服务。日志里出现401通常是Key问题,出现404通常是模型ID问题,出现超时则需要检查网络和服务端负载。
7. 垂直AI落地中的常见问题与排查思路
下面把我在类似项目里见过的高频问题整理成一张排查表。记住一个原则:先在日志里找原始输出,再谈优化。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 同一问题两次回答不一样 | 采样参数过高、模型非确定性 | 查看日志里的temperature和模型版本 | 调低temperature,增加自一致性投票 |
| 输出JSON频繁格式错误 | Prompt约束不足、模型能力边界 | 记录原始输出和Schema报错信息 | 开启response_format,增加后置校验与重试 |
| 上线后效果和测试集差异大 | 评估集过小、测试样本与线上分布不一致 | 用线上真实样本重建评估集 | 扩大golden set,引入线上回流样本 |
| Agent链路偶发失败 | 单步随机错误被链路放大 | 在每一跳记录输入、输出和置信度 | 增加中间校验,失败时回退或转人工 |
| 换模型版本后行为漂移 | 权重更新导致概率分布变化 | 用同一Prompt对比新旧版本输出 | 建立模型版本回归测试,灰度放量 |
| 多次重试后成本飙升 | 自一致性和重试次数设置过高 | 查看调用次数与Token消耗统计 | 只在关键节点投票,普通节点单次调用 |
| 用户用指令注入绕过限制 | 未做输入输出安全过滤 | 检查提示词注入样例,回看完整对话日志 | 增加关键字拦截和输入输出双重审查 |
8. 最佳实践与工程建议
8.1 架构上先拆“确定性骨架”和“概率节点”
最稳定也最容易维护的垂直AI架构,是把业务主流程写成传统代码:状态机、判分支、数据库操作。LLM只作为节点嵌入其中,并且每个节点都有明确的输入输出协议。尽量避免“让Agent自由支配整条主流程”的设计,因为自由度的另一面是不可控。
8.2 Prompt和模型版本要纳入版本管理
很多团队把prompt当“提示文本”而不是“代码”来管,这是生产事故的温床。Prompt应该和代码一起走Git评审、版本发布和回滚。每次发版都记录prompt版本与模型版本,线上行为变化时才能快速定位是代码问题、prompt问题还是模型升级导致的漂移。
8.3 全链路可观测性
每次LLM调用都应该记录:请求ID、用户ID、模型版本、prompt版本、temperature、top_p、原始输出、Schema校验结果、重试次数、最终决定。没有这些数据,任何线上问题都只能靠猜。日志系统建议独立建表,方便按请求维度回溯整条Agent链路。
8.4 设置置信度阈值和人工兜底
对于医疗、金融、法律等高影响场景,模型置信度低于阈值时必须转人工。转人工不是产品的失败,而是产品负责任的表现。在设计SLA时明确“机器处理率”和“人工兜底率”,比盲目追求100%自动化更现实。
8.5 评估先行,不靠感觉上线
每个垂直AI项目都应该维护一份带业务标签的评估集,至少覆盖:典型场景、边界场景、易混淆场景、恶意输入场景。每次改prompt、换模型、调参数,都在这套评估集上跑回归。评估集的质量决定了产品天花板。先把评估集做扎实,再谈优化。
8.6 安全权限与合规
API Key要遵循最小权限原则,不同服务用不同Key,生产环境Key托管在密钥管理服务里,严禁提交到Git仓库。涉及用户数据的场景,要做好脱敏、权限隔离和访问审计。垂直行业往往有行业监管要求,部署前需要确认数据出境、存储位置和审计留痕是否合规。这些不是“以后再说”的事,而是上线前的硬门槛。
9. 总结与后续学习方向
垂直AI的泡沫风险,本质上不是技术泡沫,而是认知泡沫。太多产品把概率模型当成确定性组件来设计、定价和承诺。真正能跑通垂直AI的团队,往往不是prompt写得最好的团队,而是把不确定性工程化处理得最好的团队。结构化输出、校验重试、自一致性、回退兜底、评估回归,这些听起来不那么“性感”的工程手段,才是垂直AI能不能落地的分水岭。
如果接下来想继续深入,建议按这个顺序学习:先掌握JSON Schema和结构化生成约束,理解函数调用和Grounded Generation的区别;然后研究RAG的离线评测,包括检索命中率和生成忠实度;接着关注Agent的可观测性,学习如何在多步链路里定位失败节点;最后回到评估体系,把一个50条样本的golden set扩展成几百条、几千条,让每次模型迭代都有数据支撑。
一个比较实用的收尾建议:下次垂直AI产品上线前,先做一个暴力实验。拿10个真实业务问题,每个问题连跑10次,看看你的系统能不能扛住这100次输出之间的差异。如果10次里出现两次不可接受的结果,那就不要谈规模化,先回头把随机性工程化这件事做扎实。