一次线上事故让我盯上了上下文工程。当时某个智能客服系统上线没两周,用户问“订单超过七天能不能退”,模型回答“根据规则可以办理”,但实际上运费险条款里明确写着“签收七天后不予受理”。客服主管把对话记录甩过来的时候,我第一反应是模型又犯病了——没错,大模型幻觉,一本正经地胡说八道。
后来我在项目里陆续试了提示词加固、检索增强、输出约束、推理引导等一批手段,才慢慢摸清一件事:幻觉不能靠“换个更强的大模型”根除,真正可控的办法是把你给模型的上下文设计好。说得直接一点,大模型的输出质量,很大程度不取决于模型本身,而取决于你喂给它的上下文有多“干净”、多“有结构”、多“能约束它”。这就是上下文工程(Context Engineering)。
这篇文章就把我实际项目里用得最多、见效最快的6大核心技术整理出来,从原理到写法、从参数选择到踩坑记录都讲清楚。适合正在调大模型API的开发者、做AI应用落地的产品技术团队,以及所有被“AI乱编”折磨过的人。内容全部来自实战验证,照着抄能直接用。
1. 先搞清楚幻觉是怎么来的,才能知道上下文工程该往哪儿使劲
1.1 模型的“自信”来自概率,不是来自事实
大模型的本质是一个超大的概率模型,它生成每一个词的时候,都在计算“在已有上下文的情况下,下一个词最可能是什么”。所以当你问它一个它没记住的知识点,它并不会像搜索引擎那样返回“没有结果”,而是会根据训练时见过的相似文本模式,现场编一段看起来很像样的内容。
这个机制决定了幻觉的三个根源:
- 知识截止:模型的训练数据有截止日期,之后发生的事它根本不知道。
- 知识稀疏:某些低频知识在训练集里出现次数太少,模型没有真正记住,只有模糊印象。
- 迎合倾向:模型被对齐训练得过猛,倾向于“用户问什么就答什么”,而不是“我不知道就直说”。
我在实际项目里最头疼的就是第三点。用户问“你确定吗”,很多模型会为了显得靠谱而坚持错误答案,甚至会越编越详细。
1.2 幻觉的四种典型形态
把症状分清楚,才能对症下药。我一般把幻觉分成四类:
- 事实性幻觉:时间、地点、人名、数据出错。“某产品发布于2023年”实际是2024年,这类最常见。
- 逻辑性幻觉:步骤之间自相矛盾,前后对不上。推理类任务里特别多。
- 指令性幻觉:模型没按你的格式要求输出,擅自加内容、换结构。
- 引用性幻觉:最坑的一种,模型会编造参考文献、链接、引文,看起来有鼻子有眼,实际全是假的。
测试下来,纯靠模型自我修正几乎压不住这四类问题。必须从上下文层面入手。
1.3 为什么上下文工程是“性价比最高”的解法
调大模型的传统路子就两条:换更大规模的模型,或者做微调。前者成本高,后者周期长,而且微调对“未知知识”的注入效果并不好——模型该不知道的还是不知道。
上下文工程不一样。它不改变模型参数,只改变模型“看到什么”。你让模型看到的上下文里直接包含正确答案、明确边界和输出要求,它就只能在你的框子里作答,幻觉空间被物理压缩。
打个比方。模型就像一个记性不太好但表现欲很强的实习生。你考他不知道的事,他会编;但如果你把答案写在纸条上放他桌上,再告诉他“只准看纸条,没有纸条就说不知道”,他就算想编也编不出来了。上下文工程干的就是“写纸条、定规矩”这件事。
2. 核心技术一:系统提示词,给模型划定作答边界
2.1 系统提示词为什么是上下文工程的第一道防线
系统提示词是所有上下文中最特殊的一部分。它的优先级高于用户输入,而且通常情况下,模型会把系统提示词当成“自己的行为准则”来执行。
很多开发者的误区是,系统提示词只写一句“你是一个智能助手,请回答用户问题”。这不叫提示词工程,这叫啥也没干。一个合格的系统提示词,至少要承担三个职责:定义身份与职责范围、明确知识与事实边界、规定输出格式与行为约束。
2.2 一个可直接复用的系统提示词模板
我项目里用下来效果最好的结构是“身份定义 + 知识约束 + 行为边界 + 兜底声明”四段式:
你是[某电商平台]的售后政策咨询助手。你的唯一职责是解答关于[订单退款、运费险、售后时效]三个范围内的问题。 知识依据: - 仅允许参考下方[知识库片段]中的内容进行回答。 - 如果[知识库片段]中没有相关信息,必须明确回答“该问题超出我掌握的知识范围,请转人工客服”,禁止自行推断。 行为边界: - 禁止编造订单编号、金额、时间等具体数据。 - 禁止对政策条款做延伸解读。 - 回答时先给出结论,再给出依据,引用条款时说明条款编号。 输出格式: - 结论:一句话概括。 - 依据:引用具体条款原文。 - 建议:基于条款给出的操作建议。 - 若无法回答:直接输出“转人工”话术。这个模板第一次上线就把客服场景的事实性幻觉降低了大约五成。关键点在于“禁止自行推断”和“兜底话术”两条,模型在没知识时会倾向于触发兜底,而不是硬编。
2.3 系统提示词的几个实操坑
- 别把系统提示词写太长。超过300字之后收益递减,核心规则会被稀释,模型反而记不住。真需要长篇背景,放在用户内容里给。
- 否定式指令要写得具体。“不要胡说”这种模糊表述等于没说,模型不知道啥算“胡说”。要写“不要编造订单编号”,具体名词才有约束力。
- 系统提示词也要测回归。改一次提示词,别只测两三条用例就上线。我吃过亏,某次改完客服问答准确率提了,结果转人工率暴涨,用户投诉更多了。提示词的修改要当代码需求一样走测试流程。
3. 核心技术二:少样本示例,用标准答案把模型“教会”
3.1 少样本示例为什么能压住幻觉
系统提示词定规矩,少样本示例给答案。模型基于模式匹配生成内容,给它几个“问题-标准答案”的配对示例,等于直接告诉它:同样类型的问题,我要的是这种质量的答案。
我在总结归纳场景里验证过,加上3到5个少样本示例之后,回答风格的稳定性提升非常明显,而且模型更容易“照葫芦画瓢”。它有示例可抄的时候,倾向于模仿示例的结构和严谨度,而不是自由发挥。
3.2 示例选择的三个原则
少样本不是随便丢几个例子进去就完事。我一般按三个原则挑:
- 同类型:示例必须和用户当前问题属于同一范畴。问的是退货政策,示例就不能是发货时间。
- 覆盖边界:至少要有一个“无法回答”的示例。这一步很多人漏掉,导致模型以为所有问题都必须给出答案,反而诱发编造。
- 对比反差:如果预算允许,给一个“错误示范 + 正确示范”的对比组合,让模型直观看到什么写法是错的。
3.3 一个实际的少样本配置示例
文本分类场景里我是这样配置的:
请根据用户描述,将售后问题分类为:退货退款、换货、物流查询、维修服务、其他。 分类时遵循以下示例: 示例1: 用户:我前天申请退货,今天收到短信说拒收了,凭什么? 分类:退货退款 示例2: 用户:订单显示签收了,但我没收到货。 分类:物流查询 示例3: 用户:这台冰箱的门关不严,能上门修吗? 分类:维修服务 示例4: 用户:我不想聊了,叫你们经理来。 分类:其他 现在请对以下用户描述进行分类: 用户:收到货发现颜色不对,想换一个。这个例子看着简单,实际上手时最大的坑是:示例太少不管用,示例太多有副作用。少于3个示范效果不稳,超过8个,一是浪费token,二是某些模型会因为示例太杂而“不知所从”。5个左右是甜点区间,个别复杂任务可以到7个,再多就要考虑规则引擎了。
4. 核心技术三:RAG检索增强,把模型的知识库“搬”到外部
4.1 RAG的本质:给模型一个可以查询的“外挂记忆”
大模型的知识是从训练语料里学到的,既可能过时,也可能记错。RAG(Retrieval-Augmented Generation)的思路很简单:回答之前,先从外部知识库里检索相关片段,把检索结果拼进上下文,再让模型基于检索内容生成答案。
这套路在知识密集场景里效果最明显。我有一个文档问答项目,用RAG之前,员工手册类问题的正确率只有七成,引入RAG之后九成五以上。核心原因就是:模型不再依赖它记得多少,而是依赖你的知识库里有什么。
4.2 一个最小可用的RAG链路实现
我用一个极简例子说明整条链路。假设我要做一个“公司报销制度问答”,知识库里是几份PDF文档。
第一步:把文档切成片段。切分直接影响检索质量。
from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=80, separators=["\n\n", "\n", "。", "!", "?", ".", " ", ""] ) chunks = text_splitter.split_text(document_text) print(len(chunks))第二步:给片段做向量化,存入向量数据库。切好的片段要被转成向量,之后才能做语义检索。
from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import FAISS embeddings = OpenAIEmbeddings() vectorstore = FAISS.from_texts(chunks, embeddings)第三步:查询时检索相关片段,和问题一起喂给模型。这就是最核心的“检索-增强-生成”闭环:
from langchain.chains import RetrievalQA qa_chain = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", retriever=vectorstore.as_retriever(search_kwargs={"k": 3}) ) answer = qa_chain.run("报销打车费需要什么材料?")4.3 RAG链路里五个关键参数怎么设
- chunk_size(分块大小):我一般从500到800字起步。太小则上下文碎片化,关键信息被切散;太大则检索精度下降,还容易塞进无关内容。具体取值要看你文档的语料结构。
- chunk_overlap(重叠长度):80到120字比较稳,防止关键句正好落在两块的切割缝上。
- top_k(检索条数):先设3,再根据效果调。多了模型容易被无关片段带跑,少了覆盖不住。
- 相似度阈值:低于阈值的片段宁可不要。设了阈值之后,“查不到”的情况会明显减少。
- 检索后重排:简单场景可跳过,但文档多、片段杂的时候,加一层重排能显著提升喂给模型的上下文质量。
4.4 我对RAG的一点批评
RAG不是万能药。很多人以为装好向量库就完事,结果上线后幻觉照样多。我排查过不少项目,发现绝大多数是三个原因:分段切得稀碎,检索召回的内容不对,或者把检索结果一股脑全塞给模型不做筛选。RAG的成败七成在检索阶段,生成阶段只是“照着念”。别只盯着向量库选型,先把切分和召回调明白,效果能翻一倍。
5. 核心技术四:思维链推理,逼模型“先想后答”
5.1 为什么让模型多“想”几步能减少幻觉
大模型的错误常常出在“跳步”。你让它直接给结论,它会跳过推理过程,凭直觉蹦出一个听起来合理的答案。思维链(Chain-of-Thought)的作用就是强制模型把推理过程展开:先理解问题,再列出已知条件,然后逐步推导,最后才下结论。
过程一旦展开,模型的注意力就会被分散到“步骤是否正确”上,而不是执着于“结论是否像样”。实测中,逻辑推理类的题目加了思维链要求之后,正确率能从五成提到八成以上。
5.2 触发链式推理的几种写法
触发思维链不一定非要写“请一步一步思考”,这个说法现在被很多模型的安全对齐拦得很死。我自己试下来有效的写法是:
“在给出最终结论前,先展示你的推理步骤。”“请分步骤分析这个问题。”“把解题过程拆成三步:已知条件、分析过程、最终结论。”“先不要回答,先告诉我你打算怎么分析这个问题。”
以数学题为例:
用户:某商品的进价是120元,加价30%后标价,然后打8折出售,问每件商品的利润率是多少? 请按以下步骤作答: 1. 写出已知条件。 2. 计算标价。 3. 计算折后价。 4. 计算利润率并保留一位小数。模型按步骤走下来,每一步都基于前一步的结果,一旦中间出错你也知道错在哪,方便定位问题。
5.3 思维链不适合用的场景
思维链不是什么时候都好用。两类场景别硬套:
- 简单事实问答:“北京在哪里?”这类问题强行拆步骤,反而增加错误路径。
- 低延迟场景:思维链会显著增加输出token数,响应时间可能翻倍。线上高并发服务要慎用。
另外,思维链输出会暴露模型的“思考过程”,在某些对文本有洁癖的场景(比如生成正式报告)里头,你得把推理过程和最终答案在结构上分开,只抽出结论部分给用户看。
6. 核心技术五:输出约束与结构强制,让模型“没机会乱编”
6.1 结构化输出的核心逻辑
大模型乱编,很多时候不是它不知道答案,而是它“自由发挥”的空间太大。输出约束的思路是:用格式和结构把模型的发挥空间框死,让它只能在固定的字段里填内容。
拿客服工单解析来说。如果你让模型“从对话里提取用户信息和诉求”,它可能输出好几种格式,有的还夹带私货。但如果用JSON Schema约束它,它就只能输出你定好的那几项,多一个字段都不行。
6.2 一个实际的结构化输出配置
在API调用层面,直接用JSON Schema做约束:
{ "name": "order_refund_info", "strict": true, "schema": { "type": "object", "properties": { "order_id": {"type": "string"}, "user_complaint": {"type": "string"}, "decision": { "type": "string", "enum": ["approve_refund", "reject_refund", "need_human_review"] }, "basis": {"type": "string"} }, "required": ["order_id", "user_complaint", "decision", "basis"], "additionalProperties": false } }这套配置跑下来的效果是:字段永远不会缺、不会多,decision只会取枚举值里的三个之一。模型想乱写都没有空间。这就是把上下文工程从“文字层面”推进到“结构层面”的威力。
6.3 输出约束的三个坑
- 别把枚举值写太宽。我见过有人把“退款原因”的枚举写了二十多个值,模型频繁选错。值越少越准,能归为“其他”就归为“其他”。
- 字段描述要详细。JSON Schema里加description字段,告诉模型每个字段到底是啥意思,准确性明显提升。
- 别让结构化输出吃掉系统提示词。有些模型对JSON模式的约束是硬性的,如果和系统提示词里的格式要求冲突,会出现互相打架。系统提示词里的格式规范要跟JSON Schema保持一致。
7. 核心技术六:不确定性声明,允许模型“说不知道”
7.1 “敢于认怂”的模型更可靠
这一条可能是六条里最反直觉的,但效果极其明显。很多人把“模型能回答所有问题”当成好事,实际上,模型在不知道的时候硬答,才是产生幻觉的最大来源。
在上下文里明确允许甚至强制模型表达不确定性,等于给了它一条“合法退路”。它不用为了保住面子而编答案,安全的表达方式意味着更低的幻觉率。我在客服问答场景里加了一条“不确定就转人工”,转人工率只增加了一点,但用户投诉“AI乱说”的比例直接降了一大截。
7.2 在提示词里怎么写不确定性声明
我用过最有效的一套写法:
回答规则: 1. 如果问题在知识库中有明确答案,直接回答并标注依据来源。 2. 如果问题在知识库中没有明确答案,或答案可能受时效性影响,必须回答“该问题涉及的信息可能已变动,建议以官方渠道为准”,不得自行推断。 3. 如果问题包含无法核实的具体数据(如订单金额、库存数量),必须说明“我无法核实该数据”,并引导用户提供截图或订单号。7.3 配合置信度阈值,让模型自查
更进阶的做法是让模型输出一个“置信度”字段,然后你在代码层做拦截。
请回答用户问题并在JSON中返回结果: - confidence:0到1之间的小数,表示你对答案的把握程度。 - answer:你的回答内容。 当confidence低于0.6时,answer字段必须返回“无法确认,需要人工介入”。拿到结构化输出后,代码里做一个简单的阈值判断,低于阈值就走人工兜底流程。这套“模型自查 + 程序拦截”的双保险,是我在金融文档问答项目里压幻觉最有效的手段。
8. 六大技术的组合策略与实战选型
8.1 不同场景该用哪几招
六个技术各有侧重,但真正的实战项目从来不是只用一招。我整理了一张技术选型对照表:
| 场景特征 | 首选组合 | 推荐说明 |
|---|---|---|
| 客服问答(知识密集) | RAG + 系统提示词 + 不确定性声明 | 知识库兜底,边界约束,答不上来就走人工 |
| 信息抽取/工单解析 | 输出约束 + 少样本示例 | JSON Schema框死结构,少样本教会标签映射 |
| 逻辑推理/数学计算 | 思维链 + 结构化输出 | 先展开过程,再收敛到标准格式 |
| 低延迟在线服务 | 系统提示词 + 输出约束 | 避免思维链带来的额外延迟 |
| 开放域写作(低风险) | 少样本示例 + 系统提示词 | 保证风格稳定,不必过度约束 |
8.2 一个完整的多技术组合示例
我的实际项目里做得最多的就是把这几个技术叠在一起用,效果远胜单发。举个例子,一个售后工单自动分类 + 处理建议的系统,完整提示词长这样:
[系统提示词] 你是售后工单处理助手。仅使用以下知识库内容回答。若知识库无相关内容,回复“政策更新中,请转人工”。禁止编造。 [知识库片段] (这里塞入RAG检索回来的3条相关政策原文) [少样本示例] 示例1: 用户:退款怎么还没到账? 意图:退款进度查询 处理建议:查询财务系统退款状态,若超过3个工作日需升级处理 示例2: 用户:我不小心把地址填错了,能改吗? 意图:订单地址变更 处理建议:确认订单是否已发货,未发货可支持修改 [用户问题] 用户:我退货的快递显示签收三天了,怎么还不确认收货? [输出要求] 请按以下JSON格式输出: {"intent": "", "policy_basis": "", "handling_suggestion": "", "confidence": 0.0} 若confidence小于0.6,handling_suggestion必须为“转人工处理”。这个组合里动用了RAG、少样本、系统提示词、结构化输出、置信度兜底五招,剩下思维链没有硬套,因为工单分类不需要多步推理,加了反而拖慢响应。
8.3 怎么评估这六招到底有没有用
调上下文工程,最怕就是“凭感觉说变好了”。我给自己的项目定了三组量化指标,每改一次上下文都对这三项跑回归:
- 准确率:标注一批问答对,人工判断回答是否正确。这是最硬的指标。
- 幻觉率:统计所有错误回答里“编造事实”的比例。这个指标能直接反映上下文工程的效果。
- 兜底触发率:回答“不知道/转人工”的比例。这个值不是越低越好,降得太狠通常意味着模型在硬答。
我个人实操中的感受是,上下文工程做到位以后,幻觉率能压到个位数,但“零幻觉”是不现实的。评估时要给系统留一个容错空间,设定不可答场景的人工兜底链路,比追求一个不切实际的指标更有意义。
9. 上下文工程的调试心法与实操避坑
9.1 我调试上下文的工作流
好用的上下文不是一次写出来的,是测出来的。我调一个项目时通常分四步走:
第一步,先建评测集。挑至少五十条代表性输入,覆盖正常情况、边界情况和完全超出知识库的情况。没有评测集就调上下文,等于闭眼开车。
第二步,单点测试定位问题。逐条看模型输出,判断错误类型是“约束不到位”还是“知识缺失”还是“输出格式跑偏”,对应去改系统提示词、补RAG片段、加JSON约束。
第三步,做组合对比。同一个输入,分别用“只有系统提示词”“系统提示词+RAG”“全套组合”跑一遍,看每一层到底贡献了多少提升。
第四步,灰度回归。上线前用小流量跑一阵,重点关注兜底触发率和人工介入率,确认没有引入新的体验问题。
9.2 高频踩坑清单
- 上下文塞太满。把整本手册都喂进去,模型反而抓不住重点。上下文不是越全越好,是越精准越好。
- 提示词和知识库打架。系统提示词说“必须引用来源”,知识库片段里却没有来源信息,模型就会编一个来源。这种冲突是硬编码的幻觉源。
- 少样本示例带错风格。示例的严谨程度决定了输出的严谨程度。示例里如果有玩笑话,模型也会学去。
- 全依赖模型自查。模型说“我确定”不代表它真确定。置信度字段要用程序二次校验,别把希望全押在模型自己身上。
- 忽略输出解析的失败率。结构化输出偶尔会解析失败,触发重试时如果把同样的错误上下文原样发回去,错得一模一样。重试时要把上一次的失败反馈也放进上下文。
9.3 最后的实战建议
我见过太多团队在微调和换模型上砸资源,却忽略了最简单的上下文调整。实际上,上下文工程是所有大模型落地手段里投入产出比最高的一环。可以先从系统提示词加RAG开始,把流程打通,再加输出约束,最后逐步叠加思维链和置信度兜底。每一层都有可量化的收益,每一层也都有对应的调试方法。
上下文工程不是一个“配一次就完事”的静态工作。知识库要更新,提示词要维护,评测集要扩充,这是一个需要持续运营的工程体系。但它带来的回报是确定的——你不需要换模型,不需要昂贵的微调算力,只靠更好地组织上下文,就能让大模型从“愛說胡話”变得“言之有据、理之有据”。这可能是目前大模型应用落地里最被低估、也最值得花时间深耕的方向之一。