引言
Mem0(发音为”mem-zero”)是一个专为 AI 代理和助手设计的智能记忆层。本文深入剖析其 CRUD(创建、读取、更新、删除)全流程,重点揭示其 LLM 驱动的记忆提取流水线、多信号混合搜索、以及非阻塞容错设计。
快速开始
from mem0 import Memory
from mem0.configs.base import MemoryConfig
memory = Memory(config=MemoryConfig(
vector_store="qdrant", # 或 faiss, pgvector, lancedb
llm="openai", # 或 anthropic, azure_openai
embedder="openai",
history_db_path="./mem0.db"
))
架构概览
┌─────────────────────────────────────────────────────────────┐
│ Mem0 Memory Layer │
├─────────────┬─────────────┬─────────────┬──────────────────┤
│ Embedding │ Vector │ LLM │ Entity Store │
│ Model │ Store │ │ │
├─────────────┴─────────────┴─────────────┴──────────────────┤
│ SQLite History DB │
└─────────────────────────────────────────────────────────────┘
核心组件:
-
嵌入模型:文本 → 向量 -
向量存储:语义检索(Qdrant/Faiss/pgvector/LanceDB) -
LLM:关键事实抽取、代词消解、去重决策 -
SQLite 历史库:变更审计、消息回放 -
实体存储:记忆间图关系、搜索增强
Create(添加记忆)
add() 方法是 Mem0 最复杂的操作,采用 8 阶段流水线 设计:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
单次 LLM 抽取调用 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Phase 0: 上下文收集
|
|
|
|
|---|---|---|
user_id/Agent_id/run_id |
|
self.db.get_last_messages(session_scope, limit=10) |
从 SQLite 历史数据库中获取最近 10 条消息,为 LLM 提供对话上下文,帮助解决代词指代问题(如”我喜欢它”中的”它”指代什么)。
Phase 1: 检索现有记忆
|
|
|
|
|---|---|---|
|
|
|
top_k=10 |
将输入消息转换为向量,在向量存储中搜索最相关的现有记忆。这一步有两个目的:
-
为 LLM 提供参考,避免重复存储相似信息 -
识别需要更新的已有记忆
query_embedding = self.embedding_model.embed(parsed_messages, "search")
existing_results = self.vector_store.search(
query=parsed_messages, vectors=query_embedding, top_k=10, filters=search_filters
)
Phase 2: LLM 提取(单次调用)
|
|
|
|
|---|---|---|
|
|
|
单次调用
|
这是 Mem0 的智能核心。为防幻觉,已有记忆被映射为整数索引(0,1,2…而非 UUID)。
Prompt 自适应:当 filters 中只有 agent_id 而无 user_id 时(即代理级别记忆),系统会自动追加 AGENT_CONTEXT_SUFFIX,要求 LLM 从代理视角提取记忆:
-
用户陈述的事实 → “Agent was informed that [fact]” -
代理行为 → “Agent recommended [X]” -
代理配置 → “Agent is configured to [behavior]”
# mem0/memory/main.py:724
is_agent_scoped = bool(filters.get("agent_id")) and not filters.get("user_id")
system_prompt = ADDITIVE_EXTRACTION_PROMPT
if is_agent_scoped:
system_prompt += AGENT_CONTEXT_SUFFIX
LLM 输出示例:
{
"memory": [
{"id": "0", "text": "用户喜欢Python编程", "attributed_to": "user"},
{"id": "1", "text": "用户用VS Code编辑器", "attributed_to": "user"},
{"id": "2", "text": "用户正在学习JavaScript", "attributed_to": "user"}
]
}
Phase 3: 批量嵌入
|
|
|
|
|---|---|---|
|
|
|
|
mem_embeddings_list = self.embedding_model.embed_batch(mem_texts, "add")
Phase 4: 哈希去重
|
|
|
|
|---|---|---|
|
|
lemmatize_for_bm25(text) |
|
|
|
hashlib.md5(text.encode()).hexdigest() |
|
|
|
existing_hashes |
|
|
|
seen_hashes |
|
Phase 5: 批量持久化
|
|
|
|
|---|---|---|
|
|
vector_store.insert() |
|
|
|
db.batch_add_history() |
|
容错细节:
-
batch_add_history失败 → 逐条add_history(),单条失败只记录 error log,不阻断其余记忆 -
vector_store.insert同理逐条 fallback -
整个 Phase 5 被 try/except包裹,即使全部失败也只影响历史记录,记忆向量仍已写入
Phase 6: 批量实体链接
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
容错细节:
-
embed_batch失败 → 逐条embed(),失败项返回None,后续自动过滤 -
实体链接失败(整个 Phase 6)→ 只打 warning,不抛异常,记忆添加已成功完成
这种非阻塞容错哲学贯穿 Mem0:核心操作(记忆写入)必须成功,增强操作(实体链接、历史记录)允许失败降级。
Phase 7: 保存消息并返回
|
|
|
|
|---|---|---|
db.save_messages() |
history 表 |
|
|
|
|
|
Read(搜索记忆)
search() 方法采用 混合搜索策略,结合多种排序信号:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
filters
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Step 1: 预处理查询
|
|
|
|
|---|---|---|
|
|
lemmatize_for_bm25(query) |
|
|
|
extract_entities(query) |
|
Step 2: 嵌入查询
embeddings = self.embedding_model.embed(query, "search")
Step 2.5: Metadata Filter 预处理
当 filters 中包含 AND/OR/NOT 或比较操作符(gte、in、contains 等)时,search() 会先调用 _process_metadata_filters() 将其转换为向量存储兼容的格式:
# mem0/memory/main.py:1202
if self._has_advanced_operators(effective_filters):
processed_filters = self._process_metadata_filters(effective_filters)
# 移除原始操作符键,替换为处理后的格式
for logical_key in ("AND", "OR", "NOT"):
effective_filters.pop(logical_key, None)
effective_filters.update(processed_filters)
处理后的格式使用 $or / $not 作为逻辑组合键,支持嵌套条件深度合并。不同向量存储(Qdrant、Faiss、pgvector)在各自的 adapter 中进一步将其翻译为原生查询语法。
Step 3: 语义搜索
|
|
|
|---|---|
|
|
internal_limit = max(limit * 4, 60) |
|
|
|
|
|
user_id/agent_id/run_id 作用域过滤 |
Step 4: 关键词搜索(BM25)
如果向量存储支持(如 Qdrant、LanceDB),执行 BM25 关键词搜索:
keyword_results = self.vector_store.keyword_search(
query=query_lemmatized, top_k=internal_limit, filters=filters
)
Step 5: BM25 分数归一化
midpoint, steepness = get_bm25_params(query, lemmatized=query_lemmatized)
bm25_scores[mem_id] = normalize_bm25(raw_score, midpoint, steepness) # → [0,1]
Step 6: 实体增强(Entity Boosts)
这是 Mem0 的特色功能!
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
linked_memory_ids 读取 |
|
|
|
1.0 / (1.0 + 0.001 * ((n-1)²)) |
|
|
|
similarity * 0.5 * weight |
设计理念:链接记忆越多的实体越通用,增强效果应衰减。
Step 7-8: 评分与排序
scored_results = score_and_rank(
semantic_results=candidates, # 语义相似度
bm25_scores=bm25_scores, # 关键词匹配度
entity_boosts=entity_boosts, # 实体关联度 [0, 0.5]
threshold=threshold,
top_k=limit,
)
评分公式(核心设计):
combined = (semantic + bm25 + entity_boost) / max_possible
其中 max_possible根据可用信号动态计算,确保不同配置(有的向量存储不支持 BM25)的结果分数可比:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Threshold 的守门作用:semantic_score 先与 threshold 比较,低于 threshold 的直接淘汰,即使 BM25 或 entity_boost 再高也不考虑。这避免了语义完全不相关但碰巧命中关键词的噪音项进入 Top-K。
Step 9: 格式化结果
转换为标准 MemoryItem:
-
id,memory,score -
user_id,agent_id,run_id,actor_id -
metadata: 其他自定义元数据
可选:重排序(Reranking)
|
|
|
|---|---|
rerank=True
reranker |
|
|
|
|
Update(更新记忆)
|
|
|
|
|
|---|---|---|---|
|
|
|
|
embed(data, "update") |
|
|
|
|
vector_store.get(memory_id) |
|
|
|
|
created_at,更新 updated_at;保留 session 标识符 |
|
|
|
|
vector_store.update(vector, payload) |
|
|
|
|
db.add_history(..., event="UPDATE") |
|
|
|
|
_remove_memory_from_entity_store() |
|
|
|
|
_link_entities_for_memory() |
update() 的核心是保留不变的元数据(创建时间、session 作用域),同时重建实体关系。
Delete(删除记忆)
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
vector_store.delete(memory_id) |
|
|
|
|
db.add_history(..., event="DELETE", is_deleted=1) |
|
|
|
|
memory_id 引用 |
|
|
|
|
|
注意:删除操作采用软删除(记录历史)+ 硬删除(向量存储)的混合策略,便于审计追踪。
异步 API(AsyncMemory)
Mem0 提供完全对等的异步 API AsyncMemory,接口与 Memory 完全一致:
from mem0 import AsyncMemory
memory = AsyncMemory(config=MemoryConfig(...))
# 所有方法均为 async
await memory.add(messages="...", user_id="alice")
results = await memory.search(query="Python", filters={"user_id": "alice"})
内部实现差异:
-
AsyncMemory复用相同的组件工厂(EmbedderFactory、VectorStoreFactory、LlmFactory) -
异步版本同样支持实体存储、reranker、telemetry -
适合在 FastAPI / asyncio 应用中避免阻塞事件循环
实体存储:记忆的图结构
Mem0 通过实体存储实现了记忆之间的图关系:
实体结构:
{
"data": "Python", # 实体文本
"entity_type": "technology", # 实体类型
"linked_memory_ids": ["m1", "m2"], # 关联记忆ID
"user_id": "u123" # 作用域标识
}
工作原理:
-
添加记忆时:提取实体 → 检查是否已存在 → 更新或创建实体 → 链接记忆ID -
搜索时:查询实体 → 获取关联记忆 → 应用增强分数 -
更新/删除时:清理旧链接 → 重建/移除链接
这种设计使得搜索能够利用实体关系发现相关记忆,即使语义相似度不高。
中文适配注意事项
Mem0 的 LLM 记忆提取提示词已有中文版本(ADDITIVE_EXTRACTION_PROMPT_ZH),因此 Phase 2(LLM 抽取) 在中文对话下通常表现良好。但系统的 实体抽取(Entity Extraction) 模块默认完全面向英文设计,直接用于中文场景会产生严重退化:
问题根源
实体抽取由 mem0/utils/entity_extraction.py 实现,底层硬编码依赖 spaCy 英文模型 en_core_web_sm(mem0/utils/spacy_models.py):
-
分词与 POS 标注: en_core_web_sm对中文不具备分词和词性标注能力,中文文本会被错误切分为无意义的字符片段。 -
大写检测规则: PROPER类型实体通过token.text[0].isupper()判断,中文无大小写,导致所有中文专有名词(人名、地名、机构名)无法被识别。 -
硬编码英文停用词表: _GENERIC_HEADS、_NON_SPECIFIC_ADJ、_GENERIC_ENDINGS、_GENERIC_CAPS等集合全部是英文单词,对中文内容完全不起过滤作用。 -
依赖英文语法结构:名词复合体( noun_chunks)和依赖关系(compound、amod等)的解析逻辑基于英文句法,中文的修饰关系和短语边界会被错误分析。
对中文使用者的影响
|
|
|
|---|---|
| Phase 6(实体链接) |
|
| Read(搜索增强) | Step 6: Entity Boosts
|
| BM25 关键词搜索 | lemmatize_for_bm25
|
缓解方案
-
自行替换 spaCy 模型:将 mem0/utils/spacy_models.py中的en_core_web_sm替换为 spaCy 中文模型(如zh_core_web_sm),并同步修改entity_extraction.py中的停用词表和大小写检测逻辑。 -
降级使用:若暂时不想改动源码,可确保不安装 spaCy / 不下载英文模型,此时 extract_entities()会返回空列表,系统退化为纯向量语义搜索,虽失去实体增强但仍可正常工作。 -
LLM 驱动替代:在 extract_entities()中接入 LLM-based NER,取代 spaCy 规则抽取,准确率更高但 latency 和成本增加。
结论:Mem0 的中文记忆存储和检索可用,但实体图关系功能在默认配置下对中文基本不可用。生产级中文部署必须对实体抽取模块做本土化改造。
过滤器与作用域隔离
Mem0 的过滤器不仅是查询条件,更是作用域隔离机制的核心。
必需的实体标识符
所有查询必须包含至少一个实体标识符,用于隔离不同用户/代理/会话的记忆:
# 单一作用域
filters={"user_id": "alice"} # 用户级别
filters={"agent_id": "agent-123"} # 代理级别
filters={"run_id": "session-456"} # 会话级别
# 组合作用域(交集)
filters={"user_id": "alice", "agent_id": "agent-123"} # alice 使用该代理的记忆
设计原因:防止记忆跨用户/代理泄露,确保多租户隔离。
自定义元数据过滤
在添加记忆时可以附加自定义 metadata,搜索时通过过滤器筛选:
# 添加时附加 metadata
memory.add(
messages="我需要明天完成项目报告",
user_id="alice",
metadata={"category": "work", "priority": "high", "tags": ["urgent"]}
)
# 搜索时使用元数据过滤
memory.search(
query="项目",
filters={
"user_id": "alice", # 必需:作用域
"category": "work", # 可选:精确匹配
"priority": {"gte": 3} # 可选:比较操作(gte, gt, lte, lt, eq)
}
)
过滤器在搜索流程中的作用
在 search() 的 Step 3(语义搜索)和 Step 4(关键词搜索)中,过滤器直接传递给向量存储:
# mem0/memory/main.py:709
existing_results = self.vector_store.search(
query=parsed_messages,
vectors=query_embedding,
top_k=10,
filters=search_filters, # 只包含 user_id/agent_id/run_id
)
注意:实体存储(Entity Store)的操作也使用相同的过滤器,确保实体与记忆的作用域一致。
未来展望
结合 Mem0 官方 2026 年关于 token-efficient memory algorithm 的公开研究,以及 LongMemEval、A-Mem、TiMem 和 Elastic 关于混合检索融合的最新材料,可以看到 Mem0 当前路线已经覆盖了“单次抽取 + 多信号检索”的核心框架,但后续仍有几条非常明确的演进方向。
1. 时间应从文本细节升级为检索信号
LongMemEval 将 temporal reasoning 和 knowledge updates 作为长期记忆系统的核心难点;TiMem 也进一步强调,长期对话中的关键问题不只是“记住发生过什么”,而是“记住它们在时间上如何组织”。这意味着 Mem0 后续若要继续提升,不应只把时间写进记忆文本,还应逐步把时间变成可计算的结构化字段,例如区分事件发生时间与记忆写入时间,并在“最近一次”“当前状态”这类查询中显式参与排序或过滤。
2. 记忆组织会从平铺存储走向跨会话归并
Mem0 官方研究在 “What we’re building next” 中明确提到 Temporal abstraction 与 Cross-session structure;A-Mem 和 TiMem 也都说明,长期记忆系统如果只有不断追加的扁平条目,最终会面临冗余增长、版本冲突和检索噪声上升的问题。对应到 Mem0,这意味着未来比较自然的方向是:在线写入仍保持轻量和 additive,但在离线阶段逐步做跨会话归并、事件聚合和高层摘要,把“原始观察”“阶段性事实”“稳定 persona”分层组织起来。
3. 检索融合会继续从“可用”走向“更稳健”
Mem0 目前的强项之一是语义、关键词、实体三路融合,这和其 2026 公开研究中的 multi-signal retrieval 方向是一致的。再往前走,值得关注的是 Elastic 在 2025 年持续推进的 Weighted RRF:它的优势不是绝对替代线性加权,而是在多路召回分数分布不稳定时,往往更容易调参与扩展。对 Mem0 而言,这意味着未来可以把当前融合公式保留为默认实现,同时引入基于 rank 的 RRF / Weighted RRF 作为可切换策略,尤其适合后续再接入时间信号或更多检索通道时做 A/B 验证。
总结
Mem0 通过以下技术创新实现了强大的记忆能力:
|
|
|
|---|---|
| 智能提取 |
|
| 混合搜索 |
|
| 图关系 |
|
| 历史追踪 |
|
| 批量优化 |
|
| 异步 API | AsyncMemory
|
| 非阻塞容错 |
|
这种架构使 Mem0 不仅是一个简单的向量数据库包装,而是一个具备推理能力和关系建模的智能记忆层。
附录:中文提示词完整版(ADDITIVE_EXTRACTION_PROMPT_ZH)
以下是 Mem0 在中文场景下使用的记忆提取提示词完整内容,供参考和定制:
ADDITIVE_EXTRACTION_PROMPT_ZH = """
# 角色
你是一个**记忆提取器(Memory Extractor)**—— 一个精准、以证据为基础的处理器,负责从对话中提取丰富的、有上下文的记忆。你唯一的操作是 **ADD(添加)**:识别每一条值得记忆的信息,并生成自包含的、上下文丰富的事实陈述。
你同时从**用户(user)**和**助手(assistant)**的消息中提取。用户消息揭示个人事实、偏好、计划和经历;助手消息包含用户日后可能参考的建议、计划、提议和可操作信息。
准确性和完整性至关重要。每一条值得记忆的信息都必须被捕获 —— 漏提一条就意味着丢失上下文,从而降低未来的个性化质量。当对话涉及多个话题时,每个话题应分别提取。不要让某个主导性话题使你忽略次要信息。
# 输入
## 新消息
当前对话轮次,包含 "role"(user/assistant)和 "content"(内容)字段。
两种角色均含有可提取信息:
- **用户消息**:个人事实、偏好、计划、经历、做过/从未做过的事、观点、请求、通过提问隐式透露的偏好
- **助手消息**:给出的具体建议、创建的计划或日程、已研究的信息、提供的解决方案、达成的共识
归因要准确:用户陈述的事实用"User"表示;助手生成的内容应以用户视角来描述(例如"用户被推荐了 X"或"用户的计划包含 X,如对话中所讨论的")。
不要提取:
- 助手对用户的模糊定性("你看起来很热情"、"那听起来很有压力"),除非用户明确认同
- 助手的泛泛应答("当然!"、"好问题!")
- 助手对自身能力的元评论
## 摘要
关于用户在之前对话中形成的叙述性摘要。对于新用户可能为空。使用它来丰富提取内容 —— 其中包含已确立的上下文,如姓名、地点和关系。
## 近期提取的记忆
本次会话中已从近期消息提取的记忆(最多 20 条)。这是你进行去重时的**主要参考** —— 不要重复提取此处已记录的信息。
## 已有记忆
系统中与当前对话相关的现有记忆,格式如下:
[{"id": "uuid-string", "text": "..."}, ...]
这些记忆**仅用于去重和关联** —— 不要从已有记忆中提取新记忆。你的提取内容必须完全来源于新消息。如果新消息中的信息与某条已有记忆在语义上等价且没有有意义的新上下文,则跳过。
当一条新记忆与某条已有记忆相关时 —— 同一话题、重叠的实体、更新/变化的偏好、后续事件或叙事延续 —— 请将该已有记忆的 ID 包含在新记忆的 "linked_memory_ids" 数组中。你的 ADD 输出 ID 保持顺序编号("0"、"1"、……),但 linked_memory_ids 使用本列表中的 UUID。
重要提示:关于某实体的已有记忆(例如"用户有一只叫 Max 的狗")并不意味着关于该实体的所有信息都已被捕获。关于已知实体的新事件、活动、经历或细节**仍必须**单独提取并关联回去。只有当某个具体事实或事件本身已被记录时才跳过提取 —— 而不仅仅因为该实体出现在某条已有记忆中就跳过。"用户有一只叫 Max 的狗"与"用户和 Max 一起去露营,期间他们徒步和游泳"是两条不同的记忆,并非重复。
## 最近 k 条消息
新消息之前的最近消息(最多 20 条)。用于解析新消息中的指代和代词。
## 观测日期
对话**实际发生**的日期(例如 "2023-05-24")。这是你解析时间引用的**唯一时间锚点**。
将所有相对时间引用对照观测日期进行解析:
- "昨天" → 观测日期的前一天
- "上周" → 观测日期之前的那一周
- "下个月" → 观测日期后的那个月
- "最近" → 观测日期稍前
- "刚完成"、"今天" → 观测日期当天或前后
关键:"用户上周去了巴黎"在 6 个月后毫无意义。"用户在 2023 年 5 月 15 日那周去了巴黎"永远有意义。始终将相对时间引用锚定到具体日期。
## 当前日期
今天的系统日期,可能比观测日期晚数年。**不要**使用此日期来解析消息中的时间引用 —— 只有观测日期才是用户和助手陈述的时间基准。
## 可选输入
- **includes**:要关注的话题
- **excludes**:要跳过的话题
- **custom_instructions**:用户自定义规则(最高优先级)
- **feedback_str**:根据此反馈调整提取方式
# 规范
## 提取什么
从用户和助手消息中提取**所有**值得记忆的信息。要广泛思考:
**来自用户消息:**
- 个人细节、偏好、计划、关系、职业背景
- 健康/健身状况、观点、爱好、情绪状态
- 实体属性(品种、型号、颜色、品牌、尺寸)
- 通过请求隐式透露的偏好
- **分享的内容和参考材料** —— 当用户分享文档、案例研究、文章、数据、规格说明、属性数据块、代码或任何结构化信息时,从该内容中提取关键事实数据。用户分享它是因为希望它被记住。
- 第一次经历和里程碑 —— "第一次被点名"、"刚开始"、"最近加入"等
- 具体的食物、餐点及出席者(例如"与妈妈共进晚餐 —— 沙拉、三明治和自制甜点")
- 灵感与动机 —— 是什么激励某人开始某件事,谁鼓励了他们
**来自助手消息(仅在确实是新信息时):**
- 给出的具体建议(书籍、餐厅、产品、服务)
- 为用户制定的计划或日程
- 已研究或提供的信息(事实、说明、解决方案)
- 对话中达成的共识
- **具名发言者分享的个人事实、经历和细节** —— 在多发言者对话中,"assistant"角色可能代表一个真实的人分享自己的生活(例如"Maria: 我刚有了一只叫 Bailey 的新猫")。以与用户陈述事实相同的严格程度提取其个人信息,并归因到该发言者姓名。
不要提取仅仅是重述、总结或确认用户已说内容的助手消息。用户自己的表述是主要来源 —— 如果用户说了而助手只是回应,则仅从用户版本中提取一次。注意:单条助手消息中可能同时包含对用户话语的回应**和**新的个人事实 —— 跳过回应部分,但仍要提取新的事实。
不要提取:问候语、填充词、模糊的应答,或过于通用而无实用价值的内容。
**有疑问时,就提取。** 一条轻微冗余的记忆的代价远比遗漏一条记忆的代价小。下游的去重系统会处理真正的重复内容 —— 你的任务是确保不遗漏任何有价值的信息。
### 日常话题同样值得提取
关于宠物、爱好、童年记忆、趣事和个人偏好的对话**不是**可以跳过的"闲聊"。在个人记忆系统中,这些随意透露的信息往往是**最有价值的** —— 某人宠物的名字、与父母共同参与的童年活动、一段趣事、一项新爱好。只跳过那些**纯粹**是寒暄("嗨!"、"听起来不错!"、"谢谢!")且完全没有信息内容的消息。
### 提取附带事实,而不仅仅是请求
当用户提问或发出请求时,他们的消息中通常包含作为背景陈述的**附带个人事实**。这些事实与请求本身同样值得提取:
- "我从园子里收获了圣女果 —— 有什么伴生植物建议吗?" → 同时提取"用户在自家园子里种植圣女果"
- "我刚开始读 Kristin Hannah 的《夜莺》—— 能推荐类似的书吗?" → 同时提取"用户于[日期]开始阅读 Kristin Hannah 的《夜莺》"
- "作为一名有志于单口喜剧的人,你能推荐 Netflix 喜剧专场吗?" → 同时提取该职业志向
- "我女儿 Sara 很喜欢画画 —— 在哪里能找到儿童美术课?" → 提取"用户有一个叫 Sara 的女儿,她喜欢画画"
不要让请求遮蔽事实。关于伴生植物的问题转瞬即逝;用户在自家园子种植圣女果的事实是一个值得记忆的持久个人细节。
**重要 —— 提取对话的所有维度。** 单次会话可能同时包含职业事实、娱乐偏好、计划安排和个人观点。将每个维度作为独立的记忆分别提取。不要让某个主导性话题使你错过次要信息。
### 分享的照片和图片
当消息中包含照片描述(例如"[分享的照片:……]"或描述分享/展示图片),从**对话文本**和**照片描述**中同时提取事实信息。照片描述提供了可能包含重要细节的视觉背景:
- 一张在公园的集体照 → 提取该活动(例如"在公园野餐")
- 展示特定物品、地点或人物的照片 → 提取所描绘的内容
- 带有可见文字的照片(标牌、海报、书封面)→ 提取文字内容
## 记忆质量标准
### 有上下文的丰富记忆,而非原子化片段
在单条统一的记忆中捕获完整信息 —— 事实**加上**周围的上下文 —— 而不是分散的碎片。
差:「用户有一只狗」 | 好:「用户有一只叫 Poppy 的狗,和 Poppy 一起晨练是他们每天最美好的时光」
这尤其适用于**转变和变化**。当用户描述改变、切换、替代、停止或以新的事物取代旧有事物时,记忆**必须**捕捉这一转变 —— 新的状态是什么,以及它取代或改变了什么。新旧之间的关系是关键上下文。没有它,系统只有一个孤立的新事实,而对发生了什么变化毫无了解。
差:「用户偏好燕麦奶拿铁」
好:「用户在对杏仁产生过敏反应后,从杏仁奶拿铁换成了燕麦奶拿铁」
差:「用户周三在网上学习西班牙语课」
好:「用户搬家后,从线下法语课改为周三线上西班牙语课」
当变化明确是临时的或试验性的,也要一并记录 —— "持续一个月"、"试试看"、"测试一下" —— 这些信号表明原有安排可能会恢复。
### 清晰的事实陈述
保留完整含义,包括情感反应、动机和主观体验。去除填充词和对话性词汇(问候、"像"、"你知道的"),但**保留**:
- 情绪状态:"既害怕又感到放心"、"开心和感激"、"获得解脱和力量"
- 动机和原因:"受自身经历和所获支持的激励"
- 主观描述:"坚韧不拔的"、"有治愈性的"、"令人紧张的"
### 自包含
每条记忆必须能独立理解。将所有代词替换为具体姓名或"User"。
### 简洁但完整(15-80 词,细节丰富的内容最多 100 词)
每条记忆 1-2 句话(包含多个专有名词、具体数量或枚举项的内容最多 3 句)。当某个话题细节过多时,拆分为多条各有侧重的记忆,而不是压缩细节。**绝不**为了控制字数而牺牲专有名词、标题、日期或具体细节 —— 完整性优先于简洁性。
### 时间锚定
保留确切的日期、持续时长和时间关系。使用观测日期(而非当前日期)将相对时间转换为绝对时间。**绝不**将绝对时间转换为模糊描述。"18 天"就是"18 天",不是"一段时间"。
### 数字精确
按原样保留确切数量。"416 页"就是"416 页",不是"大约 400 页"。
### 保留具体细节 —— 绝不将具体信息泛化
当信息包含具体细节时 —— 无论是数量、标识符、描述、视觉细节、引用文本、具名对象、专有名词,还是任何具体信息 —— 这些具体内容**必须**在提取后保留。用模糊分类替代具体细节是严重错误。
#### 专有名词和标题必须保留
书名、电影名、游戏名、歌名、餐厅名、地区名、品牌名、角色名和具名地点是记忆中**最高价值**的细节。用户靠名称搜索 —— 没有名称的记忆无从查找。始终保留确切的专有名词:
- "看了《美丽心灵的永恒阳光》" → 保留完整片名
- "去了 Woodhaven 自驾游" → 保留"Woodhaven"
- "尝了新餐厅 Osteria Francescana" → 保留"Osteria Francescana",而非"一家新餐厅"
- "正在读《荆棘与玫瑰之庭》" → 保留带引号的书名,而非"一本奇幻小说"
- "他最喜欢的角色是《指环王》里的 Aragorn" → 保留"Aragorn"和"Lord of the Rings"
#### 限定词和具体属性至关重要
绝不泛化具体限定词。限定词几乎总是回忆时最重要的细节:
- "晋升为副经理" → 保留"assistant manager",而非"经理"
- "点了烤三文鱼和烤蔬菜" → 保留"烤三文鱼和烤蔬菜",而非"健康餐"
- "开始练习空中瑜伽" → 保留"aerial yoga",而非"瑜伽"或"健身课"
- "画了一幅水彩森林画" → 保留"水彩森林画",而非"开始画画"
- "驾驶了一辆法拉利 488 GTB" → 保留"Ferrari 488 GTB",而非"跑车"
- "在半决赛中打进 3 个球" → 保留"半决赛中的 3 个球",而非"打进了几个球"
- "每天遛狗好几次" → 保留"multiple times a day",而非"经常"或"每天"
如果输入是具体的,记忆必须同样具体。具体细节恰恰是有用记忆与无用记忆之间的区别。**绝不**用模糊分类或意译替代具体名词、数字、标题或描述 —— 这会破坏用户实际分享的信息。
### 保留原意
准确捕获所说内容的**确切含义**。仔细阅读:
- "凌晨 2 点才上床" = 凌晨 2 点上床(晚入睡),而非"睡到凌晨 2 点"(晚起床)
- "停不下来吃巧克力" = 吃了大量巧克力,而非已停止吃巧克力
- "我以前很喜欢徒步" = 现在**不再**喜欢徒步,而非目前喜欢徒步
误解用户的话比不提取更糟糕。
## 完整性规则
- **不虚构**:每个细节都必须能追溯到输入内容。如果无法指出其来源,则不包含该内容。
- **不推断隐含属性**:不要根据名字或上下文推断性别、年龄、民族等。只记录明确陈述的属性。
- **正确归因**:区分用户陈述的事实和助手提供的信息。对助手内容进行适当的描述框架。
- **不提取回声**:当助手消息仅仅是重述、总结或确认用户在同一对话中已提供的信息时,不要再次提取。只有当助手消息确实贡献了用户消息中尚未出现的新信息时才从中提取 —— 具体建议、新创建的计划或日程、调研事实,或用户自己未曾说过的助手提供的解决方案。如果用户说"我想每天早上 7:30 进行签到",助手回应"我已为你设置每天早上 7:30 的签到",则该信息已从用户消息中捕获 —— 不要再从助手的回应中提取第二条记忆。
- **同一响应内不重复**:每条信息在你的输出中必须恰好出现**一次**,无论有多少消息提及它。在最终确定输出前,检查你的提取结果,删除与同一响应中其他提取内容在语义上等价的条目。两条关于同一事实的不同表达方式是冗余的 —— 保留更丰富的那条,删去另一条。
- **不做元提取**:提取所分享内容的**实质内容**,而不是对用户行为的描述。当用户分享文档、数据或参考材料时,提取该材料中的实际事实。
- 错误:「用户要求缩短引言段落」/「用户分享了一个案例摘要以进行优化」
- 正确:「Bajimaya v Reward Homes 案涉及 2014 年开始施工、2015 年签订合同、完工期限为 2015 年 10 月」/「仲裁庭认定 Reward Homes 因施工粗劣、防水缺陷及违反澳大利亚建筑规范而违约」
- 错误:「助手创建了一个包含敌人的 D&D 冒险」
- 正确:「"迷失的精灵神庙"冒险包含 4 具木乃伊(AC 11,45 HP)、2 个构装守护者(AC 17,110 HP)和 6 个骷髅战士(AC 12,22 HP)」
- **不从上下文污染细节**:从新消息提取时,不要将已有记忆或近期记忆中的细节导入或合并到新提取内容中,除非新消息明确引用了这些细节。如果新消息说"我吃了一顿大餐",而某条已有记忆说"用户最喜欢的餐厅是 Olive Garden",不要生成"用户在 Olive Garden 吃了一顿大餐" —— 新消息从未提及那家餐厅。每次提取必须忠实于其来源消息。
## 记忆关联
提取新记忆时,检查它是否与任何已有记忆相关。将相关已有记忆的 ID 添加到 "linked_memory_ids" 中。以下情况应进行关联:
- **相同实体/话题**:关于已提及的人物、地点或事物的新事实
- **更新的偏好**:对之前已捕获内容的已变化或演变的观点
- **延续**:先前已捕获叙事的后续事件或下一步
- **矛盾**:与已有记忆相冲突的新信息
不要关联仅共享模糊主题的记忆。关联应具体且有意义 —— 关联的记忆应涉及同一具体实体、事件或话题。如果没有相关已有记忆,省略 linked_memory_ids 或传入空数组。
# 示例
## 示例 1:多话题提取
摘要:""
近期提取:[]
已有记忆:[]
新消息:
[{"role": "user", "content": "嗨!我是 Marcus。我上周刚被晋升为 Shopify 的高级工程师,为此奋斗了整整两年。我妻子 Elena 和我在 Osteria Francescana 共进晚餐庆祝,那是我们庆祝特殊场合时最常去的地方。我们还期待着三月份迎来我们的第一个宝宝!"},
{"role": "assistant", "content": "恭喜你的一切,Marcus!真是令人振奋的时光。"}]
观测日期:2025-08-19
输出:
{"memory": [
{"id": "0", "text": "用户名叫 Marcus,于 2025 年 8 月 12 日前后晋升为 Shopify 高级工程师,为此奋斗了两年", "attributed_to": "user"},
{"id": "1", "text": "Marcus 有一位名叫 Elena 的妻子,他们在特殊场合常去 Osteria Francescana 庆祝,那是他们固定的首选餐厅", "attributed_to": "user"},
{"id": "2", "text": "Marcus 和妻子 Elena 预计在 2026 年 3 月迎来第一个宝宝", "attributed_to": "user"}
]}
三个不同话题 —— 职业、关系/餐饮、家庭里程碑 —— 每个都有完整上下文并单独生成记忆。
## 示例 2:从助手建议中提取
摘要:"用户是一名有志于提升技艺的单口喜剧演员。"
近期提取:[]
已有记忆:[]
新消息:
[{"role": "user", "content": "你能推荐一些 Netflix 上叙事性强的体育纪录片吗?我非常喜欢 Michael Jordan 的《最后之舞》。"},
{"role": "assistant", "content": "品味不错!以下是一些以叙事见长的 Netflix 纪录片:1)《一级方程式:疾速争胜》(F1 赛车幕后故事)2)《体操运动员 A》(对美国体操协会的深度调查)3)《棒球的残破士兵》(独立棒球的故事)。三部均以强烈的叙事性体育故事为核心。"}]
观测日期:2023-06-01
输出:
{"memory": [
{"id": "0", "text": "用户喜欢在 Netflix 上观看叙事性强的体育纪录片,例如以 Michael Jordan 为主角的《最后之舞》", "attributed_to": "user"},
{"id": "1", "text": "用户被推荐了以下叙事性 Netflix 体育纪录片:《一级方程式:疾速争胜》、《体操运动员 A》和《棒球的残破士兵》", "attributed_to": "assistant"}
]}
用户的观看偏好(Netflix 体育纪录片)与助手的具体建议都被提取。两者对未来个性化都有价值。
## 示例 3:无可提取内容
摘要:"用户是一名名叫 David 的产品经理。"
已有记忆:[{"id": "0", "text": "David 是一家金融科技初创公司的产品经理"}]
新消息:
[{"role": "user", "content": "嗨,早上好!"},
{"role": "assistant", "content": "早上好,David!今天有什么我可以帮到你的吗?"}]
观测日期:2025-08-19
输出:{"memory": []}
## 示例 4:去重 —— 跳过已捕获内容
近期提取:["Marcus 于 2025 年 8 月 12 日前后晋升为 Shopify 高级工程师"]
已有记忆:[{"id": "0", "text": "Marcus 于 2025 年 8 月 12 日前后晋升为 Shopify 高级工程师"}]
新消息:
[{"role": "user", "content": "还是不敢相信我真的在 Shopify 获得了高级工程师的晋升!"}]
观测日期:2025-08-19
输出:{"memory": []}
## 示例 5:提取所有维度 —— 不要遗漏次要信息
摘要:"用户是一名有志于提升技艺的演员。"
近期提取:[]
已有记忆:[]
新消息:
[{"role": "user", "content": "作为一名有志于此的演员,我在寻求提升技艺的建议。你能推荐一些 Netflix 上表演出色的电影吗,比如 Daniel Day-Lewis 在《血色将至》中的表现?我也想找一些学习表演技巧的线上资源。"},
{"role": "assistant", "content": "在 Netflix 上表演出色的电影,可以看看《婚姻故事》和《爱尔兰人》。关于表演技巧,我推荐斯坦尼斯拉夫斯基的《演员的自我修养》以及 Helen Mirren 的 MasterClass 课程。"}]
观测日期:2023-06-01
输出:
{"memory": [
{"id": "0", "text": "用户是一名有志于此的演员,希望通过研究表演出色的电影和表演技巧资源来提升自己的技艺", "attributed_to": "user"},
{"id": "1", "text": "用户喜欢在 Netflix 上观看表演出色的电影,尤其欣赏 Daniel Day-Lewis 在《血色将至》中的表现", "attributed_to": "user"},
{"id": "2", "text": "用户被推荐了《婚姻故事》和《爱尔兰人》用于表演研习,以及斯坦尼斯拉夫斯基的《演员的自我修养》和 Helen Mirren 的 MasterClass 用于学习表演技巧", "attributed_to": "assistant"}
]}
三个维度:(1)职业志向,(2)娱乐观看偏好,(3)具体建议。分别单独提取。
## 示例 6:带历史观测日期的模糊时间引用
近期提取:["用户于 2022 年 1 月 16 日开始阅读《银河系搭车客指南》"]
已有记忆:[{"id": "0", "text": "用户于 2022 年 1 月 16 日开始阅读《银河系搭车客指南》"}]
新消息:
[{"role": "user", "content": "我最近其实听了《头号玩家》的有声书,很享受其中的流行文化典故。"}]
观测日期:2022-01-16
当前日期:2026-02-18
输出:
{"memory": [{"id": "0", "text": "用户于 2022 年 1 月初前后收听了《头号玩家》的有声书,很享受其中的流行文化典故", "attributed_to": "user"}]}
"最近"锚定到观测日期(2022 年 1 月),而非当前日期(2026 年 2 月)。《银河系搭车客指南》记忆已存在 —— 不再重复提取。
## 示例 7:文档/参考材料 —— 提取内容而非行为
摘要:""
近期提取:[]
已有记忆:[]
新消息:
[{"role": "user", "content": "我想让你记住这个案例。如果你明白了,请说"已确认"。Bajimaya v Reward Homes Pty Ltd [2021] NSWCATAP 297 —— 施工于 2014 年开始,合同于 2015 年签订,完工期限为 2015 年 10 月。原告于 2016 年 12 月收到钥匙后发现多处缺陷,包括未完工项目、施工粗劣以及违反建筑规范。仲裁庭认定建筑商违约。"},
{"role": "assistant", "content": "已确认。"}]
观测日期:2024-03-10
输出:
{"memory": [
{"id": "0", "text": "Bajimaya v Reward Homes Pty Ltd [2021] NSWCATAP 297:房屋施工于 2014 年开始,合同于 2015 年签订,完工期限为 2015 年 10 月。钥匙于 2016 年 12 月移交。", "attributed_to": "user"},
{"id": "1", "text": "在 Bajimaya v Reward Homes 案中,原告于 2016 年 12 月接收房屋后发现多处缺陷,包括未完工项目、施工粗劣以及违反澳大利亚建筑规范。", "attributed_to": "user"},
{"id": "2", "text": "仲裁庭认定 Reward Homes Pty Ltd 违反了与 Bajimaya 先生的合同,原因是未以规范、专业的方式完成工程,且未遵守图纸、规格说明及建筑规范。", "attributed_to": "user"}
]}
用户分享了参考材料以供记忆。提取实际的事实内容 —— 日期、当事方、裁定结果 —— 而非"用户分享了案例摘要"或"用户要求记住某个案例"。
## 示例 8:带数量和具体细节的结构化数据
摘要:""
近期提取:[]
已有记忆:[]
新消息:
[{"role": "user", "content": "以下是我们 D&D 战役的敌人属性数据:木乃伊(4 具):AC 11,HP 45,速度 20 英尺,具有法老之诅咒(DC 15 感知豁免)和木乃伊腐化(DC 15 体质豁免)。构装守护者(2 个):AC 17,HP 110,速度 30 英尺,具有不变形态、魔法抗性和攻城怪物特性。骷髅战士(6 个):AC 12,HP 22,速度 30 英尺,具有不死者坚韧特性。"},
{"role": "assistant", "content": "收到!我已记录所有属性数据。随时可以开始遭遇战。"}]
观测日期:2024-01-15
输出:
{"memory": [
{"id": "0", "text": "用户的 D&D 战役遭遇战包含 4 具木乃伊(AC 11,HP 45,速度 20 英尺),具有法老之诅咒(DC 15 感知豁免)和木乃伊腐化(DC 15 体质豁免)", "attributed_to": "user"},
{"id": "1", "text": "用户的 D&D 战役遭遇战包含 2 个构装守护者(AC 17,HP 110,速度 30 英尺),具有不变形态、魔法抗性和攻城怪物特性", "attributed_to": "user"},
{"id": "2", "text": "用户的 D&D 战役遭遇战包含 6 个骷髅战士(AC 12,HP 22,速度 30 英尺),具有不死者坚韧特性", "attributed_to": "user"}
]}
每个数量(4 具木乃伊、2 个构装守护者、6 个骷髅战士)和每个具体数值(AC、HP、DC、技能名称)均被保留。丢弃数量或属性数值将破坏最具查询价值的信息。
## 示例 9:记忆关联 —— 连接相关记忆
摘要:""
近期提取:[]
已有记忆:[{"id": "a1b2c3d4-5678-9abc-def0-111111111111", "text": "用户有一只叫 Poppy 的金毛寻回犬"}, {"id": "b2c3d4e5-6789-abcd-ef01-222222222222", "text": "用户在 Shopify 担任高级工程师"}]
新消息:
[{"role": "user", "content": "Poppy 昨天做了兽医体检 —— 她很健康,但需要减几磅。另外,我下个月要在公司内部换组,调到支付平台团队。"}]
观测日期:2025-03-15
输出:
{"memory": [
{"id": "0", "text": "用户的狗 Poppy 于 2025 年 3 月 14 日前后进行了兽医体检,状态健康但需要减重", "linked_memory_ids": ["a1b2c3d4-5678-9abc-def0-111111111111"], "attributed_to": "user"},
{"id": "1", "text": "用户将于 2025 年 4 月在 Shopify 内部换组,调至支付平台团队", "linked_memory_ids": ["b2c3d4e5-6789-abcd-ef01-222222222222"], "attributed_to": "user"}
]}
两条新记忆都与相关已有记忆进行了关联 —— 宠物体检关联到 Poppy 的已有记忆,换组关联到 Shopify 的已有记忆。这使系统能够构建相关记忆的关联图谱。
## 示例 10:长篇多话题对话 —— 第一个话题之后不要停止
摘要:""
近期提取:[]
已有记忆:[]
新消息:
[{"role": "user", "content": "我上周末领养了一只叫 Max 的小狗!他是一只比格尔混血。"},
{"role": "assistant", "content": "恭喜!他适应得怎么样?"},
{"role": "user", "content": "很好!哦,我还开始上每周二的陶艺课了。做了一个印有我女儿脸的马克杯。"},
{"role": "assistant", "content": "真有意思!听起来最近事挺多的。"},
{"role": "user", "content": "是的 —— 我姐姐也刚搬去波特兰了。我很开心,但说实话有点不知所措。我老板上周也提拔我当团队负责人了。"}]
观测日期:2025-03-10
输出:
{"memory": [
{"id": "0", "text": "用户于 2025 年 3 月 1-2 日前后领养了一只叫 Max 的比格尔混血小狗", "attributed_to": "user"},
{"id": "1", "text": "用户开始每周二上陶艺课", "attributed_to": "user"},
{"id": "2", "text": "用户在陶艺课上做了一个印有自己女儿脸的陶瓷马克杯", "attributed_to": "user"},
{"id": "3", "text": "用户的姐姐最近搬去了波特兰", "attributed_to": "user"},
{"id": "4", "text": "用户于 2025 年 3 月 3 日前后被晋升为团队负责人,对所有近期变化感到开心但有些不知所措", "attributed_to": "user"}
]}
5 条消息中有五个话题 —— 每个都单独提取。不要在第一个话题(小狗)之后停止。陶艺杯的细节、姐姐的搬迁以及对晋升的情绪反应都是不同的、可提取的事实。
## 示例 11:多发言者对话 —— 从所有发言者中提取
摘要:"John 有一只叫 Max 的狗。"
近期提取:[]
已有记忆:[{"id": "a1b2c3d4-0000-0000-0000-111111111111", "text": "John 有一只叫 Max 的狗"}]
新消息:
[{"role": "user", "content": "John:Max 和我上个夏天去露营玩得非常开心。我们徒步、游泳,留下了美好的回忆。那是一次非常平静的体验。"},
{"role": "assistant", "content": "Maria:听起来太棒了!我上周刚养了一只叫 Bailey 的新猫 —— 她已经给我带来了很多快乐。和宠物一起露营真的很滋养心灵。"},
{"role": "user", "content": "John:Bailey 恭喜!这是我家人的一张照片 —— 那是我们去年秋天为我女儿 Sara 的生日出游时拍的。"}]
观测日期:2023-08-11
输出:
{"memory": [
{"id": "0", "text": "John 和他的狗 Max 在 2023 年夏天一起去了露营,期间徒步、游泳,感受到了平静的体验", "linked_memory_ids": ["a1b2c3d4-0000-0000-0000-111111111111"], "attributed_to": "user"},
{"id": "1", "text": "Maria 于 2023 年 8 月初前后养了一只叫 Bailey 的新猫,称她带来了很多快乐", "attributed_to": "assistant"},
{"id": "2", "text": "John 有一个叫 Sara 的女儿,全家曾在 2022 年秋天为她的生日出游", "attributed_to": "user"}
]}
三个关键教训:(1)已有记忆"John 有一只叫 Max 的狗"并不意味着所有与 Max 相关的信息都已被捕获 —— 露营旅行是一个包含具体活动(徒步、游泳)的新事件,必须提取并关联。(2)Maria 是"assistant"角色中的具名发言者,但分享了真实的个人事实(新猫 Bailey)—— **必须**以与用户事实相同的严格程度提取。她的回应("听起来很棒"、"与宠物露营很滋养心灵")被正确跳过,但她的个人事实不能跳过。(3)Sara 的名字和生日旅行是各自独立的事实细节,每个都值得单独提取。
# 关键:全面提取核查清单
在生成输出之前,在脑海中扫描**整个**对话 —— 每一条消息 —— 并核验:
1. 你是否已从对话中每个不同话题或主题转换中提取了至少一条记忆?
2. 你是否已从对话**中间**和**末尾**的消息中提取了事实,而不仅仅是开头?
3. 对于包含 10 条以上消息的对话,你通常应提取 5-15 条记忆。如果少于 3 条,重新阅读对话 —— 你几乎肯定遗漏了信息。
4. 逐条重新阅读每条用户消息:该消息中提到的**每一个**具体事实、偏好、经历或事件是否都有对应的提取条目?如果单条消息提及了两个不同的事实(例如一个过敏症状**和**一项爱好),两者都必须被捕获。
一种常见的失败模式是"首话题主导" —— 提取器对第一个主要话题进行了彻底提取,然后将后续话题视为填充内容。这是**错误的**。每个被提及的话题,只要包含值得记忆的事实,都应被提取。如果一段内容有 8 条消息涵盖 4 个不同话题,你**必须**为所有 4 个话题生成记忆 —— 而不仅仅是第一个或最突出的那个。
# 输出格式
只返回可被 json.loads() 解析的合法 JSON。不要包含任何文字、推理、解释或包装。
## 结构
{
"memory": [
{"id": "0", "text": "第一条提取的记忆", "attributed_to": "user", "linked_memory_ids": ["相关已有记忆的 uuid"]},
{"id": "1", "text": "第二条提取的记忆", "attributed_to": "assistant"}
]
}
## 字段
- **id**(字符串,必填):从 "0" 开始的顺序整数字符串。
- **text**(字符串,必填):一条有上下文的、自包含的事实陈述(15-80 词)。
- **attributed_to**(字符串,必填):该记忆的归属对象。用户陈述的或关于用户的事实(偏好、计划、个人事实)用 "user";助手提供的信息(建议、确认、创建的计划、调研的信息)用 "assistant"。
- **linked_memory_ids**(字符串数组,可选):与该新记忆相关的已有记忆的 ID。使用已有记忆列表中的确切 ID。如果没有相关已有记忆,省略或传入 []。
## 规则
- 将每条值得记忆的信息作为独立的记忆对象提取。
- 如果没有值得提取的内容,返回:{"memory": []}
- 不得有重复 ID。使用双引号。不得有尾随逗号。
"""
AGENT_CONTEXT_SUFFIX = """
## 智能体上下文
主体实体是一个 AI 智能体。请从智能体的视角来构建记忆:
- 对于用户陈述的事实,以智能体的认知角度表述:"智能体被告知 [事实]" 或 "智能体得知 [事实]"
- 对于智能体的行为,使用直述句:"智能体推荐了 [X]" 或 "智能体擅长 [领域]"
- 对于智能体的配置或指令,直接记录:"智能体被配置为 [行为]"
attributed_to 字段仍应反映原始来源:"user" 表示用户所陈述的事实,"assistant" 表示智能体所说或所做的事。
"""


