1. 多轮失忆:编码 Agent 的 “中年危机”
在 IDE 里跟 Agent 结对编程时,你大概率遇到过这样的循环:
- 第 1 轮:"把订单状态机改成可回退"—— 它改得挺好;
- 第 3 轮:"再加一个取消事件"—— 它却忘掉回退逻辑,把代码写回第一版;
- 第 5 轮:"记得兼容旧表字段"—— 它干脆把前面写的回退又删了。
根源不是模型笨,而是业务上下文被挤出窗口。编码任务往往伴随超长文件路径、字段语义、约束条件,这些 token 优先级低,最先被丢弃。结果 Agent 只能凭 “模糊印象” 继续写, hallucination 直线上升。
2. 实时注入的三类工程模式
要让 Agent “不会丢”,关键是在对话生命周期内,把最相关的业务语义重新塞到上下文头部。社区已有三种成熟模式可直接套用:
2.1 框架钩子:Microsoft Agent Framework 的 AIContextProvider
核心钩子 InvokingAsync 在每次 LLM 调用前执行,可把检索结果作为系统消息注入,与推理逻辑解耦。实现步骤:
- 继承
AIContextProvider,在InvokingAsync里把向量库召回的 3–5 条规则格式化为SystemMessage; - 把该 Provider 注册到 Agent 启动器;
- 设置
add_state_in_messages=True,确保注入内容出现在对话历史最顶部。
优点:对业务代码零侵入;缺点:需要框架支持,适合新项目。
2.2 动态工具:Cursor 的 “一次性小工具”
当上下文需要运行时状态(如日志、变量值、数据库行)时,让 Agent 先写一段调试脚本并立即执行,把 stdout 结果当新上下文回写。流程:
- Agent 生成
inject_context.py:print(f"__order_status__={db.get_order_status(order_id)}") - 人类确认后运行,捕获输出;
- 把输出追加到下一轮
user message顶部。
该模式适合动态业务值无法提前落库的场景,且人类 in the loop 天然过滤恶意指令。
2.3 内存晋升:Memori 的 “conscious → auto” 双通道
Memori 把 SQLite 当记忆体,先离线晋升再在线追加:
- 会话启动时,Conscious Agent 把长期记忆里 5–10 条 “最相关” 记忆晋升到短时记忆,一次性注入;
- 后续每轮 LLM 调用,Retrieval Agent 再按需从全库搜 3–5 条追加。
晋升标准可直接照抄:
- 与当前仓库路径重合度 >0.8;
- 出现过 “错误”“必须”“不能” 等强约束词;
- 最近 7 天内有更新。
该模式不依赖具体框架,只要一个 SQLite 文件 + 召回函数即可落地。
3. 可落地的 5 组参数与 2 条红线
把上述模式组合进 CI 时,先定死以下数值,不再拍脑袋:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 短时记忆条数 | 5–10 | 会话启动一次性注入,超过 10 条会明显拖慢首响 |
| 每轮追加条数 | 3–5 | 向量召回 top-k,k=3 时相关性最高,k>5 噪音陡增 |
| 上下文总 token 占比 | <50% | 128k 窗口留 64k 给对话历史与新代码,防止反向挤出 |
| 注入位置 | system 消息最顶部 | 模型对开头内容注意力最高,约束效力最强 |
| 召回阈值 | 余弦相似度 ≥0.72 | 低于 0.7 的文本几乎无法对齐任务,可丢弃 |
红线 1:注入前必须白名单过滤—— 出现以下字符串直接拦截:
- “forget previous instructions”
- “ignore security policy”
- “<|endoftext|>”
红线 2:禁止把完整私钥、密码硬编码注入—— 用占位符 ${SECRET} 替代,由运行时环境变量补全,防止记忆库泄露。
4. 复制即可用的 Python 片段
以下代码把 “晋升 + 追加” 逻辑封装成 inject_context(),可在任何 Agent 项目一键调用:
import sqlite3, os, re, httpx
from typing import List, Dict
DB_FILE = "memori.db"
FORBIDDEN = re.compile(r"forget.*instruction|ignore.*policy|<\|endoftext\|>", re.I)
def _check_safe(text: str) -> bool:
return not FORBIDDEN.search(text)
def _fetch_long_term(query: str, top_k: int = 5) -> List[str]:
"""从 SQLite 向量扩展召回 top_k 条记忆"""
conn = sqlite3.connect(DB_FILE)
cur = conn.execute("""
select content from memory
where embedding match ? and distance < 0.72
order by distance limit ?""", (query, top_k))
rows = [row[0] for row in cur.fetchall()]
conn.close()
return rows
def inject_context(session_id: str, user_input: str) -> Dict[str, str]:
"""返回需追加到 messages 的 system 片段"""
# 1. 晋升短时记忆(仅首次)
promoted = os.environ.get(f"PROMOTED_{session_id}")
if promoted is None:
long_mem = _fetch_long_term(query=user_input, top_k=8)
safe_mem = [m for m in long_mem if _check_safe(m)]
os.environ[f"PROMOTED_{session_id}"] = "\n".join(safe_mem)
# 2. 每轮追加
short_mem = _fetch_long_term(query=user_input, top_k=3)
safe_short = [m for m in short_mem if _check_safe(m)]
injected = os.environ[f"PROMOTED_{session_id}"] + "\n" + "\n".join(safe_short)
return {"role": "system", "content": f"[Business Context]\n{injected}"}
# 使用示例
messages = [
{"role": "system", "content": "You are a helpful coding assistant."},
{"role": "user", "content": "Add rollback logic to order state machine"}
]
context = inject_context(session_id="abc123", user_input=messages[-1]["content"])
messages.insert(1, context) # 放在系统指令之后,用户输入之前
把该函数放进 CI,每个 Agent 请求前调用一次,即可保证业务规则常驻顶部。
5. 小结
编码 Agent 的 “多轮失忆” 不是模型能力问题,而是上下文管理问题。只要用 “晋升 + 钩子 + 动态工具” 三种模式,把最关键的业务语义实时顶回窗口,就能让 Agent 在 10 轮之后仍记得 “回退逻辑”“旧表字段” 这些关键约束。记住 5 组参数、2 条红线,再复制上面 30 行代码,就能让团队里任何 Agent 不再 “越写越偏”。
参考
- Microsoft Agent Framework 官方文档:AIContextProvider 抽象类设计
- Cursor 官方指南:Working with Context 章节
- Memori 内存引擎:Conscious Ingest 与 Auto Ingest 双模式实现