21 KiB
21 KiB
07 - Memory Manager Agent (记忆管理) 设计详解
1. 核心职责
Memory Manager 是系统中的用户信息管家。它负责从对话中提取用户信息、组织存储到长期记忆、以及按需检索用户历史上下文。它替代了旧架构中 StrategyEngineService.extractUserInfo() 的手动提取逻辑。
核心能力:
- 信息提取 -- 从对话文本中自动识别和提取用户的个人信息(年龄、学历、工作等)
- 记忆存储 -- 将提取的信息分类标注后保存到 knowledge-service 的长期记忆
- 上下文加载 -- 检索用户的历史记忆,为其他 Agent 提供用户背景
- 摘要生成 -- 将散落在多次对话中的用户信息整合为结构化的用户画像
- 去重合并 -- 避免重复存储相同信息,新信息覆盖旧信息
设计原则:只提取用户明确表述的事实,不做推测。 "我大概三十多岁"提取为"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类最感兴趣,希望尽快办理。家庭情况和语言能力等信息尚未收集。"
}