上个月有个做内部知识库的客户跑来问我:现在团队天天在用各种Agent写代码、做分析,但他们最头疼的反而不是模型效果差,而是上下文老是不够用。对话稍微长一点,Agent就开始“失忆”,要么答非所问,要么干脆报错。我当时的回答是,别总想着把上下文塞给模型,你得教会 Agent 自己管理上下文。这个思路,后来就落到了 Elastic Agent Builder 这套实践上。
Elastic Agent Builder 是 Elastic 平台里用来构建和托管 AI Agent 的能力,底层衔接了 Elasticsearch 的数据存储与检索、ES|QL 查询,以及外部大模型接口。它的核心价值在于:让 AI 代理不只依赖模型自带的上下文窗口,而是通过一套机制主动决定“什么该记住、什么该查、什么该丢”。这篇博文就完整拆解我们是怎么做到的,包括设计思路、核心配置、System Prompt 的写法、踩过的坑,以及实测数据,适合正在用 RAG 做应用、或者被上下文长度困扰的开发者参考。
1. 整体设计:为什么“上下文管理”不能靠模型自己硬扛
先聊一个很实际的问题:上下文到底是什么?对 AI 应用来说,上下文就是你在一次对话或一次任务里喂给大模型的全部信息,包括用户问题、历史对话、检索到的文档片段、工具返回结果等。大模型能同时处理的信息量是有限的,用行话讲叫“上下文窗口”,比如几千到几十万 token 不等。一旦超出,要么直接报错,要么模型强行截断,结果就是核心信息丢失。
1.1 “无限制上下文”为什么是个伪命题
网上很多人追捧“无限制上下文”或者“超长上下文”,但实际上这是一个工程取舍问题,而不是简单的容量问题。即使模型的上下文窗口能撑到 100 万 token,或者通过某些手段做“上下文压缩”,也不意味着你该把所有东西都塞进去,原因有三点。
第一,成本和延迟会线性上涨。大模型是按照输入 token 数计费的,假设一次查询只用了 5 万 token 的上下文,看起来不多,但如果一个 Agent 一天被调用 1000 次,一个月下来成本就是不可忽视的。第二,信息干扰会变严重。当上下文里混入大量无关文本时,模型反而容易被噪声带偏,专业说法叫“注意力分散”,表现出来就是幻觉变多、回答质量下降。第三,服务端硬限制仍然存在。即便是 Claude、GPT 这些头部模型,API 层面也有明确的 max_tokens 上限,比如 Claude 单次请求超过 200K tokens 就会强制失败。
所以在实际项目里,追求“无限制”没有意义,你真正要解决的是有限的上下文窗口如何装下最关键的信息。
1.2 传统做法有哪些隐藏成本
目前大多数 RAG 应用的上下文管理方式,是外部代码写死逻辑:先把用户问题拿去向量检索,取回 top K 条结果,拼到 prompt 里,再丢给模型。这个方案能跑,但有几个很别扭的地方。
一是路由不灵活。不管用户的意图是什么,检索逻辑永远是“查相似度最高的那几条”,不会区分“这事我可以用工具直接算出来”还是“这事我需要去翻历史工单”。二是历史记忆基本靠堆。很多产品直接把整段对话记录 append 到 prompt 里,没有摘要、没有裁剪,聊到第十轮时前面的信息早就被截断了。三是为了解决这些问题,你可能要额外写一套调度逻辑,比如判断何时该摘要、何时该清空、何时该触发检索。这些逻辑写多了,代码就变成一座维护成本很高的屎山。
那时候我就在想,为什么不把“决定怎么管理上下文”这件事直接交给 Agent 本身?毕竟 Agent 是有推理能力的,它比写死的规则更知道当前这一步需要什么信息、不需要什么信息。
1.3 Elastic Agent Builder 的思路转变
Elastic Agent Builder 在架构上理顺了这条逻辑。它不把 Agent 定义成一个“输入 prompt 就返回结果”的黑盒,而是拆成了“规划 -> 工具调用 -> 检索 -> 再规划”的循环。Agent 内部有一个编排器(Orchestrator),可以感知当前任务状态,决定下一步是调用一个外部工具、发起 ES|QL 数据聚合、还是结束对话并给出答案。
触发检索、触发查询这些动作,是由 Agent 自己判断的,不是外部代码强行塞给它的。这样带来的好处非常明显:上下文里不再需要预置一堆“可能有用”的文档,Agent 只会在真正需要时把相关信息拉进来,用完之后还能主动清理对话历史,只留摘要。这才是“AI 代理自己管理上下文”的真实含义,也是整个项目里体验最明显的变化。
我用一个生活化的类比来解释:传统的 RAG 方案像是你去饭店吃饭,不管你点什么菜,后厨都先把所有食材堆到桌子上,让你自己挑。Agent Builder 的方案则是你点菜,后厨按需配菜,用不到的食材永远待在冰箱里。显然后者更省空间、更准确。
2. 核心机制拆解:Agent 到底怎么“管理”上下文
这个部分我会拆开讲 Elastic Agent Builder 内部的核心机制,不涉及太多源码,重点说清楚设计原理。理解了这些,你写配置和 System Prompt 时才能真正得心应手,而不是套模板。
2.1 工具调用:上下文的最小加载单元
Agent Builder 里的一个核心概念是“工具”或“技能”。一个技能可以被理解成一个独立的动作,比如“运行一次 ES|QL 查询”或者“搜索知识库内的相似文档”。关键的设计在于:技能不是一次性全部加载到上下文里的。在每一轮规划时,Agent 只会选择需要的 1 到 2 个技能,并且只把这次技能调用需要的最小参数带过来。
举个例子,如果用户问“上个月订单失败率最高的三个地区是哪些”,Agent 的规划结果是调用“ES|QL 数据查询”这个技能,那么上下文里加载的只是这次查询的条件和返回结果,历史对话里那些关于登录、权限、测试数据闲聊的内容,都会先被摘出来放到一边。这种方式让上下文的使用效率提升了非常多,实测同等任务下 token 消耗能减少一半以上,原因就是模型每一轮“看到”的内容都更精准了。
2.2 检索时机:由模型意图决定
Agent Builder 的另一个机制是“检索时机控制”。它不是每轮都做向量检索,而是把检索当成一个可选的工具。模型会根据用户问题的意图自主决定是否调用检索。比如用户问的是实时统计数据,Agent 可能会直接选择 ES|QL 聚合而不是向量检索。但如果用户问的是“我们之前有没有处理过类似的网络超时问题”,Agent 就会调用语义检索,在历史工单里找相似记录。
这种“按需检索”的模式有一个很实在的收益:把你每次请求的上下文平均体积降下来了。过去做一个固定的 RAG 流程,一次请求至少带 3 到 5 条文档片段,每条可能 500 到 1000 token,总共就是 3000 到 5000 token。现在 Agent 只有在语义需求明确时才会检索,很多统计类问题甚至完全不检索,直接执行 ES|QL 就返回了,上下文体积可以控制在 1000 token 以内。
2.3 对话历史压缩:智能摘要代替无限堆叠
对话历史的管理,是上下文工程里最容易翻车的一块。简单粗暴地把之前的对话全量附上,越到后面上下文越长,模型越容易迷失重点。Agent Builder 内置一个策略:当对话轮次超过设定阈值时,触发一次“历史摘要动作”。系统会先让模型把前面的关键信息压缩成一段摘要,比如用户需求、已经确认的时间范围、已经排除掉的方案,这部分摘要替换掉原始对话,继续参与后续推理。
这里有个容易被忽略的细节:实现时要处理好“阶段性摘要的累积误差”。也就是说,第一轮压缩成摘要 A,第二轮把摘要 A 和新对话再压缩成摘要 B,如果压缩逻辑设计得不好,信息会被层层丢失。我们在实际方案里做了个改进——摘要生成时,要求模型保留“结构化标签”,比如“用户限制条件”“已确认事实”“待办事项”,并且在新一轮压缩前做一次关键字段比对,确保这些信息没有被遗漏。你如果自己去配 Agent,也可以参考这个思路,关键是不要偷懒只让模型写一段自然语言总结,要给一个固定的摘要模板。
2.4 策略编排:写“规则”让 Agent 执行
上面这些机制听起来很美好,但要是没有好的策略编排,Agent 很容易“自由发挥”得过了头,比如该检索的时候不去检索、不需要工具时硬去调用。所以我们在 System Prompt 里塞入了一套显式规则,告诉 Agent 在什么情况下必须走什么路径。
规则本身并不复杂,重要的是颗粒度要合适。不能太粗,太粗 Agent 抓不住边界;也不能太细,太细会变成“手写规则系统”,失去了 Agent 自主推理的意义。我们最后落地的规则大致如下:
- 用户询问历史工单、历史经验、知识库内容,必须触发语义检索技能;
- 用户询问统计指标、趋势、聚合结果,必须触发 ES|QL 数据查询技能;
- 如果问题能通过数据查询直接回答,则不需要额外检索文档;
- 当对话轮次超过 6 轮,触发历史摘要,并在摘要中保留“用户限制条件”和“未解决问题”;
- 如果检索结果为空,必须明确告知“知识库内未找到相关内容”,而不是自由发挥编答案。
你可以在部署时按需调整这些规则,但核心思想是一致的:Agent 是执行者,但策略的边界该由人来定。上下文管理这件事,本质上就是“在什么时机加载什么信息”,而策略编排决定了这个“时机”是否可靠。
3. 实操过程:从零配好一个会管上下文的 Agent
下面进入完整实操环节。我会按步骤说明整个配置流程,包括环境、数据准备、连接器配置、Agent Builder 创建、System Prompt 写法、技能定义和测试验证。这套流程以 Elastic Cloud 为基础,如果你用的是本地部署,步骤几乎一致,只是连接器地址有些差异。
3.1 环境准备:Elastic Cloud 与基础组件
我这边用的是 Elastic Cloud 最新的版本,版本号不是核心,因为 Agent Builder 能力基本已经进入稳定的通用可用阶段。你需要确保环境中已经启用了以下基础组件:
- Elasticsearch 索引,用于存储知识库文档和对话记录;
- Inference API,用于调用外部大模型接口,比如 OpenAI 兼容接口或者 Claude 接口;
- Connector API,用于连接外部数据源(比如企业内部 Wiki、工单系统)或模型服务;
- Kibana 里的 Search AI Lake 或 AI Assistant 模块,因为 Agent Builder 的图形化配置界面就在这里。
如果你的环境里还没有 Elasticsearch,建议先创建一个最小规模的部署,两节点起步,数据量不大时完全够用。
3.2 准备知识库和索引映射
先准备一份知识库数据。比如我们这里使用了一个“历史工单数据集”,包含工单标题、问题描述、解决方案、发生时间、影响系统等字段。把这些数据导入 Elasticsearch 后,要创建一个给 Agent 用的搜索管道,核心是定义一个向量字段映射,用于后续的语义检索。
你需要确保索引的 mapping 里有类似于下面的字段结构:
{ "mappings": { "properties": { "title": { "type": "text" }, "description": { "type": "text" }, "solution": { "type": "text" }, "created_at": { "type": "date" }, "embedding": { "type": "dense_vector", "dims": 1024, "index": true, "similarity": "cosine" } } } }如果你的 embedding 模型输出维度不同,比如 OpenAI 的 text-embedding-3-small 是 1536 维,把 dims 改成对应值就行。测试阶段数据量少可以用较小维度的模型,跑通流程再切换正式模型。
3.3 新建 Agent 并配置模型连接器
进入 Kibana 的 AI Assistant 或 Search AI Lake 页面,选择 Agent Builder。创建新 Agent 后,会要求你绑定一个模型连接器。这里我们选择 OpenAI 兼容连接器,因为内部已有的模型网关支持该协议。连接器配置有几个关键点:
- URL 填模型服务的 API 地址;
- API Key 填你的密钥;
- Model ID 填你希望 Agent 底层使用的模型名称,比如 gpt-4o-mini 或你自己的微调模型;
- Prompt 里的温度和 max_tokens 可以先保持默认,后面调优再处理。
如果你要用 Claude 系列模型,Elastic 官方也有对应的连接器模板,但需要特别注意:Claude 对 system prompt 的格式要求比较严格,里面不要有多余的 markdown 表格字符,否则容易解析异常。这个坑我下面会细讲。
3.4 设计并写入 System Prompt,这一步最关键
Agent 的“上下文管理性格”,有一大半是 System Prompt 决定的。我和团队在调试过程中,前前后后改了几十个版本,最后沉淀出一套相对通用的写法。这里给出我们的模板,你可以直接抄,但建议结合自己场景微调。
你是企业内部知识助手,负责基于工单知识库和数据分析回答问题。 你拥有两个核心工具: 1. knowledge_base_search:当用户询问历史工单、解决方案、经验总结等内容时,你必须优先调用此工具进行语义检索。如果检索结果为空,直接告诉用户知识库中没有相关内容,不得编造。 2. esql_data_query:当用户询问统计指标、趋势、聚合结果等数据类问题时,你必须调用此工具,使用 ES|QL 查询相关索引,基于真实数据回答问题。 上下文管理规则: - 当对话轮次超过 6 轮时,你需要对前面的对话进行摘要压缩。摘要必须保留以下结构:用户核心需求、已确认的限制条件、未解决的问题、最近一次查询的数据范围。 - 当你准备进行工具调用时,先把与当前任务无关的历史对话暂时隔离,不要携带进工具调用请求中。 - 当工具返回的结果足以回答问题,不要再额外检索知识库。 - 若一个查询可以用 ES|QL 完成,就不要用知识库搜索替代。 - 每次回答尽量控制信息冗余,不输出与问题无关的背景知识。这套 Prompt 的关键不是辞藻华丽,而是把“什么时候调用什么工具”“什么时候压缩历史”“什么时候停止检索”这些上下文管理策略,全部显式地写给了模型。你可以把这段 Prompt 理解成一份给实习生看的操作手册:规则越清晰,执行越稳定。
3.5 定义技能:让 Agent 真正能“动手查”
除了 System Prompt,你还需要在 Agent Builder 里定义技能(Skills)。技能本质上是给 Agent 提供了可执行的工具封装。我们这里定义了两个技能。
第一个技能是知识库检索。Provider 类型选择 Elasticsearch,Index 填工单索引名称,Query 类型选 semantic。在这个技能配置里,有一个“结果数量限制”(size)参数,建议先设为 5,后续根据效果调整。还有一个容易被忽略但重要的设置:最大检索窗口,它控制每一轮检索最多消耗多少 token。默认值可能偏大,我们实际调到了 2000 token,够用且不浪费。
第二个技能是 ES|QL 查询。这个技能里要预设一批常用查询模板,因为 Agent 直接用自然语言生成 ES|QL 是有学习成本的,稍微复杂一点就容易出错。预设模板的好处是:Agent 只需要做“参数填充”和“查询选择”,而不是从零生成语法。我们在模板里预置了按时间范围聚合、按字段分组统计、计算平均值或百分比等常见模式。你别小看这一步,它能极大减少 Agent 因为查询写错而反复重试的问题。
3.6 上下文压缩期的触发条件配置
接下来是动手配置“上下文管理”的关键开关。在 Agent 配置页面,你会看到一个跟对话轮次、记忆策略相关的区域。这里可以设置“历史摘要触发条件”和“历史保留策略”。我们的配置如下:
- 触发轮次阈值:6 轮;
- 摘要间隔:每 3 轮压缩一次;
- 保留策略:保留最近 2 轮完整原文,其余全部替换为摘要;
- 摘要长度上限:500 token。
这个配置的含义是:对话进行到第 6 轮时,模型会把前 4 轮的内容压缩成摘要,保留最近 2 轮完整对话。当新对话达到第 9 轮时,再做一次摘要,把前 7 轮(含第一次摘要)再次压缩,并仍然保留最近 2 轮。这样一来,长对话的上下文体积会被控制在一个相对稳定的范围,不会无限膨胀。
这里有一个值得多说的设计细节:为什么只保留最近 2 轮原文?因为很多问题的语义是跟最近几轮强相关的,如果全部压缩,模型可能理解不了“那按刚才的逻辑再算一遍”。但如果保留太多轮原文,压缩机制就形同虚设了。2 轮是一个经过测试后的折中值,你可以在自己场景里调整,但思路是类似的。
3.7 完整测试:从简单统计到复杂多轮对话
配置完成后,建议做一套覆盖不同场景的测试集,不要只测几个简单问题就上线。我这边一般会准备这样的测试用例:
- 场景一:数据查询。问“今年第三季度各业务线的工单量排名”,验证 ES|QL 技能是否能被正确触发,上下文是否干净;
- 场景二:知识检索。问“我们之前有没有处理过 SSL 连接超时的工单”,验证语义检索是否能召回相关历史记录;
- 场景三:多轮对话。连续问 8 到 10 个问题,观察第 6 轮之后是否自动触发摘要,且摘要后能否继续准确回答;
- 场景四:混合场景。先问统计数据,再接着问历史工单,验证 Agent 是否正确切换技能,且没有把过多无关信息带入第二次检索。
我强烈建议你每组测试都把请求日志打开,逐条看 Agent 决策记录和 token 消耗。不要只看最终回答对不对,要关注“为了回答这个问题,Agent 实际向模型发了多少 token”。这一步是我们优化上下文管理时最重要的数据来源。
4. 常见问题、踩坑与场景扩展
最后这部分,我把实际部署过程中遇到的典型问题整理成一份速查表,并分享几个解决“上下文管理”痛点的诀窍。同时聊聊这套方案可以用在哪些场景,帮助判断它到底适不适合你的项目。
4.1 上下文管理常见问题速查表
| 问题现象 | 可能原因 | 解决思路 |
|---|---|---|
| Agent 长对话后突然丢失关键信息 | 摘要压缩策略过于激进,丢失了核心条件 | 在 System Prompt 中强制摘要保留“用户限制条件”和“未解决问题”字段 |
| Agent 每轮都触发检索,回答很慢 | 检索被当成默认动作,没有按需触发 | 调整 Prompt:非语义需求不检索,统计数据直接走 ES|QL |
| 工具调用返回片段太长,浪费 token | 技能配置中最大检索窗口偏大 | 将 semantic search 的 size 从 5 降到 3,限制最大检索窗口 |
| 模型生成的 ES|QL 查询总是语法报错 | 没有预设查询模板,模型从零生成 SQL/ES|QL | 新增查询模板技能,限定 Agent 只能模板匹配和参数填充 |
| Claude 模型偶尔返回顽固格式错误 | prompt 中包含复杂 markdown 表格字符 | 简化 System Prompt 格式,去掉表格,改用短句 |
| 混合意图场景下判断不准确 | 只依赖模型常识判断,缺少外部约束 | 增加“意图预分类”步骤:先让模型输出 intent 标签再选技能 |
| 长对话 token 消耗仍然只增不减 | 上下文压缩没有按轮次触发 | 检查 Agent 配置,确认摘要触发阈值已生效,查询日志中应有 summary 记录 |
表格里列出的这些场景,都是我们在真实环境中跑出来的问题,不是凭空想象的。其中“摘要丢失关键限制条件”那条最典型,早期版本我们的摘要策略只是让模型“总结前面的对话”,结果模型把所有关于时间范围、业务线范围的信息都丢了,导致后面回答牛头不对马嘴。后来改成结构化摘要模板,问题才缓解。
4.2 独门技巧:在摘要中保留“禁止遗忘”清单
说到这里,额外分享一个小技巧。如果你希望 Agent 在压缩后仍然不能遗忘某些用户侧的关键条件,可以在 System Prompt 的最后追加一段“禁止遗忘清单”,例如:
在整个对话过程中,你必须始终记住以下关键用户条件: - 用户指定的时间范围(比如近 7 天、本月、第三季度等); - 用户指定的数据筛选条件(如特定业务线、特定系统); - 用户明确表达的不需要项(如“不要包含测试数据”)。 即使发生历史摘要压缩,你也要在摘要保留字段中记录这些内容。如果后续回答与这些条件冲突,以用户最新明确条件的表述为准。这是一个低成本但见效极快的技巧。我们测试后发现,加上这段提示之后,Agent 在压缩历史后的回答准确率明显上升,原因是模型在压缩时会把“禁止遗忘清单”当成高优先级任务来对待,而不是凭感觉提取信息。
4.3 适合扩展的场景与落地建议
这套“Agent 自主管理上下文”的思路,并不局限于 Elastic 这一个平台。虽然我手里写的是 Agent Builder 的实操,但底层理念可以平移到任何具备工具调用能力的 Agent 框架上,比如 LangChain、Semantic Kernel 等。你只要把“工具按需加载”“对话历史智能摘要”“意图决定检索时机”这三个机制实现出来,就能在不少场景中看到效果。
比较典型的适用场景包括:
- 企业知识库问答:历史文档、工单、Wiki 混合问答,需要区分查资料和查数据;
- 数据分析助手:业务人员用自然语言查指标、看趋势,不能让 Agent 每次把无关查询条件都塞进上下文;
- 智能客服升级:多轮对话中要保留客户诉求,同时筛选历史解决方案;
- 文档审阅与合规检查:需要读取多种来源的文本,但每个来源只抽取相关片段,而不是整套加载。
如果你准备把这些场景落地,我建议先不要一上来就追求复杂的 Agent 编排能力。先用 Agent Builder 跑通一个最小闭环,比如只配置一个知识库检索技能,然后再增加 ES|QL 查询技能,最后再加入历史摘要压缩。每一步都记录日志、观察上下文变化,会比一次性配全所有功能稳得多。
从我个人实际操作体会来说,Elastic Agent Builder 最有价值的地方,并不在于它提供了多炫酷的 Agent UI,而在于它把“上下文管理”这个原本要靠手工代码维护的工作,变成了有规则、有策略、可观测的系统能力。你不再需要为了一个截断问题反复改提示词拼接逻辑,只需要把策略写清楚,剩下的推理和判断,交给 Agent 自己完成。这个转变带来的效率提升,比单纯换一个更大的模型明显得多,而且成本还更可控。