--- tags: [ai/agent, rag, context-engineering, memory-management, prompt-compression] create time: 2026-08-08 18:43 update time: 2026-08-08 18:43 --- # 上下文工程与记忆管理 ## 概述 LLM 本身是无状态的,它的"知识"完全依赖每次请求时注入的 Prompt Context。上下文工程的本质是在有限的 token 预算内,最大化信息密度和相关性。记忆管理则是跨会话持久化用户偏好和事实,让 Agent 具备成长能力。 ## 详解 ### RAG 流程:从文档到可检索的向量 RAG(Retrieval-Augmented Generation)将外部知识库与生成式模型结合,解决 LLM 知识截止和幻觉问题。完整链路如下: ```mermaid graph LR A["原始文档"] --> B["Chunking\n分块处理"] B --> C["Embedding\n向量化"] C --> D["向量数据库"] D --> E["Top-K 相似度检索"] E --> F["Prompt Context 组装"] F --> G["LLM 生成答案"] ``` #### Chunking 分块策略 把长文档切分为适合 Embedding 的片段是关键,直接影响检索精度: | 策略 | 实现方式 | 优缺点 | |------|---------|--------| | 固定长度 | 按字符数 N 切割,重叠 M | 简单快速;可能截断语义 | | 语义边界 | 在标题、段落间切割 | 保持结构完整;需启发式规则 | | 递归分块 | Markdown 级别逐层深入 | Hierarchy-aware;实现复杂 | | 文档感知 | 基于 PDF/Docx 解析的结构信息 | 最精准;依赖文档格式质量 | 实际项目中推荐**递归分块 + 重叠**:先按 Markdown 层级分,同一块超过阈值再按句子切,相邻块保留 10-15% 重叠区避免关键句被拆分。 #### Embedding → 向量存储 将文本映射为高维稠密向量(通常 768-1536 维),然后存入向量数据库: ```go // Go 示例:Embedding + 存储到 pgvector func StoreDocument(ctx context.Context, db *sql.DB, doc string) error { // 1. 调用 Embedding API 获取向量 vec, err := embeddingClient.Embed(ctx, doc) if err != nil { return err } // 2. 存入 PostgreSQL + pgvector _, err = db.ExecContext(ctx, `INSERT INTO documents (title, content, embedding) VALUES ($1, $2, $3::vector)`, doc.Title, doc.Content, pq.Array(vec)) return err } ``` > [!NOTE] > 向量维度选择是权衡:768 维够用且快,1536 维更精细但消耗更多内存和计算资源。国内常用的 text-embedding-v3 默认 1536 维。 #### Top-K 检索与重排 检索不是终点——召回 Top-K 后还需要精排: ``` 检索阶段(召回): BM25 / 向量相似度 → 取 Top 50 ↓ 重排阶段(精排): Cross-Encoder 对 50 条打分 → 取 Top 5 ↓ 过滤阶段: 关键词匹配校验 + 去重 → 最终 Top 3 注入 Prompt ``` Cross-Encoder 比 Single-Encoder 精度高约 10-15%,但速度慢 20 倍,所以只做二次筛选而非全量检索。 #### Prompt Context 组装策略 组装顺序和内容直接影响生成质量: 1. **系统提示**(固定,不可变):定义 Agent 角色和行为准则 2. **事实上下文**(动态检索):从向量库查得的 Top-K 文档片段 3. **对话历史**(滑动窗口):最近 N 轮消息 4. **用户最新提问** 组装优先级:**系统提示 > 事实上下文 > 历史对话** > [!WARNING] > 不要盲目把全部 Top-K 塞进 Prompt。token 预算有限,超出的部分会触发 truncation 丢失重要信息。建议按相关度降序填充,直到触及 token 上限为止。 ### Prompt 压缩方法 当检索结果过多或对话历史过长时,需要压缩上下文以节省 token: | 方法 | 原理 | 效果 | |------|------|------| | 信息密度排序 | 按每 token 包含的事实数量排序,保留高密度片段 | 保留核心信息,丢弃废话 | | 摘要浓缩 | 用 LLM 将多个片段合并为一句话摘要 | 显著缩减长度,但有信息损失 | | Token 池修剪 | 计算每个片段的 Self-Influence Score,剔除冗余 | 理论最优,需额外推理开销 | | 滑动窗口 | 只保留最近 N 轮对话 | 最简单但丢弃早期关键信息 | ```go // Go 示例:基于信息密度的 Context 压缩 type Compressor struct { model *llm.Client } func (c *Compressor) Compress(ctx context.Context, segments []Segment) []Segment { // 计算每个段落的「非停用词比例」作为密度评分 scored := make([]ScoredSegment, len(segments)) for i, seg := range segments { score := float64(len(stopWordFree(seg.Text))) / float64(len(seg.Text)) scored[i] = ScoredSegment{Segment: seg, Score: score} } // 按密度降序排列,取前 K 个 sort.Slice(scored, func(i, j int) bool { return scored[i].Score > scored[j].Score }) return takeK(scored, MaxSegments) } ``` ### 记忆管理架构 Agent 的记忆分三层,对应不同的生命周期和查询方式: ``` 短期记忆 ──────→ 当前会话内的对话历史 ──────→ 滑动窗口删除 │ │ ▼ ▼ 长期记忆 ──────→ 跨会话的知识库 ───────────→ 定期增量更新 │ │ ▼ ▼ 工作记忆 ──────→ 运行时临时状态 ───────────→ 任务结束后清空 ``` #### 短期记忆 — 对话历史 维护最近 N 轮的用户-AI 交互记录。实现要点: - **双端队列**:头部保留最近 N 轮,尾部超过时淘汰 - **Token 预算优先**:不是简单地固定轮数,而是累计 token 不超过配额 - **关键信息提取**:每轮结束时自动提取实体和意图摘要,追加到摘要缓冲区 ```go type ShortTermMemory struct { messages []*ChatMessage budget int // token 预算 } func (m *ShortTermMemory) Add(msg *ChatMessage) { m.messages = append(m.messages, msg) for totalTokens(m.messages) > m.budget { m.messages = m.messages[1:] // 移除最早的一条 } } ``` #### 长期记忆 — 知识库 + 向量检索 用户在交互中表达的偏好、事实陈述、决策记录会被抽取并持久化: ``` 输入: "我偏好使用 Gin 框架,不喜欢 Echo" ↓ NLP 抽取: Entity="Gin", Relation="preferred over", Target="Echo" ↓ 存储: INSERT INTO long_term_memory (type, entity, relation, target, created_at) ↓ 检索时: 通过关键词搜索 + 向量相似度召回相关记忆片段 ``` 记忆类型包括: | 类型 | 内容示例 | 保留策略 | |------|---------|---------| | UserPreference | 技术栈偏好、代码风格偏好 | 永久保留,定期去重 | | FactStatement | "PostgreSQL 支持 JSONB" | 有效期 90 天,可续期 | | DecisionRecord | "项目选择了 gRPC 而非 REST" | 永久保留 | | TaskProgress | "已完成认证模块的开发" | 任务结束后归档 | #### 记忆更新策略 记忆不是一次性写入的,需要考虑: 1. **新增**:新发现的信息插入知识库 2. **修正**:用户说"我之前说错了",标记旧记忆为 obsolete 3. **合并**:多轮对话中提取到的碎片信息聚合为统一事实 4. **衰减**:随时间推移降低旧记忆的权重,`weight *= decay_factor` > [!TIP] > 面试常考点:为什么 RAG 不能替代记忆?答案是 RAG 面向的是外部知识(文档、手册),而记忆面向的是个性化数据(用户偏好、历史对话)。两者正交,互补而非互斥。 ## 实践场景 **ThumbUP 项目的二级缓存设计** 就是上下文工程的典型应用: 1. 第一级缓存:本地 LRU,命中率最高(热点 query) 2. 第二级缓存:Redis + 向量检索,覆盖中长尾请求 3. HeavyKeeper 算法实时探测热点 key,避免缓存雪崩 这种分层思路同样适用于记忆管理——短期记忆(本地 LRU)+ 长期记忆(远程向量库)。 ## 扩展阅读 - [[Eino DAG 工作流设计]] - [[沙箱权限治理]] - [[循环状态机在Agent中的应用]]