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

618 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 07 - Memory Manager Agent (记忆管理) 设计详解
## 1. 核心职责
Memory Manager 是系统中的**用户信息管家**。它负责从对话中提取用户信息、组织存储到长期记忆、以及按需检索用户历史上下文。它替代了旧架构中 `StrategyEngineService.extractUserInfo()` 的手动提取逻辑。
核心能力:
1. **信息提取** -- 从对话文本中自动识别和提取用户的个人信息(年龄、学历、工作等)
2. **记忆存储** -- 将提取的信息分类标注后保存到 knowledge-service 的长期记忆
3. **上下文加载** -- 检索用户的历史记忆,为其他 Agent 提供用户背景
4. **摘要生成** -- 将散落在多次对话中的用户信息整合为结构化的用户画像
5. **去重合并** -- 避免重复存储相同信息,新信息覆盖旧信息
> 设计原则:**只提取用户明确表述的事实,不做推测。** "我大概三十多岁"提取为"30+"而非具体数字。
## 2. 模型与参数
```typescript
{
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
```typescript
{
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`
```typescript
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
```typescript
{
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
**输入**
```typescript
interface MemoryManagerLoadInput {
action: 'load_context';
/** 当前用户问题(用于检索相关记忆) */
query: string;
}
```
**输出**
```typescript
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
**输入**
```typescript
interface MemoryManagerSaveInput {
action: 'save_info';
/** 用户最新消息 */
userMessage: string;
/** 助手最新回复 */
assistantMessage: string;
/** 已有的用户信息(避免重复提取) */
existingInfo?: Record<string, unknown>;
}
```
**输出**
```typescript
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
**输入**
```typescript
interface MemoryManagerSummarizeInput {
action: 'summarize';
}
```
**输出**
```typescript
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 的提取逻辑伪代码**
```typescript
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 的主回复流:
```typescript
// 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 调用**
```json
{
"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: "目前在杭州工作"})
→ ... (其他字段)
```
**返回结果**
```json
{
"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 调用**(新对话开始时):
```json
{
"tool": "invoke_memory_manager",
"input": {
"action": "load_context",
"query": "用户的基本背景和移民意向"
}
}
```
**返回结果**
```json
{
"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 调用**(准备做评估前):
```json
{
"tool": "invoke_memory_manager",
"input": {
"action": "summarize"
}
}
```
**返回结果**
```json
{
"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类最感兴趣希望尽快办理。家庭情况和语言能力等信息尚未收集。"
}
```