Rust Agent 的长期记忆设计:别再把一切都塞进 Prompt 了
344 字·3 分钟阅读
RustAI Agent
如果你已经写过 Agent,你一定遇到过这个阶段👇
一开始很聪明 跑几轮之后开始胡言乱语 再跑一会儿就忘了自己是谁
根因只有一个:记忆设计是错的。
一个残酷现实:LLM 没有“记忆”,只有上下文
LLM 所谓的“记忆”,本质是:
- 你这次 request 里给了多少 token
- 模型不会记得上一次请求的任何东西
所以很多 Agent 的“记忆方案”是:
👉 把所有历史对话,全塞进 prompt
结果是:
- ❌ token 爆炸
- ❌ 成本失控
- ❌ 信息噪声越来越大
- ❌ 模型反而抓不到重点
真正可用的 Agent,记忆一定是分层的。
Agent 的三层记忆模型(非常重要)
这是我强烈建议你在文章里强调的一张“认知地图”:
┌────────────────────┐
│ Working Memory │ 当前上下文(短期)
│ (Short-Term) │
├────────────────────┤
│ Episodic Memory │ 事件 / 行为记录
│ (Long-Term Raw) │
├────────────────────┤
│ Semantic Memory │ 可检索的知识
│ (Vector / Index) │
└────────────────────┘我们一层一层来,用 Rust 的方式实现。
Working Memory:不是“越多越好”,而是“刚刚好”
问题:为什么上下文越长,Agent 越蠢?
因为 LLM 并不会帮你做“信息优先级排序”。
所以 Working Memory 的原则是:
只保留:当前决策真正需要的信息
Rust 实现:滑动窗口 + 角色过滤
pub struct WorkingMemory {
messages: Vec<ChatMessage>,
max_tokens: usize,
}你可以在 push 的时候做三件事:
- 丢弃太老的 tool 输出
- 保留 user goal + 最近 reasoning
- 必要时用 LLM 先摘要再保留
pub fn compact(&mut self) {
if self.messages.len() > 20 {
let summary = summarize_with_llm(&self.messages[..10]);
self.messages.drain(0..10);
self.messages.insert(0, summary);
}
}📌 记忆压缩是 Agent 成熟的标志
Episodic Memory:Agent 的“黑匣子”
这是很多教程完全不讲、但生产环境必不可少的部分。
什么是 Episodic Memory?
👉 Agent 每一次重要行为的不可变记录
比如:
{
"ts": "2026-01-04T01:21:00Z",
"step": 7,
"action": "tool_call",
"tool": "http_get",
"input": {"url": "..."},
"result": "timeout"
}Rust 实现:Append-only JSONL
pub struct EpisodicMemory {
writer: tokio::fs::File,
}
impl EpisodicMemory {
pub async fn record(&mut self, event: &Value) -> Result<()> {
use tokio::io::AsyncWriteExt;
self.writer.write_all(event.to_string().as_bytes()).await?;
self.writer.write_all(b"\n").await?;
Ok(())
}
}💡 为什么不用数据库?
- append-only:崩溃不丢数据
- 人类可读
- 后期可离线分析 / 回放 / 微调
Agent 没有 episodic memory,就无法 Debug。
真正拉开差距的:Semantic Memory(向量记忆)
这一步,Agent 才真的“像在学习”。
Semantic Memory 的作用只有一句话:
当遇到相似问题时,能想起“以前发生过什么”
Semantic Memory 的最小可用设计
我们不一上来就上 Milvus / Pinecone。
先做一个 可控、可嵌入的本地方案。
数据结构
pub struct MemoryChunk {
pub id: Uuid,
pub embedding: Vec<f32>,
pub text: String,
}存储方案(极简但好用)
- SQLite(元数据)
- embeddings 存 BLOB
- 余弦相似度在内存算
Embedding 是“慢操作”,一定要异步化
这是一个很多人踩过的坑。
pub async fn embed(text: &str) -> Result<Vec<f32>> {
// 调用 embedding model
}原则:
- ❌ 不要在 Agent 主循环同步调用
- ✅ 后台异步写入 semantic memory
tokio::spawn(async move {
let emb = embed(&text).await?;
semantic_memory.insert(text, emb).await?;
});检索:不是“最相似”,而是“最有用”
天真的写法:
top_k_by_cosine_similarity(query_embedding)但真正好用的做法是:
相似度 × 时间衰减 × 成功权重
score = similarity * freshness * success_rate这一步非常适合你在公众号里强调:
Agent 的“记忆质量”,比记忆数量重要 100 倍。
Semantic Memory 如何参与 Agent 推理?
在 每一轮 LLM 调用之前:
1. 取当前 goal
2. embedding
3. 从 semantic memory 检索 top-k
4. 作为“参考资料”拼进 system prompt示例 Prompt 片段:
Relevant past experiences:
- Previously, HTTP timeout was solved by lowering concurrency.
- Similar error occurred when RPC rate limit exceeded.📌 注意: 不是当作“对话历史”,而是参考知识
记忆系统的 4 条工程铁律(强烈建议原文照抄)
你可以在文章结尾用“总结金句”:
- Working Memory 越短,Agent 越聪明
- Episodic Memory 是为了 Debug,不是为了推理
- Semantic Memory 只存“值得被想起的事”
- 记忆不是数据结构,是策略
到这里,你的 Agent 已经不再是 Demo
如果你已经跟到第三篇,你的 Agent 现在已经具备:
- ✅ 并发执行器
- ✅ 成本可控
- ✅ 可 Debug
- ✅ 可学习
- ✅ 可长期运行
这已经远远超过 90% 开源 Agent 框架的工程深度。