34097 字
约 113 分钟
1
第6章 Agent的记忆系统

第6章 Agent的记忆系统

来源:https://ai-agent-guide.xiaofuge.cn/chapters/ch05-memory.html 所属:第二篇-Agent的大脑


让 Agent 拥有短期的专注和长期的经验 📝 先看一个最小记忆例子

如果用户第一轮说"我喜欢简洁风格",第二轮说"帮我写周报",没有记忆的 Agent 会重新追问风格;有记忆的 Agent 会直接按简洁风格输出。

所以你可以把本章先简单理解成一句话:记忆不是为了存很多内容,而是为了让 Agent 在下一轮做出更一致、更省心的决定。

6.1 没有记忆的 Agent 会怎样?

想象一个失忆的助手:你上午告诉他你喜欢简洁的报告,下午他又问你"你需要什么风格?";你刚给了他一份资料,下一秒他就忘了。

这就是没有记忆的 Agent。它只能处理单轮、即时的请求,无法积累经验、无法保持一致性、无法处理需要跨轮引用的复杂任务。 💡 记忆的本质

记忆让 Agent 从"活在当下"变成"有过去、有经验"。它影响 Agent 的连贯性(同一对话内的一致性)、个性化(记住用户偏好)和学习进化(从历史中优化)。

6.2 三层记忆架构

人类有感觉记忆、短期记忆、长期记忆三层结构。Agent 也有类似的设计: | 类型 | 类比 | 实现 | 容量 | 持久性 | | --- | --- | --- | --- | --- | | 短期记忆 | 工作台上的文件 | LLM 上下文窗口 | 4K~200K token | 对话期间 | | 工作记忆 | 便签纸 | Scratchpad / 变量 | 任务相关 | 任务期间 | | 长期记忆 | 图书馆/档案室 | 向量数据库 | 理论无限 | 永久 | ## 6.3 短期记忆的管理策略

短期记忆 = LLM 上下文窗口。对话越长,上下文越大。但窗口有上限,必须管理:

四种管理策略

1

滑窗截断(Sliding Window)

保留最近 N 轮对话,丢弃最旧的消息。简单粗暴,但可能丢失关键早期信息。

← 1 / 4 → 重播

6.4 长期记忆:向量数据库

长期记忆让 Agent 能回忆起几天前甚至几个月前的交互。核心技术是向量数据库 + 语义检索

主流向量数据库对比 | 数据库 | 类型 | 特点 | 适用场景 | | --- | --- | --- | --- | | Chroma | 嵌入式 | 轻量、Python原生、零配置 | 原型开发、小规模 | | Pinecone | 云服务 | 全托管、高性能、易扩展 | 生产环境、大规模 | | Weaviate | 自部署 | 支持混合搜索、GraphQL | 需要复杂查询 | | Milvus | 分布式 | 高可用、支持十亿级向量 | 超大规模企业 | | pgvector | PostgreSQL扩展 | SQL生态、事务支持 | 已有PG基础设施 | ## 6.5 上下文压缩实战

在长任务中,Agent 的上下文窗口会不断膨胀--每次工具调用返回结果、每次推理步骤、每次对话轮次都在累积 token。这种现象称为上下文膨胀(Context Bloat),也叫上下文腐烂(Context Rot)。 ⚠️ Context Rot 的典型症状

  • 行为漂移:Agent 早期遵循的规则(如"用中文回答"),在 50 轮对话后可能被遗忘
  • 注意力稀释:上下文中有太多无关历史,LLM 的注意力被分散,输出质量下降
  • 成本飙升:每次调用都要处理全部历史 token,费用随对话长度线性增长
  • 窗口溢出:超过模型最大上下文长度后,被迫截断或报错

五种压缩策略详解

1

截断(Truncation)

最简单的方案:超过窗口上限时,直接丢弃最早的消息。实现成本为零,但会导致行为不一致--Agent 可能忘记用户在第一轮设定的约束条件。

← 1 / 5 → 重播

压缩前后 Token 对比

# ===== 模拟一次 30 轮对话的上下文膨胀 =====

# 原始对话(无压缩)
raw_tokens_per_turn = 150          # 每轮平均 150 token
turns = 30
total_raw = raw_tokens_per_turn * turns + 500  # +500 为 System Prompt
print(f"原始上下文: {total_raw} tokens")  # 输出: 5000 tokens

# ----- 策略 1: 截断 -----
# 保留最近 10 轮,丢弃其余
truncated = raw_tokens_per_turn * 10 + 500
print(f"截断后:     {truncated} tokens")      # 输出: 2000 tokens
# 损失: 20 轮对话完全丢失,包含用户初始约束条件

# ----- 策略 2: 摘要 -----
# 旧 20 轮压缩为一段摘要(约 200 token)
summarized = 200 + raw_tokens_per_turn * 10 + 500
print(f"摘要后:     {summarized} tokens")      # 输出: 2200 tokens
# 损失: 细节丢失,但保留了关键决策和偏好

# ----- 策略 3: 滑动窗口 -----
# 等同于截断策略(按时间维度)
sliding = truncated
print(f"滑动窗口后: {sliding} tokens")         # 输出: 2000 tokens

# ----- 策略 4: 选择性保留 -----
# 从 30 轮中标记 8 轮为"重要",其余丢弃
selected = raw_tokens_per_turn * 8 + 500 + 150  # +150 为类型标签开销
print(f"选择性保留后: {selected} tokens")       # 输出: 1850 tokens
# 信息密度最高,但可能遗漏看似不重要实则关键的上下文

# ----- 策略 5: 语义保护型压缩 -----
# System Prompt 全量 + 6 个关键行动-结果对 + 12 轮推理压缩为 300 token
semantic = 500 + raw_tokens_per_turn * 6 + 300 + 100  # +100 为因果链标注
print(f"语义保护型后: {semantic} tokens")       # 输出: 1800 tokens
# 最低压缩率 + 最高信息完整性

# ===== 效果总结 =====
print("\n--- 压缩效果对比 ---")
strategies = {
    "原始":      total_raw,
    "截断":      truncated,
    "摘要":      summarized,
    "滑动窗口":  sliding,
    "选择性保留": selected,
    "语义保护型": semantic
}
for name, tokens in strategies.items():
    ratio = tokens / total_raw
    print(f"{name:10s}: {tokens} tokens (压缩率 {ratio:.1%})")

# 输出:
# 原始      : 5000 tokens (压缩率 100.0%)
# 截断      : 2000 tokens (压缩率 40.0%)
# 摘要      : 2200 tokens (压缩率 44.0%)
# 滑动窗口  : 2000 tokens (压缩率 40.0%)
# 选择性保留: 1850 tokens (压缩率 37.0%)
# 语义保护型: 1800 tokens (压缩率 36.0%)
// ===== 模拟一次 30 轮对话的上下文膨胀 =====

// 原始对话(无压缩)
const rawTokensPerTurn = 150;       // 每轮平均 150 token
const turns = 30;
const totalRaw = rawTokensPerTurn * turns + 500; // +500 为 System Prompt
console.log(`原始上下文: ${totalRaw} tokens`);  // 输出: 5000 tokens

// ----- 策略 1: 截断 -----
const truncated = rawTokensPerTurn * 10 + 500;
console.log(`截断后:     ${truncated} tokens`);   // 输出: 2000 tokens

// ----- 策略 2: 摘要 -----
const summarized = 200 + rawTokensPerTurn * 10 + 500;
console.log(`摘要后:     ${summarized} tokens`);   // 输出: 2200 tokens

// ----- 策略 3: 滑动窗口 -----
const sliding = truncated;
console.log(`滑动窗口后: ${sliding} tokens`);      // 输出: 2000 tokens

// ----- 策略 4: 选择性保留 -----
const selected = rawTokensPerTurn * 8 + 500 + 150;
console.log(`选择性保留后: ${selected} tokens`);    // 输出: 1850 tokens

// ----- 策略 5: 语义保护型压缩 -----
const semantic = 500 + rawTokensPerTurn * 6 + 300 + 100;
console.log(`语义保护型后: ${semantic} tokens`);    // 输出: 1800 tokens

// ===== 效果总结 =====
console.log("\n--- 压缩效果对比 ---");
const strategies: Record = {
    "原始":      totalRaw,
    "截断":      truncated,
    "摘要":      summarized,
    "滑动窗口":  sliding,
    "选择性保留": selected,
    "语义保护型": semantic
};
for (const [name, tokens] of Object.entries(strategies)) {
    const ratio = tokens / totalRaw;
    console.log(`${name.padEnd(10)}: ${tokens} tokens (压缩率 ${(ratio * 100).toFixed(1)}%)`);
}

// 输出:
// 原始      : 5000 tokens (压缩率 100.0%)
// 截断      : 2000 tokens (压缩率 40.0%)
// 摘要      : 2200 tokens (压缩率 44.0%)
// 滑动窗口  : 2000 tokens (压缩率 40.0%)
// 选择性保留: 1850 tokens (压缩率 37.0%)
// 语义保护型: 1800 tokens (压缩率 36.0%)
package main

import "fmt"

func main() {
    // ===== 模拟一次 30 轮对话的上下文膨胀 =====

    // 原始对话(无压缩)
    rawTokensPerTurn := 150 // 每轮平均 150 token
    turns := 30
    totalRaw := rawTokensPerTurn*turns + 500 // +500 为 System Prompt
    fmt.Printf("原始上下文: %d tokens\n", totalRaw) // 输出: 5000 tokens

    // ----- 策略 1: 截断 -----
    truncated := rawTokensPerTurn*10 + 500
    fmt.Printf("截断后:     %d tokens\n", truncated) // 输出: 2000 tokens

    // ----- 策略 2: 摘要 -----
    summarized := 200 + rawTokensPerTurn*10 + 500
    fmt.Printf("摘要后:     %d tokens\n", summarized) // 输出: 2200 tokens

    // ----- 策略 3: 滑动窗口 -----
    sliding := truncated
    fmt.Printf("滑动窗口后: %d tokens\n", sliding) // 输出: 2000 tokens

    // ----- 策略 4: 选择性保留 -----
    selected := rawTokensPerTurn*8 + 500 + 150
    fmt.Printf("选择性保留后: %d tokens\n", selected) // 输出: 1850 tokens

    // ----- 策略 5: 语义保护型压缩 -----
    semantic := 500 + rawTokensPerTurn*6 + 300 + 100
    fmt.Printf("语义保护型后: %d tokens\n", semantic) // 输出: 1800 tokens

    // ===== 效果总结 =====
    fmt.Println("\n--- 压缩效果对比 ---")
    strategies := []struct {
        name   string
        tokens int
    }{
        {"原始", totalRaw},
        {"截断", truncated},
        {"摘要", summarized},
        {"滑动窗口", sliding},
        {"选择性保留", selected},
        {"语义保护型", semantic},
    }
    for _, s := range strategies {
        ratio := float64(s.tokens) / float64(totalRaw)
        fmt.Printf("%-10s: %d tokens (压缩率 %.1f%%)\n", s.name, s.tokens, ratio*100)
    }

    // 输出:
    // 原始      : 5000 tokens (压缩率 100.0%)
    // 截断      : 2000 tokens (压缩率 40.0%)
    // 摘要      : 2200 tokens (压缩率 44.0%)
    // 滑动窗口  : 2000 tokens (压缩率 40.0%)
    // 选择性保留: 1850 tokens (压缩率 37.0%)
    // 语义保护型: 1800 tokens (压缩率 36.0%)
}
import java.util.LinkedHashMap;
import java.util.Map;

public class TokenCompressionComparison {
    public static void main(String[] args) {
        // ===== 模拟一次 30 轮对话的上下文膨胀 =====

        // 原始对话(无压缩)
        int rawTokensPerTurn = 150;  // 每轮平均 150 token
        int turns = 30;
        int totalRaw = rawTokensPerTurn * turns + 500; // +500 为 System Prompt
        System.out.printf("原始上下文: %d tokens%n", totalRaw); // 输出: 5000 tokens

        // ----- 策略 1: 截断 -----
        int truncated = rawTokensPerTurn * 10 + 500;
        System.out.printf("截断后:     %d tokens%n", truncated); // 输出: 2000 tokens

        // ----- 策略 2: 摘要 -----
        int summarized = 200 + rawTokensPerTurn * 10 + 500;
        System.out.printf("摘要后:     %d tokens%n", summarized); // 输出: 2200 tokens

        // ----- 策略 3: 滑动窗口 -----
        int sliding = truncated;
        System.out.printf("滑动窗口后: %d tokens%n", sliding); // 输出: 2000 tokens

        // ----- 策略 4: 选择性保留 -----
        int selected = rawTokensPerTurn * 8 + 500 + 150;
        System.out.printf("选择性保留后: %d tokens%n", selected); // 输出: 1850 tokens

        // ----- 策略 5: 语义保护型压缩 -----
        int semantic = 500 + rawTokensPerTurn * 6 + 300 + 100;
        System.out.printf("语义保护型后: %d tokens%n", semantic); // 输出: 1800 tokens

        // ===== 效果总结 =====
        System.out.println("\n--- 压缩效果对比 ---");
        Map strategies = new LinkedHashMap<>();
        strategies.put("原始", totalRaw);
        strategies.put("截断", truncated);
        strategies.put("摘要", summarized);
        strategies.put("滑动窗口", sliding);
        strategies.put("选择性保留", selected);
        strategies.put("语义保护型", semantic);

        for (Map.Entry entry : strategies.entrySet()) {
            double ratio = (double) entry.getValue() / totalRaw;
            System.out.printf("%-10s: %d tokens (压缩率 %.1f%%)%n",
                entry.getKey(), entry.getValue(), ratio * 100);
        }

        // 输出:
        // 原始      : 5000 tokens (压缩率 100.0%)
        // 截断      : 2000 tokens (压缩率 40.0%)
        // 摘要      : 2200 tokens (压缩率 44.0%)
        // 滑动窗口  : 2000 tokens (压缩率 40.0%)
        // 选择性保留: 1850 tokens (压缩率 37.0%)
        // 语义保护型: 1800 tokens (压缩率 36.0%)
    }
}

Solon AI 框架的 SummarizationInterceptor

Solon AI 是一个国产的 Java AI 框架,它提供了 SummarizationInterceptor 作为上下文压缩的内置方案。其核心思路是:当对话轮次超过阈值时,自动触发摘要压缩。

// Solon AI 框架 - 上下文压缩拦截器配置

@Configuration
public class AiConfig {

    @Bean
    public ChatService chatService() {
        return new ChatService.builder()
            .model(openAiModel)                    // LLM 模型
            .interceptor(summarizationInterceptor()) // 添加压缩拦截器
            .build();
    }

    @Bean
    public SummarizationInterceptor summarizationInterceptor() {
        return new SummarizationInterceptor()
            .threshold(10)              // 超过 10 轮对话时触发压缩
            .summaryModel(openAiModel)  // 用哪个模型做摘要(可以用更便宜的模型)
            .preserveSystemPrompt(true) // 保护 System Prompt 不被压缩
            .preserveLastN(3)           // 保留最近 3 轮原始对话
            .summaryPrompt("请将以下对话历史压缩为简洁摘要," +
                           "保留所有关键决策、用户偏好和事实信息。" +
                           "丢弃闲聊和重复内容。");
    }
}

// ===== 工作原理 =====
// 1. 用户每轮对话后,Interceptor 检查当前对话轮次
// 2. 轮次  threshold:
//    - System Prompt 全量保留(preserveSystemPrompt=true)
//    - 最近 3 轮完整保留(preserveLastN=3)
//    - 其余轮次调用 summaryModel 生成摘要
//    - 摘要替代原始对话,拼接到上下文中

// ===== 压缩前后对比 =====
// 压缩前: [System] + [15轮原始对话] = ~3000 tokens
// 压缩后: [System] + [摘要~200tokens] + [最近3轮] = ~1150 tokens
// 压缩率: 38.3%,且核心指令和近期上下文无损保留
// // Solon AI 框架 - 上下文压缩拦截器配置

  // @Configuration
  // public class AiConfig {

  // @Bean
  // public ChatService chatService() {
  return new ChatService.builder();
  // .model(openAiModel)                    // LLM 模型
  // .interceptor(summarizationInterceptor()) // 添加压缩拦截器
  // .build();
  // }

  // @Bean
  // public SummarizationInterceptor summarizationInterceptor() {
  return new SummarizationInterceptor();
  // .threshold(10)              // 超过 10 轮对话时触发压缩
  // .summaryModel(openAiModel)  // 用哪个模型做摘要(可以用更便宜的模型)
  // .preserveSystemPrompt(true) // 保护 System Prompt 不被压缩
  // .preserveLastN(3)           // 保留最近 3 轮原始对话
  // .summaryPrompt("请将以下对话历史压缩为简洁摘要," +
  // "保留所有关键决策、用户偏好和事实信息。" +
  // "丢弃闲聊和重复内容。");
  // }
  // }

  // // ===== 工作原理 =====
  // // 1. 用户每轮对话后,Interceptor 检查当前对话轮次
  // // 2. 轮次  threshold:
  // //    - System Prompt 全量保留(preserveSystemPrompt=true)
  // //    - 最近 3 轮完整保留(preserveLastN=3)
  // //    - 其余轮次调用 summaryModel 生成摘要
  // //    - 摘要替代原始对话,拼接到上下文中

  // // ===== 压缩前后对比 =====
  // // 压缩前: [System] + [15轮原始对话] = ~3000 tokens
  // // 压缩后: [System] + [摘要~200tokens] + [最近3轮] = ~1150 tokens
  // // 压缩率: 38.3%,且核心指令和近期上下文无损保留
package main

import (
	"fmt"
	"os"
	"os/exec"
	"strings"
)

	// Python: // Solon AI 框架 - 上下文压缩拦截器配置

	// Python: public class AiConfig {

	// Python: public ChatService chatService() {
	return new ChatService.builder()
	// Python: .model(openAiModel)                    // LLM 模型
	// Python: .interceptor(summarizationInterceptor()) // 添加压缩拦截器
	// Python: .build();
	// Python: }

	// Python: public SummarizationInterceptor summarizationInterceptor() {
	return new SummarizationInterceptor()
	// Python: .threshold(10)              // 超过 10 轮对话时触发压缩
	// Python: .summaryModel(openAiModel)  // 用哪个模型做摘要(可以用更便宜的模型)
	// Python: .preserveSystemPrompt(true) // 保护 System Prompt 不被压缩
	// Python: .preserveLastN(3)           // 保留最近 3 轮原始对话
	// Python: .summaryPrompt("请将以下对话历史压缩为简洁摘要," +
	// Python: "保留所有关键决策、用户偏好和事实信息。" +
	// Python: "丢弃闲聊和重复内容。");
	// Python: }
	// Python: }

	// Python: // ===== 工作原理 =====
	// Python: // 1. 用户每轮对话后,Interceptor 检查当前对话轮次
	// Python: // 2. 轮次  threshold:
	// Python: //    - System Prompt 全量保留(preserveSystemPrompt=true)
	// Python: //    - 最近 3 轮完整保留(preserveLastN=3)
	// Python: //    - 其余轮次调用 summaryModel 生成摘要
	// Python: //    - 摘要替代原始对话,拼接到上下文中

	// Python: // ===== 压缩前后对比 =====
	// Python: // 压缩前: [System] + [15轮原始对话] = ~3000 tokens
	// Python: // 压缩后: [System] + [摘要~200tokens] + [最近3轮] = ~1150 tokens
	// Python: // 压缩率: 38.3%,且核心指令和近期上下文无损保留
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.*;
import java.io.*;

        // Python: // Solon AI 框架 - 上下文压缩拦截器配置

        // Python: public class AiConfig {

        // Python: public ChatService chatService() {
        return new ChatService.builder();
        // Python: .model(openAiModel)                    // LLM 模型
        // Python: .interceptor(summarizationInterceptor()) // 添加压缩拦截器
        // Python: .build();
        // Python: }

        // Python: public SummarizationInterceptor summarizationInterceptor() {
        return new SummarizationInterceptor();
        // Python: .threshold(10)              // 超过 10 轮对话时触发压缩
        // Python: .summaryModel(openAiModel)  // 用哪个模型做摘要(可以用更便宜的模型)
        // Python: .preserveSystemPrompt(true) // 保护 System Prompt 不被压缩
        // Python: .preserveLastN(3)           // 保留最近 3 轮原始对话
        // Python: .summaryPrompt("请将以下对话历史压缩为简洁摘要," +
        // Python: "保留所有关键决策、用户偏好和事实信息。" +
        // Python: "丢弃闲聊和重复内容。");
        // Python: }
        // Python: }

        // Python: // ===== 工作原理 =====
        // Python: // 1. 用户每轮对话后,Interceptor 检查当前对话轮次
        // Python: // 2. 轮次  threshold:
        // Python: //    - System Prompt 全量保留(preserveSystemPrompt=true)
        // Python: //    - 最近 3 轮完整保留(preserveLastN=3)
        // Python: //    - 其余轮次调用 summaryModel 生成摘要
        // Python: //    - 摘要替代原始对话,拼接到上下文中

        // Python: // ===== 压缩前后对比 =====
        // Python: // 压缩前: [System] + [15轮原始对话] = ~3000 tokens
        // Python: // 压缩后: [System] + [摘要~200tokens] + [最近3轮] = ~1150 tokens
        // Python: // 压缩率: 38.3%,且核心指令和近期上下文无损保留
}

压缩不是删减,而是蒸馏。好的压缩策略就像化学蒸馏--把混合物中的精华提取出来,丢弃的是杂质而非有效成分。

6.6 记忆架构实战

选择合适的向量数据库和 Embedding 模型是构建记忆系统的关键决策。本节提供完整的选型对比和架构实现。

向量数据库深度对比 | 数据库 | 部署方式 | 最大向量数 | 混合搜索 | 过滤查询 | 事务支持 | 成本 | 适用场景 | | --- | --- | --- | --- | --- | --- | --- | --- | | Chroma | 嵌入式/内存 | ~百万级 | ❌ | 基础 | ❌ | 免费 | 原型/小规模 | | Pinecone | 全托管云服务 | 亿级 | ✅(稀疏+稠密) | 丰富 | ❌ | 按量计费 | 生产/大规模 | | Weaviate | 自部署/Docker | 千万级 | ✅(BM25+向量) | GraphQL | ❌ | 免费/云付费 | 复杂查询场景 | | Milvus | 分布式集群 | 十亿级 | ✅ | 丰富 | ❌ | 免费/运维成本高 | 超大规模企业 | | pgvector | PG扩展/Docker | 百万级 | ✅(全文+向量) | SQL全能力 | ✅ | 免费/低成本 | 已有PG基础设施 | 🎯 选型决策树

  • 快速原型验证 → Chroma(零配置,pip install 即用)
  • 不想运维 → Pinecone(全托管,按量付费,注册即用)
  • 需要混合搜索(关键词+语义) → Weaviate(BM25+向量双路召回)
  • 已有 PostgreSQL 且数据量不大 → pgvector(不用引入新组件,SQL+向量一体化)
  • 十亿级向量 + 高可用 → Milvus(分布式架构,专门为超大规模设计)

完整记忆系统架构设计

短期/长期/工作记忆协同代码实现

import chromadb
from openai import OpenAI

class MemorySystem:
    """三层记忆协同系统"""

    def __init__(self, embedding_model="text-embedding-3-small"):
        self.client = OpenAI()
        self.chroma = chromadb.PersistentClient(path="./memory_db")
        self.collection = self.chroma.get_or_create_collection(
            name="agent_memory",
            metadata={"hnsw:space": "cosine"}
        )
        self.short_term = []     # 短期记忆:对话历史列表
        self.working = {}        # 工作记忆:当前任务的中间变量
        self.compress_threshold = 15  # 超过 15 轮触发压缩

    # ===== 短期记忆管理 =====
    def add_to_short_term(self, role, content):
        """添加一条消息到短期记忆"""
        self.short_term.append({"role": role, "content": content})

        # 检查是否需要压缩
        if len(self.short_term) > self.compress_threshold:
            self._compress_short_term()

    def _compress_short_term(self):
        """压缩短期记忆:摘要 + 转存长期记忆"""
        # 保留最近 3 轮和 System Prompt
        system_msgs = [m for m in self.short_term if m["role"] == "system"]
        recent_msgs = self.short_term[-6:]  # 最近 3 轮(每轮2条)

        # 其余消息交给 LLM 生成摘要
        old_msgs = self.short_term[len(system_msgs):-6]
        if old_msgs:
            summary = self._summarize(old_msgs)

            # 摘要存入长期记忆(向量数据库)
            self._save_to_long_term(
                text=summary,
                metadata={"type": "summary", "turn_range":
                          f"turns {len(system_msgs)}-{len(self.short_term)-6}"}
            )

            # 用摘要替代原始对话
            self.short_term = system_msgs + [
                {"role": "system", "content": f"[历史摘要]: {summary}"}
            ] + recent_msgs

    def _summarize(self, messages):
        """用 LLM 对旧对话生成摘要"""
        text = "\n".join([f"{m['role']}: {m['content']}" for m in messages])
        response = self.client.chat.completions.create(
            model="gpt-4o-mini",  # 用便宜模型做摘要
            messages=[
                {"role": "system", "content":
                 "将以下对话压缩为简洁摘要。保留所有关键决策、"
                 "用户偏好、事实信息和约束条件。丢弃闲聊和重复内容。"},
                {"role": "user", "content": text}
            ],
            max_tokens=300
        )
        return response.choices[0].message.content

    # ===== 长期记忆管理 =====
    def _save_to_long_term(self, text, metadata=None):
        """将内容存入长期记忆(向量数据库)"""
        vector = self._get_embedding(text)
        doc_id = f"mem_{len(self.short_term)}_{hash(text) % 10000}"
        self.collection.upsert(
            ids=[doc_id],
            embeddings=[vector],
            documents=[text],
            metadatas=[metadata or {}]
        )

    def retrieve_from_long_term(self, query, top_k=5):
        """从长期记忆中检索相关内容"""
        results = self.collection.query(
            query_texts=[query],
            n_results=top_k,
            include=["documents", "metadatas", "distances"]
        )
        memories = []
        for doc, meta, dist in zip(
            results["documents"][0],
            results["metadatas"][0],
            results["distances"][0]
        ):
            if dist  this.compressThreshold) {
      this.compressShortTerm();
    }
  }

  private async compressShortTerm(): Promise {
    const systemMsgs = this.shortTerm.filter(m => m.role === 'system');
    const recentMsgs = this.shortTerm.slice(-6);
    const oldMsgs = this.shortTerm.slice(systemMsgs.length, -6);

    if (oldMsgs.length > 0) {
      const summary = await this.summarize(oldMsgs);
      this.saveToLongTerm(summary, {
        type: 'summary',
        turnRange: `turns ${systemMsgs.length}-${this.shortTerm.length - 6}`
      });
      this.shortTerm = [
        ...systemMsgs,
        { role: 'system', content: `[历史摘要]: ${summary}` },
        ...recentMsgs
      ];
    }
  }

  private async summarize(messages: Message[]): Promise {
    const text = messages.map(m => `${m.role}: ${m.content}`).join('\n');
    const response = await this.client.chat.completions.create({
      model: 'gpt-4o-mini',
      messages: [
        { role: 'system', content:
          '将以下对话压缩为简洁摘要。保留所有关键决策、用户偏好、事实信息和约束条件。丢弃闲聊和重复内容。' },
        { role: 'user', content: text }
      ],
      max_tokens: 300
    });
    return response.choices[0].message.content || '';
  }

  // ===== 长期记忆管理 =====
  async saveToLongTerm(text: string, metadata?: Record): Promise {
    const vector = await this.getEmbedding(text);
    const docId = `mem_${this.shortTerm.length}_${text.length % 10000}`;
    this.collection.push({ id: docId, embedding: vector, document: text, metadata: metadata || {} });
  }

  async retrieveFromLongTerm(query: string, topK = 5): Promise {
    const queryVector = await this.getEmbedding(query);
    // Simplified: in production use proper vector similarity search
    const results = this.collection.slice(0, topK);
    return results.filter(r => true).map(r => ({
      text: r.document,
      metadata: r.metadata,
      similarity: 0.85
    }));
  }

  // ===== 工作记忆管理 =====
  updateWorking(key: string, value: any): void { this.working[key] = value; }
  getWorking(key: string): any { return this.working[key]; }
  clearWorking(): void { this.working = {}; }

  // ===== Embedding =====
  private async getEmbedding(text: string): Promise {
    const response = await this.client.embeddings.create({
      model: 'text-embedding-3-small',
      input: text
    });
    return response.data[0].embedding;
  }

  // ===== 完整响应流程 =====
  async respond(userInput: string): Promise {
    const longMemories = await this.retrieveFromLongTerm(userInput, 3);
    const memoryContext = longMemories.map(m => m.text).join('\n');
    const workingContext = Object.entries(this.working)
      .map(([k, v]) => `${k}: ${v}`).join('\n');

    const messages: Message[] = [
      ...this.shortTerm,
      { role: 'system', content:
        `[相关历史记忆]\n${memoryContext}\n\n[当前任务状态]\n${workingContext}` },
      { role: 'user', content: userInput }
    ];

    const response = await this.client.chat.completions.create({
      model: 'gpt-4o',
      messages: messages as any
    });
    const answer = response.choices[0].message.content || '';

    this.addToShortTerm('user', userInput);
    this.addToShortTerm('assistant', answer);
    await this.saveToLongTerm(
      `用户: ${userInput}\n助手: ${answer}`,
      { type: 'interaction' }
    );

    return answer;
  }
}

// ===== 使用示例 =====
const memory = new MemorySystem();
const answer = await memory.respond("帮我分析一下最近的销售数据趋势");
console.log(answer);
package main

import (
    "context"
    "fmt"
    "log"

    openai "github.com/sashabaranov/go-openai"
)

// MemorySystem 三层记忆协同系统
type MemorySystem struct {
    client           *openai.Client
    collection       []MemoryEntry // 简化的向量存储
    shortTerm        []Message
    working          map[string]string
    compressThreshold int
}

type Message struct {
    Role    string `json:"role"`
    Content string `json:"content"`
}

type MemoryEntry struct {
    ID        string
    Embedding []float32
    Document  string
    Metadata  map[string]string
}

type Memory struct {
    Text       string
    Metadata   map[string]string
    Similarity float64
}

func NewMemorySystem() *MemorySystem {
    return &MemorySystem{
        client:            openai.NewClient(),
        collection:        []MemoryEntry{},
        shortTerm:         []Message{},
        working:           make(map[string]string),
        compressThreshold: 15,
    }
}

// ===== 短期记忆管理 =====
func (m *MemorySystem) AddToShortTerm(role, content string) {
    m.shortTerm = append(m.shortTerm, Message{Role: role, Content: content})
    if len(m.shortTerm) > m.compressThreshold {
        m.compressShortTerm()
    }
}

func (m *MemorySystem) compressShortTerm() {
    var systemMsgs, recentMsgs, oldMsgs []Message
    for _, msg := range m.shortTerm {
        if msg.Role == "system" {
            systemMsgs = append(systemMsgs, msg)
        }
    }
    if len(m.shortTerm) >= 6 {
        recentMsgs = m.shortTerm[len(m.shortTerm)-6:]
        oldMsgs = m.shortTerm[len(systemMsgs) : len(m.shortTerm)-6]
    }

    if len(oldMsgs) > 0 {
        summary := m.summarize(oldMsgs)
        m.saveToLongTerm(summary, map[string]string{
            "type":       "summary",
            "turn_range": fmt.Sprintf("turns %d-%d", len(systemMsgs), len(m.shortTerm)-6),
        })
        m.shortTerm = append(systemMsgs,
            Message{Role: "system", Content: fmt.Sprintf("[历史摘要]: %s", summary)})
        m.shortTerm = append(m.shortTerm, recentMsgs...)
    }
}

func (m *MemorySystem) summarize(messages []Message) string {
    var text string
    for _, msg := range messages {
        text += fmt.Sprintf("%s: %s\n", msg.Role, msg.Content)
    }
    resp, err := m.client.CreateChatCompletion(context.Background(),
        openai.ChatCompletionRequest{
            Model: openai.GPT4oMini,
            Messages: []openai.ChatCompletionMessage{
                {Role: "system", Content: "将以下对话压缩为简洁摘要。保留所有关键决策、用户偏好、事实信息和约束条件。丢弃闲聊和重复内容。"},
                {Role: "user", Content: text},
            },
            MaxTokens: 300,
        })
    if err != nil {
        log.Printf("summarize error: %v", err)
        return ""
    }
    return resp.Choices[0].Message.Content
}

// ===== 长期记忆管理 =====
func (m *MemorySystem) saveToLongTerm(text string, metadata map[string]string) {
    vector := m.getEmbedding(text)
    docID := fmt.Sprintf("mem_%d_%d", len(m.shortTerm), len(text)%10000)
    m.collection = append(m.collection, MemoryEntry{
        ID: docID, Embedding: vector, Document: text, Metadata: metadata,
    })
}

func (m *MemorySystem) retrieveFromLongTerm(query string, topK int) []Memory {
    _ = m.getEmbedding(query) // 简化:实际应做向量相似度搜索
    var memories []Memory
    for i, entry := range m.collection {
        if i >= topK {
            break
        }
        memories = append(memories, Memory{
            Text: entry.Document, Metadata: entry.Metadata, Similarity: 0.85,
        })
    }
    return memories
}

// ===== 工作记忆管理 =====
func (m *MemorySystem) updateWorking(key, value string) { m.working[key] = value }
func (m *MemorySystem) getWorking(key string) string    { return m.working[key] }
func (m *MemorySystem) clearWorking()                   { m.working = make(map[string]string) }

// ===== Embedding =====
func (m *MemorySystem) getEmbedding(text string) []float32 {
    resp, err := m.client.CreateEmbeddings(context.Background(),
        openai.EmbeddingRequest{
            Model: openai.SmallEmbedding3,
            Input: []string{text},
        })
    if err != nil {
        log.Printf("embedding error: %v", err)
        return nil
    }
    return resp.Data[0].Embedding
}

// ===== 完整响应流程 =====
func (m *MemorySystem) respond(userInput string) string {
    longMemories := m.retrieveFromLongTerm(userInput, 3)
    var memoryContext string
    for _, mem := range longMemories {
        memoryContext += mem.Text + "\n"
    }
    var workingContext string
    for k, v := range m.working {
        workingContext += fmt.Sprintf("%s: %s\n", k, v)
    }

    messages := append(m.shortTerm,
        Message{Role: "system", Content: fmt.Sprintf("[相关历史记忆]\n%s\n\n[当前任务状态]\n%s", memoryContext, workingContext)},
        Message{Role: "user", Content: userInput},
    )

    resp, err := m.client.CreateChatCompletion(context.Background(),
        openai.ChatCompletionRequest{
            Model:    openai.GPT4o,
            Messages: toOpenAIMessages(messages),
        })
    if err != nil {
        log.Printf("respond error: %v", err)
        return ""
    }
    answer := resp.Choices[0].Message.Content

    m.AddToShortTerm("user", userInput)
    m.AddToShortTerm("assistant", answer)
    m.saveToLongTerm(fmt.Sprintf("用户: %s\n助手: %s", userInput, answer),
        map[string]string{"type": "interaction"})

    return answer
}

func toOpenAIMessages(msgs []Message) []openai.ChatCompletionMessage {
    result := make([]openai.ChatCompletionMessage, len(msgs))
    for i, m := range msgs {
        result[i] = openai.ChatCompletionMessage{Role: m.Role, Content: m.Content}
    }
    return result
}

// ===== 使用示例 =====
func main() {
    memory := NewMemorySystem()
    answer := memory.respond("帮我分析一下最近的销售数据趋势")
    fmt.Println(answer)
}
import com.openai.client.OpenAIClient;
import com.openai.models.*;
import java.util.*;

public class MemorySystem {
    private OpenAIClient client;
    private List collection = new ArrayList<>();
    private List shortTerm = new ArrayList<>();
    private Map working = new HashMap<>();
    private int compressThreshold = 15;

    static class Message {
        String role;
        String content;
        Message(String role, String content) { this.role = role; this.content = content; }
    }

    static class MemoryEntry {
        String id;
        String document;
        Map metadata;
    }

    static class Memory {
        String text;
        Map metadata;
        double similarity;
    }

    public MemorySystem() {
        this.client = new OpenAIClient();
    }

    // ===== 短期记忆管理 =====
    public void addToShortTerm(String role, String content) {
        shortTerm.add(new Message(role, content));
        if (shortTerm.size() > compressThreshold) {
            compressShortTerm();
        }
    }

    private void compressShortTerm() {
        List systemMsgs = new ArrayList<>();
        for (Message m : shortTerm) {
            if ("system".equals(m.role)) systemMsgs.add(m);
        }
        List recentMsgs = shortTerm.subList(
            Math.max(shortTerm.size() - 6, 0), shortTerm.size());
        List oldMsgs = shortTerm.subList(
            systemMsgs.size(), Math.max(shortTerm.size() - 6, systemMsgs.size()));

        if (!oldMsgs.isEmpty()) {
            String summary = summarize(oldMsgs);
            saveToLongTerm(summary, Map.of("type", "summary"));
            shortTerm.clear();
            shortTerm.addAll(systemMsgs);
            shortTerm.add(new Message("system", "[历史摘要]: " + summary));
            shortTerm.addAll(recentMsgs);
        }
    }

    private String summarize(List messages) {
        StringBuilder text = new StringBuilder();
        for (Message m : messages) {
            text.append(m.role).append(": ").append(m.content).append("\n");
        }
        ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
            .model("gpt-4o-mini")
            .addMessage(ChatCompletionMessageParam.builder()
                .role(ChatCompletionMessageParam.Role.SYSTEM)
                .content("将以下对话压缩为简洁摘要。保留所有关键决策、用户偏好、事实信息和约束条件。")
                .build())
            .addMessage(ChatCompletionMessageParam.builder()
                .role(ChatCompletionMessageParam.Role.USER)
                .content(text.toString())
                .build())
            .maxCompletionTokens(300)
            .build();
        ChatCompletion resp = client.chat().completions().create(params);
        return resp.choices().get(0).message().content().orElse("");
    }

    // ===== 长期记忆管理 =====
    public void saveToLongTerm(String text, Map metadata) {
        MemoryEntry entry = new MemoryEntry();
        entry.id = "mem_" + shortTerm.size() + "_" + (text.hashCode() % 10000);
        entry.document = text;
        entry.metadata = metadata != null ? metadata : new HashMap<>();
        collection.add(entry);
    }

    public List retrieveFromLongTerm(String query, int topK) {
        List memories = new ArrayList<>();
        for (int i = 0; i (shortTerm);
        messages.add(new Message("system",
            "[相关历史记忆]\n" + memoryContext + "\n[当前任务状态]\n" + workingContext));
        messages.add(new Message("user", userInput));

        // 调用 LLM 生成响应
        String answer = callLLM(messages);

        addToShortTerm("user", userInput);
        addToShortTerm("assistant", answer);
        saveToLongTerm("用户: " + userInput + "\n助手: " + answer,
            Map.of("type", "interaction"));

        return answer;
    }

    private String callLLM(List messages) {
        // Simplified - build params and call OpenAI
        return "Response from LLM";
    }

    // ===== 使用示例 =====
    public static void main(String[] args) {
        MemorySystem memory = new MemorySystem();
        String answer = memory.respond("帮我分析一下最近的销售数据趋势");
        System.out.println(answer);
    }
}

Embedding 模型选择 | 模型 | 提供商 | 维度 | 质量 | 成本 | 延迟 | 适用场景 | | --- | --- | --- | --- | --- | --- | --- | | text-embedding-3-large | OpenAI | 3072 | ⭐⭐⭐⭐⭐ | $0.13/1M | ~100ms | 高���度检索 | | text-embedding-3-small | OpenAI | 1536 | ⭐⭐⭐⭐ | $0.02/1M | ~50ms | 通用场景 | | bge-large-zh-v1.5 | BAAI(本地) | 1024 | ⭐⭐⭐⭐ | 免费 | ~20ms | 中文场景最佳 | | all-MiniLM-L6-v2 | SentenceTransformers | 384 | ⭐⭐⭐ | 免费 | ~5ms | 快速原型/轻量 | 💡 Embedding 选型建议

  • 中文为主 → BGE 系列(BAAI 北京智源),中文语义理解显著优于 OpenAI 模型
  • 成本敏感 → 本地模型(bge / MiniLM),零 API 费用,但需要 GPU 或 CPU 推理
  • 追求最高质量 → OpenAI text-embedding-3-large,MTEB 排行榜领先
  • 平衡成本与质量 → OpenAI text-embedding-3-small,性价比最优

6.7 反思记忆与情景记忆

前面介绍的三层记忆架构解决了存储和检索问题,但记忆系统还有更高阶的能力:从经验中学习(反思记忆)和记录完整事件(情景记忆)。

🪞 反思记忆(Reflective Memory)

从执行结果中提炼经验教训。不是存储原始对话,而是存储"我学到了什么"。

例:任务失败后反思 → "查询天气 API 时需要加超时处理,否则会卡死整个流程"

📖 情景记忆(Episodic Memory)

记录具体事件的完整上下文:谁、什么、何时、何地、为什么、结果如何。

例:记录完整事件 → "2024-03-15,用户请求分析报告,用了 Python 分析工具,耗时 3 分钟,输出 500 字摘要,用户满意"

与三层记忆的关系

反思记忆和情景记忆都存储在长期记忆(向量数据库)中,但它们的元数据标签不同,检索时可以按类型过滤: | 记忆类型 | 存储内容 | 元数据标签 | 检索场景 | | --- | --- | --- | --- | | 短期记忆 | 原始对话轮次 | 无(直接拼接) | 当前对话上下文 | | 反思记忆 | 提炼的经验教训 | {type: "reflection", task: "xxx"} | 遇到相似任务时参考经验 | | 情景记忆 | 完整事件的5W1H | {type: "episodic", timestamp: "xxx"} | 需要历史上下文时参考事件 | | 工作记忆 | 当前任务中间变量 | 无(Scratchpad) | 多步推理过程中的状态 | ### Reflexion 框架的反思循环

Reflexion 是一个经典的反思型 Agent 框架,其核心创新在于引入反思循环:Agent 执行任务后,如果结果不理想,会进行自我反思,将反思结果存入记忆,下次执行时参考这些经验。

class ReflexionAgent:
    """基于 Reflexion 框架的反思型 Agent"""

    def __init__(self, llm, memory_system):
        self.llm = llm
        self.memory = memory_system
        self.max_retries = 3  # 最大反思重试次数

    def execute_task(self, task_description):
        """执行任务,失败时触发反思循环"""

        for attempt in range(self.max_retries):
            # 1. 检索相关反思经验
            past_reflections = self.memory.retrieve_from_long_term(
                query=task_description,
                top_k=3
            )
            reflection_context = "\n".join([
                f"[过往经验 #{i+1}]: {r['text']}"
                for i, r in enumerate(past_reflections)
                if r.get('metadata', {}).get('type') == 'reflection'
            ])

            # 2. 构建带反思经验的 prompt
            prompt = f"""
            任务: {task_description}

            {f'过往反思经验:\n{reflection_context}' if reflection_context else '这是首次尝试此任务。'}

            请执行任务,注意避免过去犯过的错误。
            """

            # 3. 执行 ReAct 循环
            result = self._react_loop(prompt)

            # 4. 评估结果
            evaluation = self._evaluate(task_description, result)

            if evaluation["success"]:
                # 成功 → 记录为情景记忆
                self.memory._save_to_long_term(
                    text=f"任务成功: {task_description}\n方法: {result['approach']}\n结果: {result['output']}",
                    metadata={"type": "episodic", "outcome": "success"}
                )
                return result

            # 5. 失败 → 触发反思
            reflection = self._reflect(task_description, result, evaluation)

            # 6. 将反思存入记忆
            self.memory._save_to_long_term(
                text=reflection,
                metadata={
                    "type": "reflection",
                    "task": task_description,
                    "attempt": attempt + 1,
                    "outcome": "failure"
                }
            )

            print(f"第 {attempt+1} 次尝试失败,反思已存入记忆")

        return {"output": "任务失败,已达最大重试次数", "success": False}

    def _reflect(self, task, result, evaluation):
        """对失败结果进行反思"""
        reflect_prompt = f"""
        任务: {task}
        我的行动: {result['actions_taken']}
        得到的结果: {result['output']}
        评估反馈: {evaluation['feedback']}

        请反思以下问题:
        1. 我哪里做错了?具体是哪个步骤出了问题?
        2. 为什么会犯这个错误?根本原因是什么?
        3. 下次应该怎么避免?给出具体的改进策略。

        用简洁的要点总结你的反思。
        """
        response = self.llm.generate(reflect_prompt)
        return response

    def _evaluate(self, task, result):
        """用 LLM 评估任务结果"""
        eval_prompt = f"""
        任务: {task}
        结果: {result['output']}

        这个结果是否成功完成了任务?请判断:
- 成功: 返回 JSON {"success": true}
- 失败: 返回 JSON {"success": false, "feedback": "具体问题描述"}
        """
        response = self.llm.generate(eval_prompt)
        # 解析评估结果...
        return {"success": False, "feedback": "结果不完整"}  # 简化示例
class ReflexionAgent {
  private llm: { generate: (prompt: string) => Promise };
  private memory: MemorySystem;
  private maxRetries = 3;

  constructor(llm: any, memorySystem: MemorySystem) {
    this.llm = llm;
    this.memory = memorySystem;
  }

  async executeTask(taskDescription: string): Promise {
    for (let attempt = 0; attempt  r.metadata?.type === 'reflection')
        .map((r, i) => `[过往经验 #${i + 1}]: ${r.text}`)
        .join('\n');

      // 2. 构建带反思经验的 prompt
      const prompt = `任务: ${taskDescription}\n\n` +
        (reflectionContext ? `过往反思经验:\n${reflectionContext}` : '这是首次尝试此任务。') +
        '\n\n请执行任务,注意避免过去犯过的错误。';

      // 3. 执行 ReAct 循环
      const result = await this.reactLoop(prompt);

      // 4. 评估结果
      const evaluation = await this.evaluate(taskDescription, result);

      if (evaluation.success) {
        await this.memory.saveToLongTerm(
          `任务成功: ${taskDescription}\n方法: ${result.approach}\n结果: ${result.output}`,
          { type: 'episodic', outcome: 'success' }
        );
        return result;
      }

      // 5. 失败 → 触发反思
      const reflection = await this.reflect(taskDescription, result, evaluation);

      // 6. 将反思存入记忆
      await this.memory.saveToLongTerm(reflection, {
        type: 'reflection', task: taskDescription, attempt: attempt + 1, outcome: 'failure'
      });

      console.log(`第 ${attempt + 1} 次尝试失败,反思已存入记忆`);
    }
    return { output: '任务失败,已达最大重试次数', success: false };
  }

  private async reflect(task: string, result: any, evaluation: any): Promise {
    const prompt = `任务: ${task}\n我的行动: ${result.actions_taken}\n` +
      `得到的结果: ${result.output}\n评估反馈: ${evaluation.feedback}\n\n` +
      '请反思:\n1. 哪里做错了?\n2. 为什么会犯这个错误?\n3. 下次怎么避免?';
    return await this.llm.generate(prompt);
  }

  private async evaluate(task: string, result: any): Promise {
    const prompt = `任务: ${task}\n结果: ${result.output}\n这个结果是否成功完成任务?`;
    const response = await this.llm.generate(prompt);
    return { success: false, feedback: '结果不完整' };
  }

  private async reactLoop(prompt: string): Promise {
    const response = await this.llm.generate(prompt);
    return { output: response, approach: 'ReAct', actions_taken: 'execute' };
  }
}
package main

import "fmt"

// ReflexionAgent 基于Reflexion框架的反思型Agent
type ReflexionAgent struct {
    llm        LLMInterface
    memory     *MemorySystem
    maxRetries int
}

type LLMInterface interface {
    Generate(prompt string) string
}

type Evaluation struct {
    Success  bool
    Feedback string
}

type TaskResult struct {
    Output       string
    Approach     string
    ActionsTaken string
    Success      bool
}

func NewReflexionAgent(llm LLMInterface, memory *MemorySystem) *ReflexionAgent {
    return &ReflexionAgent{llm: llm, memory: memory, maxRetries: 3}
}

func (a *ReflexionAgent) executeTask(taskDescription string) *TaskResult {
    for attempt := 0; attempt ←
    1 / 5
    →
    重播

### 记忆治理策略代码实现

import time from datetime import datetime, timedelta

class MemoryGovernor: """记忆生命周期治理引擎"""

def init(self, memory_system): self.memory = memory_system self.config = { "max_short_term_turns": 15, # 短期记忆最大轮次 "compress_threshold": 12, # 触发压缩的轮次 "preserve_last_n": 3, # 压缩时保留最近 N 轮 "preserve_system_prompt": True, # 保护 System Prompt "eviction_age_days": 90, # 超过 90 天的低重要性记忆淘汰 "eviction_max_count": 10000, # 总记忆条数上限 "retrieval_similarity_threshold": 0.7, # 检索相似度阈值 "retrieval_top_k": 5, # 检索返回 Top-K "jit_mode": True # JIT 上下文模式 } self.access_stats = {} # 记忆访问统计(用于淘汰决策)

===== 写入阶段 =====

def write(self, content, metadata=None): """写入记忆,自动评估重要性""" importance = self._assess_importance(content)

enriched_meta = { **(metadata or {}), "importance": importance, "created_at": datetime.now().isoformat(), "access_count": 0, "last_accessed": None }

self.memory._save_to_long_term(content, enriched_meta) self.memory.add_to_short_term( enriched_meta.get("role", "user"), content )

def _assess_importance(self, content): """用 LLM 快速评估内容重要性(1-5分)"""

简化版:基于规则的启发式评估

score = 1 if any(kw in content for kw in ["决策", "偏好", "规则", "约束"]): score += 2 if any(kw in content for kw in ["失败", "错误", "反思"]): score += 2 if len(content) > 200: # 长内容通常更重要 score += 1 return min(score, 5)

===== 索引阶段 =====

def index(self): """确保记忆索引完整且高效"""

Chroma/Milvus 等向量数据库自动维护向量索引

额外维护:时间索引、类型索引、访问频率索引

pass # 由向量数据库自动处理

===== 检索阶段(JIT 模式) =====

def retrieve_jit(self, current_step_description): """Just-in-Time 检索:只取当前步骤需要的信息""" if not self.config["jit_mode"]:

非 JIT 模式:一次性加载所有相关记忆

return self.memory.retrieve_from_long_term( current_step_description, top_k=self.config["retrieval_top_k"] )

JIT 模式:分步检索,按需注入

results = self.memory.retrieve_from_long_term( current_step_description, top_k=3 # JIT 模式下减少初始检索量 )

更新访问统计(用于淘汰决策)

for r in results: doc_id = r.get("metadata", {}).get("doc_id", "") if doc_id: self.access_stats[doc_id] =
self.access_stats.get(doc_id, 0) + 1

相似度过滤

filtered = [ r for r in results if r["similarity"] >= self.config["retrieval_similarity_threshold"] ]

return filtered

===== 压缩阶段 =====

def compress_if_needed(self): """检查并执行上下文压缩""" if len(self.memory.short_term) > self.config["compress_threshold"]: self._semantic_preserving_compress()

def _semantic_preserving_compress(self): """语义保护型压缩""" short = self.memory.short_term

分层提取

system_msgs = [m for m in short if m["role"] == "system"] recent = short[-(self.config["preserve_last_n"] * 2):] middle = short[len(system_msgs):-(self.config["preserve_last_n"] * 2)]

识别行动-结果对(工具调用 + 返回值)

action_pairs = [] i = 0 while i content.includes(kw))) score += 2; if (['失败', '错误', '反思'].some(kw => content.includes(kw))) score += 2; if (content.length > 200) score += 1; return Math.min(score, 5); }

// ===== 检索阶段(JIT模式) ===== retrieveJit(currentStepDescription: string): any[] { if (!this.config.jitMode) { return this.memory.retrieveFromLongTerm(currentStepDescription, this.config.retrievalTopK); } const results = this.memory.retrieveFromLongTerm(currentStepDescription, 3); for (const r of results) { const docId = r.metadata?.docId || ''; if (docId) this.accessStats[docId] = (this.accessStats[docId] || 0) + 1; } return results.filter(r => r.similarity >= this.config.retrievalSimilarityThreshold); }

// ===== 压缩阶段 ===== compressIfNeeded(): void { if (this.memory.shortTerm.length > this.config.compressThreshold) { this.semanticPreservingCompress(); } }

private semanticPreservingCompress(): void { // 简化版:保留system和最近N轮,其余摘要 const short = this.memory.shortTerm; const systemMsgs = short.filter(m => m.role === 'system'); const recent = short.slice(-(this.config.preserveLastN * 2)); // 中间内容做摘要... this.memory.shortTerm = [...systemMsgs, ...recent]; }

// ===== 淘汰阶段 ===== evict(): void { const cutoff = new Date(Date.now() - this.config.evictionAgeDays * 86400000); // 时效淘汰 + 容量淘汰 + 质量淘汰 const leastAccessed = Object.entries(this.accessStats) .sort((a, b) => a[1] - b[1]).slice(0, 100); // 删除最少访问的记忆... } }

// ===== 使用示例 ===== const governor = new MemoryGovernor(new MemorySystem()); governor.write("用户偏好简洁的中文回答", { type: 'preference' }); const context = governor.retrieveJit("分析销售数据趋势"); governor.compressIfNeeded(); governor.evict();


package main

import ( "fmt" "time" )

// MemoryGovernor 记忆生命周期治理引擎 type MemoryGovernor struct { memory *MemorySystem config GovernorConfig accessStats map[string]int }

type GovernorConfig struct { MaxShortTermTurns int CompressThreshold int PreserveLastN int PreserveSystemPrompt bool EvictionAgeDays int EvictionMaxCount int RetrievalSimilarityThreshold float64 RetrievalTopK int JITMode bool }

func NewMemoryGovernor(mem *MemorySystem) *MemoryGovernor { return &MemoryGovernor{ memory: mem, config: GovernorConfig{ MaxShortTermTurns: 15, CompressThreshold: 12, PreserveLastN: 3, PreserveSystemPrompt: true, EvictionAgeDays: 90, EvictionMaxCount: 10000, RetrievalSimilarityThreshold: 0.7, RetrievalTopK: 5, JITMode: true, }, accessStats: make(map[string]int), } }

// ===== 写入阶段 ===== func (g *MemoryGovernor) write(content string, metadata map[string]string) { importance := g.assessImportance(content) enriched := map[string]string{ "importance": fmt.Sprintf("%d", importance), "created_at": time.Now().Format(time.RFC3339), } for k, v := range metadata { enriched[k] = v } g.memory.saveToLongTerm(content, enriched) role := "user" if r, ok := metadata["role"]; ok { role = r } g.memory.addToShortTerm(role, content) }

func (g *MemoryGovernor) assessImportance(content string) int { score := 1 keywords := []string{"决策", "偏好", "规则", "约束"} for _, kw := range keywords { if contains(content, kw) { score += 2; break } } failKeywords := []string{"失败", "错误", "反思"} for _, kw := range failKeywords { if contains(content, kw) { score += 2; break } } if len(content) > 200 { score += 1 } if score > 5 { score = 5 } return score }

func contains(s, sub string) bool { return len(s) >= len(sub) && (s == sub || len(sub) == 0 || findSubstring(s, sub)) } func findSubstring(s, sub string) bool { for i := 0; i = g.config.RetrievalSimilarityThreshold { filtered = append(filtered, r) } } return filtered }

// ===== 压缩阶段 ===== func (g *MemoryGovernor) compressIfNeeded() { if len(g.memory.shortTerm) > g.config.CompressThreshold { g.semanticPreservingCompress() } }

func (g MemoryGovernor) semanticPreservingCompress() { short := g.memory.shortTerm var systemMsgs, recent []Message for _, m := range short { if m.Role == "system" { systemMsgs = append(systemMsgs, m) } } if len(short) >= g.config.PreserveLastN2 { recent = short[len(short)-g.config.PreserveLastN*2:] } g.memory.shortTerm = append(systemMsgs, recent...) }

// ===== 淘汰阶段 ===== func (g *MemoryGovernor) evict() { cutoff := time.Now().AddDate(0, 0, -g.config.EvictionAgeDays) _ = cutoff // 时效淘汰 // 容量淘汰 + 质量淘汰 }

// ===== 使用示例 ===== func main() { governor := NewMemoryGovernor(NewMemorySystem()) governor.write("用户偏好简洁的中文回答", map[string]string{"type": "preference"}) _ = governor.retrieveJIT("分析销售数据趋势") governor.compressIfNeeded() governor.evict() }


import java.util.*;

public class MemoryGovernor { private MemorySystem memory; private Map config = new HashMap<>(); private Map accessStats = new HashMap<>();

public MemoryGovernor(MemorySystem memorySystem) { this.memory = memorySystem; config.put("maxShortTermTurns", 15); config.put("compressThreshold", 12); config.put("preserveLastN", 3); config.put("evictionAgeDays", 90); config.put("evictionMaxCount", 10000); config.put("retrievalSimilarityThreshold", 0.7); config.put("retrievalTopK", 5); config.put("jitMode", true); }

// ===== 写入阶段 ===== public void write(String content, Map metadata) { int importance = assessImportance(content); Map enriched = new HashMap<>(metadata != null ? metadata : Map.of()); enriched.put("importance", String.valueOf(importance)); enriched.put("createdAt", java.time.Instant.now().toString()); memory.saveToLongTerm(content, enriched); String role = enriched.getOrDefault("role", "user"); memory.addToShortTerm(role, content); }

private int assessImportance(String content) { int score = 1; if (content.contains("决策") || content.contains("偏好") || content.contains("规则")) score += 2; if (content.contains("失败") || content.contains("错误") || content.contains("反思")) score += 2; if (content.length() > 200) score += 1; return Math.min(score, 5); }

// ===== 检索阶段(JIT模式) ===== @SuppressWarnings("unchecked") public List retrieveJit(String stepDesc) { boolean jitMode = (Boolean) config.get("jitMode"); int topK = (Integer) config.get("retrievalTopK"); if (!jitMode) return memory.retrieveFromLongTerm(stepDesc, topK); List results = memory.retrieveFromLongTerm(stepDesc, 3); for (Memory r : results) { String docId = r.metadata.getOrDefault("docId", ""); if (!docId.isEmpty()) accessStats.merge(docId, 1, Integer::sum); } double threshold = (Double) config.get("retrievalSimilarityThreshold"); List filtered = new ArrayList<>(); for (Memory r : results) { if (r.similarity >= threshold) filtered.add(r); } return filtered; }

// ===== 压缩阶段 ===== public void compressIfNeeded() { int threshold = (Integer) config.get("compressThreshold"); if (memory.shortTermSize() > threshold) { semanticPreservingCompress(); } }

private void semanticPreservingCompress() { // 保留system和最近N轮,其余摘要压缩 int preserveN = (Integer) config.get("preserveLastN"); memory.compressShortTerm(preserveN); }

// ===== 淘汰阶段 ===== public void evict() { int maxCount = (Integer) config.get("evictionMaxCount"); // 时效淘汰 + 容量淘汰 + 质量淘汰 if (accessStats.size() > maxCount) { // 删除最少访问的 } }

public static void main(String[] args) { MemoryGovernor governor = new MemoryGovernor(new MemorySystem()); governor.write("用户偏好简洁的中文回答", Map.of("type", "preference")); List context = governor.retrieveJit("分析销售数据趋势"); governor.compressIfNeeded(); governor.evict(); } }

**✅ 记忆治理的黄金法则**
- **写入要慷慨**:宁可多存,后续靠检索和淘汰来筛选
- **检索要精准**:JIT 原则,只在需要时取需要的量
- **压缩要保守**:保护核心指令和行动-结果对,只压缩中间推理
- **淘汰要渐进**:先降优先级再删除,给"冷记忆"一个回暖的机会
- **指标要量化**:追踪命中率、压缩率、检索延迟,用数据驱动治理策略

## 6.9 长期记忆的静态与动态分类

长期记忆不是铁板一块。仔细观察就会发现,有些记忆**几乎不变**,有些记忆**持续更新**。把长期记忆分为**静态长期记忆**和**动态长期记忆**,是面试中的高频考点,也是工程落地时的关键设计决策。

### 为什么要做这个分类?

如果所有长期记忆都用同一种策略管理,会出现两类问题:
**❌ 不分类的后果**
- **存储浪费**:用户偏好这种不变的信息,如果存入向量数据库每次做语义检索,检索开销大且没有必要--它永远都该被注入
- **信息过时**:历史对话摘要这种高频更新的信息,如果像用户偏好一样"写入就不管了",很快就会变得过时和冗余
- **召回混乱**:不同性质的记忆混在一起检索,模型很难区分"这是永远有效的规则"还是"这是上次的经验"

所以,分类的核心目的是**让不同性质的记忆用不同的管理策略**,从而提升存储效率、召回精准度和上下文利用率。

### 静态长期记忆:几乎不变的核心知识

静态长期记忆的特点是**变化频率极低**,写入后几乎不需要更新。它像一本"个人手册",定义了 Agent 与用户交互的基本规则。
**📖 静态长期记忆的典型内容**
- **用户偏好**:"用户偏好简洁的中文回答"、"用户不喜欢表格输出"--这些偏好一旦确定,几乎不变
- **系统规则**:"不要生成代码中的硬编码密码"、"回答必须附带引用来源"--规则是确定性的约束
- **领域知识**:"公司 API 文档的 Base URL 是 https://api.example.com/v2"--事实性知识,除非系统变更才更新

### 动态长期记忆:持续更新的经验积累

动态长期记忆的特点是**变化频率高**,需要持续更新、压缩和淘汰。它像一本"工作日记",记录了 Agent 的成长轨迹。
**📝 动态长期记忆的典型内容**
- **历史对话摘要**:之前多轮对话的压缩摘要--每次新对话都可能产生新的摘要,旧的摘要需要定期合并
- **任务执行经验**:"上次用 pandas 读 CSV 时遇到了编码问题,解决方案是指定 encoding='utf-8'"--经验会不断积累
- **用户行为模式**:"用户通常在下午 3 点后提问"、"用户习惯先问背景再问具体方案"--模式需要持续观察和修正

### 分类的意义:不同策略适配不同性质

分类带来三方面的差异化策略:
- **不同的存储策略**:静态记忆存本地配置文件(YAML/JSON),读写简单、确定性高;动态记忆存向量数据库,支持语义检索和大规模管理
- **不同的召回策略**:静态记忆直接注入 System Prompt,每次对话都有,不需要检索;动态记忆按相关性从向量库检索,只在需要时注入
- **不同的压缩策略**:静态记忆本身已经是精炼的表达(一条偏好就是一句话),不需要压缩;动态记忆需要定期压缩摘要、淘汰过时内容

### 对比表格 | 维度 | 静态长期记忆 | 动态长期记忆 | | --- | --- | --- | | 典型内容 | 用户偏好、系统规则、领域知识 | 对话摘要、执行经验、行为模式 | | 变化频率 | 极低,几乎不变 | 高,持续更新 | | 存储方式 | 本地配置文件(YAML/JSON) | 向量数据库(Chroma/Milvus) | | 召回方式 | 直接注入 System Prompt | 按相关性语义检索 | | 压缩策略 | 不压缩(本身已精炼) | 定期压缩摘要 + 淘汰过时内容 | | 更新触发 | 用户明确修改偏好时 | 每轮有价值交互后 | **✅ 面试要点**

    面试中回答"长期记忆的管理策略"时,**先分类再谈策略**。不要一上来就说"存向量数据库",而要先区分:"静态记忆存配置文件、直接注入 Prompt;动态记忆存向量库、按需检索、定期压缩"。这体现了你对记忆系统不同性质的深刻理解。

    关联面试题 → Q10

## 6.10 记忆存储触发链路

一个常见误解是:**"每轮对话都触发长期记忆写入"**。如果真的每轮都写,长期记忆库会变成完整对话日志的备份--噪音爆炸,检索失效。

  记忆写入不是"录一切",而是"记值得记的"。就像人类不会记住每顿饭吃了什么,但会记住第一次吃到惊艳味道的那顿。

### 不是每轮都触发!有筛选机制

记忆写入有明确的**触发条件**,只有满足条件的信息才进入长期记忆:
**🎯 记忆写入触发条件**
- **用户表达了新偏好** → 写入**静态长期记忆**(如"我不喜欢表格输出")
- **完成了一个有价值的任务** → 写入**动态长期记忆**(执行经验,如"CSV 编码问题的解决方案")
- **发现了新的上下文信息** → 写入**动态长期记忆**(知识片段,如"公司的 API 版本升级到 v2 了")
- **简单闲聊** → **不触发长期记忆写入**(如"你好"、"今天天气不错")

### 判断标准:什么信息"值得记住"?

判断一条信息是否值得写入长期记忆,核心标准是两个问题:

❓ 问题一:是否有长期参考价值?

      如果这条信息**在未来的对话中还可能被用到**,就值得记住。用户的偏好、问题的解决方案、关键的事实信息--这些都可能在下次交互中再次需要。

      反例:"今天中午吃什么"只在当天有价值,不值得写入长期记忆。

❓ 问题二:是否包含决策/偏好/知识点?

      如果这条信息包含**明确的决策、稳定的偏好或可复用的知识**,就值得记住。

      反例:"嗯嗯好的"这种确认性回复不包含任何决策或知识,不值得写入。

### 链路设计:对话→信息提取→价值评估→分类→写入

记忆写入是一个**五步链路**,每一步都有明确的职责:

### 记忆写入触发引擎代码示例

class MemoryWriteTrigger: """记忆写入触发引擎:判断什么信息值得写入长期记忆"""

def init(self, llm_client, static_store, dynamic_store): self.llm = llm_client self.static_store = static_store # 本地配置文件存储 self.dynamic_store = dynamic_store # 向量数据库存储

===== 第一步:信息提取 =====

def extract_key_info(self, conversation_turn): """从对话中提取关键信息片段""" prompt = f"""从以下对话中提取关键信息。只提取可能具有长期参考价值的内容:

对话内容: {conversation_turn}

提取格式(JSON):

  • type: preference / decision / knowledge / experience / none
  • content: 提取的关键内容(精炼为一句话)
  • confidence: 置信度(0-1)

如果对话中没有值得长期记住的信息,返回 type=none."""

result = self.llm.generate(prompt) return self._parse_extraction(result)

===== 第二步:价值评估 =====

def evaluate_value(self, extracted_info): """评估信息是否值得写入长期记忆""" if extracted_info["type"] == "none": return False, "无关键信息"

规则+LLM 双重评估

规则层:简单闲聊模式直接跳过

trivial_patterns = ["你好", "谢谢", "好的", "嗯嗯", "再见"] if any(p in extracted_info["content"] for p in trivial_patterns): return False, "简单闲聊,无长期价值"

LLM 层:评估长期参考价值

prompt = f"""评估以下信息是否具有长期参考价值(未来对话中可能再次需要):

信息:{extracted_info["content"]} 类型:{extracted_info["type"]}

回答 YES 或 NO,并简要说明理由."""

evaluation = self.llm.generate(prompt) is_valuable = "YES" in evaluation.upper() reason = evaluation.strip()

return is_valuable, reason

===== 第三步:分类 =====

def classify_memory(self, extracted_info): """判断信息属于静态记忆还是动态记忆""" type_mapping = { "preference": "static", # 用户偏好 → 静态记忆 "decision": "static", # 决策规则 → 静态记忆 "knowledge": "dynamic", # 知识片段 → 动态记忆 "experience": "dynamic", # 执行经验 → 动态记忆 } return type_mapping.get(extracted_info["type"], "dynamic")

===== 第四步:写入 =====

def write_memory(self, extracted_info, memory_type): """写入对应类型的记忆存储""" if memory_type == "static":

静态记忆:写入本地配置文件

self.static_store.save( key=extracted_info["type"], value=extracted_info["content"], metadata={"updated_at": datetime.now().isoformat()} ) else:

动态记忆:Embedding → 向量数据库

vector = self.llm.embed(extracted_info["content"]) self.dynamic_store.save( vector=vector, content=extracted_info["content"], metadata={ "type": extracted_info["type"], "created_at": datetime.now().isoformat(), "confidence": extracted_info.get("confidence", 0.8) } )

===== 完整链路 =====

def process_turn(self, conversation_turn): """处理一轮对话的完整记忆写入链路"""

Step 1: 信息提取

extracted = self.extract_key_info(conversation_turn)

Step 2: 价值评估

is_valuable, reason = self.evaluate_value(extracted) if not is_valuable: return {"action": "skip", "reason": reason}

Step 3: 分类

memory_type = self.classify_memory(extracted)

Step 4: 写入

self.write_memory(extracted, memory_type)

return { "action": "write", "type": memory_type, "content": extracted["content"], "reason": reason }

===== 使用示例 =====

trigger = MemoryWriteTrigger(llm, static_config, vector_db)

场景 1:用户表达偏好 → 写入静态记忆

result = trigger.process_turn("以后回答都用简洁的中文,不要用英文")

→ {"action": "write", "type": "static", "content": "用户偏好简洁中文回答"}

场景 2:任务经验 → 写入动态记忆

result = trigger.process_turn("用 pandas 读 CSV 遇到编码问题,指定 encoding='utf-8' 解决了")

→ {"action": "write", "type": "dynamic", "content": "CSV编码问题解决方案..."}

场景 3:简单闲聊 → 不触发写入

result = trigger.process_turn("你好,今天天气不错")

→ {"action": "skip", "reason": "简单闲聊,无长期价值"}


class MemoryWriteTrigger { private llm: any; private memory: MemorySystem; private static readonly TRIGGER_KEYWORDS = ['偏好', '喜欢', '讨厌', '规则', '记住', '别再']; private static readonly VALUE_KEYWORDS = ['决策', '偏好', '规则', '约束', '失败', '错误', '经验'];

constructor(llm: any, memory: MemorySystem) { this.llm = llm; this.memory = memory; }

// 判断是否触发记忆写入 shouldWrite(userInput: string, agentResponse: string): boolean { // 1. 用户明确要求记住 if (MemoryWriteTrigger.TRIGGER_KEYWORDS.some(kw => userInput.includes(kw))) return true; // 2. 包含决策/偏好/知识点 const combined = userInput + agentResponse; if (MemoryWriteTrigger.VALUE_KEYWORDS.some(kw => combined.includes(kw))) return true; // 3. 任务完成(简化判断) if (agentResponse.includes('完成') || agentResponse.includes('成功')) return true; return false; }

// 提取值得记住的信息 async extract(userInput: string, agentResponse: string): Promise { const prompt = 从以下对话中提取值得长期记住的信息,返回JSON数组:\n + 用户: ${userInput}\n助手: ${agentResponse}\n + 提取规则:1.用户偏好 2.关键决策 3.事实信息 4.经验教训; const result = await this.llm.generate(prompt); try { return JSON.parse(result); } catch { return []; } }

// 完整写入流程 async process(userInput: string, agentResponse: string): Promise { if (!this.shouldWrite(userInput, agentResponse)) return; const memories = await this.extract(userInput, agentResponse); for (const mem of memories) { const type = this.classify(mem.content); if (type === 'static') { this.memory.saveToLongTerm(mem.content, { type: 'preference', storage: 'config' }); } else { this.memory.saveToLongTerm(mem.content, { type: 'experience', storage: 'vector' }); } } }

private classify(content: string): 'static' | 'dynamic' { if (['偏好', '规则', '约束'].some(kw => content.includes(kw))) return 'static'; return 'dynamic'; } }


package main

import ( "fmt" "strings" )

// MemoryWriteTrigger 记忆写入触发引擎 type MemoryWriteTrigger struct { llm LLMInterface memory *MemorySystem }

var triggerKeywords = []string{"偏好", "喜欢", "讨厌", "规则", "记住", "别再"} var valueKeywords = []string{"决策", "偏好", "规则", "约束", "失败", "错误", "经验"}

func NewMemoryWriteTrigger(llm LLMInterface, mem *MemorySystem) *MemoryWriteTrigger { return &MemoryWriteTrigger{llm: llm, memory: mem} }

// 判断是否触发记忆写入 func (t *MemoryWriteTrigger) shouldWrite(userInput, agentResponse string) bool { for _, kw := range triggerKeywords { if strings.Contains(userInput, kw) { return true } } combined := userInput + agentResponse for _, kw := range valueKeywords { if strings.Contains(combined, kw) { return true } } if strings.Contains(agentResponse, "完成") || strings.Contains(agentResponse, "成功") { return true } return false }

// 完整写入流程 func (t *MemoryWriteTrigger) process(userInput, agentResponse string) { if !t.shouldWrite(userInput, agentResponse) { return } // 提取值得记住的信息 prompt := fmt.Sprintf("从以下对话中提取值得长期记住的信息:\n用户: %s\n助手: %s", userInput, agentResponse) result := t.llm.Generate(prompt) // 分类并写入 memType := t.classify(result) metadata := map[string]string{"type": memType} t.memory.saveToLongTerm(result, metadata) }

func (t *MemoryWriteTrigger) classify(content string) string { for _, kw := range []string{"偏好", "规则", "约束"} { if strings.Contains(content, kw) { return "static" } } return "dynamic" }


import java.util.*;

public class MemoryWriteTrigger { private LLMInterface llm; private MemorySystem memory; private static final String[] TRIGGER_KEYWORDS = {"偏好", "喜欢", "讨厌", "规则", "记住", "别再"}; private static final String[] VALUE_KEYWORDS = {"决策", "偏好", "规则", "约束", "失败", "错误", "经验"};

public MemoryWriteTrigger(LLMInterface llm, MemorySystem memory) { this.llm = llm; this.memory = memory; }

public boolean shouldWrite(String userInput, String agentResponse) { for (String kw : TRIGGER_KEYWORDS) { if (userInput.contains(kw)) return true; } String combined = userInput + agentResponse; for (String kw : VALUE_KEYWORDS) { if (combined.contains(kw)) return true; } if (agentResponse.contains("完成") || agentResponse.contains("成功")) return true; return false; }

public void process(String userInput, String agentResponse) { if (!shouldWrite(userInput, agentResponse)) return; String prompt = "从以下对话中提取值得长期记住的信息:\n用户: " + userInput + "\n助手: " + agentResponse; String result = llm.generate(prompt); String type = classify(result); memory.saveToLongTerm(result, Map.of("type", type)); }

private String classify(String content) { String[] staticKws = {"偏好", "规则", "约束"}; for (String kw : staticKws) { if (content.contains(kw)) return "static"; } return "dynamic"; } }

**✅ 面试要点**

    面试中回答"记忆写入机制"时,**重点强调筛选而非全量**。链路是:对话→信息提取→价值评估→分类→写入。不是每轮都触发,只有"有长期参考价值"的信息才写入。简单闲聊不触发,偏好/规则写入静态记忆,经验/知识写入动态记忆。

    关联面试题 → Q11

## 6.11 记忆召回决策与上下文污染防控

记忆召回的核心问题不是"能不能找到",而是**"该不该注入"**。召回太多无关记忆,比没有记忆更危险--这就是**上下文污染**。

  上下文污染就像往一杯清水里倒泥浆--信息越多,水质越差。精准的召回不是"把所有相关的都找出来",而是"只把最必要的放进去"。

### 召回决策机制:如何判断哪些记忆需要召回?

大模型判断哪些长期记忆需要召回,依赖三个核心维度的综合评分:
**🎯 意图匹配**

      当前对话意图与记忆标签的**语义相似度**。

      如用户问"怎么处理 CSV 编码问题",与记忆标签"CSV编码"的相似度高,应召回。

⏰ 时间衰减

      **越近期的记忆权重越高**。

      时间衰减函数:weight × e^(-λ × Δt)

      λ 是衰减系数,Δt 是距今时间。3天前的经验比3个月前的更有参考价值。
**📊 频率加权**

      **多次被召回验证有效的记忆权重更高**。

      如果一条经验被召回了5次且每次都有帮助,说明它是高价值的。从未被召回的记忆权重低。

综合评分公式:

recall_score = intent_similarity × w1 + time_decay_weight × w2 + frequency_weight × w3

其中: intent_similarity = cosine_similarity(query_embedding, memory_embedding) time_decay_weight = e^(-λ × days_since_creation) frequency_weight = log(1 + successful_recall_count) w1 + w2 + w3 = 1(权重归一化)


### 召回数量限制:不是越多越好

很多开发者认为"多召回一些总比漏掉好",但这恰恰是上下文污染的根源。
**❌ 召回过多的三大危害**
- **注意力稀释**:模型关注无关内容,忽略核心问题。就像人在嘈杂环境中听不清关键信息
- **指令冲突**:多条记忆互相矛盾。如一条说"偏好简洁回答",另一条说"上次用户要求详细分析"--模型该听谁的?
- **Token浪费**:无关记忆消耗宝贵的上下文窗口,挤占真正有用的信息空间

### 上下文污染防控策略

防控上下文污染需要四道防线:

1

数量上限

每次召回不超过 5 条记忆。宁可少召回一条有用信息,也不要多召回三条噪音。上限可以动态调整:简单问题 ≤3 条,复杂问题 ≤5 条。

    ←
    1 / 4
    →
    重播

### 智能召回引擎代码示例

class SmartRecallEngine: """智能召回引擎:精准召回 + 上下文污染防控"""

def init(self, vector_db, llm_client, config=None): self.db = vector_db self.llm = llm_client self.config = config or { "max_recall_count": 5, # 召回数量上限 "similarity_threshold": 0.7, # 相关性阈值 "time_decay_lambda": 0.05, # 时间衰减系数 "weights": { # 评分权重 "intent": 0.5, "time": 0.3, "frequency": 0.2 }, "priority_order": ["core", "recent_experience", "supplementary"] } self.recall_stats = {} # 记忆召回统计

===== 第一步:多维度评分 =====

def compute_recall_scores(self, query_embedding, candidate_memories): """为每条候选记忆计算综合召回评分""" scores = []

for memory in candidate_memories:

意图匹配:语义相似度

intent_score = cosine_similarity( query_embedding, memory["embedding"] )

时间衰减:越近期权重越高

days_since = (datetime.now() - memory["created_at"]).days time_score = math.exp(-self.config["time_decay_lambda"] * days_since)

频率加权:多次验证有效的记忆权重更高

mem_id = memory["id"] freq_score = math.log(1 + self.recall_stats.get(mem_id, {}).get("success_count", 0))

综合评分

w = self.config["weights"] total_score = ( intent_score * w["intent"] + time_score * w["time"] + freq_score * w["frequency"] )

scores.append({ "memory": memory, "total_score": total_score, "intent_score": intent_score, "time_score": time_score, "freq_score": freq_score })

return scores

===== 第二步:污染防控过滤 =====

def pollution_defense(self, scored_memories): """四道防线过滤,防止上下文污染"""

防线 1:相关性阈值过滤

filtered = [ s for s in scored_memories if s["intent_score"] >= self.config["similarity_threshold"] ]

防线 2:数量上限截断

filtered.sort(key=lambda x: x["total_score"], reverse=True) filtered = filtered[:self.config["max_recall_count"]]

防线 3:冲突检测

filtered = self._resolve_conflicts(filtered)

防线 4:优先级排序

filtered = self._priority_sort(filtered)

return filtered

def _resolve_conflicts(self, scored_memories): """冲突检测:语义矛盾的记忆取最新的""" result = [] used_indices = set()

for i, m1 in enumerate(scored_memories): if i in used_indices: continue conflict_found = False

for j, m2 in enumerate(scored_memories): if j in used_indices or j == i: continue

检测语义矛盾(简化:同类别但内容相反)

if m1["memory"].get("category") == m2["memory"].get("category"):

同类记忆可能冲突,取时间更新的

if m2["memory"]["created_at"] > m1["memory"]["created_at"]: result.append(m2) used_indices.add(j) conflict_found = True break

if not conflict_found: result.append(m1) used_indices.add(i)

return result

def _priority_sort(self, scored_memories): """优先级排序:核心指令 > 最近经验 > 补充信息""" priority_map = {"core": 0, "recent_experience": 1, "supplementary": 2}

for m in scored_memories: cat = m["memory"].get("priority_category", "supplementary") m["priority_rank"] = priority_map.get(cat, 2)

return sorted(scored_memories, key=lambda x: x["priority_rank"])

===== 完整召回流程 =====

def recall(self, query, task_complexity="normal"): """智能召回完整流程"""

Step 1: 向量检索候选记忆

query_embedding = self.llm.embed(query) candidates = self.db.search(query_embedding, top_k=20)

Step 2: 多维度评分

scored = self.compute_recall_scores(query_embedding, candidates)

Step 3: 动态调整召回上限

max_count = self.config["max_recall_count"] if task_complexity == "simple": max_count = 3 # 简单问题召回更少 elif task_complexity == "complex": max_count = 5 # 复杂问题允许更多

self.config["max_recall_count"] = max_count

Step 4: 污染防控过滤

final_memories = self.pollution_defense(scored)

Step 5: 更新召回统计

for m in final_memories: mem_id = m["memory"]["id"] if mem_id not in self.recall_stats: self.recall_stats[mem_id] = {"success_count": 0, "total_count": 0} self.recall_stats[mem_id]["total_count"] += 1

return final_memories

===== 使用示例 =====

engine = SmartRecallEngine(chroma_db, llm)

简单问题:召回 ≤3 条

results = engine.recall("今天有什么日程?", task_complexity="simple")

→ 只返回最相关的 2-3 条近期日程记忆

复杂问题:召回 ≤5 条

results = engine.recall("分析上个季度的销售趋势并给出优化建议", task_complexity="complex")

→ 返回最多 5 条:1条核心指令 + 2条最近经验 + 2条补充知识


class SmartRecallEngine { private memory: MemorySystem; private llm: any; private maxRecall = 5; private similarityThreshold = 0.7;

constructor(memory: MemorySystem, llm: any) { this.memory = memory; this.llm = llm; }

// 三维度评分:意图匹配 × 时间衰减 × 频率加权 async recall(userInput: string, intent?: string): Promise { // 1. 向量检索候选记忆 const candidates = await this.memory.retrieveFromLongTerm( intent || userInput, this.maxRecall * 2 );

// 2. 三维度评分 const scored = candidates.map(mem => ({ ...mem, score: this.score(mem, intent || userInput), }));

// 3. 排序+截断(≤5条) scored.sort((a, b) => b.score - a.score); let recalled = scored.slice(0, this.maxRecall);

// 4. 冲突检测:矛盾记忆取最新 recalled = this.resolveConflicts(recalled);

// 5. 相关性阈值过滤 recalled = recalled.filter(r => r.similarity >= this.similarityThreshold);

return recalled; }

private score(mem: any, query: string): number { const intentScore = mem.similarity; const timeDecay = this.timeDecay(mem.metadata?.createdAt); const frequency = Math.log10((mem.metadata?.accessCount || 1) + 1); return intentScore * 0.6 + timeDecay * 0.3 + frequency * 0.1; }

private timeDecay(createdAt?: string): number { if (!createdAt) return 0.5; const days = (Date.now() - new Date(createdAt).getTime()) / 86400000; return Math.exp(-days / 30); // 30天衰减常数 }

private resolveConflicts(memories: any[]): any[] { // 简化:按内容去重,保留最新的 const seen = new Map(); for (const m of memories) { const key = m.text.substring(0, 50); const existing = seen.get(key); if (!existing || (m.metadata?.createdAt > existing.metadata?.createdAt)) { seen.set(key, m); } } return Array.from(seen.values()); } }

interface RecalledMemory { text: string; metadata: Record; similarity: number; score: number; }


package main

import ( "math" "sort" "time" )

// SmartRecallEngine 智能召回引擎 type SmartRecallEngine struct { memory *MemorySystem maxRecall int similarityThreshold float64 }

func NewSmartRecallEngine(mem *MemorySystem) *SmartRecallEngine { return &SmartRecallEngine{ memory: mem, maxRecall: 5, similarityThreshold: 0.7, } }

// 三维度评分召回 func (e SmartRecallEngine) recall(userInput string) []ScoredMemory { // 1. 向量检索候选 candidates := e.memory.retrieveFromLongTerm(userInput, e.maxRecall2)

// 2. 评分 var scored []ScoredMemory for _, mem := range candidates { scored = append(scored, ScoredMemory{ Memory: mem, Score: e.score(mem, userInput), }) }

// 3. 排序+截断 sort.Slice(scored, func(i, j int) bool { return scored[i].Score > scored[j].Score }) if len(scored) > e.maxRecall { scored = scored[:e.maxRecall] }

// 4. 相关性阈值过滤 var result []ScoredMemory for _, s := range scored { if s.Similarity >= e.similarityThreshold { result = append(result, s) } } return result }

func (e SmartRecallEngine) score(mem Memory, query string) float64 { intentScore := mem.Similarity timeDecay := e.timeDecay(mem.Metadata["created_at"]) frequency := 0.5 // 简化 return intentScore0.6 + timeDecay0.3 + frequency0.1 }

func (e *SmartRecallEngine) timeDecay(createdAt string) float64 { if createdAt == "" { return 0.5 } t, err := time.Parse(time.RFC3339, createdAt) if err != nil { return 0.5 } days := time.Since(t).Hours() / 24 return math.Exp(-days / 30) }

type ScoredMemory struct { Memory Score float64 }


import java.util.*; import java.util.stream.Collectors;

public class SmartRecallEngine { private MemorySystem memory; private int maxRecall = 5; private double similarityThreshold = 0.7;

public SmartRecallEngine(MemorySystem memory) { this.memory = memory; }

// 三维度评分召回 public List recall(String userInput) { // 1. 向量检索候选 List candidates = memory.retrieveFromLongTerm(userInput, maxRecall * 2);

// 2. 评分 List scored = candidates.stream() .map(mem -> new ScoredMemory(mem, score(mem, userInput))) .collect(Collectors.toList());

// 3. 排序+截断 scored.sort((a, b) -> Double.compare(b.score, a.score)); if (scored.size() > maxRecall) scored = scored.subList(0, maxRecall);

// 4. 相关性阈值过滤 return scored.stream() .filter(s -> s.memory.similarity >= similarityThreshold) .collect(Collectors.toList()); }

private double score(Memory mem, String query) { double intentScore = mem.similarity; double timeDecay = timeDecay(mem.metadata.get("created_at")); double frequency = Math.log10(1 + 1); return intentScore * 0.6 + timeDecay * 0.3 + frequency * 0.1; }

private double timeDecay(String createdAt) { if (createdAt == null) return 0.5; long days = (System.currentTimeMillis() - java.time.Instant.parse(createdAt).toEpochMilli()) / 86400000; return Math.exp(-days / 30.0); }

static class ScoredMemory { Memory memory; double score; ScoredMemory(Memory m, double s) { memory = m; score = s; } } }

**✅ 面试要点**

    面试中回答"记忆召回策略"时,**先谈召回决策再谈污染防控**。召回决策靠三维度评分(意图匹配×时间衰减×频率加权),污染防控靠四道防线(数量上限≤5、相关性阈值≥0.7、冲突检测取最新、优先级排序核心>经验>补充)。不要只说"用向量搜索找相关记忆"--那只是第一步,真正的难点是防控上下文污染。

    关联面试题 → Q12

## 6.11a Prompt 工程:静态框架与动态组装

面试追问:**"动态 Prompt 和静态 Prompt 有什么区别?Agent 每轮对话开始时,上下文是如何动态组装的?"**上一节讲了记忆的召回决策,这一节讲召回的结果怎么**组装进 Prompt**--以及哪些部分永远不动(静态),哪些每轮都变(动态)。

  把 Prompt 想象成一栋建筑:**静态部分是地基和框架**(角色定义、安全规则、工具列表)--不会随对话变化;**动态部分是家具和装饰**(记忆片段、对话历史、当前任务)--每轮都换。地基不稳,房子会塌;家具不换,住着不舒服。

### 静态 Prompt:不随对话变化的"固定框架"

静态 Prompt 包含**在所有对话中都相同**的内容,是 Agent 的"宪法":

🔒 静态 Prompt 的四个组成部分
- **角色定义**:"你是一个专业的代码审查专家"--定义 Agent 身份
- **安全规则**:"不要泄露用户隐私、不要执行危险命令"--行为边界
- **输出格式约束**:"回答格式:风险等级→问题→修复建议"--控制输出
- **工具/Skill 描述**:Function Calling Schema + Skill 触发词列表--Agent 的"能力菜单"
**📌 静态 Prompt 的三个特点**
- **不变性**:同一个会话内,内容完全不变(除非用户明确修改偏好)
- **最高优先级**:System Prompt 具有最高权重,模型始终遵循
- **缓存友好**:因为不变,放在 Prompt 前缀处可100%命中 Prompt Cache

在 OpenClaw 等框架中,静态 Prompt 就是从 `SOUL.md`(角色)、`AGENTS.md`(规则)和 ``(Skill列表)读取的内容,注入为 System Prompt 的一部分。这部分**每次请求都一样**,所以是最容易命中缓存的部分。

### 动态 Prompt:每轮对话变化的"活的内容"

动态 Prompt 包含**根据当前对话上下文实时变化**的内容:
**🔄 动态 Prompt 的三个来源**
- **长期记忆召回**:根据当前意图检索的5条相关记忆
- **对话历史**:累积的多轮交互记录(每轮增加)
- **当前任务上下文**:用户刚输入的指令 + 工具返回结果
**⚡ 动态内容的变化频率**
- **慢变化**:Skill完整指令(只在触发时加载)、静态记忆偏好(很少更新)
- **中变化**:长期记忆召回片段(每几轮可能换一批)
- **快变化**:对话历史和工具结果(每轮必变)
**⚠️ 动态内容的缓存特征**
- **慢变化**:多轮后可能命中缓存
- **中变化**:难命中,但可控制注入量
- **快变化**:完全不命中缓存

### 每轮对话的完整 Prompt 组装流程

每轮对话开始时,Agent 需要把静态和动态内容**按顺序组装**成完整 Prompt。顺序很重要--因为缓存是前缀匹配,���定部分必须放最前面:

class PromptAssembler: """每轮对话的 Prompt 动态组装引擎"""

def init(self, system_prompt, tool_schemas, skill_triggers, memory_engine, chat_history_manager):

Layer 1: 静态层(整个会话不变)

self.static_prefix = f"{system_prompt}\n\n{tool_schemas}\n\n{skill_triggers}" self.memory_engine = memory_engine self.history_manager = chat_history_manager

def assemble(self, user_input, intent=None): """组装当前轮次的完整 Prompt""" messages = []

===== Layer 1: 静态框架 =====

System Prompt + 工具Schema + Skill触发词

每次相同 → 100%命中缓存

messages.append({"role": "system", "content": self.static_prefix})

===== Layer 2: 记忆注入 =====

根据当前意图召回长期记忆

只注入与当前任务相关的 ≤5 条记忆

if intent: recalled = self.memory_engine.recall( query=intent, top_k=5, threshold=0.7 ) if recalled: memory_block = "# Relevant Memory\n" + "\n".join(recalled) messages.append({"role": "system", "content": memory_block})

===== Layer 3: 对话历史 =====

如果历史过长,先压缩再注入

history = self.history_manager.get_history(max_tokens=50000) for msg in history: messages.append(msg)

===== Layer 4: 当前请求 =====

messages.append({"role": "user", "content": user_input})

return messages

使用示例

assembler = PromptAssembler( system_prompt="你是代码审查专家...", tool_schemas=json.dumps(tool_definitions), skill_triggers="...", memory_engine=vector_db, chat_history_manager=history_mgr )

每轮调用

messages = assembler.assemble("帮我审查这段代码", intent="代码审查")

→ [system(fixed), system(memory), ...history..., user(current)]


class PromptAssembler { private staticPrefix: string; private memoryEngine: any; private historyManager: any;

constructor(opts: { systemPrompt: string; toolSchemas: string; skillTriggers: string; memoryEngine: any; chatHistoryManager: any; }) { // Layer 1: 静态层(整个会话不变) this.staticPrefix = ${opts.systemPrompt}\n\n${opts.toolSchemas}\n\n${opts.skillTriggers}; this.memoryEngine = opts.memoryEngine; this.historyManager = opts.chatHistoryManager; }

async assemble(userInput: string, intent?: string): Promise { const messages: Message[] = [];

// ===== Layer 1: 静态框架 ===== messages.push({ role: 'system', content: this.staticPrefix });

// ===== Layer 2: 记忆注入 ===== if (intent) { const recalled = await this.memoryEngine.recall(intent, 5, 0.7); if (recalled.length > 0) { const memoryBlock = '# Relevant Memory\n' + recalled.join('\n'); messages.push({ role: 'system', content: memoryBlock }); } }

// ===== Layer 3: 对话历史 ===== const history = this.historyManager.getHistory(50000); messages.push(...history);

// ===== Layer 4: 当前请求 ===== messages.push({ role: 'user', content: userInput });

return messages; } }

interface Message { role: string; content: string; }

// 使用示例 const assembler = new PromptAssembler({ systemPrompt: '你是代码审查专家...', toolSchemas: JSON.stringify(toolDefinitions), skillTriggers: '...', memoryEngine: vectorDb, chatHistoryManager: historyMgr, });

const messages = await assembler.assemble('帮我审查这段代码', '代码审查');


package main

import "encoding/json"

// PromptAssembler 每轮对话的Prompt动态组装引擎 type PromptAssembler struct { staticPrefix string memoryEngine *MemorySystem historyManager *HistoryManager }

type ChatMessage struct { Role string json:"role" Content string json:"content" }

func NewPromptAssembler(systemPrompt, toolSchemas, skillTriggers string, memEngine *MemorySystem, histMgr *HistoryManager) *PromptAssembler { return &PromptAssembler{ staticPrefix: systemPrompt + "\n\n" + toolSchemas + "\n\n" + skillTriggers, memoryEngine: memEngine, historyManager: histMgr, } }

func (a *PromptAssembler) assemble(userInput, intent string) []ChatMessage { var messages []ChatMessage

// ===== Layer 1: 静态框架 ===== messages = append(messages, ChatMessage{Role: "system", Content: a.staticPrefix})

// ===== Layer 2: 记忆注入 ===== if intent != "" { recalled := a.memoryEngine.retrieveFromLongTerm(intent, 5) if len(recalled) > 0 { memoryBlock := "# Relevant Memory\n" for _, r := range recalled { memoryBlock += r.Text + "\n" } messages = append(messages, ChatMessage{Role: "system", Content: memoryBlock}) } }

// ===== Layer 3: 对话历史 ===== history := a.historyManager.getHistory(50000) messages = append(messages, history...)

// ===== Layer 4: 当前请求 ===== messages = append(messages, ChatMessage{Role: "user", Content: userInput})

return messages }

// 使用示例 func main() { schemas, _ := json.Marshal(toolDefinitions) assembler := NewPromptAssembler( "你是代码审查专家...", string(schemas), "...", NewMemorySystem(), NewHistoryManager(), ) messages := assembler.assemble("帮我审查这段代码", "代码审查") _ = messages }


import java.util.*;

public class PromptAssembler { private String staticPrefix; private MemorySystem memoryEngine; private HistoryManager historyManager;

public PromptAssembler(String systemPrompt, String toolSchemas, String skillTriggers, MemorySystem memEngine, HistoryManager histMgr) { // Layer 1: 静态层(整个会话不变) this.staticPrefix = systemPrompt + "\n\n" + toolSchemas + "\n\n" + skillTriggers; this.memoryEngine = memEngine; this.historyManager = histMgr; }

public List assemble(String userInput, String intent) { List messages = new ArrayList<>();

// ===== Layer 1: 静态框架 ===== messages.add(new ChatMessage("system", staticPrefix));

// ===== Layer 2: 记忆注入 ===== if (intent != null && !intent.isEmpty()) { List recalled = memoryEngine.retrieveFromLongTerm(intent, 5); if (!recalled.isEmpty()) { StringBuilder memoryBlock = new StringBuilder("# Relevant Memory\n"); for (Memory r : recalled) { memoryBlock.append(r.text).append("\n"); } messages.add(new ChatMessage("system", memoryBlock.toString())); } }

// ===== Layer 3: 对话历史 ===== List history = historyManager.getHistory(50000); messages.addAll(history);

// ===== Layer 4: 当前请求 ===== messages.add(new ChatMessage("user", userInput));

return messages; }

static class ChatMessage { String role; String content; ChatMessage(String r, String c) { role = r; content = c; } } }

**✅ 面试要点**

    回答"动态Prompt和静态Prompt的区别"时,**先定义再对比**:

    **静态Prompt** = System Prompt(角色+规则+工具Schema+Skill触发词),整个会话不变,放Prompt最前面,100%命中缓存。

    **动态Prompt** = 记忆召回片段 + Skill完整指令 + 对话历史 + 用户当前输入,每轮都变,放Prompt后面,按变化频率分层。

    **组装策略**:固定→半固定→动态→当前请求,四层叠放。核心原则:**不变的放前面最大化缓存,变化的放后面最小化缓存失效**。

    关联面试题 → Q11(动态vs静态Prompt)、Q12(缓存命中设计)

    关联章节 → 1.1.6(上下文窗口Token经济学)、4.13(Token Budget)

## 6.11b 多轮对话状态爆炸与完整解决方案

面试追问:**"多轮对话Agent,怎么解决状态爆炸、上下文溢出?"**前面5.3~5.11分别讲了压缩、治理、召回防控,但面试需要的是**一个完整的系统级解决方案**--怎么把这些策略串起来?这一节给你一个全景框架。

  状态爆炸不是某一个环节的问题,而是**整个系统的压力累积**。就像水管爆裂--不是某一个接头坏了,而是整条管路压力超标。解决方案不是"堵一个洞",而是"设计一套泄压系统"。

### 什么是"状态爆炸"?为什么多轮对话会导致这个问题?

单轮对话很简单:用户问→模型答→结束。但多轮对话Agent中,**状态会从线性增长变为指数增长**:

💥 三种状态爆炸模式
- **对话历史膨胀**:每轮新增 ~1000-3000 Token,10轮就是 10K-30K,50轮可能50K-150K--线性增长但速度很快
- **工具调用结果嵌套**:一个工具返回3K Token,模型基于结果再调2个工具,每个又返回2K--嵌套增长呈**树状展开**
- **推理路径分叉**:ToT/Reflexion模式中,每条路径各自累积上下文,多条路径并行时Token消耗是**倍数增长**

📉 状态爆炸的四大危害
- **上下文溢出**:总Token超过窗口128K,直接截断丢失关键信息
- **注意力稀释**:模型在100K上下文中"迷失",重要信息被淹没(Lost in the Middle)
- **成本爆炸**:每轮都发50K+上下文给API,20轮就是$1+的账单
- **行为漂移**:长上下文导致模型偏离原始指令(Context Rot)

### 完整解决方案:四层泄压体系

解决状态爆炸不是单一策略,而是**四层递进的泄压体系**--从预防到压缩到淘汰到兜底:
**🛡️ Layer 1: 预防--不让压力累积**
- **Token Budget**:设定全局上限(如20万Token),实时累加,超预算立即停止(详见4.13节)
- **JIT召回原则**:只检索当前步骤需要的记忆,不提前加载全量(详见6.8节)
- **输出长度约束**:要求模型"只输出关键结论"而非长篇大论,减少输出Token
- **工具结果精简**:工具返回后先做格式化过滤(只保留关键行),不原样塞入上下文
**📦 Layer 2: 压缩--在信息完整的前提下缩减体积**
- **语义保护型压缩**:锁定System Prompt+原始问题,保护行动-结果对完整性,只压缩中间推理(详见6.5节)
- **三层压缩触发**:主动压缩(80%阈值)→后台压缩(60%)→反应式压缩(413状态码触发紧急压缩)
- **增量压缩**:只压缩"上次压缩后新增"的部分,避免全量重复压缩(详见6.12节)
- **对话历史摘要**:旧对话压缩为摘要存入长期记忆,上下文中只保留摘要和最近3轮原文

🗑️ Layer 3: 淘汰--扔掉不再需要的
- **记忆淘汰策略**:时效淘汰(过期记忆清理)+容量淘汰(超上限淘汰最低价值)+质量淘汰(从未被召回的记忆降优先级)(详见6.8节)
- **召回数量硬限制**:每次召回≤5条,简单问题≤3条,宁可少召回也不多注入噪音
- **工具结果裁剪**:长输出只保留Top-K关键行,命令输出只保留最后20行
- **淘汰前归档**:淘汰的记忆做最终摘要存入"归档层",彻底删除前保留摘要

🚨 Layer 4: 兜底--当所有策略都不够时
- **���急压缩(413反应式)**:上下文超限导致API返回413状态码时,立即触发最激进压缩--只保留System Prompt + 最近1轮 + 任务摘要
- **降级回答**:压缩后仍然超限,告诉用户"当前对话过长,建议开启新会话"
- **自动会话切分**:超过阈值(如50轮)后自动将当前对话摘要存入长期记忆,开启新会话继续
- **人工介入**:预算超限+压缩失败+降级回答后,通知用户人工决策

class StateOverflowGuard: """多轮对话状态爆炸的完整解决方案:四层泄压体系"""

def init(self, token_budget=200000, context_window=128000): self.token_budget = token_budget # 全局Token预算 self.context_window = context_window # 上下文窗口大小 self.current_tokens = 0 # 当前累计Token self.compressor = SemanticCompressor() # 语义保护型压缩器 self.memory_governor = MemoryGovernor() # 记忆治理引擎

===== Layer 1: 预防 =====

def check_budget(self, new_tokens): """预算检查:预防状态累积""" self.current_tokens += new_tokens if self.current_tokens > self.token_budget: raise BudgetExceededError( f"Token预算耗尽: {self.current_tokens}/{self.token_budget}"+ "建议开启新会话或人工介入" )

JIT召回:只检索当前需要的记忆

输出约束:限制模型输出长度

工具精简:过滤工具返回结果

return True

===== Layer 2: 压缩 =====

def compress_if_needed(self, messages): """压缩检查:超80%阈值触发语义保护型压缩""" total = count_tokens(messages) threshold = self.context_window * 0.8 # 80%阈值

if total > threshold:

语义保护型压缩:

锁定层(System Prompt+原始问题) → 不压缩

压缩层(中间推理步骤) → 摘要化

保留层(最近3轮) → 原样保留

compressed = self.compressor.compress(messages) return compressed return messages

===== Layer 3: 淘汰 =====

def evict_if_needed(self, messages): """淘汰检查:压缩后仍超阈值时淘汰低价值内容""" total = count_tokens(messages) threshold = self.context_window * 0.6 # 60%阈值

if total > threshold:

淘汰策略:时效淘汰 + 容量淘汰 + 质量淘汰

召回限制:每次≤5条记忆

工具裁剪:只保留关键行

淘汰前归档:做最终摘要存入长期记忆

evicted = self.memory_governor.evict(messages) return evicted return messages

===== Layer 4: 兜底 =====

def emergency_handler(self, error): """紧急兜底:413状态码或预算超限""" if isinstance(error, ContextOverflowError):

紧急压缩:只保留System Prompt + 最近1轮 + 摘要

return self.compressor.emergency_compress() elif isinstance(error, BudgetExceededError):

降级回答 + 自动会话切分 + 人工介入

return { "action": "degrade", "message": "对话过长,建议开启新会话", "summary": self.memory_governor.archive_session() }

===== 完整流程 =====

def process_turn(self, messages, new_tokens): """每轮对话的状态管理完整流程""" try:

Layer 1: 预防(预算检查)

self.check_budget(new_tokens)

Layer 2: 压缩(超80%触发)

messages = self.compress_if_needed(messages)

Layer 3: 淘汰(超60%触发)

messages = self.evict_if_needed(messages)

return messages except (ContextOverflowError, BudgetExceededError) as e:

Layer 4: 兜底(紧急处理)

return self.emergency_handler(e)


class StateOverflowGuard { private tokenBudget: number; private contextWindow: number; private currentTokens = 0; private compressor: SemanticCompressor; private memoryGovernor: MemoryGovernor;

constructor(tokenBudget = 200000, contextWindow = 128000) { this.tokenBudget = tokenBudget; this.contextWindow = contextWindow; this.compressor = new SemanticCompressor(); this.memoryGovernor = new MemoryGovernor(); }

// ===== Layer 1: 预防 ===== checkBudget(newTokens: number): boolean { this.currentTokens += newTokens; if (this.currentTokens > this.tokenBudget) { throw new BudgetExceededError( Token预算耗尽: ${this.currentTokens}/${this.tokenBudget}建议开启新会话 ); } return true; }

// ===== Layer 2: 压缩 ===== compressIfNeeded(messages: Message[]): Message[] { const total = countTokens(messages); const threshold = this.contextWindow * 0.8; if (total > threshold) { return this.compressor.compress(messages); } return messages; }

// ===== Layer 3: 淘汰 ===== evictIfNeeded(messages: Message[]): Message[] { const total = countTokens(messages); const threshold = this.contextWindow * 0.6; if (total > threshold) { return this.memoryGovernor.evict(messages); } return messages; }

// ===== Layer 4: 兜底 ===== emergencyHandler(error: Error): any { if (error instanceof ContextOverflowError) { return this.compressor.emergencyCompress(); } else if (error instanceof BudgetExceededError) { return { action: 'degrade', message: '对话过长,建议开启新会话', summary: this.memoryGovernor.archiveSession(), }; } }

// ===== 完整流程 ===== processTurn(messages: Message[], newTokens: number): Message[] | any { try { this.checkBudget(newTokens); messages = this.compressIfNeeded(messages); messages = this.evictIfNeeded(messages); return messages; } catch (e) { return this.emergencyHandler(e as Error); } } }

function countTokens(messages: Message[]): number { return messages.reduce((sum, m) => sum + m.content.length / 4, 0); }


package main

import ( "fmt" )

// StateOverflowGuard 多轮对话状态爆炸的完整解决方案 type StateOverflowGuard struct { tokenBudget int contextWindow int currentTokens int compressor *SemanticCompressor memoryGovernor *MemoryGovernor }

func NewStateOverflowGuard(tokenBudget, contextWindow int) *StateOverflowGuard { return &StateOverflowGuard{ tokenBudget: tokenBudget, contextWindow: contextWindow, compressor: NewSemanticCompressor(), memoryGovernor: NewMemoryGovernor(NewMemorySystem()), } }

// ===== Layer 1: 预防 ===== func (g *StateOverflowGuard) checkBudget(newTokens int) error { g.currentTokens += newTokens if g.currentTokens > g.tokenBudget { return fmt.Errorf("Token预算耗尽: %d/%d建议开启新会话", g.currentTokens, g.tokenBudget) } return nil }

// ===== Layer 2: 压缩 ===== func (g *StateOverflowGuard) compressIfNeeded(messages []Message) []Message { total := countTokens(messages) threshold := int(float64(g.contextWindow) * 0.8) if total > threshold { return g.compressor.compress(messages) } return messages }

// ===== Layer 3: 淘汰 ===== func (g *StateOverflowGuard) evictIfNeeded(messages []Message) []Message { total := countTokens(messages) threshold := int(float64(g.contextWindow) * 0.6) if total > threshold { return g.memoryGovernor.evictMessages(messages) } return messages }

// ===== Layer 4: 兜底 ===== func (g *StateOverflowGuard) emergencyHandler(err error) interface{} { if isContextOverflow(err) { return g.compressor.emergencyCompress() } if isBudgetExceeded(err) { return map[string]interface{}{ "action": "degrade", "message": "对话过长,建议开启新会话", "summary": g.memoryGovernor.archiveSession(), } } return nil }

// ===== 完整流程 ===== func (g *StateOverflowGuard) processTurn(messages []Message, newTokens int) interface{} { err := g.checkBudget(newTokens) if err != nil { return g.emergencyHandler(err) } messages = g.compressIfNeeded(messages) messages = g.evictIfNeeded(messages) return messages }

func countTokens(messages []Message) int { total := 0 for _, m := range messages { total += len(m.Content) / 4 } return total }

func isContextOverflow(err error) bool { return false } func isBudgetExceeded(err error) bool { return false }


import java.util.*;

public class StateOverflowGuard { private int tokenBudget; private int contextWindow; private int currentTokens = 0; private SemanticCompressor compressor; private MemoryGovernor memoryGovernor;

public StateOverflowGuard(int tokenBudget, int contextWindow) { this.tokenBudget = tokenBudget; this.contextWindow = contextWindow; this.compressor = new SemanticCompressor(); this.memoryGovernor = new MemoryGovernor(new MemorySystem()); }

// ===== Layer 1: 预防 ===== public boolean checkBudget(int newTokens) throws BudgetExceededException { currentTokens += newTokens; if (currentTokens > tokenBudget) { throw new BudgetExceededException( "Token预算耗尽: " + currentTokens + "/" + tokenBudget + "建议开启新会话"); } return true; }

// ===== Layer 2: 压缩 ===== public List compressIfNeeded(List messages) { int total = countTokens(messages); int threshold = (int) (contextWindow * 0.8); if (total > threshold) { return compressor.compress(messages); } return messages; }

// ===== Layer 3: 淘汰 ===== public List evictIfNeeded(List messages) { int total = countTokens(messages); int threshold = (int) (contextWindow * 0.6); if (total > threshold) { return memoryGovernor.evictMessages(messages); } return messages; }

// ===== Layer 4: 兜底 ===== public Object emergencyHandler(Exception error) { if (error instanceof ContextOverflowException) { return compressor.emergencyCompress(); } else if (error instanceof BudgetExceededException) { Map result = new HashMap<>(); result.put("action", "degrade"); result.put("message", "对话过长,建议开启新会话"); result.put("summary", memoryGovernor.archiveSession()); return result; } return null; }

// ===== 完整流程 ===== public Object processTurn(List messages, int newTokens) { try { checkBudget(newTokens); messages = compressIfNeeded(messages); messages = evictIfNeeded(messages); return messages; } catch (Exception e) { return emergencyHandler(e); } }

private int countTokens(List messages) { int total = 0; for (Message m : messages) total += m.content.length() / 4; return total; } }

**✅ 面试要点:四层泄压体系答题模板**

    面试回答"多轮对话状态爆炸"时,**不要只说"压缩上下文"**,要给系统级方案:

    **1 先定义问题**--多轮对话中状态从线性增长变为树状展开(工具嵌套)和倍数增长(推理分叉),导致上下文溢出、注意力稀释、成本爆炸和行为漂移。

    **2 再给方案框架**--四层泄压体系递进式处理:

    Layer 1 预防:Token Budget全局预算 + JIT召回原则 + 输出长度约束 + 工具结果精简

    Layer 2 压缩:语义保护型压缩(锁定System Prompt和行动-结果对)+ 三层触发机制

    Layer 3 淘汰:记忆治理策略 + 召回硬限制≤5条 + 工具结果裁剪 + 淘汰前归档

    Layer 4 兜底:紧急压缩(413)+ 降级回答 + 自动会话切分 + 人工介入

    **3 最后强调**--不是某一层单独起作用,而是四层递进。预防失败→压缩兜住,压缩不够→淘汰兜住,淘汰也不够→紧急兜底。

    关联面试题 → Q23(状态爆炸)

    关联章节 → 4.13(Token Budget)、5.3(短期记忆管理)、5.5(上下文压缩)、5.8(记忆治理)、5.11(召回防控)、5.12(生产级压缩系统)

## 6.12 工程深度:生产级上下文压缩系统

4.5 节介绍了上下文压缩的概念,但生产级系统远比概念复杂:什么时候压缩?用什么策略压缩?压缩后如何避免频繁重复压缩?压缩失败怎么办?本节基于 WaLiCode 项目的 contextCompressor.ts 和 ChatContextManager.ts 真实实现,讲解生产级上下文压缩系统的六大设计。

### 6.12.1 轮次分割算法

压缩的第一步是理解对话的结构。对话不是扁平的消息列表,而是由多个"轮次"组成--每个轮次以 user message 开始,包含后续的所有 assistant/tool 消息。WaLiCode 的 splitIntoRounds 算法实现了这个分割:

interface ConversationRound { startIndex: number; endIndex: number; messages: ClaudeMessage[]; tokenCount: number; }

function splitIntoRounds(messages: ClaudeMessage[]): ConversationRound[] { const rounds: ConversationRound[] = []; let currentRound: ConversationRound | null = null;

for (let i = 0; i c.type === 'tool_result');

if (!isInlineToolResult && currentRound) { // 结束上一轮 rounds.push(currentRound); currentRound = null; }

if (!currentRound) { currentRound = { startIndex: i, endIndex: i, messages: [msg], tokenCount: estimateMessageTokens(msg), }; continue; } }

// 继续当前轮次 if (currentRound) { currentRound.endIndex = i; currentRound.messages.push(msg); currentRound.tokenCount += estimateMessageTokens(msg); } else { // 开头的 system/assistant 消息 currentRound = { startIndex: i, endIndex: i, messages: [msg], tokenCount: estimateMessageTokens(msg), }; } }

if (currentRound) rounds.push(currentRound); return rounds; }


关键细节:**inline tool_result 不开新轮次**。Anthropic 格式中,工具结果以 `{ role: 'user', content: [{ type: 'tool_result', ... }] }` 形式出现。如果把它当新轮次,会把一个完整的"AI 调工具→获取结果→继续推理"轮次错误地拆成两半。

### 6.12.2 三种压缩触发模式

什么时候该压缩?WaLiCode 设计了三种触发模式,形成三层防线: | 模式 | 触发条件 | 执行方式 | 阻塞对话 | 压缩力度 | | --- | --- | --- | --- | --- | | **主动压缩** | token 占比 > 80% | 同步执行 | 是(短暂) | 中等(保留近期 3-5 轮) | | **后台压缩** | token 占比 > 60% | 异步执行 | 否 | 中等 | | **反应式压缩** | API 返回 413 | 同步紧急执行 | 是 | 激进(只保留 1-2 轮) | ### 6.12.3 AI 摘要 vs 规则摘要

压缩的核心是生成摘要。WaLiCode 支持两种摘要方式:

**AI 摘要(generateAiSummary)**:用专门的压缩 Prompt 让 AI 生成 500 字以内的摘要,保留:用户意图、文件操作、错误信息与解决方案、关键决策、当前进度。质量高但消耗 token。

**规则摘要(generateRuleSummary)**:纯规则提取关键信息,不调 AI。质量低但零成本、零延迟。

// AI 摘要的 System Prompt const COMPRESSION_SYSTEM_PROMPT = `你是一个对话压缩器。将长对话压缩为 500 字以内的摘要。 必须保留:

  1. 用户的原始意图和需求
  2. 已执行的关键文件操作(创建/修改/删除)
  3. 遇到的错误和解决方案
  4. 做出的重要决策
  5. 当前进度和下一步计划 可以省略:
  • 工具调用的详细参数
  • AI 的推理过程
  • 重复的失败尝试`;

async function generateAiSummary(messages: ClaudeMessage[]): Promise { const conversationText = messages.map(msg => { const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content); const label = msg.role === 'user' ? '👤 用户' : msg.role === 'assistant' ? '🤖 助手' : msg.role === 'tool' ? '🔧 工具' : '📋 系统'; // 截断过长内容(避免压缩本身消耗太多 token) const truncated = content.length > 2000 ? content.slice(0, 2000) + '\n... (已截断)' : content; return ${label}:\n${truncated}; }).join('\n\n---\n\n');

try { let fullResponse = ''; for await (const event of ai.stream([ { role: 'user', content: COMPRESSION_SYSTEM_PROMPT }, { role: 'user', content: 请压缩以下对话历史:\n\n${conversationText} }, ], undefined, undefined, undefined, { disableTools: true })) { if (event.type === 'text') fullResponse += event.content || ''; } return fullResponse || generateRuleSummary(messages); } catch (err) { // 降级为规则摘要 return generateRuleSummary(messages); } }


// // AI 摘要的 System Prompt // const COMPRESSION_SYSTEM_PROMPT = 你是一个对话压缩器。将长对话压缩为 500 字以内的摘要。 // 必须保留: // 1. 用户的原始意图和需求 // 2. 已执行的关键文件操作(创建/修改/删除) // 3. 遇到的错误和解决方案 // 4. 做出的重要决策 // 5. 当前进度和下一步计划 // 可以省略: // - 工具调用的详细参数 // - AI 的推理过程 // - 重复的失败尝试;

// async function generateAiSummary(messages: ClaudeMessage[]): Promise { // const conversationText = messages.map(msg => { // const content = typeof msg.content === 'string' // ? msg.content : JSON.stringify(msg.content); // const label = msg.role === 'user' ? '👤 用户' // : msg.role === 'assistant' ? '🤖 助手' // : msg.role === 'tool' ? '🔧 工具' : '📋 系统'; // // 截断过长内容(避免压缩本身消耗太多 token) // const truncated = content.length > 2000 // ? content.slice(0, 2000) + '\n... (已截断)' : content; return ${label}:\n${truncated};; // }).join('\n\n---\n\n');

// try { // let fullResponse = ''; // for await (const event of ai.stream([ // { role: 'user', content: COMPRESSION_SYSTEM_PROMPT }, // { role: 'user', content: 请压缩以下对话历史:\n\n${conversationText} }, // ], undefined, undefined, undefined, { disableTools: true })) { // if (event.type === 'text') fullResponse += event.content || ''; // } return fullResponse || generateRuleSummary(messages);; // } catch (err) { // // 降级为规则摘要 return generateRuleSummary(messages);; // } // }


package main

import ( "fmt" "os" "os/exec" "strings" )

// Python: // AI 摘要的 System Prompt
// Python: const COMPRESSION_SYSTEM_PROMPT = `你是一个对话压缩器。将长对话压缩为 500 字以内的摘要。
// Python: 必须保留:
// Python: 1. 用户的原始意图和需求
// Python: 2. 已执行的关键文件操作(创建/修改/删除)
// Python: 3. 遇到的错误和解决方案
// Python: 4. 做出的重要决策
// Python: 5. 当前进度和下一步计划
// Python: 可以省略:
// Python: - 工具调用的详细参数
// Python: - AI 的推理过程
// Python: - 重复的失败尝试`;

// Python: async function generateAiSummary(messages: ClaudeMessage[]): Promise {
// Python: const conversationText = messages.map(msg => {
// Python: const content = typeof msg.content === 'string'
// Python: ? msg.content : JSON.stringify(msg.content);
// Python: const label = msg.role === 'user' ? '👤 用户'
// Python: : msg.role === 'assistant' ? '🤖 助手'
// Python: : msg.role === 'tool' ? '🔧 工具' : '📋 系统';
// Python: // 截断过长内容(避免压缩本身消耗太多 token)
// Python: const truncated = content.length > 2000
// Python: ? content.slice(0, 2000) + '\n... (已截断)' : content;
return `${label}:\n${truncated}`;
// Python: }).join('\n\n---\n\n');

// Python: try {
// Python: let fullResponse = '';
// Python: for await (const event of ai.stream([
// Python: { role: 'user', content: COMPRESSION_SYSTEM_PROMPT },
// Python: { role: 'user', content: `请压缩以下对话历史:\n\n${conversationText}` },
// Python: ], undefined, undefined, undefined, { disableTools: true })) {
// Python: if (event.type === 'text') fullResponse += event.content || '';
// Python: }
return fullResponse || generateRuleSummary(messages);
// Python: } catch (err) {
// Python: // 降级为规则摘要
return generateRuleSummary(messages);
// Python: }
// Python: }

import java.util.; import java.util.concurrent.; import java.util.regex.; import java.io.;

// Python: // AI 摘要的 System Prompt // Python: const COMPRESSION_SYSTEM_PROMPT = 你是一个对话压缩器。将长对话压缩为 500 字以内的摘要。 // Python: 必须保留: // Python: 1. 用户的原始意图和需求 // Python: 2. 已执行的关键文件操作(创建/修改/删除) // Python: 3. 遇到的错误和解决方案 // Python: 4. 做出的重要决策 // Python: 5. 当前进度和下一步计划 // Python: 可以省略: // Python: - 工具调用的详细参数 // Python: - AI 的推理过程 // Python: - 重复的失败尝试;

// Python: async function generateAiSummary(messages: ClaudeMessage[]): Promise { // Python: const conversationText = messages.map(msg => { // Python: const content = typeof msg.content === 'string' // Python: ? msg.content : JSON.stringify(msg.content); // Python: const label = msg.role === 'user' ? '👤 用户' // Python: : msg.role === 'assistant' ? '🤖 助手' // Python: : msg.role === 'tool' ? '🔧 工具' : '📋 系统'; // Python: // 截断过长内容(避免压缩本身消耗太多 token) // Python: const truncated = content.length > 2000 // Python: ? content.slice(0, 2000) + '\n... (已截断)' : content; return ${label}:\n${truncated};; // Python: }).join('\n\n---\n\n');

// Python: try { // Python: let fullResponse = ''; // Python: for await (const event of ai.stream([ // Python: { role: 'user', content: COMPRESSION_SYSTEM_PROMPT }, // Python: { role: 'user', content: 请压缩以下对话历史:\n\n${conversationText} }, // Python: ], undefined, undefined, undefined, { disableTools: true })) { // Python: if (event.type === 'text') fullResponse += event.content || ''; // Python: } return fullResponse || generateRuleSummary(messages);; // Python: } catch (err) { // Python: // 降级为规则摘要 return generateRuleSummary(messages);; // Python: } // Python: } }


**降级策略**:AI 摘要失败(API 错误、超时、返回空)时自动降级为规则摘要。为什么不直接报错?因为压缩是基础设施--如果压缩本身都能失败,那对话就没法继续了。规则摘要虽然质量低,但至少保留了基本信息,让对话能继续。

### 6.12.4 增量压缩

问题:如果每次 token 超过 80% 就压缩,压缩后降到 50%,用户继续对话又涨到 80%,又要压缩......这会导致频繁压缩,每次压缩都消耗 token(AI 摘要本身也要花 token)。

WaLiCode 的解决方案:**增量压缩阈值**--只有当 token 再次上升到上次压缩水平的 80% 时才再次压缩。

class ContextCompressor { private lastCompressTokenLevel: number = 0; // 上次压缩时的 token 水平

shouldCompress(currentTokens: number, maxTokens: number): boolean { const ratio = currentTokens / maxTokens;

// 首次压缩:80% 阈值 if (this.lastCompressTokenLevel === 0) { return ratio >= 0.8; }

// 后续压缩:必须超过上次压缩水平的 80% // 例如:上次在 80000 token 时压缩,这次必须到 80000 * 0.8 = 64000 才再压缩 // 但由于压缩后降到了 ~50000,用户需要再聊 14000 token 才触发 const threshold = this.lastCompressTokenLevel * 0.8; return currentTokens >= threshold; }

async compress(messages: ClaudeMessage[], maxTokens: number) { const summary = await generateAiSummary(messages); this.lastCompressTokenLevel = countTokens(messages); return this.applySummary(messages, summary); } }


// class ContextCompressor { // private lastCompressTokenLevel: number = 0; // 上次压缩时的 token 水平

// shouldCompress(currentTokens: number, maxTokens: number): boolean { // const ratio = currentTokens / maxTokens;

// // 首次压缩:80% 阈值 // if (this.lastCompressTokenLevel === 0) { return ratio >= 0.8;; // }

// // 后续压缩:必须超过上次压缩水平的 80% // // 例如:上次在 80000 token 时压缩,这次必须到 80000 * 0.8 = 64000 才再压缩 // // 但由于压缩后降到了 ~50000,用户需要再聊 14000 token 才触发 // const threshold = this.lastCompressTokenLevel * 0.8; return currentTokens >= threshold;; // }

// async compress(messages: ClaudeMessage[], maxTokens: number) { // const summary = await generateAiSummary(messages); // this.lastCompressTokenLevel = countTokens(messages); return this.applySummary(messages, summary);; // } // }


package main

import ( "fmt" "os" "os/exec" "strings" )

// ContextCompressor - CLI Agent class type ContextCompressor struct { // Python: private lastCompressTokenLevel: number = 0; // 上次压缩时的 token 水平

// Python: shouldCompress(currentTokens: number, maxTokens: number): boolean {
// Python: const ratio = currentTokens / maxTokens;

// Python: // 首次压缩:80% 阈值
// Python: if (this.lastCompressTokenLevel === 0) {
return ratio >= 0.8;
// Python: }

// Python: // 后续压缩:必须超过上次压缩水平的 80%
// Python: // 例如:上次在 80000 token 时压缩,这次必须到 80000 * 0.8 = 64000 才再压缩
// Python: // 但由于压缩后降到了 ~50000,用户需要再聊 14000 token 才触发
// Python: const threshold = this.lastCompressTokenLevel * 0.8;
return currentTokens >= threshold;
// Python: }

// Python: async compress(messages: ClaudeMessage[], maxTokens: number) {
// Python: const summary = await generateAiSummary(messages);
// Python: this.lastCompressTokenLevel = countTokens(messages);
return this.applySummary(messages, summary);
// Python: }
// Python: }

}


import java.util.; import java.util.concurrent.; import java.util.regex.; import java.io.;

public class ContextCompressor { // Python: private lastCompressTokenLevel: number = 0; // 上次压缩时的 token 水平

// Python: shouldCompress(currentTokens: number, maxTokens: number): boolean { // Python: const ratio = currentTokens / maxTokens;

// Python: // 首次压缩:80% 阈值 // Python: if (this.lastCompressTokenLevel === 0) { return ratio >= 0.8;; // Python: }

// Python: // 后续压缩:必须超过上次压缩水平的 80% // Python: // 例如:上次在 80000 token 时压缩,这次必须到 80000 * 0.8 = 64000 才再压缩 // Python: // 但由于压缩后降到了 ~50000,用户需要再聊 14000 token 才触发 // Python: const threshold = this.lastCompressTokenLevel * 0.8; return currentTokens >= threshold;; // Python: }

// Python: async compress(messages: ClaudeMessage[], maxTokens: number) { // Python: const summary = await generateAiSummary(messages); // Python: this.lastCompressTokenLevel = countTokens(messages); return this.applySummary(messages, summary);; // Python: } // Python: } } }


### 6.12.5 Provider/Reducer 可插拔架构

WaLiCode 的上下文管理采用**Provider/Reducer 可插拔架构**,这是生产级系统与 Demo 级系统的核心区别:

**Provider 模式**:可插拔的上下文来源,每个 Provider 负责一种类型的上下文。

interface ContextProvider { name: string; provide(): MessageContext[]; }

// 文件上下文:当前打开的文件、最近修改的文件 class FileProvider implements ContextProvider { name = 'FileProvider'; provide(): MessageContext[] { return openFiles.map(f => ({ role: 'system', content: 当前文件: ${f.path}\n${f.content.slice(0, 2000)}, priority: MessagePriority.High, })); } }

// 任务上下文:当前任务的目标和进度 class TaskProvider implements ContextProvider { name = 'TaskProvider'; provide(): MessageContext[] { return [{ role: 'system', content: 当前任务: ${currentTask}, priority: MessagePriority.Critical, }]; } }

// 里程碑上下文:关键决策记录 class MilestoneProvider implements ContextProvider { name = 'MilestoneProvider'; provide(): MessageContext[] { return milestones.map(m => ({ role: 'system', content: [${m.type}] ${m.summary}, priority: MessagePriority.High, })); } }


// interface ContextProvider { // name: string; // provide(): MessageContext[]; // }

// // 文件上下文:当前打开的文件、最近修改的文件 // class FileProvider implements ContextProvider { const name = 'FileProvider';; // provide(): MessageContext[] { return openFiles.map(f => ({; // role: 'system', // content: 当前文件: ${f.path}\n${f.content.slice(0, 2000)}, // priority: MessagePriority.High, // })); // } // }

// // 任务上下文:当前任务的目标和进度 // class TaskProvider implements ContextProvider { const name = 'TaskProvider';; // provide(): MessageContext[] { return [{; // role: 'system', // content: 当前任务: ${currentTask}, // priority: MessagePriority.Critical, // }]; // } // }

// // 里程碑上下文:关键决策记录 // class MilestoneProvider implements ContextProvider { const name = 'MilestoneProvider';; // provide(): MessageContext[] { return milestones.map(m => ({; // role: 'system', // content: [${m.type}] ${m.summary}, // priority: MessagePriority.High, // })); // } // }


package main

import ( "fmt" "os" "os/exec" "strings" )

// Python: interface ContextProvider {
// Python: name: string;
// Python: provide(): MessageContext[];
// Python: }

// Python: // 文件上下文:当前打开的文件、最近修改的文件

// FileProvider - CLI Agent class type FileProvider struct { // Python: name = 'FileProvider'; // Python: provide(): MessageContext[] { return openFiles.map(f => ({ // Python: role: 'system', // Python: content: 当前文件: ${f.path}\n${f.content.slice(0, 2000)}, // Python: priority: MessagePriority.High, // Python: })); // Python: } // Python: }

// Python: // 任务上下文:当前任务的目标和进度

// TaskProvider - CLI Agent class type TaskProvider struct { // Python: name = 'TaskProvider'; // Python: provide(): MessageContext[] { return [{ // Python: role: 'system', // Python: content: 当前任务: ${currentTask}, // Python: priority: MessagePriority.Critical, // Python: }]; // Python: } // Python: }

// Python: // 里程碑上下文:关键决策记录

// MilestoneProvider - CLI Agent class type MilestoneProvider struct { // Python: name = 'MilestoneProvider'; // Python: provide(): MessageContext[] { return milestones.map(m => ({ // Python: role: 'system', // Python: content: [${m.type}] ${m.summary}, // Python: priority: MessagePriority.High, // Python: })); // Python: } // Python: } }


import java.util.; import java.util.concurrent.; import java.util.regex.; import java.io.;

// Python: interface ContextProvider { // Python: name: string; // Python: provide(): MessageContext[]; // Python: }

// Python: // 文件上下文:当前打开的文件、最近修改的文件 public class FileProvider { // Python: name = 'FileProvider'; // Python: provide(): MessageContext[] { return openFiles.map(f => ({; // Python: role: 'system', // Python: content: 当前文件: ${f.path}\n${f.content.slice(0, 2000)}, // Python: priority: MessagePriority.High, // Python: })); // Python: } // Python: }

// Python: // 任务上下文:当前任务的目标和进度 public class TaskProvider { // Python: name = 'TaskProvider'; // Python: provide(): MessageContext[] { return [{; // Python: role: 'system', // Python: content: 当前任务: ${currentTask}, // Python: priority: MessagePriority.Critical, // Python: }]; // Python: } // Python: }

// Python: // 里程碑上下文:关键决策记录 public class MilestoneProvider { // Python: name = 'MilestoneProvider'; // Python: provide(): MessageContext[] { return milestones.map(m => ({; // Python: role: 'system', // Python: content: [${m.type}] ${m.summary}, // Python: priority: MessagePriority.High, // Python: })); // Python: } // Python: } } }


**Reducer 模式**:可插拔的裁剪策略,按不同策略裁剪消息。

interface MessageReducer { name: string; reduce(messages: MessageContext[], maxTokens: number): MessageContext[]; }

class PriorityReducer implements MessageReducer { name = 'PriorityReducer';

reduce(messages: MessageContext[], maxTokens: number): MessageContext[] { // 第一轮:Critical 和 High 无条件保留 // 第二轮:按优先级从高到低,在预算内逐条追加 // 第三轮:按时间排序输出 const result: MessageContext[] = []; let usedChars = 0; const maxChars = maxTokens * 4; // ~4 chars/token

// Critical + High 无条件保留 for (const msg of messages) { if (msg.priority === MessagePriority.Critical || msg.priority === MessagePriority.High) { result.push(msg); usedChars += msg.content.length; } }

// 按优先级填充剩余预算 for (const priority of [MessagePriority.Medium, MessagePriority.Low]) { for (const msg of messages) { if (msg.priority !== priority || result.includes(msg)) continue; if (usedChars + msg.content.length > maxChars) continue; result.push(msg); usedChars += msg.content.length; } }

// 按时间排序 return result.sort((a, b) => a.timestamp - b.timestamp); } }


// interface MessageReducer { // name: string; // reduce(messages: MessageContext[], maxTokens: number): MessageContext[]; // }

// class PriorityReducer implements MessageReducer { const name = 'PriorityReducer';;

// reduce(messages: MessageContext[], maxTokens: number): MessageContext[] { // // 第一轮:Critical 和 High 无条件保留 // // 第二轮:按优先级从高到低,在预算内逐条追加 // // 第三轮:按时间排序输出 // const result: MessageContext[] = []; // let usedChars = 0; // const maxChars = maxTokens * 4; // ~4 chars/token

// // Critical + High 无条件保留 // for (const msg of messages) { // if (msg.priority === MessagePriority.Critical // || msg.priority === MessagePriority.High) { // result.push(msg); // usedChars += msg.content.length; // } // }

// // 按优先级填充剩余预算 // for (const priority of [MessagePriority.Medium, MessagePriority.Low]) { // for (const msg of messages) { // if (msg.priority !== priority || result.includes(msg)) continue; // if (usedChars + msg.content.length > maxChars) continue; // result.push(msg); // usedChars += msg.content.length; // } // }

// // 按时间排序 return result.sort((a, b) => a.timestamp - b.timestamp);; // } // }


package main

import ( "fmt" "os" "os/exec" "strings" )

// Python: interface MessageReducer {
// Python: name: string;
// Python: reduce(messages: MessageContext[], maxTokens: number): MessageContext[];
// Python: }

// PriorityReducer - CLI Agent class type PriorityReducer struct { // Python: name = 'PriorityReducer';

// Python: reduce(messages: MessageContext[], maxTokens: number): MessageContext[] {
// Python: // 第一轮:Critical 和 High 无条件保留
// Python: // 第二轮:按优先级从高到低,在预算内逐条追加
// Python: // 第三轮:按时间排序输出
// Python: const result: MessageContext[] = [];
// Python: let usedChars = 0;
// Python: const maxChars = maxTokens * 4; // ~4 chars/token

// Python: // Critical + High 无条件保留
// Python: for (const msg of messages) {
// Python: if (msg.priority === MessagePriority.Critical
// Python: || msg.priority === MessagePriority.High) {
// Python: result.push(msg);
// Python: usedChars += msg.content.length;
// Python: }
// Python: }

// Python: // 按优先级填充剩余预算
// Python: for (const priority of [MessagePriority.Medium, MessagePriority.Low]) {
// Python: for (const msg of messages) {
// Python: if (msg.priority !== priority || result.includes(msg)) continue;
// Python: if (usedChars + msg.content.length > maxChars) continue;
// Python: result.push(msg);
// Python: usedChars += msg.content.length;
// Python: }
// Python: }

// Python: // 按时间排序
return result.sort((a, b) => a.timestamp - b.timestamp);
// Python: }
// Python: }

}


import java.util.; import java.util.concurrent.; import java.util.regex.; import java.io.;

// Python: interface MessageReducer { // Python: name: string; // Python: reduce(messages: MessageContext[], maxTokens: number): MessageContext[]; // Python: }

public class PriorityReducer { // Python: name = 'PriorityReducer';

// Python: reduce(messages: MessageContext[], maxTokens: number): MessageContext[] { // Python: // 第一轮:Critical 和 High 无条件保留 // Python: // 第二轮:按优先级从高到低,在预算内逐条追加 // Python: // 第三轮:按时间排序输出 // Python: const result: MessageContext[] = []; // Python: let usedChars = 0; // Python: const maxChars = maxTokens * 4; // ~4 chars/token

// Python: // Critical + High 无条件保留 // Python: for (const msg of messages) { // Python: if (msg.priority === MessagePriority.Critical // Python: || msg.priority === MessagePriority.High) { // Python: result.push(msg); // Python: usedChars += msg.content.length; // Python: } // Python: }

// Python: // 按优先级填充剩余预算 // Python: for (const priority of [MessagePriority.Medium, MessagePriority.Low]) { // Python: for (const msg of messages) { // Python: if (msg.priority !== priority || result.includes(msg)) continue; // Python: if (usedChars + msg.content.length > maxChars) continue; // Python: result.push(msg); // Python: usedChars += msg.content.length; // Python: } // Python: }

// Python: // 按时间排序 return result.sort((a, b) => a.timestamp - b.timestamp);; // Python: } // Python: } } }


消息优先级推断规则:
- **Critical**:错误消息、系统指令、当前任务
- **High**:文件操作、用户配置、里程碑事件
- **Medium**:工具调用结果、AI 推理过程
- **Low**:AI 长文本解释、闲聊内容

为什么用可插拔设计?因为不同场景需要不同的 Provider 和 Reducer 组合。编码场景需要 FileProvider + TaskProvider + PriorityReducer;聊天场景可能只需要 MemoryProvider + SlidingWindowReducer。写死在代码里意味着每次改需求都要改核心代码,可插拔意味着只需注册新组件。

### 6.12.6 双作用域记忆系统

WaLiCode 的记忆系统分为两个作用域: | 维度 | User Scope(用户级) | Project Scope(项目级) | | --- | --- | --- | | 作用范围 | 跨项目共享 | 仅当前项目 | | 存储内容 | 用户偏好、通用知识、历史决策 | 架构决策、模块依赖、编码规范 | | 容量限制 | LRU 淘汰,最多 20 条 | LRU 淘汰,最多 20 条 | | 示例 | "用户喜欢用 TypeScript" | "本项目用 pnpm,不要用 npm" | ```
class MemoryService {
  private userMemory: Map = new Map();
  private projectMemory: Map = new Map();
  private readonly MAX_ENTRIES = 20;

  set(scope: 'user' | 'project', key: string, value: string) {
    const store = scope === 'user' ? this.userMemory : this.projectMemory;

    // LRU 淘汰:超过 20 条时删除最久未访问的
    if (store.size >= this.MAX_ENTRIES && !store.has(key)) {
      const oldest = [...store.entries()]
        .sort((a, b) => a[1].lastAccessed - b[1].lastAccessed)[0];
      store.delete(oldest[0]);
    }

    store.set(key, { value, lastAccessed: Date.now() });
  }

  get(scope: 'user' | 'project', key: string): string | undefined {
    const store = scope === 'user' ? this.userMemory : this.projectMemory;
    const entry = store.get(key);
    if (entry) entry.lastAccessed = Date.now(); // LRU 更新
    return entry?.value;
  }

  // 生成记忆 Prompt 注入
  toPrompt(scope: 'user' | 'project' | 'both'): string {
    const entries: MemoryEntry[] = [];
    if (scope === 'user' || scope === 'both') {
      entries.push(...this.userMemory.values());
    }
    if (scope === 'project' || scope === 'both') {
      entries.push(...this.projectMemory.values());
    }
    return entries.map(e => `- ${e.value}`).join('\n');
  }
}
// class MemoryService {
  // private userMemory: Map = new Map();
  // private projectMemory: Map = new Map();
  // private readonly MAX_ENTRIES = 20;

  // set(scope: 'user' | 'project', key: string, value: string) {
  // const store = scope === 'user' ? this.userMemory : this.projectMemory;

  // // LRU 淘汰:超过 20 条时删除最久未访问的
  // if (store.size >= this.MAX_ENTRIES && !store.has(key)) {
  // const oldest = [...store.entries()]
  // .sort((a, b) => a[1].lastAccessed - b[1].lastAccessed)[0];
  // store.delete(oldest[0]);
  // }

  // store.set(key, { value, lastAccessed: Date.now() });
  // }

  // get(scope: 'user' | 'project', key: string): string | undefined {
  // const store = scope === 'user' ? this.userMemory : this.projectMemory;
  // const entry = store.get(key);
  // if (entry) entry.lastAccessed = Date.now(); // LRU 更新
  return entry?.value;;
  // }

  // // 生成记忆 Prompt 注入
  // toPrompt(scope: 'user' | 'project' | 'both'): string {
  // const entries: MemoryEntry[] = [];
  // if (scope === 'user' || scope === 'both') {
  // entries.push(...this.userMemory.values());
  // }
  // if (scope === 'project' || scope === 'both') {
  // entries.push(...this.projectMemory.values());
  // }
  return entries.map(e => `- ${e.value}`).join('\n');;
  // }
  // }
package main

import (
	"fmt"
	"os"
	"os/exec"
	"strings"
)

// MemoryService - CLI Agent class
type MemoryService struct {
	// Python: private userMemory: Map = new Map();
	// Python: private projectMemory: Map = new Map();
	// Python: private readonly MAX_ENTRIES = 20;

	// Python: set(scope: 'user' | 'project', key: string, value: string) {
	// Python: const store = scope === 'user' ? this.userMemory : this.projectMemory;

	// Python: // LRU 淘汰:超过 20 条时删除最久未访问的
	// Python: if (store.size >= this.MAX_ENTRIES && !store.has(key)) {
	// Python: const oldest = [...store.entries()]
	// Python: .sort((a, b) => a[1].lastAccessed - b[1].lastAccessed)[0];
	// Python: store.delete(oldest[0]);
	// Python: }

	// Python: store.set(key, { value, lastAccessed: Date.now() });
	// Python: }

	// Python: get(scope: 'user' | 'project', key: string): string | undefined {
	// Python: const store = scope === 'user' ? this.userMemory : this.projectMemory;
	// Python: const entry = store.get(key);
	// Python: if (entry) entry.lastAccessed = Date.now(); // LRU 更新
	return entry?.value;
	// Python: }

	// Python: // 生成记忆 Prompt 注入
	// Python: toPrompt(scope: 'user' | 'project' | 'both'): string {
	// Python: const entries: MemoryEntry[] = [];
	// Python: if (scope === 'user' || scope === 'both') {
	// Python: entries.push(...this.userMemory.values());
	// Python: }
	// Python: if (scope === 'project' || scope === 'both') {
	// Python: entries.push(...this.projectMemory.values());
	// Python: }
	return entries.map(e => `- ${e.value}`).join('\n');
	// Python: }
	// Python: }
}
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.*;
import java.io.*;

public class MemoryService {
        // Python: private userMemory: Map = new Map();
        // Python: private projectMemory: Map = new Map();
        // Python: private readonly MAX_ENTRIES = 20;

        // Python: set(scope: 'user' | 'project', key: string, value: string) {
        // Python: const store = scope === 'user' ? this.userMemory : this.projectMemory;

        // Python: // LRU 淘汰:超过 20 条时删除最久未访问的
        // Python: if (store.size >= this.MAX_ENTRIES && !store.has(key)) {
        // Python: const oldest = [...store.entries()]
        // Python: .sort((a, b) => a[1].lastAccessed - b[1].lastAccessed)[0];
        // Python: store.delete(oldest[0]);
        // Python: }

        // Python: store.set(key, { value, lastAccessed: Date.now() });
        // Python: }

        // Python: get(scope: 'user' | 'project', key: string): string | undefined {
        // Python: const store = scope === 'user' ? this.userMemory : this.projectMemory;
        // Python: const entry = store.get(key);
        // Python: if (entry) entry.lastAccessed = Date.now(); // LRU 更新
        return entry?.value;;
        // Python: }

        // Python: // 生成记忆 Prompt 注入
        // Python: toPrompt(scope: 'user' | 'project' | 'both'): string {
        // Python: const entries: MemoryEntry[] = [];
        // Python: if (scope === 'user' || scope === 'both') {
        // Python: entries.push(...this.userMemory.values());
        // Python: }
        // Python: if (scope === 'project' || scope === 'both') {
        // Python: entries.push(...this.projectMemory.values());
        // Python: }
        return entries.map(e => `- ${e.value}`).join('\n');;
        // Python: }
        // Python: }
    }
}

6.12.6 压缩可靠性保障

上下文压缩是 Agent 对话稳定性的最后一道防线,但如果压缩本身不可靠,反而会成为新的问题源。生产环境中必须解决三个可靠性挑战:压缩失败回退语义丢失检测压缩对对话连贯性的影响

挑战 1:压缩失败回退

AI 摘要依赖 LLM 生成,而 LLM 可能超时、限流或返回乱码。WaLiCode 的做法是三层降级链: | 层级 | 触发条件 | 策略 | 代价 | | --- | --- | --- | --- | | L1 AI 摘要 | 正常流程 | 调 LLM 生成 500 字以内摘要 | 消耗 ~2000 token | | L2 规则摘要 | AI 超时/限流/JSON 解析失败 | 正则提取关键信息(用户意图/文件操作/错误信息/决策) | 质量较低,零 token | | L3 硬截断 | 规则摘要也失败(极端情况) | 只保留最近 2 轮对话 + system prompt | 丢失全部历史 | ``` class CompressionFallbackChain: """三层降级压缩链"""

def init(self, llm_client): self.llm = llm_client self.max_ai_attempts = 2 self.max_ai_tokens = 2000

def compress(self, messages: list, target_ratio: float = 0.3) -> dict: """压缩消息列表,返回 {success, strategy, compressed, error}"""

L1: 尝试 AI 摘要

for attempt in range(self.max_ai_attempts): try: result = self._ai_compress(messages, target_ratio) if result and self._validate(result): return {"success": True, "strategy": "ai", "compressed": result} except (TimeoutError, RateLimitError) as e: print(f"AI 摘要第{attempt+1}次失败: {e}") continue except Exception as e: print(f"AI 摘要异常: {e}") break

L2: 降级为规则摘要

try: result = self._rule_compress(messages) if result: return {"success": True, "strategy": "rule", "compressed": result} except Exception as e: print(f"规则摘要失败: {e}")

L3: 硬截断

return { "success": True, "strategy": "truncate", "compressed": messages[:4], # system + 最近2轮 "warning": "硬截断,历史已丢失" }

def _ai_compress(self, messages, ratio): """AI 摘要""" prompt = f"将以下对话压缩为500字以内的摘要,保留:用户意图/文件操作/错误与修复/关键决策/当前进度\n\n{messages}" return self.llm.chat(prompt, max_tokens=self.max_ai_tokens)

def _rule_compress(self, messages): """规则摘要:正则提取关键信息""" import re summary_parts = [] for msg in messages: content = msg.get("content", "")

提取用户意图

if msg["role"] == "user": summary_parts.append(f"[用户] {content[:100]}")

提取错误信息

elif "error" in content.lower() or "报错" in content: error_match = re.search(r'(error|报错|异常)[:\s]*([^\n]+)', content, re.I) if error_match: summary_parts.append(f"[错误] {error_match.group(2)[:80]}")

提取文件操作

file_match = re.search(r'(创建|修改|删除|读取)[文件了]*(.+?.[a-z]+)', content) if file_match: summary_parts.append(f"[文件] {file_match.group(0)[:80]}") return "\n".join(summary_parts) if summary_parts else None

def _validate(self, result): """校验摘要质量""" if not result or len(result) 2000: return False return True


interface CompressionResult { success: boolean; strategy: 'ai' | 'rule' | 'truncate'; compressed: Message[]; warning?: string; }

class CompressionFallbackChain { private llm: LLMClient; private maxAiAttempts = 2;

constructor(llm: LLMClient) { this.llm = llm; }

async compress(messages: Message[], targetRatio = 0.3): Promise { // L1: AI 摘要 for (let i = 0; i { const prompt = 将以下对话压缩为500字以内的摘要...\n\n${JSON.stringify(messages)}; const result = await this.llm.chat(prompt, { maxTokens: 2000 }); return [{ role: 'system', content: [历史摘要] ${result} }]; }

private ruleCompress(messages: Message[]): Message[] | null { const parts: string[] = []; for (const msg of messages) { if (msg.role === 'user') parts.push([用户] ${msg.content.slice(0, 100)}); if (/error|报错|异常/i.test(msg.content)) { const m = msg.content.match(/(error|报错|异常)[:\s]*([^\n]+)/i); if (m) parts.push([错误] ${m[2].slice(0, 80)}); } } return parts.length ? [{ role: 'system', content: [历史摘要] ${parts.join('\n')} }] : null; }

private validate(result: Message[]): boolean { const content = result[0]?.content ?? ''; return content.length >= 20 && content.length 2 { parts = append(parts, "[错误] "+truncate(m[2], 80)) } } } if len(parts) == 0 { return nil, errors.New("无关键信息") } return []Message{{Role: "system", Content: "[历史摘要] " + strings.Join(parts, "\n")}}, nil }

func (c *CompressionFallbackChain) validate(result []Message) bool { if len(result) == 0 { return false } content := result[0].Content return len(content) >= 20 && len(content) > compressed; String warning; }

class CompressionFallbackChain { private final LLMClient llm; private final int maxAiAttempts = 2;

public CompressionFallbackChain(LLMClient llm) { this.llm = llm; }

public CompressionResult compress(List> messages, double targetRatio) { // L1: AI 摘要 for (int i = 0; i > result = aiCompress(messages, targetRatio); if (result != null && validate(result)) { CompressionResult r = new CompressionResult(); r.success = true; r.strategy = "ai"; r.compressed = result; return r; } } catch (Exception e) { System.err.println("AI 摘要第" + (i + 1) + "次失败: " + e.getMessage()); } }

// L2: 规则摘要 try { List> result = ruleCompress(messages); if (result != null) { CompressionResult r = new CompressionResult(); r.success = true; r.strategy = "rule"; r.compressed = result; return r; } } catch (Exception e) { System.err.println("规则摘要失败: " + e.getMessage()); }

// L3: 硬截断 CompressionResult r = new CompressionResult(); r.success = true; r.strategy = "truncate"; int endIdx = Math.min(4, messages.size()); r.compressed = new ArrayList<>(messages.subList(0, endIdx)); r.warning = "硬截断,历史已丢失"; return r; }

private List> aiCompress(List> messages, double ratio) throws Exception { String prompt = "将以下对话压缩为500字以内的摘要...\n\n" + messages.toString(); String result = llm.chat(prompt, 2000); if (result == null || result.length() 2000) { return null; } return List.of(Map.of("role", "system", "content", "[历史摘要] " + result)); }

private List> ruleCompress(List> messages) { List parts = new ArrayList<>(); Pattern errorPattern = Pattern.compile("(?i)(error|报错|异常)[:\\s]*([^\\n]+)"); for (Map msg : messages) { String content = msg.getOrDefault("content", ""); if ("user".equals(msg.get("role"))) { parts.add("[用户] " + content.substring(0, Math.min(100, content.length()))); } Matcher m = errorPattern.matcher(content); if (m.find()) { parts.add("[错误] " + m.group(2).substring(0, Math.min(80, m.group(2).length()))); } } if (parts.isEmpty()) return null; return List.of(Map.of("role", "system", "content", "[历史摘要] " + String.join("\n", parts))); }

private boolean validate(List> result) { if (result == null || result.isEmpty()) return false; String content = result.get(0).getOrDefault("content", ""); return content.length() >= 20 && content.length() !constraintsAfter.includes(c));

if (lost.length > 0) { // 将丢失的约束重新注入摘要 summary = [重要约束] ${lost.join('; ')}\n + summary; }


**实现要点**:约束提取用正则匹配常见模式("不要..."/"必须..."/"用..."/"目标..."),不依赖 LLM。这是一个**低成本高收益**的保障措施——零额外 API 调用,只需几十行正则代码,就能避免最常见的压缩事故。

#### 挑战 3:压缩对对话连贯性的影响

压缩会改变上下文结构,可能导致 LLM 的回答出现**断层**——比如用户问"刚才那个文件改好了吗?",但压缩后历史中已经没有文件操作的记录了。三种应对策略: | 策略 | 做法 | 适用场景 | | --- | --- | --- | | **压缩标记注入** | 在压缩位置插入 `[已压缩 N 轮对话]` 标记,让 LLM 知道有历史被省略 | 大多数场景 | | **关键实体保留** | 压缩时强制保留最近提及的文件名、函数名、变量名,即使轮次被压缩 | 代码 Agent / IDE Agent | | **按需展开** | 用户追问历史细节时,从向量库检索被压缩的原始对话片段,临时注入上下文 | 长对话 > 50 轮 | **🔗 5.12 核心要点**

**轮次分割**:按 user message 分割对话为多个轮次,inline tool_result 不开新轮次。

**三层压缩触发**:主动(80%)→ 后台(60%)→ 反应式(413),层层递进。

**AI 摘要 vs 规则摘要**:AI 质量高但贵,规则零成本但糙,AI 失败降级为规则。

**增量压缩**:上次压缩水平的 80% 才再压缩,避免频繁压缩浪费 token。

**Provider/Reducer 架构**:可插拔设计,按场景组合不同的上下文来源和裁剪策略。

**双作用域记忆**:User Scope 跨项目,Project Scope 仅当前项目,LRU 淘汰。

## 6.13 主流记忆框架对比(2026更新)

前面几节讲了记忆系统的原理和工程实践。2024-2025 年,一批专门的**记忆框架**崛起,它们把记忆管理从 Agent 主逻辑中解耦出来,提供开箱即用的记忆层。面试中经常被问到:"你了解哪些记忆框架?它们有什么区别?"

### Mem0:三层记忆架构(Episodic + Semantic + Procedural)

Mem0 是 2024 年开源的记忆框架,核心理念是把记忆按**性质**分为三层,而非简单堆在向量库里:
**💡 Mem0 的三层记忆**
- **Episodic(情景记忆)**:存储具体的交互片段,如"用户上周问了X问题,Agent回答了Y"。按时间线组织,支持回溯。
- **Semantic(语义记忆)**:从交互中提取的结构化知识,如"用户偏好简洁风格""用户是Java开发者"。去重后存储,支持增量更新。
- **Procedural(过程记忆)**:从历史交互中总结的"怎么做"类知识,如"处理JSON解析错误时,先检查编码再检查格式"。类似人类的技能记忆。

Mem0 的工作流程:**对话输入 → LLM 提取记忆 → 分类(episodic/semantic/procedural)→ 去重/合并 → 存储**。检索时按当前意图从三层记忆分别召回,合并后注入上下文。面试要点:Mem0 的三层不是物理分区,而是**逻辑分类**,底层可以共用同一个向量库。

### Letta(原 MemGPT):自编辑记忆 + 记忆分页

Letta 是 MemGPT 团队的商业化产品,核心创新是**把操作系统的虚拟内存概念引入 Agent 记忆**:
**💡 Letta 的两大核心机制**
- **自编辑记忆(Self-Editing Memory)**:Agent 可以主动修改自己的记忆。当发现旧记忆过时或矛盾时,LLM 自行调用 memory_edit 工具更新记忆内容,而非简单追加。类似人类"纠正记忆"的过程。
- **记忆分页(Memory Paging)**:把记忆分为**Core Memory**(常驻上下文,类似RAM)和**Archival Memory**(外部存储,类似磁盘)。Core Memory 满了时,自动把低优先级记忆"换出"到 Archival,需要时再"换入"。这和操作系统的页面置换机制几乎一模一样。

面试要点:Letta 的记忆分页解决的是**有限上下文窗口 vs 无限记忆需求**的矛盾。Core Memory 放最关键的用户信息和当前任务状态(通常 < 2000 token),Archival Memory 放历史交互。LLM 通过 search_archival 和 edit_core_memory 两个工具自主管理记忆流转。

### Zep:时序知识图谱 + 自动摘要

Zep 走了一条完全不同的路线——用**知识图谱**组织记忆,并引入**时间维度**:
**💡 Zep 的两个核心设计**
- **时序知识图谱(Temporal Knowledge Graph)**:把对话中的实体、关系、事件抽取为三元组,并附带时间戳。当用户说"我上个月换了一台Mac"时,Zep 不会只存原始文本,而是建一条 (用户, 换设备, Mac, 2025-06) 的图谱边。后续查询时可以回答"用户什么时候换的电脑"这类时序问题。
- **自动摘要(Auto-Summarization)**:每轮对话后自动生成摘要,摘要本身也会被图谱化。旧摘要会随时间衰减,新摘要权重更高。这解决了"半年前的摘要和昨天的摘要权重一样"的问题。

Zep 的优势在于**结构化检索**。向量检索只能回答"什么和X相关",Zep 的图谱检索还能回答"X是什么时候发生的""X和Y是什么关系""X最近有变化吗"。代价是抽取质量依赖 NLP 能力,复杂场景下图谱构建成本较高。

### 三者对比 | 维度 | Mem0 | Letta (MemGPT) | Zep | | --- | --- | --- | --- | | 记忆组织 | 三层分类(episodic/semantic/procedural) | 两级分页(core/archival) | 时序知识图谱 | | 记忆更新 | LLM 提取 + 去重合并 | Agent 自编辑(工具调用) | 图谱增量 + 自动摘要 | | 检索方式 | 向量相似度(分层召回) | Core 直接注入 + Archival 语义搜索 | 图谱查询 + 向量检索 + 时序过滤 | | 核心优势 | 分类清晰,开箱即用 | Agent 自主管理记忆,最像OS | 支持时序查询,结构化最强 | | 适用场景 | 通用对话 Agent、客服 | 长期伴随型 Agent、个人助手 | 需要时序推理、实体关系追踪 | | 开源协议 | Apache 2.0 | Apache 2.0 | Apache 2.0 | **🔗 5.13 核心要点**

**Mem0**:三层记忆(episodic/semantic/procedural)按性质分类,LLM 自动提取+去重,适合通用场景。

**Letta**:OS 式记忆分页(core/archival),Agent 自编辑记忆,最接近"Agent 自我管理"的理念。

**Zep**:时序知识图谱 + 自动摘要,支持"什么时候""什么关系"类时序查询,结构化最强。

**选型建议**:通用对话选 Mem0,长期伴随选 Letta,需要时序推理选 Zep。也可组合使用——Zep 做图谱存储 + Mem0 做分层检索。
**📋 八股总结 - 面试高频考点**

Q1: Agent 的记忆系统分为哪几层?各自的特点和实现方式?

      **短期记忆**:= LLM 上下文窗口,存当前对话历史。容量 4K~200K token,速度最快,对话结束即消失。

      **工作记忆**:= Scratchpad/变量,存当前任务的中间状态。任务结束清除,辅助多步推理。

      **长期记忆**:= 向量数据库,存历史交互经验。理论无限容量,需语义检索,永久持久化。

      三者关系:短期→工作(任务相关提取),短期→长期(压缩存储),长期→工作(检索回忆)。

Q2: 短期记忆的压缩策略有哪些?各有什么优缺点?

      **1 滑窗截断**:保留最近 N 轮,丢弃旧的。简单但可能丢失关键信息。

      **2 摘要压缩**:用 LLM 对旧对话生成摘要替代原文。保留关键信息但消耗额外 token。

      **3 选择性保留**:用 LLM 判断消息重要性,只保留重要的。信息密度高但判断可能不准。

      **4 分层压缩**:结合以上策略,近期完整保留、中期摘要、远期存入长期记忆。效果最好但实现最复杂。

Q3: 向量数据库在 Agent 记忆中的作用?检索流程是什么?

      **作用**:作为长期记忆的存储引擎,支持语义相似度检索,让 Agent 能"回忆"与当前查询相关的历史交互。

      **写入流程**:交互文本 → Embedding 模型 → 向量 → 存入向量数据库(附带元数据如时间、用户ID)。

      **检索流程**:用户查询 → Embedding → 在向量库中做相似度搜索(如余弦相似度)→ 返回 Top-K 最相关记忆 → 注入 prompt。

Q4: 反思记忆、实体记忆、情景记忆分别是什么?

      **反思记忆**:定期对历史交互进行反思,提取规律和偏好(如"用户偏好简洁回答"),存储提炼后的知识而非原始对话。

      **实体记忆**:以实体为中心组织记忆,维护实体知识图谱(如"小明"的属性和关系),实现实体级别的快速回忆。

      **情景记忆**:按时间线存储情节,带时间戳和上下文标签,适合"上次聊到哪了"这类时间敏感场景。

Q5: 为什么不用 SQL 数据库而用向量数据库存 Agent 记忆?

      SQL 数据库做精确查询(WHERE name='小明'),但记忆检索是**语义相似度**查询--"和用户当前问题意思相近的历史交互"。这需要把文本转为向量,在向量空间中计算距离。

      SQL 无法高效做语义相似度搜索(只能用 LIKE 做模糊匹配,效果差)。向量数据库专门优化了高维向量的相似度搜索,能在毫秒级从百万条记忆中找到最相关的几条。

      实践中也可用 pgvector(PostgreSQL 扩展)兼顾两者。

Q6: 上下文膨胀(Context Rot)是什么?五种压缩策略的优先级如何排序?

      **Context Rot**:长任务中上下文不断膨胀,导致行为漂移、注意力稀释、成本飙升和窗口溢出。

      五种压缩策略优先级(从最优到最差):

      **1 语义保护型**:锁定核心指令 + 保护行动-结果对完整性,只压缩中间推理。压缩率和信息完整性最优。

      **2 选择性保留**:按消息类型分别设定保留策略,信息密度高。

      **3 摘要压缩**:有损压缩,保留关键信息但可能丢失细节。

      **4 滑动窗口**:按时间丢弃,不考虑重要性。

      **5 截断**:最简单但最粗暴,可能导致行为不一致。

Q7: Context Engineering 和 Prompt Engineering 的区别是什么?

      **Prompt Engineering**:关注单次请求的措辞优化,是静态的、一次性的。如:优化 System Prompt、设计 Few-shot 示例。

      **Context Engineering**:关注整个上下文的动态编排,是持续性的、动态的。如:记忆的检索/压缩/淘汰策略、工具结果的格式化过滤、多源信息的优先级排序。

      类比:Prompt Engineering 是写一首诗(关注措辞),Context Engineering 是管理一个图书馆(关注信息流治理)。

Q8: 记忆生命周期包含哪些阶段?各阶段的治理要点是什么?

      五个阶段:

      **1 写入**:记录交互 + 附带元数据,默认慷慨写入,后续靠淘汰筛选。

      **2 索引**:向量索引 + 关键词索引 + 时间索引 + 类型索引,多路召回提高命中率。

      **3 检索**:JIT 模式按需取信息,相似度阈值过滤 + 元数据过滤 + Top-K 截断。

      **4 压缩**:语义保护型优先,锁定 System Prompt 和行动-结果对。

      **5 淘汰**:时效淘汰 + 容量淘汰 + 质量淘汰,淘汰前可做最终摘要归档。

Q9: Reflexion 框架的反思循环是如何工作的?

      Reflexion 的核心流程:

      **1 接收任务** → 检索相关反思经验(如有)

      **2 执行行动** → 用 ReAct 循环完成任务

      **3 评估结果** → 判断是否成功

      **4 成功** → 记录为情景记忆(完整事件)

      **5 失败** → 触发反思(分析失败原因、提炼改进策略)

      **6 存入反思记忆** → 下次重试时参考经验教训

      关键创新:不是简单重试,而是每次失败都提炼经验存入记忆,使 Agent 从"重复犯错的傻瓜"进化为"从失败中学习的聪明人"。

Q10: 长期记忆为什么要分为静态和动态?各自的存储、召回、压缩策略有何不同?

      不分类会导致三类问题:**存储浪费**(偏好存向量库每次检索没必要)、**信息过时**(摘要不更新会冗余)、**召回混乱**(规则和经验混在一起模型无法区分)。

      **静态长期记忆**(偏好/规则/知识):变化频率低 → 存本地配置文件 → 直接注入 System Prompt → 不压缩(本身已精炼)。

      **动态长期记忆**(摘要/经验/模式):变化频率高 → 存向量数据库 → 按相关性语义检索 → 定期压缩摘要+淘汰过时内容。

      面试要点:先分类再谈策略,不要一上来就说"存向量数据库"。

Q11: 为什么不是每轮对话都触发长期记忆写入?什么信息"值得记住"?

      **不是每轮都触发**,否则长期记忆库会变成完整对话日志的备份,噪音爆炸,检索失效。

      记忆写入触发条件:用户表达新偏好→写入静态记忆;完成有价值任务→写入动态记忆(执行经验);发现新上下文信息→写入动态记忆(知识片段);简单闲聊→不触发写入。

      判断"值得记住"的两个标准:**1 是否有长期参考价值**(未来对话中可能再次需要);**2 是否包含决策/偏好/知识点**(明确的、可复用的信息)。

      链路:对话→信息提取→价值评估→分类→写入。重点强调筛选而非全量。

Q12: 召回太多长期记忆会导致什么问题?如何防控上下文污染?

      **三大危害**:

      **1 注意力稀释**:模型关注无关内容,忽略核心问题

      **2 指令冲突**:多条记忆互相矛盾(如"偏好简洁"vs"要求详细")

      **3 Token浪费**:无关记忆消耗宝贵上下文窗口

      **四道防线**:

      **1 数量上限**:每次召回 ≤5 条,简单问题 ≤3 条

      **2 相关性阈值**:相似度  最近经验 > 补充信息

      召回决策靠三维度评分:意图匹配×时间衰减×频率加权。面试要点:先谈召回决策再谈污染防控,不要只说"用向量搜索"。

Q13: 多轮对话 Agent 怎么解决状态爆炸 / 上下文溢出?生产级的三板斧方案是什么?

      这是落地项目一定会遇到的痛点,面试官深挖项目必问。我主要靠三套方案组合解决:

      **1 规范状态结构**:用 LangGraph 的 TypedDict 定义固定的状态模板,只留存核心业务变量,去除无效冗余信息,防止状态字段无限膨胀。

      **2 智能截断**:不做简单粗暴的字符删减,而是按优先级保留上下文--**系统提示词 > 最近对话记录 > 当前核心任务目标**。低优先级的早期内容优先被截断。

      **3 对话摘要**:把老旧的历史对话压缩成简短摘要,放在上下文头部。既保留完整语境,又大幅减少上下文占用。类似人类的"回忆"而非"逐字回放"。

      面试要点:不要只说"用向量数据库",要展示"规范状态→智能截断→摘要压缩"三板斧组合思维,体现生产级落地经验而非纸上谈兵。

Q14: 动态 Prompt 和静态 Prompt 有什么区别?Agent 每轮对话开始时,上下文是如何动态组装的?

      **静态Prompt** = System Prompt(角色定义+安全规则+输出格式约束) + 工具Schema + Skill触发词。整个会话内不变,放在Prompt最前面,100%命中缓存,是Agent的"宪法"。

      **动态Prompt** = 记忆召回片段(按意图检索≤5条) + Skill完整指令(触发时加载) + 对话历史(逐轮增长) + 用户当前输入。每轮都变,放Prompt后面,按变化频率分层。

      **组装策略**:四层叠放,固定→半固定→动态→当前请求:

      Layer 1 静态框架:System Prompt + 工具Schema + Skill触发词(100%缓存命中)

      Layer 2 记忆注入:长期记忆召回片段(同会话内部分命中缓存)

      Layer 3 对话历史:多轮交互记录+工具返回结果(旧部分可缓存)

      Layer 4 当前请求:用户输入(完全不命中缓存)

      核心原则:**不变的放前面最大化缓存,变化的放后面最小化缓存失效**。面试要点:先定义静态和动态,再说组装顺序和缓存策略,不要只说"每次都重新生成Prompt"。

Q15: 多轮对话Agent的状态爆炸,除了三板斧还有什么更完整的系统级解决方案?

      Q13的三板斧(规范状态+智能截断+对话摘要)是入门级回答。**完整生产级方案是四层递进的泄压体系**:

      **Layer 1 预防**——不让压力累积:

      ① Token Budget全局预算控制(超预算立即停止)

      ② JIT召回原则(只检索当前步骤需要的记忆)

      ③ 输出长度约束(要求模型只输出关键结论)

      ④ 工具结果精简(只保留关键行而非原样塞入)

      **Layer 2 压缩**——在信息完整前提下缩减体积:

      ① 语义保护型压缩(锁定System Prompt和行动-结果对,只压缩中间推理)

      ② 三层压缩触发(主动80%→后台60%→反应式413)

      ③ 增量压缩(只压缩上次压缩后新增部分)

      **Layer 3 淘汰**——扔掉不再需要的:

      ① 记忆治理策略(时效+容量+质量三维度淘汰)

      ② 召回数量硬限制(≤5条)

      ③ 工具结果裁剪(只保留Top-K关键行)

      ④ 淘汰前归档(做最终摘要存入长期记忆)

      **Layer 4 兜底**——当所有策略都不够时:

      ① 紧急压缩(413状态码触发最激进压缩)

      ② 降级回答(告诉用户建议开新会话)

      ③ 自动会话切分(超50轮自动摘要+开新会话)

      ④ 人工介入(预算超限+压缩失败后通知用户)

      面试要点:不是某一层单独起作用,而是四层递进。预防失败→压缩兜住,压缩不够→淘汰兜住,淘汰也不够→紧急兜底。体现"从预防到兜底的全链路思维"。

Q16: Mem0 的三层记忆(Episodic / Semantic / Procedural)分别是什麽?如何协同工作?

      **Episodic(情景记忆)**:存储具体交互片段,按时间线组织,支持回溯。如"用户上周问了X,Agent回答了Y"。

      **Semantic(语义记忆)**:从交互中提取的结构化知识,去重后增量更新。如"用户偏好简洁风格""用户是Java开发者"。

      **Procedural(过程记忆)**:从历史中总结的"怎么做"类技能知识。如"处理JSON解析错误时,先检查编码再检查格式"。

      **协同流程**:对话输入 → LLM 提取记忆 → 分类(episodic/semantic/procedural)→ 去重/合并 → 存储。检索时按当前意图从三层分别召回,合并后注入上下文。三层是**逻辑分类**而非物理分区,底层可共用同一个向量库。

      面试要点:先说三层分别是什么,再说提取→分类→去重→存储的写入流程,最后说分层召回的检索策略。不要把三层说成三个数据库。

Q17: Letta(MemGPT)如何实现记忆分页?Zep 的时序知识图谱有什么优势?

      **Letta 记忆分页**:借鉴 OS 虚拟内存机制,把记忆分为 Core Memory(常驻上下文,类似 RAM,放最关键的用户信息和任务状态,通常 < 2000 token)和 Archival Memory(外部存储,类似磁盘,放历史交互)。Core 满了自动把低优先级记忆"换出"到 Archival,需要时通过 search_archival 工具"换入"。Agent 还能通过 edit_core_memory 工具**自编辑**记忆,纠正过时信息。

      **Zep 时序知识图谱**:把对话中的实体、关系、事件抽取为带时间戳的三元组,如 (用户, 换设备, Mac, 2025-06)。优势:①支持时序查询("用户什么时候换的电脑"),向量检索做不到;②支持关系查询("X和Y什么关系");③自动摘要随时间衰减,新摘要权重更高,解决旧摘要和新摘要同等权重的问题。代价是图谱构建质量依赖 NLP 抽取能力。

      面试要点:Letta 重点说"OS 式分页 + 自编辑",Zep 重点说"时序 + 结构化"。对比时强调:Letta 解决的是有限窗口 vs 无限记忆的矛盾,Zep 解决的是向量检索无法回答时序和关系查询的矛盾。

### 🧠 L3 开放题(面试高频)

    深度思考 · 无标准答案

开放题

为什么不是每轮对话都触发长期记忆写入?什么信息"值得记住"?请描述记忆写入的完整链路。

        **参考思路:**

        不是每轮都触发,否则记忆库变成对话日志备份,噪音爆炸检索失效。

        **触发条件**:用户表达新偏好→静态记忆;完成有价值任务→动态记忆(经验);发现新上下文→动态记忆(知识);简单闲聊→不触发。

        **判断标准**:1是否有长期参考价值(未来可能再次需要)2是否包含决策/偏好/知识点(明确的、可复用的信息)。

        **完整链路**:对话→信息提取→价值评估→分类(静态/动态)→写入(配置文件/向量数据库)。

        关键词:筛选机制、价值评估、分类写入、不是全量记录。

        关联章节:4.10 | 面试题:Q11

开放题

如果召回太多长期记忆,会导致什么问题?如何防控上下文污染?请描述召回决策机制和四道防线。

        **参考思路:**

        **三大危害**:1注意力稀释(模型关注无关内容忽略核心问题)2指令冲突(多条记忆互相矛盾)3Token浪费(无关记忆消耗上下文窗口)。

        **召回决策机制**(三维度评分):意图匹配(语义相似度)× 时间衰减(越近期权重越高)× 频率加权(多次验证有效的记忆权重更高)。综合评分决定是否召回。

        **四道防线**:1数量上限(≤5条,简单≤3)2相关性阈值(相似度最近经验>补充信息)。

        关键词:上下文污染、注意力稀释、指令冲突、四道防线、召回评分公式。

        关联章节:4.11 | 面试题:Q12

开放题

动态Prompt和静态Prompt有什么区别?Agent每轮对话开始时,上下文是如何动态组装的?为什么固定前缀放最前面能最大化缓存命中率?

        **参考思路:**

        **静态Prompt** = System Prompt(角色+规则) + 工具Schema + Skill触发词,整个会话不变,是Agent的"宪法"。

        **动态Prompt** = 记忆召回片段 + Skill完整指令 + 对话历史 + 用户当前输入,每轮都变。

        **组装策略**:四层叠放,固定→半固定→动态→当前请求。

        Layer 1 静态框架(100%缓存命中)

        Layer 2 记忆注入(同会话内部分命中缓存)

        Layer 3 对话历史(旧部分可缓存)

        Layer 4 当前请求(完全不命中缓存)

        **为什么固定前缀放最前面**:Prompt Caching是前缀缓存,只有Prompt开头的不变部分才能命中缓存。System Prompt+工具Schema放在最前面确保每轮前缀一致,100%命中缓存,节省50%+输入成本。合并固定层为一个system消息增加前缀连续性,更容易达到OpenAI缓存最低5个消息触发阈值。

        关键词:静态框架、动态组装、四层叠放、缓存命中率、前缀缓存。

        关联章节:5.11a | 面试题:Q14

开放题

多轮对话Agent的状态爆炸怎么解决?请描述四层泄压体系的完整框架,以及每层递进的关系。

        **参考思路:**

        **问题定义**:多轮对话中状态从线性增长变为树状展开(工具嵌套)和倍数增长(推理分叉),导致上下文溢出、注意力稀释、成本爆炸和行为漂移。

        **四层泄压体系**:

        **Layer 1 预防**——不让压力累积:Token Budget全局预算控制、JIT召回原则、输出长度约束、工具结果精简。

        **Layer 2 压缩**——在信息完整前提下缩减体积:语义保护型压缩(锁定System Prompt和行动-结果对)、三层压缩触发(主动80%→后台60%→反应式413)、增量压缩。

        **Layer 3 淘汰**——扔掉不再需要的:记忆治理策略(时效+容量+质量三维度淘汰)、召回数量硬限制≤5条、工具结果裁剪、淘汰前归档。

        **Layer 4 兜底**——当所有策略都不够时:紧急压缩(413触发最激进压缩)、降级回答、自动会话切分、人工介入。

        **递进关系**:预防失败→压缩兜住,压缩不够→淘汰兜住,淘汰也不够→紧急兜底。不是某一层单独起作用,而是四层递进式泄压。

        关键词:状态爆炸、四层泄压、预算控制、语义保护型压缩、记忆治理、紧急兜底。

        关联章节:5.11b | 面试题:Q15
第6章 Agent的记忆系统
http://www.clxhxhhr.top/posts/709/
作者
clxstart
发布于
2026-09-18
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。
文章目录
目录