从提示工程转向 上下文工程,6种让LLM在生产环境中稳定输出的技术

发布时间:2026-02-26 20:59  浏览量:1

RAG系统返回了完美的文本块,提示词写得很漂亮,但LLM还是在产生幻觉;文档加得越多,回复质量反而越差。这些问题问题不出在提示词上,而是出在上下文上。

提示工程告诉模型怎么说话;context engineering 控制模型说话时看到什么。以下是把生产系统和Demo区分开的6种上下文工程技术。

Context engineering 是在运行时决定AI模型看到什么信息、何时看到、以何种结构看到的工程实践。

提示工程关注的是如何提问,context engineering 关注的是提供什么。

实际系统中,大语言模型出错往往不是因为理解不了指令,而是因为看到了太多不相关的信息、相互矛盾的数据,或者缺失了关键事实。Context engineering 把上下文当作一条动态管道来处理,而非一段静态提示词:选对文档而不是全量灌入;将长文档压缩为面向任务的摘要;在检索之前重新表述模糊的用户查询;跨会话注入记忆和用户状态;用实时工具和数据锚定答案;组织所有输入,让模型知道什么最重要。

一句话概括:context engineering 是在生产环境中控制模型注意力的手段。

上下文工程做得好,小模型也能有不错的表现;做得差,最好的模型照样幻觉。

如果我们把50个文档全部塞进上下文指望模型自己找到需要的内容。即便上下文窗口足够大,模型的注意力分布仍然不均匀,它会重点关注开头和结尾的 Token,中间部分被忽略。这就是所谓的"lost in the middle"效应。

正确的做法是评分、重排、裁剪,只让相关且不重复的片段进入上下文窗口。

以下三步逐层过滤。

相关性重排:初始搜索基于向量相似度或关键词匹配返回前50个结果,但相似不等于相关。交叉编码器会把查询和每个文档放在一起联合阅读,重新排序。速度慢一些准确度高得多,重排之后只保留前5个。

冗余消除:同一个概念在文档库里出现在多个地方是常态,比如营销材料、技术规格、FAQ都可能提到同一功能。用 Embedding 对相似文本块做聚类,余弦相似度超过0.9的基本就是重复内容删掉一个即可。模型不需要看同一个事实10遍。

任务感知过滤:利用元数据做筛选。每个文档都应打上标签:文档类型、最后更新日期、产品版本、地区、部门。查询进来时按相关维度过滤。

举个实际例子:查询是"总结最新的退款政策变更"。

过滤之前向量搜索返回50个关于退款的文本块,有些来自2018年旧政策,有些是其他公司的文档,有些是从未面向客户的内部备忘录。LLM看到了14天和30天窗口期的矛盾说法、不同的排除条款、相互冲突的流程,试图综合所有信息后幻觉出了一条根本不存在的政策。

过滤之后加上 region='CN' 和 updated_at >= 2025-01-01 两个条件,立刻排除40个文本块。剩余10个用交叉编码器重排,保留前5个再检查近似重复(余弦相似度 > 0.9)。最终送进上下文的只有3个高相关、无冗余的文本块。

LLM现在看到的是清晰的政策声明、具体的时间线和例外清单——全部最新,全部对齐。没有幻觉,没有混淆。

效果:提示更短,回答更清晰,没有矛盾。实验数据显示,移除噪声上下文后准确率提高15–30%,Token消耗降低20–40%。但真正的收益在于可追溯性——确切知道模型看到了什么上下文,才能调试失败;50个文本块全灌进去,只能靠运气。

实现上可以从简单处着手:给所有文档加 last_updated 时间戳,按日期过滤,仅凭这一步就能消除大部分噪声。接着加重排,再加去重,根据实际效果逐步叠加复杂度。

长篇原始文档容易撑破上下文限制,同时稀释注意力。多个案例研究表明,经过压缩可以在保持甚至提升准确率的同时砍掉50–75%的 Token。

核心思路是在把长文档放进上下文之前,将其压缩成面向当前任务的密集摘要:不是通用摘要,而是针对当前查询定制的摘要。

三种压缩策略各有侧重。

带约束的LLM摘要:不要只说"总结这个文档",而是说"总结这个文档,只保留2025年1月之后关于定价变更的事实"。约束条件指明了保留什么、丢弃什么。每个检索到的文档根据查询生成各自的约束。产出从3000个 Token 缩减到5–10个要点。

句子级评分:用较小的模型(如BERT变体)为每个句子计算与查询的相关性分数,按分数排序后只保留前20%。这种方法叫Context-Preserving Compression,速度快,效果好,自动留下最相关的信息。

层次化摘要:适合非常长的文档。先按章节分块每个章节独立生成摘要,再把这些摘要归纳成一个元摘要。最终形成三级结构,完整文档 → 章节摘要 → 最终摘要。根据上下文预算选用合适的层级。

看一个实际例子。查询是"比较API文档中Plan A与Plan B的速率限制"。

API文档共30页,涵盖认证、速率限制、错误代码、Webhook、分页、SDK和变更日志,其中只有2页涉及速率限制。检索管道取回3个相关章节(共30页),每章10页。LLM摘要器收到的指令是:"仅提取Plan A和Plan B的速率限制和配额,包括具体数字,忽略认证、示例和其他功能。"

第一个章节的摘要(从10页压缩到100个 Token):"Plan A:1000次请求/小时,10,000次/天。Plan B:5000次请求/小时,50,000次/天。两个计划均允许1分钟内20%的突发流量。"

第二个章节的摘要:"速率限制错误返回HTTP 429。Retry-After头部指示等待时间。速率限制在UTC午夜重置。"

第三个章节的摘要:"企业版计划可定制速率限制。请联系销售团队。"

三份摘要合计500个 Token,加上查询一起送入最终生成。模型看到的恰好是它需要的内容,不必在认证流程或SDK示例里翻找。

模型拿到的是聚焦后的信息,而不是期望它从30页文档里正确提取相关部分。

代价是多了一步延迟,因为每个文档额外做一次LLM调用,3个文档就是3次。但最终生成调用中节省的 Token 往往更值钱。具体值不值得要看场景,文档超过2000个 Token 时,压缩的收益大于开销。

不要把所有内容混成一面文字墙。LLM对上下文前部和后部的注意力分配不同,结构化的分区能帮助它区分指令、数据和示例。

想想阅读研究论文的过程:摘要是总结,引言提供背景,方法部分有技术细节,讨论部分解读结果。这种结构让信息提取变得高效,LLM从同样的结构中受益。因此需要在上下文内部设计一套固定的、分区清晰的格式。

以下是一个经过验证的布局:

[System Rules] You are a precise financial research assistant. Answer only from provided context. If information is missing, say "I don't have that information." Never make assumptions about numerical data.[Task] Goal: Answer user question using context below. Output format: Start with direct answer, then provide supporting details. [User Profile / Memory] - Risk tolerance: Low - Investment horizon: 5-10 years - Region: India - Previous sessions: Asked about HDFC Bank 3 times, showed interest in banking sector - Preferences: Conservative investments, dividend-paying stocks [Retrieved Context] DOC 1: HDFC Bank Q4 2024 earnings report - Revenue: ₹45,000 crores (up 15% YoY) - Net profit: ₹12,000 crores (up 18% YoY) - NPA ratio: 1.2% (improved from 1.5%) DOC 2: Competitor analysis Q4 2024 - ICICI Bank revenue growth: 12% YoY - SBI profit growth: 10% YoY - HDFC Bank leading in digital banking adoption [Tool Outputs] - live_price("HDFCBANK"): ₹1,842.50 (updated 2 minutes ago) - news_summary("HDFCBANK"): "Announced dividend of ₹19 per share for FY2024. Ex-dividend date March 15, 2025." - sector_analysis("Banking"): "Banking sector up 8% this month due to positive earnings" [Question] User: What's the latest on HDFC Bank?

系统规则排在最前面,模型在任何其他内容之前先看到它们,用来划定行为边界。任务说明紧随其后,让模型明确目标。用户档案提供个性化信息——模型知道该用户偏好保守投资,之前多次问过HDFC,于是在组织答案时会相应调整侧重点。检索到的文档被标记为源材料,模型将其视为引用依据。工具输出标记为实时数据,意味着当前且权威,不是猜测。问题放在最后,模型在看到要回答什么之前已经掌握了所有上下文。

分区带来的好处很直接:每种信息的角色一目了然,矛盾指令减少,各部分可以独立替换而不破坏整体。在多智能体系统中这一点尤为关键,不同智能体需要不同的上下文布局。

为什么比非结构化上下文更好

可以自己做个测试,把5000 Token 的指令、数据、示例和问题全部混在一起扔给LLM,再用同样的信息以结构化分区呈现。对比准确率,结构化版本在多数领域胜出10–20%。

原因在于注意力模式:LLM在训练中学到了开头和结尾的文本更重要。把关键指令放在开头、问题放在结尾,是在顺应模型的注意力偏好而非与之对抗。

用户提出的问题往往是模糊的:缺少关键词、实体、时间范围。直接拿原始问题去检索效果不好,应该先让LLM重写或扩展查询。

已有研究证实,在检索前生成一条优化过的搜索查询能带来可观的准确率提升。

这看起来有点混乱,因为多加一步似乎应该让事情变复杂,但是其实逻辑很简单:模糊的查询返回模糊的结果,精确的查询返回精确的结果。

三种可行的模式。

澄清优先(适用于智能体场景),与其猜用户什么意思不如直接问。智能体回复:"要比较业绩表现,需要明确几个信息——哪个时间段?包含哪些竞争对手?最关注哪些指标(收入、利润还是市场份额)?"用户给出具体条件后,检索随之变得精确。这种策略只适用于多轮对话的智能体,单次问答系统用不上。但在智能体场景下效果很好——用户并不介意回答两三个澄清问题,如果换来的是更准确的答案。

HyDE(Hypothetical Document Embeddings)用户问"产品最新的改进有哪些",不直接用这个问题去搜索,而是让LLM先生成一段假答案:"最新的产品改进包括重新设计的仪表板、40%的加载速度提升、新增的协作功能和增强的移动端应用。"把这段假答案做 Embedding 后用于检索。为什么有效?因为假答案的措辞和实际文档的措辞一致。文档里不会写"最新的改进是什么",而会写"重新设计了仪表板""加载时间缩短了40%"。假答案的 Embedding 比原始问题的 Embedding 更贴近真实文档。

多查询扩展,对原始查询生成3–5个改写版本。"最新产品改进"可以展开为"最近的产品更新""本季度发布的新功能""产品增强变更日志""2.0版本有什么新内容"。每个查询分别检索,合并去重。这种方式能覆盖语义变体和不同的表述习惯。

举个实际例子。用户问"上季度的表现与竞争对手相比如何"。

这个问题里缺了太多信息:哪些竞争对手?哪一年的上季度?衡量什么指标——收入、利润、市场份额还是用户增长?

LLM将其重写为"比较2024年第四季度(2024年10月至12月)公司X与竞争对手A、B、C在内部财务报告中的收入增长和利润率"。注意具体程度——精确的时间段、精确的竞争对手、精确的指标、明确的数据来源。检索时还会结合记忆中已知的竞争对手名单(层次化布局的用户档案部分记录了该用户之前多次查询过竞争对手A和B)。

最终上下文对齐的是用户的意图,而非用户键入的字面内容。LLM拿到的是精确数据,不是模糊相关的内容。

实现模式如下:

User query ↓ LLM rewriter: "Expand this into a precise search query. Add time ranges, entity names, and specific metrics." ↓ Rewritten query ↓ Retrieval

重写器需要能访问当前日期、用户档案和会话中的先前查询,从而推断出"上季度"对应的具体日期,以及"竞争对手"指的是哪几家公司。

初学者容易在这里混淆两个概念。检索回答的是当前问题;记忆保留的是用户关系。

检索是实时的,每次查询都在全部文档中搜索相关文本块,每次查询都被当作独立事件。今天问HDFC银行,明天再问,检索都是从零开始。

记忆则不同,它记住的是该用户三次问过HDFC银行,偏好保守型股票,住在印度,投资期限5–10年。这些信息跨会话持续存在。

正确的做法是给智能体配备持久化记忆,而不是依赖一条不断增长的历史字符串。把摘要、偏好和关键事实存下来,在每轮对话时重新注入。

三种记忆类型各有分工。

情景记忆,即过去对话的摘要。例如:"上次会话讨论了为法律文档构建RAG系统,用户关心的是如何处理100多页的合同,最终决定采用512 Token 块配合50 Token 重叠的语义分块策略。"注意,这不是完整的对话记录,而是一份200 Token 左右的摘要,只保留了关键内容。

语义记忆,存储在向量数据库中的过去交互记录。例如:"用户3周前问过关于HDFC的类似问题,当时问的是季度收益,当前查询是关于股息公告,两者都涉及HDFC的财务表现,可以复用之前查询中关于公司基本面的上下文。"语义记忆的价值在于发现模式:用户依次问了HDFC的收益、竞争对手、风险因素,可以判断他正在对HDFC做深度研究,从而主动推送相关信息。

偏好记忆,很少变化的稳定事实。"用户是初学投资者""偏好TypeScript""风险承受能力低""在医疗保健领域工作""位于孟买时区"。这些事实影响每一次交互——初学者得到更简洁的解释,低风险用户和高风险用户收到的建议完全不同。

为什么记忆比塞入完整聊天历史更好?

设想一个50轮的对话,轻松超过20,000个 Token。这些内容既塞不进上下文,也不应该塞进去——大部分与当前问题无关。换一种做法:50轮对话压缩为5个情景摘要(1000 Token),提取10个稳定偏好(200 Token),检索与当前问题最相关的3个历史情景(600 Token),合计1800 Token。信息量没有减少,聚焦程度高出一个量级。

只压缩和选取与当前轮次相关的部分,就能避开上下文限制,保持注意力清晰,让智能体在不依赖更大模型的情况下呈现出个性化效果。

落地时有两个执行点。每轮对话结束后做一次LLM摘要——"用户问了什么?提供了什么?用户表达了哪些偏好或需求?"——将摘要与 Embedding 一起存入向量数据库。每轮对话开始前,用当前查询在历史情景摘要库中做向量搜索,取回最相关的3条,再从另一张表加载稳定偏好(这些不需要向量搜索,每次都带上),全部插入层次化布局的 [User Profile / Memory] 区块。

用编程助手的场景来说明。

某用户在构建一个React仪表板,10次会话中先后问过状态管理、API集成、组件组合和测试方面的问题。存入记忆的内容如下:

技术栈:React、TypeScript、Redux、Jest编码风格:偏好函数组件,使用 Hooks,喜欢描述性变量名项目上下文:为医疗保健数据可视化构建仪表板过去的问题:Redux异步操作曾遇到困难,最终用Redux Toolkit解决常见模式:使用自定义 Hooks 发起API调用,偏好Material-UI

当用户提了新问题"仪表板中如何处理实时更新",智能体在检索之前已经从记忆中看到了完整背景:React/TypeScript仪表板、Redux、Hooks偏好、医疗保健数据。于是可以直接给出上下文化的回答:"考虑到现有的Redux架构和对Hooks的偏好,建议使用Redux Toolkit的RTK Query配合WebSocket订阅,与当前模式完全兼容。"

对比一下没有记忆的智能体——它会先问"用什么框架?状态管理是什么?数据类型是什么?"用户已经回答过这些问题10遍了。记忆消除的正是这种重复带来的挫败感。

上下文保持聚焦,智能体行为保持一致,用户感到被理解。

通过Model Context Protocol (MCP)等协议配合函数调用,可以让模型以统一格式看到来自工具、API和数据库的实时数据。不要依赖静态的文本知识。这正是 context engineering 的一部分——改变的是运行时有什么信息进入上下文、以何种形式进入。工具输出作为结构化上下文注入后,幻觉率会大幅下降,答案的时效性也有保障。

纯LLM知识有一个根本问题:它是静态的。训练数据有截止日期,不知道今天的股价、当前天气、最新新闻或内部数据库的状态。工具通过运行时数据弥补了这个缺口,但集成不当的工具会引入新的问题。

MCP风格的工具注册。智能体没有硬编码的工具集成,而是在运行时发现可用工具。智能体发出请求:"什么工具可以解决当前问题?"MCP服务器返回工具描述、输入Schema和能力清单,模型据此决定调用什么。这意味着新增工具无需重新部署智能体——加到MCP服务器上即可,智能体自动发现。生产系统正是靠这种机制扩展到数十乃至上百个工具。

结构化的工具返回值。工具不应返回原始字符串,而应返回带有明确键的JSON:price、date、source、confidence。把这些结果作为层次化布局中的独立区块插入,标记为权威事实。例如,工具返回 {"price": 1842.50, "currency": "INR", "timestamp": "2025-02-19T14:30:00Z", "source": "NSE", "change": "+2.3%"},模型看到的是结构化数据,知道1842.50是价格而不是文本中随意出现的一个数字。

带护栏的回答。对可靠性至关重要。在指令中写明:"只从 [Tool Outputs] 和 [Retrieved Context] 中回答。如果信息缺失,说'当前数据源中没有该信息。'绝不编造数字或事实。"一旦价格工具调用失败,模型会如实说明,而不是自行猜测。

用一个金融助手的场景贯穿整个流程。

查询是"HDFCBANK的最新股价和今天的新闻"。

智能体通过MCP发现可用工具:get_live_price、get_news、get_historical_data、get_competitors、get_analyst_ratings。根据查询判断需要调用 get_live_price 和 get_news,拿到结构化响应:

{ "get_live_price": { "symbol": "HDFCBANK", "price": 1842.50, "currency": "INR", "timestamp": "2025-02-19T14:30:00Z", "change": "+2.3%", "volume": 12500000 }, "get_news": { "articles": [ { "headline": "HDFC Bank Announces ₹19 Dividend", "summary": "Board approves dividend of ₹19 per share for FY2024", "date": "2025-02-19", "source": "Economic Times" } ] } }

这些内容插入层次化布局的 [Tool Outputs] 区块,末尾附上用户问题。模型生成的答案是:"HDFC Bank当前交易价格₹1,842.50,今日上涨2.3%。该银行宣布FY2024每股派息₹19。"

注意模型引用了来自工具输出的具体数字,没有产生幻觉,也没有把股息金额和股价搞混。

这里的关键信息是:提示词本身很简单,真正起作用的是工程化后的运行时上下文。模型表现好,是因为它看到了结构化的权威数据,而不是因为有一段完美的提示词。

实现路径上建议从3–5个关键工具开始,确保它们稳定可靠后再扩展。不要第一天就试图接入50个工具——每个工具都需要错误处理、重试逻辑、超时管理和结果校验,先把基础设施为少数工具搭建好。

选择性检索适用于文档集合庞大(1000+个文档)、检索返回结果过多(20+个文本块)、上下文接近容量上限(32K Token 附近)、或需要控制成本的场景。

压缩适用于文档篇幅长(单个超过5000 Token)、所需信息深埋在文本中、或按 Token 计费且需要成本优化的场景。如果文档本身已经很短(少于1000 Token),压缩带来的开销不划算。

层次化布局适用于多智能体系统(不同智能体需要不同的上下文结构)、多种上下文来源并存(文档、工具、记忆同时出现)、或需要分段调试(结构化分区让定位故障变得容易)的场景。单轮问答且只有一个来源时,布局多半是多余的。

查询重构适用于用户常提出模糊问题(消费类产品中的常见情况)、领域有专业术语但用户不使用(医疗、法律、金融领域)、或查询与文档之间存在词汇鸿沟(用户说"退款",文档写"退货授权")的场景。

记忆适用于对话式智能体(聊天产品、编程助手)、用户跨会话回访(有登录体系的B2B产品)、需要个性化(推荐、偏好起决定性作用)、或对话轮数超过20轮导致历史上下文溢出的场景。

工具感知上下文适用于答案依赖实时数据(股价、天气、库存)、构建的是智能体而非纯对话机器人(智能体会采取行动,聊天机器人只输出文本)、准确性取决于信息时效(不能凭空生成数字)、或需要降低幻觉率(工具提供事实依据)的场景。

每种技术都有代价。重排消耗算力,压缩需要额外的LLM调用,记忆需要存储空间,工具需要API调用。收益是否覆盖成本,需要衡量。一个更简单的管道准确率稍低一点,有时候比一个复杂10倍、成本也高10倍的管道更合适。

by Divy Yadav