1. 上下文工程:解决大模型幻觉的关键技术
在构建基于大语言模型(LLM)的应用系统时,我们常常遇到一个令人头疼的问题:明明RAG系统返回了完美的文本块,提示词也精心设计过,但模型仍然会产生幻觉(hallucination)。更令人困惑的是,有时候文档加得越多,回复质量反而越差。这些问题往往不是出在提示词上,而是出在上下文管理上。
提示工程(Prompt Engineering)告诉模型"怎么说话",而上下文工程(Context Engineering)则控制模型"说话时看到什么"。这两者相辅相成,但在生产环境中,后者往往是被忽视的关键环节。
1.1 什么是上下文工程
Context Engineering是在运行时决定AI模型看到什么信息、何时看到、以何种结构看到的工程实践。它把上下文当作一条动态管道来处理,而非一段静态提示词:
- 选对文档而不是全量灌入
- 将长文档压缩为面向任务的摘要
- 在检索之前重新表述模糊的用户查询
- 跨会话注入记忆和用户状态
- 用实时工具和数据锚定答案
- 组织所有输入,让模型知道什么最重要
简单来说,上下文工程是在生产环境中控制模型注意力的手段。做得好,小模型也能有不错的表现;做得差,最好的模型照样会产生幻觉。
2. 选择性检索:避免信息过载
2.1 信息过载的危害
如果我们把50个文档全部塞进上下文,指望模型自己找到需要的内容,结果往往会适得其反。即便上下文窗口足够大,模型的注意力分布仍然不均匀——它会重点关注开头和结尾的Token,中间部分被忽略。这种现象被称为"lost in the middle"效应。
正确的做法是通过评分、重排、裁剪,只让相关且不重复的片段进入上下文窗口。
2.2 三步过滤法
2.2.1 相关性重排
初始搜索基于向量相似度或关键词匹配返回前50个结果,但相似不等于相关。交叉编码器(cross-encoder)会把查询和每个文档放在一起联合阅读,重新排序。虽然速度慢一些,但准确度高得多。重排之后只保留前5个最相关的结果。
2.2.2 冗余消除
同一个概念在文档库中出现在多个地方是常态。比如营销材料、技术规格、FAQ都可能提到同一功能。用Embedding对相似文本块做聚类,余弦相似度超过0.9的基本就是重复内容,可以删掉一个。模型不需要看同一个事实10遍。
2.2.3 任务感知过滤
利用元数据做筛选。每个文档都应打上标签:文档类型、最后更新日期、产品版本、地区、部门等。查询进来时按相关维度过滤。
2.3 实际案例
查询:"总结最新的退款政策变更"
过滤前:向量搜索返回50个关于退款的文本块,有些来自2018年旧政策,有些是其他公司的文档,有些是从未面向客户的内部备忘录。LLM看到了14天和30天窗口期的矛盾说法、不同的排除条款、相互冲突的流程,试图综合所有信息后幻觉出了一条根本不存在的政策。
过滤后:加上region='CN'和updated_at >= 2025-01-01两个条件,立即排除40个文本块。剩余10个用交叉编码器重排,保留前5个再检查近似重复(余弦相似度 > 0.9)。最终送进上下文的只有3个高相关、无冗余的文本块。
效果:提示更短,回答更清晰,没有矛盾。实验数据显示,移除噪声上下文后准确率提高15-30%,Token消耗降低20-40%。真正的收益在于可追溯性——确切知道模型看到了什么上下文,才能调试失败;50个文本块全灌进去,只能靠运气。
3. 上下文压缩:让每个Token都有价值
3.1 长文档的问题
长篇原始文档容易撑破上下文限制,同时稀释注意力。多个案例研究表明,经过压缩可以在保持甚至提升准确率的同时砍掉50-75%的Token。
核心思路是在把长文档放进上下文之前,将其压缩成面向当前任务的密集摘要:不是通用摘要,而是针对当前查询定制的摘要。
3.2 三种压缩策略
3.2.1 带约束的LLM摘要
不要只说"总结这个文档",而是说"总结这个文档,只保留2025年1月之后关于定价变更的事实"。约束条件指明了保留什么、丢弃什么。每个检索到的文档根据查询生成各自的约束。产出从3000个Token缩减到5-10个要点。
3.2.2 句子级评分
用较小的模型(如BERT变体)为每个句子计算与查询的相关性分数,按分数排序后只保留前20%。这种方法叫Context-Preserving Compression,速度快,效果好,自动留下最相关的信息。
3.2.3 层次化摘要
适合非常长的文档。先按章节分块,每个章节独立生成摘要,再把这些摘要归纳成一个元摘要。最终形成三级结构:完整文档 → 章节摘要 → 最终摘要。根据上下文预算选用合适的层级。
3.3 实际案例
查询:"比较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示例里翻找。
代价是多了一步延迟,因为每个文档额外做一次LLM调用,3个文档就是3次。但最终生成调用中节省的Token往往更值钱。具体值不值得要看场景,文档超过2000个Token时,压缩的收益大于开销。
4. 层次化布局:结构传达重要性
4.1 结构化分区的价值
不要把所有内容混成一面文字墙。LLM对上下文前部和后部的注意力分配不同,结构化的分区能帮助它区分指令、数据和示例。
研究论文的结构就是一个很好的参考:摘要是总结,引言提供背景,方法部分有技术细节,讨论部分解读结果。这种结构让信息提取变得高效,LLM从同样的结构中受益。
4.2 经过验证的布局
code复制[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?
系统规则排在最前面,模型在任何其他内容之前先看到它们,用来划定行为边界。任务说明紧随其后,让模型明确目标。用户档案提供个性化信息。检索到的文档被标记为源材料,模型将其视为引用依据。工具输出标记为实时数据,意味着当前且权威。问题放在最后,模型在看到要回答什么之前已经掌握了所有上下文。
分区带来的好处很直接:每种信息的角色一目了然,矛盾指令减少,各部分可以独立替换而不破坏整体。在多智能体系统中这一点尤为关键,不同智能体需要不同的上下文布局。
5. 动态查询重构:修复模糊问题
5.1 模糊查询的问题
用户提出的问题往往是模糊的:缺少关键词、实体、时间范围。直接拿原始问题去检索效果不好,应该先让LLM重写或扩展查询。
已有研究证实,在检索前生成一条优化过的搜索查询能带来可观的准确率提升。
5.2 三种重构模式
5.2.1 澄清优先(适用于智能体场景)
与其猜用户什么意思不如直接问。智能体回复:"要比较业绩表现,需要明确几个信息——哪个时间段?包含哪些竞争对手?最关注哪些指标(收入、利润还是市场份额)?"用户给出具体条件后,检索随之变得精确。
5.2.2 HyDE(Hypothetical Document Embeddings)
用户问"产品最新的改进有哪些",不直接用这个问题去搜索,而是让LLM先生成一段假答案:"最新的产品改进包括重新设计的仪表板、40%的加载速度提升、新增的协作功能和增强的移动端应用。"把这段假答案做Embedding后用于检索。
5.2.3 多查询扩展
对原始查询生成3-5个改写版本。"最新产品改进"可以展开为"最近的产品更新""本季度发布的新功能""产品增强变更日志""2.0版本有什么新内容"。每个查询分别检索,合并去重。
5.3 实际案例
用户问"上季度的表现与竞争对手相比如何"。
LLM将其重写为"比较2024年第四季度(2024年10月至12月)公司X与竞争对手A、B、C在内部财务报告中的收入增长和利润率"。注意具体程度——精确的时间段、精确的竞争对手、精确的指标、明确的数据来源。
实现模式:
code复制User query
↓
LLM rewriter: "Expand this into a precise search query.
Add time ranges, entity names, and specific metrics."
↓
Rewritten query
↓
Retrieval
6. 记忆与状态:保留关系而不仅是事实
6.1 记忆与检索的区别
检索回答的是当前问题;记忆保留的是用户关系。检索是实时的,每次查询都在全部文档中搜索相关文本块。记忆则不同,它记住的是该用户三次问过HDFC银行,偏好保守型股票,住在印度,投资期限5-10年。这些信息跨会话持续存在。
6.2 三种记忆类型
6.2.1 情景记忆
即过去对话的摘要。例如:"上次会话讨论了为法律文档构建RAG系统,用户关心的是如何处理100多页的合同,最终决定采用512 Token块配合50 Token重叠的语义分块策略。"
6.2.2 语义记忆
存储在向量数据库中的过去交互记录。例如:"用户3周前问过关于HDFC的类似问题,当时问的是季度收益,当前查询是关于股息公告,两者都涉及HDFC的财务表现,可以复用之前查询中关于公司基本面的上下文。"
6.2.3 偏好记忆
很少变化的稳定事实。"用户是初学投资者""偏好TypeScript""风险承受能力低""在医疗保健领域工作""位于孟买时区"。
6.3 实现方式
每轮对话结束后做一次LLM摘要,将摘要与Embedding一起存入向量数据库。每轮对话开始前,用当前查询在历史情景摘要库中做向量搜索,取回最相关的3条,再从另一张表加载稳定偏好,全部插入层次化布局的[User Profile / Memory]区块。
7. 工具感知上下文:锚定答案在现实中
7.1 工具的价值
通过Model Context Protocol (MCP)等协议配合函数调用,可以让模型以统一格式看到来自工具、API和数据库的实时数据。不要依赖静态的文本知识。
7.2 实现要点
7.2.1 MCP风格的工具注册
智能体没有硬编码的工具集成,而是在运行时发现可用工具。智能体发出请求:"什么工具可以解决当前问题?"MCP服务器返回工具描述、输入Schema和能力清单。
7.2.2 结构化的工具返回值
工具不应返回原始字符串,而应返回带有明确键的JSON:price、date、source、confidence。把这些结果作为层次化布局中的独立区块插入,标记为权威事实。
7.2.3 带护栏的回答
在指令中写明:"只从[Tool Outputs]和[Retrieved Context]中回答。如果信息缺失,说'当前数据源中没有该信息。'绝不编造数字或事实。"
7.3 实际案例
查询:"HDFCBANK的最新股价和今天的新闻"
智能体通过MCP发现可用工具:get_live_price、get_news、get_historical_data、get_competitors、get_analyst_ratings。根据查询判断需要调用get_live_price和get_news,拿到结构化响应:
json复制{
"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。"
8. 技术选型指南
8.1 选择性检索适用场景
- 文档集合庞大(1000+个文档)
- 检索返回结果过多(20+个文本块)
- 上下文接近容量上限(32K Token附近)
- 需要控制成本的场景
8.2 压缩适用场景
- 文档篇幅长(单个超过5000 Token)
- 所需信息深埋在文本中
- 按Token计费且需要成本优化
- 文档本身已经很短(少于1000 Token)时不划算
8.3 层次化布局适用场景
- 多智能体系统
- 多种上下文来源并存(文档、工具、记忆同时出现)
- 需要分段调试
- 单轮问答且只有一个来源时可能多余
8.4 查询重构适用场景
- 用户常提出模糊问题
- 领域有专业术语但用户不使用
- 查询与文档之间存在词汇鸿沟
8.5 记忆适用场景
- 对话式智能体
- 用户跨会话回访
- 需要个性化
- 对话轮数超过20轮导致历史上下文溢出
8.6 工具感知上下文适用场景
- 答案依赖实时数据
- 构建的是智能体而非纯对话机器人
- 准确性取决于信息时效
- 需要降低幻觉率
9. 实施建议
每种技术都有代价。重排消耗算力,压缩需要额外的LLM调用,记忆需要存储空间,工具需要API调用。收益是否覆盖成本,需要衡量。一个更简单的管道准确率稍低一点,有时候比一个复杂10倍、成本也高10倍的管道更合适。
建议从以下几个方面入手:
-
从简单开始:先给所有文档加
last_updated时间戳,按日期过滤,仅凭这一步就能消除大部分噪声。 -
逐步叠加复杂度:先加重排,再去重,根据实际效果逐步叠加复杂度。
-
工具集成:从3-5个关键工具开始,确保它们稳定可靠后再扩展。
-
记忆系统:先实现基本的用户偏好记忆,再逐步增加情景记忆和语义记忆。
-
监控与优化:持续监控系统表现,根据实际效果调整各环节的参数和策略。
上下文工程不是一蹴而就的工作,而是一个持续优化的过程。通过系统地应用这些技术,可以显著提升大语言模型在生产环境中的表现,减少幻觉,提高回答的准确性和可靠性。