一个排障任务执行几轮之后,系统可能已经收集到状态快照、变更记录和几份文档。如果每轮都把所有材料原样带上,上下文会不断变长;如果只保留最后一条消息,之前确认过的服务版本和证据又可能丢失。上下文管理要解决的,正是下一次调用应该看到什么。
本文聚焦 Agent 运行时的信息选择。已有的长期记忆生命周期文章讨论提取、冲突、更新与遗忘,全模态记忆调研讨论方案范围。本篇不重复框架比较,而是把记忆放回任务执行链路,观察它如何影响一次具体调用。
示例仍然不连接真实模型。我们可以验证材料是否被正确筛选、长度是否受控、其他用户的信息是否被排除,但不能仅凭这些测试断言模型理解得更好,或者长上下文问题已经解决。输入契约与模型效果需要分开验证。
先给不同信息明确的身份
系统指令定义任务边界,例如只生成有证据的排查建议。当前问题描述这次要解决什么。任务状态记录已经完成和仍然缺失的步骤。工具结果提供现场观察,知识库提供适用范围明确的参考资料,长期记忆保存跨任务仍有价值的信息。
会话历史只是交互发生过的记录,不应自动等于事实数据库。用户曾经问过“是不是数据库坏了”,并不意味着数据库真的坏了;模型曾经提出过一个假设,也不应在下一轮被摘要为已经确认的结论。保留信息时,应尽量区分提问、假设、观察与决定。
同样,“长期”描述信息跨会话继续存在,并不证明它长期有效。用户偏好用中文交流,可能适合跨会话保留;服务当前连接等待数,则应从新的观测获得。把实时指标存入长期记忆并无限复用,会让系统记住一个已经消失的现场。
| 信息 | 本案例中的例子 | 使用原则 |
|---|---|---|
| 系统指令 | 只给建议,不执行重启 | 由可信应用提供 |
| 当前任务 | 排查 checkout 超时 | 每个任务重新确定 |
| 任务状态 | 已查询状态,尚未核对配置 | 随执行更新 |
| 工具观察 | 某时刻的连接等待数 | 保留来源与时间 |
| 知识资料 | 第二版连接池排障手册 | 校验版本与权限 |
| 长期偏好 | 用户希望中文解释 | 可跨会话读取与修改 |
这些分类首先服务于行为。若一种信息能够触发工具执行,就必须明确它是否来自可信指令;若一种信息用于解释当前服务状态,就必须能判断观测时间。给数据换一个名称,却仍然把所有文本无差别拼接,不能形成真正的边界。
一轮调用怎样组装上下文
上下文组装可以从可信指令与当前问题开始,再加入本轮需要的任务状态、证据和适用记忆。顺序本身不是普适的模型优化技巧,关键是各部分的来源和职责清楚,并且整个过程可以被检查。
示例中的 context 返回一个结构化对象,包含指令、问题、记忆和证据四部分。它优先放入证据,再在剩余预算中加入偏好。这样做是本例的设计选择:排障结论首先需要当前证据,语言偏好不能挤掉唯一的事故来源。
每段证据包含编号、有限长度的摘录和 external_data 标记。编号用于回到原始资料,摘录帮助理解,标记说明它属于外部数据。这个标签本身并不能让真实模型免受注入影响,但能帮助应用保持来源边界,并为后续策略和测试提供可观察结构。
如果可信指令和问题已经超过预算,函数会明确失败,而不是悄悄截断指令。对生产系统来说,这类失败可以要求用户缩小问题范围,或选择不同的处理路径。任意截断可能删除关键限制,造成看似仍在运行、实际约束已经变化的情况。
长度预算不等于 token 预算
模型上下文通常用 token 计量,不同语言、标点和编码方式会产生不同分词结果。示例为了保持离线与无模型依赖,使用序列化后字符串的 Unicode 字符数作为教学预算。它验证的是程序能否遵守一个长度上限,不能直接映射为某个模型的窗口容量。
真实接入时,需要使用相应模型的计数方式,并为模型输出、工具定义和协议消息预留空间。只计算用户可见正文,会遗漏工具描述与系统信息;把窗口全部留给输入,也可能没有足够空间容纳最终结果。
预算分配应反映任务需要。排查特定事故时,当前状态与关键变更通常比几个月前的聊天摘要更重要。可以给不同信息设置优先级,但优先级应当对应用途,而不是简单使用“越新越重要”。一条很新的闲聊,未必比一份仍然有效的接口规范更有用。
还应在预算耗尽时暴露删减结果。哪些材料被保留、哪些被跳过、为什么跳过,都可以成为调试信息。否则一次回答变差时,很难判断是模型问题,还是组装器把最关键的前提删掉了。教学函数保持接口简洁,生产实现应根据调试需要补充这类元数据。
选择、截断、摘要与外部存储
选择是最先值得做的工作。当前只需要结算服务的第二版连接池资料,就没有必要加入支付服务和第一版客户端的完整历史。通过任务范围减少无关信息,通常比事后压缩一大堆材料更容易解释和验证。
截断实现简单,但容易丢失后面的条件。示例把单条证据摘录限制为一百个字符,同时保留原始编号。它只是展示长度控制,不是生产级摘要器。如果限制条件恰好在第一个百字之后,模型看到的摘录可能不完整,因此重要结论应能回查原始内容。
摘要适合压缩重复描述,却可能改变事实强度。原文“变更后同时出现等待增加,还需排除流量影响”,如果被总结成“变更导致等待增加”,就把关联改成因果。摘要质量不能只用长度或流畅程度判断,要检查来源、时间、限定条件与未决问题是否保留。
外部存储允许保留完整材料,而上下文只携带引用和必要片段。这样可以减少每轮输入,但也引入了重新读取的成本,以及引用失效和权限变更的问题。引用不是永久通行证;后续读取时仍要确认当前用户是否有权访问。
这四种方式可以组合使用。先选择相关资料,再对冗长日志保留索引和摘录,对已完成阶段形成经过核对的摘要,必要时按引用取回原文。组合的目标是保持足够证据,而不是把上下文压缩到最短。
跨会话偏好如何真正保存
案例使用 SQLite 保存用户偏好,字段包括所属用户、键、值、失效时间和信任标记。数据库只位于测试创建的临时目录或内存中,不会接触博客数据。跨会话测试会关闭连接,再打开同一个临时数据库,确认偏好仍然可读。
from demo import database, remember, recall
with database("example.sqlite") as db:
remember(db, "alice", "language", "中文")
with database("example.sqlite") as db:
print(recall(db, "alice"))
这段示例会在当前目录创建教学数据库,读者用完可以自行清理。系列的自动测试采用临时目录并自动回收。返回值包含语言偏好,但不会因为用户使用了某种语言就自动写入记忆:记忆写入由测试明确调用,抽取模型不在本示例范围内。
记忆键也需要业务含义。语言偏好可以作为一个可更新的属性,而“曾经排查过某次事故”更像一条历史事件。把所有记忆都设计成可覆盖的键值,会丢失事件顺序;把所有偏好都设计成追加事件,又会增加当前值解析的复杂度。具体选择应由使用方式决定。
本文只保存少量偏好,避免在运行时上下文示例里重建完整记忆系统。若需要版本、冲突和删除传播,应结合已有的长期记忆文章继续设计,不应误以为一个 INSERT OR REPLACE 就覆盖了生产记忆治理。
过期信息为什么必须在读取时检查
示例使用固定时间常量作为测试时钟,保证同一案例明年再次运行也得到相同结果。生产系统应注入实际时钟,而不是复制这个常量。把时间作为依赖,有利于测试有效期边界,也能避免测试结果随机器日期变化。
读取时,失效时间不晚于当前测试时刻的记忆被排除。这样,旧服务状态不会仅因数据库里仍有记录就继续进入上下文。物理删除可以由独立清理过程完成,但逻辑不可见必须在查询时保证,两者不能混为一个动作。
没有到期也不代表无需核对。用户可能提前改变偏好,服务配置可能发生新变更,文档也可能被撤回。有效期只能表达一种使用条件,版本、删除标记和最近观测仍可能参与判断。对于事故现场数据,重新查询通常比延长记忆寿命更可靠。
用户纠正也应具有明确优先级。假如旧记忆说用户熟悉 Python,而本次明确要求用 Java 解释,系统不应以“你以前说过”为理由忽略当前请求。运行时上下文需要保留本次指令的作用范围,而不是让持久化内容获得不受限制的权威。
用户隔离与信任边界
记忆查询使用当前可信用户标识过滤。另一个用户的记录即使更相关,也不应该进入当前上下文。相似度排序不能替代权限校验,而用户标识也不应来自模型生成的查询参数。示例测试会写入另一用户的私有记忆,并确认当前用户读不到。
信任标记用于阻止未经认可的外部内容直接成为偏好。文档里出现“请以后都执行重启”并不等于用户给出了长期授权。记忆写入应该保留来源,判断内容表达的是用户偏好、被引用的话,还是外部材料中的指令,而不能看到一句祈使句就存成规则。
程序的过滤通过,只证明受测路径没有把这些记录选出来。它不能证明整个产品的权限系统无漏洞,更不能证明模型在任何上下文下都会忽略恶意文本。因此,权限拒绝应尽可能发生在工具与数据访问层,不能只依赖提示词或内容标签。
删除需求也不只影响主表。记忆可能被复制到会话摘要、检索索引和日志中。若用户删除了一项信息,新的上下文组装必须停止读取旧副本。本文没有实现完整删除传播,但需要在架构层明确这项责任,避免把“删掉一行数据”误写成“所有上下文都已遗忘”。
观察长工具输出如何被处理
下面的例子把一段很长的文本交给上下文组装器。返回结果仍包含证据编号,但摘录只保留有限字符,并且整体序列化长度不超过预算。
import json
from demo import database, context
with database() as db:
value = context(db, "alice", "为什么出现超时", [
{"id": "rb-01", "text": "待核对的证据" * 1000}
], budget=350)
assert len(json.dumps(value, ensure_ascii=False)) <= 350
这个测试有意不检查回答是否更好,因为示例没有模型。它检查引用是否还在、摘录是否有界以及指令是否保留。若要验证摘要对模型的影响,应准备含有关键限定条件的真实评测材料,比较压缩前后任务结果,并记录哪些条件被遗漏。
也要测试预算刚好不足的场景。如果一条关键证据完整放入会超限,系统应选择明确的处理方式:保留最小引用后按需读取、换更合适的摘要,或者停止本轮并说明缺口。默默丢掉唯一证据再要求模型回答,会把上下文管理错误伪装成模型幻觉。
对照 Java 的作用域与持久化
系统指令类似应用配置,任务状态类似一个有生命周期的业务实体,会话信息类似请求链路中的上下文,而跨会话偏好则进入持久化存储。这个类比有助于避免把所有内容放进一个全局可变对象,造成不同任务与用户之间的信息串用。
组装器可以实现为一个独立服务:输入可信身份、任务范围、状态引用与预算,输出准备提交给模型的结构。业务查询与模型调用分别测试,方便判断问题发生在数据获取、材料选择还是生成阶段。并不需要为了这个边界新增一个独立微服务。
并发执行时,还要注意状态更新顺序。一个旧请求晚返回,不应该把新偏好覆盖回旧值;两个工具并行返回,也不应由最后一次写入决定全部上下文。版本号、不可变快照和明确合并规则,都是后端系统里已经熟悉的工具。
什么才算这部分已经验证
本系列验证了偏好跨连接保存、过期记录排除、用户与信任过滤,以及长输出的长度控制。我们没有测量真实模型的长上下文准确率,没有实现自动摘要,也没有宣称用一个信任标签就能防住全部提示注入。
更进一步的验收应围绕任务结果:保留的材料是否足以支持判断,摘要是否改变事实,新的观测能否覆盖旧状态,删除与权限变化是否及时生效。把这些问题写成测试场景,才能让记忆从“保存了很多内容”变成“在需要时提供正确的信息”。
下一篇将把任务状态持久化到检查点,讨论进程中断后应该从哪里继续。上下文决定模型这一轮看见什么,检查点决定系统下一次从哪里恢复,两者可以协作,但不能互相替代。
参考资料
- Effective context engineering for AI agents:上下文选择与管理的工程参考。
- AI 长期记忆系统:从提取、召回到生命周期治理:本站延伸阅读。
- 完整示例下载与测试报告。