ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent Context Server 实战:LLM Agent 上下文管理与工具结果压缩

Agent Context Server 实战:LLM Agent 上下文管理与工具结果压缩 1. 从“报告 A”说起一个被低估的上下文管理命题“报告 A”这个标题乍看之下平平无奇甚至有些过于朴素。但如果你在 LLM Agent 这个圈子里摸爬滚打过一段时间就会意识到真正难啃的骨头往往不是模型本身而是模型周围那一圈看似不起眼的工程设施。Agent Context Server 就是这样一个存在——它不训练模型不调参不搞推理加速但它决定了你的 Agent 到底能不能在真实业务里跑起来、跑得稳、跑得久。我最初接触这个方向是因为一个很具体的问题我们有一个基于 LLM 的自动化报告生成系统内部代号就叫“报告 A”。系统本身不复杂用户输入一个查询意图Agent 会调用若干工具去拉数据、做计算、查知识库最后汇总成一份结构化报告。听起来很标准对吧但上线之后问题接踵而至。最典型的一个场景是当 Agent 连续调用五六个工具之后上下文窗口里塞满了各种工具返回的原始 JSON、日志片段、中间结果模型开始“失忆”——它忘记了最初用户到底问的是什么甚至开始编造一些根本不存在的字段。这就是 Agent Context Server 要解决的核心问题。它不是简单的“把消息拼起来发给模型”而是一套完整的上下文生命周期管理系统。你可以把它理解成 Agent 的“工作记忆管理器”什么信息该保留、什么该压缩、什么该丢弃、什么该以什么格式重新注入全归它管。这篇文章我会围绕“报告 A”这个具体项目把 Agent Context Server 的设计思路、核心机制、实操细节和踩坑经验完整拆一遍。适合正在做 LLM Agent 应用、微服务架构下上下文管理、或者单纯被工具结果爆炸问题折磨的开发者。不管你是刚接触 Agent 开发的新手还是已经踩过几轮坑的老手应该都能从里面找到一些可以直接抄作业的东西。2. 为什么 Agent 需要独立的上下文服务2.1 从“把消息拼起来”到“上下文工程”的认知转变早期做 LLM 应用大家的做法都很朴素维护一个 messages 数组用户说什么就 append 进去模型回什么也 append 进去工具返回什么还是 append 进去。简单直接在 demo 阶段完全够用。但一旦进入生产环境尤其是 Agent 需要多轮工具调用的场景这套做法会迅速崩溃。崩溃的原因不复杂。LLM 的上下文窗口是有限的而 Agent 的工具调用会产生大量“中间态”数据。比如“报告 A”里有一个工具是查询销售数据返回的是一个包含几百行记录的 JSON 数组。这个数组对模型来说真正有用的可能只是其中三五个字段的汇总值但原始数据会占据大量 token。如果连续调用多个类似工具上下文窗口很快就会被这些“噪音”填满。更麻烦的是这些中间态数据不仅浪费 token还会干扰模型的注意力。我实测过一个案例当上下文里存在大量格式相似但语义无关的工具返回结果时模型对用户原始意图的召回率会下降 30% 以上。它会“迷失”在数据里把某个中间结果的字段当成用户的需求。所以 Agent Context Server 的第一个核心职责就是做上下文裁剪与压缩。它不是简单地截断而是要有策略地识别哪些信息是“当前推理步骤必需的”哪些是“可以摘要后保留的”哪些是“用完即弃的”。2.2 微服务架构下的上下文归属问题“报告 A”这个项目还有一个特殊背景它是跑在微服务架构上的。Agent 本身是一个服务工具是另外几个服务知识库又是一个服务。每个服务都有自己的状态和生命周期。这就带来一个很现实的问题上下文到底该由谁来管如果让每个工具服务自己管理上下文那 Agent 核心服务就需要在每次调用时把完整上下文传过去调用完再传回来。这不仅增加了网络开销还会导致上下文版本不一致——工具 A 修改了上下文工具 B 拿到的是旧版本最后合并时冲突。如果让 Agent 核心服务管那它就要承担所有上下文的存储、压缩、检索逻辑变成一个臃肿的单体。而且不同工具对上下文的格式要求可能不同核心服务很难做到通用。Agent Context Server 的思路是把上下文管理抽成一个独立服务。它对外提供标准的上下文读写接口Agent 核心服务和工具服务都通过这个接口来操作上下文。这样做的优势很明显上下文的状态是集中管理的版本一致性有保障压缩和裁剪策略可以统一配置和迭代不同工具可以通过标签或命名空间来隔离自己的上下文区域。这里有一个设计上的关键取舍Context Server 是做成有状态的还是无状态的我的经验是对于“报告 A”这种单次会话生命周期较短的场景有状态服务更合适因为可以避免每次请求都传输完整上下文。但如果是长会话、多用户并发的场景无状态加外部存储如 Redis可能更稳妥。2.3 工具结果压缩不是可选项是必选项在“报告 A”的早期版本里我们没有做工具结果压缩结果就是前面提到的“模型失忆”。后来我们统计了一下在一次典型的报告生成流程中工具返回的原始数据占了总上下文 token 的 70% 以上而其中真正被模型引用的信息不到 15%。这个比例意味着什么意味着你花了大价钱买的上下文窗口大部分被浪费了。而且这种浪费不是“无害”的它会主动降低模型的表现。工具结果压缩的核心思路是在工具返回结果进入上下文之前先经过一层“压缩器”。压缩器可以是基于规则的比如只保留 JSON 中的特定字段也可以是基于模型的比如用一个小模型做摘要还可以是混合的。关键是要根据工具的类型和当前推理阶段来动态选择压缩策略。举个例子“报告 A”里有一个工具是查询日志返回的是原始日志行。对于这种结果我们采用的策略是先用正则提取出关键字段时间戳、错误码、服务名然后按错误码聚合计数最后只把聚合结果和最多三条代表性日志注入上下文。这样原本几千 token 的日志压缩后可能只有一百多 token但信息密度反而更高。3. Agent Context Server 的核心机制拆解3.1 上下文分层模型热数据、温数据、冷数据在“报告 A”的 Context Server 里我们把上下文分成了三层热数据、温数据和冷数据。这个分层不是拍脑袋想的而是根据模型在不同推理阶段对信息的需求频率来划分的。热数据是当前推理步骤直接需要的信息比如用户的最新指令、当前正在调用的工具的输入参数、上一步工具返回的关键结果。这部分数据会完整地放在上下文窗口的最前面或最后面取决于模型对位置敏感度的特性确保模型能直接看到。温数据是最近几轮对话或工具调用的摘要信息。比如用户之前问过什么、Agent 已经完成了哪些步骤、得到了哪些中间结论。这部分数据会被压缩成简短的摘要以“历史回顾”的形式注入上下文。它的作用是帮助模型保持对整体任务进度的感知但不会占用太多 token。冷数据是更早之前的、当前步骤不太可能用到的信息。这部分数据不会直接进入上下文窗口而是存在 Context Server 的存储里。如果模型在推理过程中明确需要比如通过一个“回忆”工具调用才会被检索出来并重新注入。这个分层模型的关键在于动态升降级。随着推理步骤的推进热数据会变成温数据温数据会变成冷数据。Context Server 需要根据当前步骤的上下文使用情况自动调整数据的层级。比如当一个新的工具调用开始时上一个工具的结果就从热数据降级为温数据经过摘要后保留。3.2 工具结果的“压缩-索引-按需展开”流水线工具结果压缩不是简单的一刀切。在“报告 A”里我们设计了一条流水线压缩、索引、按需展开。压缩阶段工具返回的原始结果会经过一个压缩器。压缩器的选择取决于工具的类型。对于结构化数据如 JSON我们用的是字段白名单加聚合对于文本数据如日志、文档我们用的是抽取式摘要加关键词提取对于数值数据我们用的是统计摘要均值、方差、极值等。索引阶段压缩后的结果会被打上标签存入 Context Server 的索引库。标签包括工具名称、调用时间、会话 ID、数据类别等。这样做的目的是为了后续的按需展开——如果模型在后续推理中需要更详细的信息可以通过标签快速检索到原始数据。按需展开阶段当模型明确表示需要某个工具的详细结果时比如它说“我需要查看原始日志”Context Server 会根据索引把对应的原始数据重新注入上下文。这里有一个细节重新注入时我们不会把完整原始数据全部塞进去而是只注入与当前查询相关的部分。比如模型问“有哪些错误码”我们就只注入错误码相关的字段而不是整个日志。实操心得压缩器的选择不要追求“一步到位”。我一开始试图用一个通用的 LLM 摘要器处理所有工具结果结果发现对于结构化数据LLM 摘要反而会丢失关键字段的精确值。后来改成“规则优先、模型兜底”的策略效果好很多。规则处理不了的复杂文本再交给小模型做摘要。3.3 上下文窗口的“预算制”管理LLM 的上下文窗口是有限资源所以 Context Server 必须像管理内存一样管理它。在“报告 A”里我们引入了“预算制”每个会话有一个总的 token 预算每个推理步骤有一个子预算每类数据用户输入、工具结果、历史摘要有一个配额。预算制的核心是优先级抢占。当上下文窗口快满的时候Context Server 会根据优先级决定哪些数据保留、哪些数据压缩、哪些数据丢弃。优先级不是固定的而是根据当前推理步骤动态调整。比如当模型正在处理用户的最新指令时用户输入的优先级最高当模型正在分析工具结果时工具结果的优先级最高。具体实现上我们给每类数据打了一个“重要性分数”分数由几个因素决定数据的新旧程度、与当前步骤的相关性、数据本身的唯一性是否可以被其他数据推导出来。当需要腾出空间时分数最低的数据会被优先压缩或丢弃。这个机制听起来有点复杂但实际效果很好。在没有预算制之前我们经常遇到“上下文溢出”导致请求失败引入预算制之后溢出率降到了 0.5% 以下而且模型的表现反而更稳定了因为它总是能看到当前步骤最需要的信息。4. 在“报告 A”中落地 Context Server 的完整过程4.1 服务拆分与接口设计“报告 A”最初是一个单体服务Agent 逻辑、工具调用、上下文管理全在一个进程里。随着功能增加代码越来越难维护而且上下文管理的逻辑和业务逻辑耦合太深改一处动全身。所以我们决定把 Context Server 拆出来作为一个独立的微服务。拆分的第一步是定义接口。我们设计了几个核心 APIPOST /context/{session_id}/append向指定会话追加一条上下文记录需要指定数据类型用户输入、工具结果、模型输出等和优先级。GET /context/{session_id}/window获取当前会话的上下文窗口内容支持指定最大 token 数和数据类别过滤。POST /context/{session_id}/compress触发一次压缩操作可以指定压缩策略和目标任务。GET /context/{session_id}/recall根据标签或关键词检索历史上下文用于按需展开。接口设计的一个关键点是无状态化请求。虽然 Context Server 本身是有状态的它存储了会话上下文但每个 API 请求都是独立的不依赖前一个请求的状态。这样做的目的是为了方便水平扩展和故障恢复。注意事项接口的 token 计数一定要用和模型一致的 tokenizer。我们一开始用了一个近似的计数方法结果预算制经常算错导致要么浪费空间要么溢出。后来统一用模型官方的 tokenizer 库问题才解决。4.2 压缩策略的配置化实现压缩策略如果硬编码在代码里每次调整都要重新部署效率太低。所以我们在“报告 A”里把压缩策略做成了配置化的。每个工具可以关联一个压缩配置配置里定义了压缩器类型、参数、优先级规则等。比如销售数据查询工具的压缩配置是这样的tool_name: sales_query compressor: structured params: keep_fields: - total_amount - order_count - top_products aggregate: - field: region method: sum target: total_amount max_records: 5 priority: high这个配置的意思是对于销售数据查询结果只保留总金额、订单数、热门产品这几个字段按地区对总金额做求和聚合最多保留 5 条记录。这样原本可能几百行的 JSON压缩后只有几十行。配置化的好处是当业务需求变化时只需要改配置不需要改代码。比如后来业务方要求增加“按渠道统计”的维度我们只需要在配置里加一个聚合规则就行。4.3 与 Agent 核心服务的集成方式Context Server 拆出来之后Agent 核心服务怎么和它集成我们试过两种方式同步调用和异步事件。同步调用就是 Agent 在需要上下文时直接调 Context Server 的 API拿到结果后再继续推理。这种方式简单直接但会增加推理延迟因为每次工具调用前后都要多一次网络往返。异步事件是 Agent 把工具结果发到一个消息队列Context Server 消费消息后异步处理上下文。这种方式延迟低但一致性难保证因为 Agent 可能在 Context Server 还没处理完上一条结果时就开始了下一步推理。最后我们采用的是混合模式对于关键路径上的上下文操作如获取当前窗口用同步调用保证一致性对于非关键路径的操作如历史数据归档、冷数据索引用异步事件降低延迟。这个取舍是根据“报告 A”的实际业务特点做的——报告生成对实时性要求不是特别高但对结果准确性要求很高所以一致性优先。5. 实操中遇到的典型问题与排查记录5.1 工具结果格式不一致导致的压缩失败“报告 A”接入了十几个工具每个工具返回的数据格式都不一样。有的返回标准 JSON有的返回 JSON 字符串有的返回纯文本还有的返回 CSV。压缩器一开始只支持标准 JSON遇到其他格式就直接跳过压缩导致上下文里混入了大量未压缩的原始数据。排查这个问题花了不少时间因为压缩失败是静默的不会报错只是上下文 token 数异常增长。后来我们在 Context Server 里加了一个监控指标压缩前后 token 数的比值。当这个比值超过阈值比如 0.8说明压缩效果很差时就触发告警。解决方案是给压缩器加了一个“格式适配层”。对于 JSON 字符串先解析再压缩对于纯文本走文本压缩器对于 CSV先转成 JSON 再压缩。适配层的逻辑不复杂但需要覆盖所有工具的返回格式。我们的做法是让每个工具在注册时声明自己的返回格式Context Server 根据声明选择适配器。5.2 上下文窗口“抖动”导致模型输出不稳定有一个阶段我们发现同一个查询模型有时候能正确生成报告有时候会漏掉关键字段。排查后发现是上下文窗口的“抖动”导致的由于压缩策略是动态的同一个会话在不同时间点获取到的上下文窗口内容可能略有不同导致模型每次看到的输入有细微差异。这个问题在温度参数较高时尤其明显。解决方案是引入上下文快照机制在一个推理步骤开始时Context Server 会生成当前窗口的快照整个步骤内都使用这个快照直到步骤结束才更新。这样保证了同一步骤内模型看到的上下文是一致的。实操心得如果你的 Agent 输出不稳定先别急着调模型参数检查一下上下文窗口是否在推理过程中发生了变化。这个坑我们踩了很久才发现。5.3 常见问题速查表问题现象可能原因排查方法解决方案上下文 token 数异常增长压缩器未生效或格式不匹配检查压缩前后 token 比值增加格式适配层配置压缩监控模型忘记用户原始意图热数据被过早降级检查上下文分层策略调整优先级规则提高用户输入权重工具调用结果丢失索引标签错误或检索失败检查索引库和检索日志修复标签生成逻辑增加检索重试推理延迟突然增加同步调用过多或压缩耗时过长分析各环节耗时将非关键操作改为异步优化压缩算法多轮对话后上下文溢出预算制未生效或配额不合理检查预算配置和实际使用量调整配额增加自动压缩触发频率6. 一些关于上下文管理的个人体会做“报告 A”这个项目最大的收获是意识到 Agent 的上下文管理远不止“拼消息”那么简单。它更像是一个资源调度问题在有限的窗口里如何让最有价值的信息以最合适的形态呈现给模型。这里面有工程也有艺术。我现在的习惯是每接入一个新工具第一件事不是写调用逻辑而是想清楚它的返回结果该怎么压缩、怎么索引、怎么在需要时展开。这个前置思考花的时间远比后期调试上下文问题要少。另外Context Server 的监控一定要做细。token 数、压缩比、检索命中率、窗口利用率这些指标能帮你提前发现很多问题。我见过太多项目上下文管理是“黑盒”出了问题只能靠猜效率极低。最后分享一个小技巧如果你的 Agent 经常需要处理长文档或大量数据可以试试“渐进式摘要”策略。第一轮先用极简摘要注入上下文如果模型表示需要更多细节再逐层展开。这样既能控制 token 消耗又能保证模型在需要时能拿到足够的信息。
返回列表