iconsulting/docs/architecture/07-memory-manager-agent.md

21 KiB
Raw Permalink Blame History

07 - Memory Manager Agent (记忆管理) 设计详解

1. 核心职责

Memory Manager 是系统中的用户信息管家。它负责从对话中提取用户信息、组织存储到长期记忆、以及按需检索用户历史上下文。它替代了旧架构中 StrategyEngineService.extractUserInfo() 的手动提取逻辑。

核心能力:

  1. 信息提取 -- 从对话文本中自动识别和提取用户的个人信息(年龄、学历、工作等)
  2. 记忆存储 -- 将提取的信息分类标注后保存到 knowledge-service 的长期记忆
  3. 上下文加载 -- 检索用户的历史记忆,为其他 Agent 提供用户背景
  4. 摘要生成 -- 将散落在多次对话中的用户信息整合为结构化的用户画像
  5. 去重合并 -- 避免重复存储相同信息,新信息覆盖旧信息

设计原则:只提取用户明确表述的事实,不做推测。 "我大概三十多岁"提取为"30+"而非具体数字。

2. 模型与参数

{
  model: 'claude-haiku-3-5-20241022',  // 结构化提取任务Haiku 足矣
  max_tokens: 1000,
  temperature: 0,                       // 信息提取必须确定性
  system: [
    {
      type: 'text',
      text: memoryManagerPrompt,
      cache_control: { type: 'ephemeral' }
    }
  ],
}

选用 Haiku 的理由:

  • 信息提取是相对简单的 NLP 任务(实体识别 + 分类)
  • 需要高频调用(每轮对话后都可能触发),成本敏感
  • 输入输出格式高度结构化,不需要 Sonnet 的推理能力
  • 速度要求高 -- 信息保存不应阻塞主对话流

3. 可用工具 (Available Tools)

Memory Manager 有 2 个工具

3.1 save_user_memory

{
  name: 'save_user_memory',
  description: '将用户信息保存到长期记忆存储。每条记忆包含类型、内容和相关移民类别。',
  input_schema: {
    type: 'object',
    properties: {
      memoryType: {
        type: 'string',
        enum: ['FACT', 'PREFERENCE', 'INTENT'],
        description: 'FACT=用户陈述的事实年龄、学历等PREFERENCE=用户偏好倾向的移民方式INTENT=用户意图(计划时间、预算等)'
      },
      content: {
        type: 'string',
        description: '要保存的记忆内容,用简洁的陈述句表达,如"用户32岁清华大学计算机硕士"'
      },
      category: {
        type: 'string',
        enum: ['QMAS', 'GEP', 'IANG', 'TTPS', 'CIES', 'TECHTAS'],
        description: '相关的移民类别(如果有明确关联)'
      }
    },
    required: ['memoryType', 'content']
  }
}

底层实现:调用 knowledge-service 的 POST /api/v1/memory/user

const response = await axios.post('http://knowledge-service:3003/api/v1/memory/user', {
  userId: context.userId,
  tenantId: context.tenantId,
  memoryType: input.memoryType,
  content: input.content,
  category: input.category,
  importance: IMPORTANCE_MAP[input.memoryType],  // FACT:70, INTENT:80, PREFERENCE:60
  source: 'conversation',
  conversationId: context.conversationId,
});

3.2 get_user_context

{
  name: 'get_user_context',
  description: '获取用户的历史记忆和背景信息。返回按重要性和相关性排序的记忆列表。',
  input_schema: {
    type: 'object',
    properties: {
      query: {
        type: 'string',
        description: '检索查询,如"用户的基本信息和移民意向"'
      }
    },
    required: ['query']
  }
}

4. System Prompt 要点

# 身份
你是 iConsulting 的记忆管理专员。你负责从对话中提取、保存和检索用户信息。

# 核心原则
1. 只提取用户明确陈述的信息,不做推测
2. 模糊信息保留模糊性("三十多岁" → "30+岁"不要推测为35
3. 同一信息不要重复保存,检查已有记忆后再决定是否保存
4. 每条记忆用简洁的陈述句表达
5. 正确分类 memoryType
   - FACT: 客观事实(年龄、学历、工作、收入等)
   - PREFERENCE: 主观偏好(偏好的移民方式、时间偏好等)
   - INTENT: 行动意图(打算何时申请、预算范围、已在准备等)

# 需要提取的核心信息字段
## 基础信息
- age: 年龄
- education: 最高学历(博士/硕士/学士/大专/高中)
- university: 毕业院校
- major: 专业方向

## 职业信息
- currentJobTitle: 当前职位
- currentIndustry: 所属行业
- currentCompany: 当前公司(如果提到)
- totalYearsOfExperience: 工作年限
- annualIncome: 年收入(注意币种)

## 移民相关
- targetCategory: 感兴趣的移民类别
- hasHongKongEmployer: 是否有香港雇主
- hasTechBackground: 是否有科技背景
- investmentAmount: 可投资金额
- immigrationTimeline: 计划移民时间

## 家庭信息
- maritalStatus: 婚姻状况
- hasChildren: 是否有子女
- familySupport: 家人支持程度

# 操作类型
根据 Coordinator 传入的 action 参数执行不同操作:
- load_context: 加载用户上下文(调用 get_user_context
- save_info: 从对话中提取信息并保存(调用 save_user_memory
- summarize: 加载所有记忆并生成结构化摘要

5. 输入/输出格式

Memory Manager 的输入/输出根据 action 不同而变化:

Action: load_context

输入

interface MemoryManagerLoadInput {
  action: 'load_context';
  /** 当前用户问题(用于检索相关记忆) */
  query: string;
}

输出

interface MemoryManagerLoadOutput {
  action: 'load_context';
  /** 检索到的用户记忆列表 */
  memories: Array<{
    type: 'FACT' | 'PREFERENCE' | 'INTENT';
    content: string;
    importance: number;
    category?: string;
    createdAt?: string;
  }>;
  /** 用户画像摘要(如果有足够信息) */
  user_profile_summary?: string;
  /** 记忆总数 */
  total_memories: number;
}

Action: save_info

输入

interface MemoryManagerSaveInput {
  action: 'save_info';
  /** 用户最新消息 */
  userMessage: string;
  /** 助手最新回复 */
  assistantMessage: string;
  /** 已有的用户信息(避免重复提取) */
  existingInfo?: Record<string, unknown>;
}

输出

interface MemoryManagerSaveOutput {
  action: 'save_info';
  /** 本次提取并保存的信息 */
  extracted_info: Record<string, unknown>;
  /** 保存的记忆条数 */
  saved_count: number;
  /** 保存的记忆详情 */
  saved_memories: Array<{
    memoryType: 'FACT' | 'PREFERENCE' | 'INTENT';
    content: string;
    field: string;      // 对应的字段名
  }>;
  /** 跳过的信息(已存在) */
  skipped_fields: string[];
}

Action: summarize

输入

interface MemoryManagerSummarizeInput {
  action: 'summarize';
}

输出

interface MemoryManagerSummarizeOutput {
  action: 'summarize';
  /** 结构化用户画像 */
  user_profile: {
    // 基础信息
    basic: {
      age?: string;
      education?: string;
      university?: string;
      major?: string;
      nationality?: string;
      location?: string;
    };
    // 职业信息
    career: {
      jobTitle?: string;
      industry?: string;
      company?: string;
      yearsOfExperience?: string;
      annualIncome?: string;
    };
    // 移民意向
    immigration: {
      targetCategories?: string[];
      timeline?: string;
      hasHKEmployer?: boolean;
      investmentCapacity?: string;
    };
    // 家庭情况
    family: {
      maritalStatus?: string;
      hasChildren?: boolean;
      familySupport?: string;
    };
  };
  /** 信息完整度 */
  completeness: {
    score: number;            // 0-100
    filled_fields: string[];
    missing_fields: string[];
  };
  /** 一段话总结 */
  narrative_summary: string;
}

6. 触发时机 (When to Trigger)

Coordinator 根据不同的 action 在不同时机调用 invoke_memory_manager

load_context 触发时机

场景 触发条件 目的
对话开始 第 1 轮对话 加载用户历史信息,实现跨会话记忆
需要用户背景 Coordinator 需要参考用户画像 为决策提供上下文
评估前准备 准备调 Assessment Expert 前 补充已有但本次对话未提及的信息

save_info 触发时机

场景 触发条件 目的
用户分享个人信息 对话中出现年龄、学历、工作等信息 实时保存用户数据
每轮对话后 助手回复生成后 确保不遗漏任何用户信息
用户表达偏好/意图 "我比较想走高才通" / "打算明年申请" 保存用户偏好和意图

summarize 触发时机

场景 触发条件 目的
评估前摘要 准备做全面评估时 生成完整的用户画像供 Assessment Expert 使用
对话结束时 对话即将结束 更新用户画像摘要

调用频率建议

  • load_context:每次新对话开始时调用 1 次
  • save_info不需要每轮都调用Coordinator 判断用户提供了新信息时才调用
  • summarize:整个对话过程中最多调用 1-2 次

7. 内部循环 (Internal Loop)

Memory Manager 的内部循环根据 action 不同而变化:

load_context 流程

┌─────────────────────────────────────────────────────┐
│  load_context Loop (max 1 turn)                     │
│                                                      │
│  Turn 0:                                             │
│  ├── get_user_context({query: input.query})          │
│  ├── 整理返回的记忆列表                               │
│  ├── 如果记忆足够多,生成 user_profile_summary        │
│  └── 返回结构化输出                                   │
└─────────────────────────────────────────────────────┘

save_info 流程

┌─────────────────────────────────────────────────────┐
│  save_info Loop (max 2 turns)                       │
│                                                      │
│  Turn 0: 提取信息                                    │
│  ├── LLM 分析 userMessage + assistantMessage         │
│  ├── 对比 existingInfo筛除已有字段                  │
│  ├── 对新信息逐条调用 save_user_memory               │
│  │   (可能并行保存多条)                             │
│  │                                                   │
│  Turn 1: 确认保存结果(如需)                         │
│  ├── 检查 save 操作是否全部成功                       │
│  └── 返回保存结果摘要                                │
└─────────────────────────────────────────────────────┘

summarize 流程

┌─────────────────────────────────────────────────────┐
│  summarize Loop (max 2 turns)                       │
│                                                      │
│  Turn 0: 加载所有记忆                                │
│  ├── get_user_context({query: "用户所有背景信息"})    │
│  │                                                   │
│  Turn 1: 生成摘要                                    │
│  ├── 将散碎的记忆整合为结构化 user_profile            │
│  ├── 计算 completeness score                         │
│  └── 生成 narrative_summary                          │
└─────────────────────────────────────────────────────┘

save_info 的提取逻辑伪代码

async function extractAndSave(input: MemoryManagerSaveInput): Promise<MemoryManagerSaveOutput> {
  // Step 1: LLM 提取信息Haiku 快速完成)
  const extractionPrompt = `
    从以下对话中提取用户信息。只提取能明确确定的信息,不要猜测。
    已有信息(不要重复提取):${JSON.stringify(input.existingInfo)}

    用户消息:${input.userMessage}
    助手回复:${input.assistantMessage}

    返回 JSON{field: value} 格式。
  `;

  const extracted = await llm.extract(extractionPrompt);
  const newFields = filterOutExisting(extracted, input.existingInfo);

  // Step 2: 逐条保存(可并行)
  const savePromises = Object.entries(newFields).map(([field, value]) => {
    const memoryType = inferMemoryType(field);  // FACT/PREFERENCE/INTENT
    return saveUserMemory({
      memoryType,
      content: `${FIELD_LABELS[field]}${value}`,
      category: inferCategory(field, value),
    });
  });

  const results = await Promise.all(savePromises);

  return {
    action: 'save_info',
    extracted_info: newFields,
    saved_count: results.filter(r => r.success).length,
    saved_memories: results.map(r => r.detail),
    skipped_fields: Object.keys(extracted).filter(k => input.existingInfo?.[k]),
  };
}

8. 与其他 Agent 的关系

┌──────────────┐     invoke_memory_manager      ┌──────────────┐
│              │ ─────────────────────────────→  │              │
│  Coordinator │     MemoryManagerOutput        │ Memory       │
│              │ ←─────────────────────────────  │ Manager      │
└──────┬───────┘                                 └──────┬───────┘
       │                                                │
       │  Memory Manager 是其他 Agent 的                  ├── save_user_memory
       │  数据供应商                                      └── get_user_context
       │                                                       │
       │                                                       ▼
       │                                              ┌──────────────┐
       │                                              │ Knowledge    │
       │                                              │ Service      │
       │                                              │ (Memory API) │
       │                                              └──────────────┘
       │
       │  协作模式:
       │
       │  1. 对话开始:
       │     Coordinator → invoke_memory_manager({action: 'load_context'})
       │     → 获取用户画像 → 注入到后续所有 Agent 调用的上下文中
       │
       │  2. 信息收集轮:
       │     用户发消息 → Coordinator 回复 → Coordinator 调用
       │     invoke_memory_manager({action: 'save_info', userMessage, assistantMessage})
       │     → 信息自动归档(异步,不阻塞主流程)
       │
       │  3. 评估准备:
       │     Coordinator → invoke_memory_manager({action: 'summarize'})
       │     → 获取完整用户画像 → 传入 Assessment Expert
       │
       │  4. 跨会话衔接:
       │     新对话开始 → load_context → 上一次对话的信息仍然可用

与旧架构的对应关系

旧架构 新架构
StrategyEngineService.extractUserInfo() Memory Manager 的 save_info action
ConsultingState.collectedInfo Memory Manager 从 knowledge-service 加载
手动在 System Prompt 中拼接用户信息 Memory Manager 的 load_context + summarize
信息只在单次对话中有效 信息持久化到 knowledge-service跨会话有效

异步保存优化 save_info 可以异步执行,不阻塞 Coordinator 的主回复流:

// Coordinator 主流程
const reply = yield* generateReply(userMessage);  // 先回复用户

// 异步保存用户信息(不阻塞)
invokeMemoryManager({
  action: 'save_info',
  userMessage,
  assistantMessage: reply,
  existingInfo: currentCollectedInfo,
}).catch(err => logger.warn('Memory save failed:', err));

9. 示例场景

场景 1从对话中提取信息 (save_info)

Coordinator 调用

{
  "tool": "invoke_memory_manager",
  "input": {
    "action": "save_info",
    "userMessage": "我今年32岁浙大计算机硕士毕业的现在在杭州做前端开发工作8年了年薪大概80万",
    "assistantMessage": "感谢您分享这些信息!您的背景很不错...",
    "existingInfo": {}
  }
}

内部执行

Turn 0: LLM 分析对话,提取信息
→ 识别到 5 个字段age=32, university="浙江大学", education="硕士",
  major="计算机", currentJobTitle="前端开发", totalYearsOfExperience=8,
  annualIncome=800000, currentLocation="杭州"

Turn 1: 并行保存 8 条记忆
→ save_user_memory({type: "FACT", content: "用户32岁"})
→ save_user_memory({type: "FACT", content: "浙江大学计算机硕士"})
→ save_user_memory({type: "FACT", content: "前端开发工程师工作8年"})
→ save_user_memory({type: "FACT", content: "年薪约80万人民币"})
→ save_user_memory({type: "FACT", content: "目前在杭州工作"})
→ ... (其他字段)

返回结果

{
  "action": "save_info",
  "extracted_info": {
    "age": 32,
    "education": "硕士",
    "university": "浙江大学",
    "major": "计算机",
    "currentJobTitle": "前端开发",
    "totalYearsOfExperience": 8,
    "annualIncome": 800000,
    "currentLocation": "杭州"
  },
  "saved_count": 5,
  "saved_memories": [
    {"memoryType": "FACT", "content": "用户32岁", "field": "age"},
    {"memoryType": "FACT", "content": "浙江大学计算机硕士毕业", "field": "education+university+major"},
    {"memoryType": "FACT", "content": "从事前端开发工作8年", "field": "currentJobTitle+totalYearsOfExperience"},
    {"memoryType": "FACT", "content": "年薪约80万人民币", "field": "annualIncome"},
    {"memoryType": "FACT", "content": "目前在杭州工作", "field": "currentLocation"}
  ],
  "skipped_fields": []
}

场景 2跨会话加载上下文 (load_context)

Coordinator 调用(新对话开始时):

{
  "tool": "invoke_memory_manager",
  "input": {
    "action": "load_context",
    "query": "用户的基本背景和移民意向"
  }
}

返回结果

{
  "action": "load_context",
  "memories": [
    {"type": "FACT", "content": "用户32岁", "importance": 70, "createdAt": "2026-02-05"},
    {"type": "FACT", "content": "浙江大学计算机硕士毕业", "importance": 70, "createdAt": "2026-02-05"},
    {"type": "FACT", "content": "从事前端开发工作8年", "importance": 70, "createdAt": "2026-02-05"},
    {"type": "FACT", "content": "年薪约80万人民币", "importance": 70, "createdAt": "2026-02-05"},
    {"type": "INTENT", "content": "对高才通B类非常感兴趣", "importance": 80, "category": "TTPS", "createdAt": "2026-02-05"},
    {"type": "PREFERENCE", "content": "希望尽快办理,时间越短越好", "importance": 60, "createdAt": "2026-02-05"}
  ],
  "user_profile_summary": "32岁浙大计算机硕士8年前端开发经验年薪80万。上次对话中对高才通B类表现出浓厚兴趣偏好快速通道。",
  "total_memories": 6
}

场景 3生成用户画像摘要 (summarize)

Coordinator 调用(准备做评估前):

{
  "tool": "invoke_memory_manager",
  "input": {
    "action": "summarize"
  }
}

返回结果

{
  "action": "summarize",
  "user_profile": {
    "basic": {
      "age": "32岁",
      "education": "硕士",
      "university": "浙江大学",
      "major": "计算机科学",
      "location": "杭州"
    },
    "career": {
      "jobTitle": "前端开发工程师",
      "industry": "IT/互联网",
      "yearsOfExperience": "8年",
      "annualIncome": "约80万人民币"
    },
    "immigration": {
      "targetCategories": ["TTPS"],
      "timeline": "尽快",
      "hasHKEmployer": false
    },
    "family": {
      "maritalStatus": "未知",
      "hasChildren": null,
      "familySupport": "未知"
    }
  },
  "completeness": {
    "score": 65,
    "filled_fields": ["age", "education", "university", "major", "location", "jobTitle", "industry", "yearsOfExperience", "annualIncome", "targetCategories", "timeline"],
    "missing_fields": ["nationality", "maritalStatus", "hasChildren", "languageSkills", "hasHongKongEmployer", "hasTechBackground", "investmentAmount"]
  },
  "narrative_summary": "用户是一位32岁的前端开发工程师浙江大学计算机硕士毕业拥有8年IT行业经验年薪约80万人民币目前在杭州工作。用户对高才通B类最感兴趣希望尽快办理。家庭情况和语言能力等信息尚未收集。"
}