在深入实践细节之前,我们首先需要理解几个核心概念以及它们如何协同工作,构成一个完整的记忆管理闭环。
概念名称 | 英文标识 | 核心定义 | 用途 |
|---|---|---|---|
用户消息 | message | 用户单次写入的原始对话内容(单轮,含 | 记忆写入的最小单元。 |
对话链路 | conversation | 由多轮 | 关联短期记忆,维系即时对话的连贯性。 |
短期记忆 | Short-term Memory | 存在于 | 保证对话的即时上下文,响应速度快,无需检索。 |
长期记忆 | Long-term Memory | 从多轮对话中抽取、结构化并固化的核心信息(事件、用户画像等),存储于向量数据库中。 | 实现跨对话、长周期的个性化记忆,需要通过检索召回。 |
抽取周期 | session | 触发长期记忆抽取的业务单元。当一个 | 控制长期记忆的生成频率与时机。 |
整个流程的核心思想是 “写后异步抽取、读时动态拼接”:
streaming_write):你的应用将每一轮的用户对话实时发送到记忆服务。token_count)自动监控对话累积量,一旦满足条件,便异步启动一个长期记忆抽取任务。get_context):当需要与大语言模型(LLM)交互时,你的应用调用一个统一接口。接下来,我们将详细拆解每个环节的最佳实践。
streaming_write): 构建记忆的基石 高效、可靠地写入对话数据是构建智能记忆系统的第一步。streaming_write 接口为你提供了一种低延迟、高吞吐的方式来实时记录每一轮对话,并在此基础上自动化长期记忆的生成。
在接入记忆服务时,你需要首先在两种写入方式中做出选择:
写入方式 | 核心机制 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
批式写入 | 由业务方自行管理对话轮次,每次调用 API 都被视为一个完整的 | 实现简单,对抽取时机的控制完全掌握在自己手中。 | 需要业务方自行实现复杂的缓存和批处理逻辑,难以平衡抽取频率与成本;无法利用平台提供的短期记忆能力。 | 迁移过渡期或对抽取时机有极端控制需求的特殊场景。 |
实时写入 | 业务方只需实时发送每一轮对话。平台负责缓存短期记忆,并根据预设规则(token 数、消息数、超时)自动触发长期记忆抽取。 | (推荐) 将复杂的会话管理和抽取触发逻辑下沉到平台,极大简化开发成本;无缝融合长短期记忆,性能更优。 | 抽取时机由平台管理,控制粒度相对较粗。 | 绝大多数新业务和希望优化现有架构的业务。 |
说明
最佳实践: 除非处于从旧系统迁移的临时阶段,否则始终推荐使用 streaming_write 实时写入方式。它不仅能降低你的开发和维护成本,还能让你充分利用平台提供的长短期记忆一体化能力。
extract_trigger) streaming_write 接口的核心在于 extract_trigger 对象,它定义了何时将累积的短期记忆转化为长期记忆。你可以组合使用以下三个条件,平台会在满足任意一个条件时触发抽取:
参数 | 类型 | 描述 | 推荐值与决策框架 |
|---|---|---|---|
|
| (推荐) 触发抽取的累计 Token 阈值。当一个 | 默认值:
|
|
| 触发抽取的累计消息(轮次)阈值。一轮包含 | 默认值: |
|
| 抽取等待的超时时间(秒)。当一个 | 默认值: |
conversation_id 设计 conversation_id 是串联起用户一次完整对话流程的唯一标识。正确地设计和管理 conversation_id 至关重要。
streaming_write 和 get_context 请求都使用相同的 conversation_id。conversation_id 的生命周期开始;对话的结束(例如,用户关闭窗口、切换主题)即是其结束。建议为每个新的对话会话生成一个新的、唯一的 conversation_id。业务前缀-用户ID-时间戳-随机串 的组合,例如 online-cs-user_12345-1678886400-a8d3b,以保证其唯一性和可追溯性。get_context): 让 LLM 读懂记忆 get_context 接口是连接记忆服务和大型语言模型(LLM)的桥梁。它的核心任务是根据当前的对话 conversation_id,智能地将不同来源、不同类型的记忆(短期、长期、画像)融合成一段结构清晰、可直接注入到 Prompt 中的上下文,从而赋予 LLM “记忆”的能力。
理解 get_context 在整个对话流程中的位置至关重要。下图清晰地展示了它与 streaming_write 及 LLM 推理的调用关系:
核心调用节奏如下:
get_context 接口。get_context 返回的 context_str 字符串,完整地、不加修改地插入到你为 LLM 设计的 Prompt 中的指定位置。streaming_write 接口写入记忆服务,完成闭环。get_context 返回的 context_str 内部已经过精心设计,遵循了特定的优先级和格式化策略,以达到最佳的理解效果。默认的拼接顺序如下,优先级由高到低:
conversation 中尚未被抽取的、最近的几轮原始对话。这部分保证了对话的即时连贯性,让模型能“记住”刚刚发生了什么。conversation 中已经被抽取并固化的长期记忆。这部分内容是对较早前对话的总结,相比原始对话更精炼。query 从其他历史 conversation 中检索到的相关事件记忆。这部分实现了跨对话的知识关联。默认格式化示例:
""" 以下是需优先参考的用户记忆,按优先级排序: 1. 用户画像 - 用户为28岁宝妈,宝宝1岁,居住于上海 - 用户偏好无乳糖、含DHA的宝宝奶粉,关注性价比 2. 最近对话内容 2026-02-03 09:10:30 user: 我家宝宝1岁,最近喝奶粉总胀气,有没有推荐的? 2026-02-03 09:11:15 assistant: 您好,宝宝胀气大概率和奶粉乳糖含量有关,建议优先选择无乳糖配方奶粉,我给您推荐2款高性价比的~ 3. 事件记忆 - [创建时间]2026-02-02 15:30:20 [内容]用户偏好简洁的报告格式 - [创建时间]2026-02-02 15:33:20 [内容]用户反馈折线图的颜色主题不适合深色模式,已提交反馈单。 """
说明
最佳实践:
get_context 返回的 context_str 是最简单、最高效的方式。get_context 同样会在响应中返回结构化的记忆列表,你可以基于此自定义拼接逻辑。为了从海量的长期记忆中精确找到最相关的信息,get_context 提供了强大的检索配置。
query: 这是进行长期记忆检索的核心。你应该传入当前用户最新的提问作为 query,以便平台能基于最新的意图进行语义匹配。event_search_config & profile_search_config: 这两个对象允许你对事件记忆和画像记忆的检索进行精细控制。
filter: 可以基于 user_id、memory_type 等 metadata 进行精确过滤。limit: 限制返回的记忆条数,是控制 Token 成本的关键。time_decay_config):
weight: 时间衰减权重,取值 0 到 1。值越大,时间越近的记忆得分越高。推荐 0.5 ~ 0.8。no_decay_period: 最近 N 天内的记忆不受衰减影响。这对于保留近期重要事件非常有用。注入到 Prompt 中的 Context 长度直接影响 LLM 的推理成本和性能。以下是控制 get_context 返回内容长度的几种策略:
limit: 在 event_search_config 和 profile_search_config 中设置合理的 limit 是最直接的方法。例如,事件记忆通常设置在 5-10 条,画像记忆为 1-2 条。token_count 自动进行滚动裁剪,只保留最新的内容。time_decay: 适当加大时间衰减权重,可以让系统更倾向于返回最新的记忆,间接减少了旧记忆的干扰,从而可能缩短总长度。get_context 请求中通过参数(如果接口支持)或在业务逻辑中,暂时禁用某一类型的记忆(如跨会话事件记忆),以最大程度地节省 Token。streaming_write API 示例 这是一个典型的 streaming_write 请求,用于实时上传一轮对话。
curl -X POST 'https://api-knowledgebase.mlp.cn-beijing.volces.com/api/memory/session/streaming_write' \ -H 'Authorization: Bearer YOUR_MEMORY_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "collection_name": "your_collection_name", "project_name": "default", "conversation_id": "conv_20260330_user123_xyz", "messages":[ { "role": "user", "content": "我家宝宝1岁,最近喝奶粉总胀气,有没有推荐的?" }, { "role": "assistant", "content": "您好,宝宝胀气大概率和奶粉乳糖含量有关,建议优先选择无乳糖配方奶粉。" } ], "extract_trigger": { "token_count": 1000, "message_count": 20, "wait_timeout": 604800 }, "metadata": { "user_id": "user_123", "source": "app_chat" } }'
使用 SDK 可以更方便地进行集成。
import os from vikingdb.memory import VikingMem from vikingdb import APIKey # 推荐从环境变量或安全配置中读取 API Key API_KEY = os.environ.get("MEMORY_API_KEY", "YOUR_MEMORY_API_KEY") # 1. 初始化客户端 client = VikingMem( host="api-knowledgebase.mlp.cn-beijing.volces.com", region="cn-beijing", auth=APIKey(api_key=API_KEY) ) # 2. 获取记忆库实例 collection = client.get_collection( collection_name="your_collection_name", project_name="default" ) # 3. 实时写入对话 def streaming_write_message(): try: result = collection.streaming_write( conversation_id="conv_20260330_user123_xyz", messages=[ { "role": "user", "content": "我家宝宝1岁,最近喝奶粉总胀气,有没有推荐的?" }, { "role": "assistant", "content": "您好,宝宝胀气大概率和奶粉乳糖含量有关,建议优先选择无乳糖配方奶粉。" } ], extract_trigger={ "token_count": 1000, "message_count": 20, "wait_timeout": 604800 }, metadata={ "user_id": "user_123", "source": "app_chat" } ) print(f"写入成功: {result}") return result except Exception as e: print(f"写入失败: {e}") # 在此添加重试或错误上报逻辑 return None if __name__ == "__main__": streaming_write_message()
get_context API 示例 此示例展示了如何在获取上下文时,传入最新的用户 query 并配置检索参数。
curl -X POST 'https://api-knowledgebase.mlp.cn-beijing.volces.com/api/memory/get_context' \ -H 'Authorization: Bearer YOUR_MEMORY_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "collection_name": "your_collection_name", "project_name": "default", "conversation_id": "conv_20260330_user123_xyz", "query": "除了无乳糖,还有没有含DHA的推荐?", "event_search_config": { "filter": { "user_id": "user_123" }, "limit": 5, "time_decay_config": { "weight": 0.6, "no_decay_period": 3 } }, "profile_search_config": { "filter": { "user_id": "user_123" }, "limit": 1 } }'
# ... (接上文的 client 和 collection 初始化) # 4. 获取长短期记忆上下文 def get_memory_context(): try: context_response = collection.get_context( conversation_id="conv_20260330_user123_xyz", project_name="default", query="除了无乳糖,还有没有含DHA的推荐?", event_search_config={ "filter": {"user_id": "user_123"}, "limit": 5, "time_decay_config": {"weight": 0.6, "no_decay_period": 3} }, profile_search_config={ "filter": {"user_id": "user_123"}, "limit": 1 } ) # 直接获取拼接好的字符串 prompt_context = context_response.context_str print("------ 拼接好的 Prompt 上下文 ------") print(prompt_context) # 也可以访问结构化的记忆内容 # print("------ 短期记忆 ------") # for msg in context_response.short_term_memory: # print(msg) return prompt_context except Exception as e: print(f"获取上下文失败: {e}") # 异常处理:可以返回一个空的上下文或默认兜底文案 return "" if __name__ == "__main__": # 完整流程:写入 -> 获取上下文 streaming_write_message() get_memory_context()
说明
Q1: get_context 返回的长期记忆为空是什么原因?
A: 这通常不是一个错误。最常见的原因是该 conversation 累积的对话量尚未达到你在 streaming_write 中设置的 extract_trigger 触发条件(如 token_count)。因此,还没有任何长期记忆被生成。你的应用应该能正常处理这种情况,仅使用短期记忆进行对话。
说明
Q2: 我可以手动触发一次抽取吗?
A: streaming_write 的设计理念是自动化管理抽取。但如果你确实有手动触发的需求(例如,在对话结束时强制保存记忆),可以将 extract_trigger 中的 token_count 和 message_count 设置为一个极小的值(如 1),这样下一次写入时几乎必然会触发抽取。完成后,记得将配置改回正常值。
说明
Q3: 如何处理非常长的单轮对话?
A: 如果单轮 message 的 content 本身就超过了 token_count,streaming_write 接口会在该轮写入后立即触发一次抽取。这意味着长对话会被高效地处理,不会“撑爆”短期记忆缓存。
说明
Q4: 我可以自定义 get_context 返回的格式吗?
A: get_context 接口本身提供了优化好的 context_str 字符串。如果你需要完全自定义的格式,可以利用响应中的结构化字段(context_parts),在你的业务后端自行拼接成任何你想要的格式。
说明
Q5: 从批式迁移到实时后,历史数据怎么办?
A: 历史数据(已经通过批式接口抽取的长期记忆)仍然存在于你的记忆库中。当你使用 get_context 时,只要 query 和 filter 条件匹配,这些历史记忆同样可以被检索到。迁移主要影响的是未来的数据写入和短期记忆的管理方式。