Vercel AI SDK 集成持久记忆完整指南:@mem0/vercel-ai-provider 六大使用模式与源码级原理
Mem0 在 Vercel AI SDK 之上提供了一层"记忆即服务"的 Provider 封装(@mem0/verci-ai-provider,仓库内实现位于 integrations/vercel-ai-sdk),让 generateText、streamText、generateObject 等调用在发出前自动检索相关记忆、在返回后自动沉淀新记忆。本文以仓库技能文档 usage-patterns.md 为核心骨架,结合 integrations/vercel-ai-sdk/src 下的真实源码,从包装模型、独立工具函数、多 Provider 配置、Next.js 实战到内部处理流程与配置优先级,给出可直接复制运行、且有源码依据可查的完整方案。
本文代码示例中的模型名(如
gpt-5-mini、claude-sonnet-4-20250514)与默认参数来自仓库文档,实际运行请以你账号可用的模型与当版本 SDK 为准。
环境准备与文档脉络
@mem0/vercel-ai-provider 的所有用法都建立在两个前提之上:安装依赖、准备环境变量。
npm install @mem0/vercel-ai-provider ai
示例默认通过环境变量读取密钥:
export MEM0_API_KEY="m0-xxx"
export OPENAI_API_KEY="sk-xxx" # 或 ANTHROPIC_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY 等
与本文配套的还有两份参考文档,建议对照阅读:provider-api.md 讲解 createMem0、Mem0Provider 与全部类型定义;memory-utilities.md 逐一拆解四个记忆工具函数的签名、行为与参数表;SKILL.md 则给出安装、模式概览与边界场景速览。
从源码结构看,该集成对外只暴露一组精简的 API,见 index.ts:createMem0(Provider 工厂)、四个记忆工具函数 addMemories / retrieveMemories / searchMemories / getMemories,以及 mem0 单例导出。后续所有使用模式都是围绕"包装模型(Wrapped Model)"与"独立工具函数(Standalone Utilities)"两条主线展开。
模式一:包装模型 + generateText(最简接入)
让任何一次 LLM 调用自动带上记忆,最简单的方式是用 createMem0() 创建一个包装 Provider:
import { generateText } from "ai";
import { createMem0 } from "@mem0/vercel-ai-provider";
const mem0 = createMem0();
const { text } = await generateText({
model: mem0("gpt-5-mini", { user_id: "alice" }),
prompt: "Recommend a restaurant based on my preferences",
});
console.log(text);
只需做两件事:
createMem0()返回一个可调用的 Provider(默认 provider 为"openai",见 mem0-provider.ts);- 用
mem0("模型ID", { user_id: "alice" })创建带记忆能力的语言模型。
mem0("gpt-5-mini", { user_id: "alice" }) 在内部创建的是 Mem0GenericLanguageModel(mem0-generic-language-model.ts)。调用 generateText 时,"在调用前自动检索记忆、在调用后存储新对话" 分别对应 getMemories(检索)与 addMemories(写入)这两个底层步骤。
要特别说明 user_id:它把记忆限定在某个用户/实体维度上,是记忆读写作用域的关键。同一份记忆库中,不同 user_id 之间彼此隔离,只有传入一致的实体标识,才能命中正确的记忆(更完整的实体维度见下文 Mem0ConfigSettings 字段表)。
模式二:包装模型 + streamText(流式输出)
把记忆增强能力套用到流式场景同样直接:
import { streamText } from "ai";
import { createMem0 } from "@mem0/vercel-ai-provider";
const mem0 = createMem0();
const result = streamText({
model: mem0("gpt-5-mini", { user_id: "alice" }),
prompt: "What should I cook for dinner tonight?",
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
要点有两个:
- 记忆检索发生在流式开始之前。
Mem0GenericLanguageModel.doStream会先执行processMemories拿到增强后的 prompt,再去调用底层模型真正的流式接口(mem0-generic-language-model.ts),因此首字节延迟包含了一次记忆检索的开销; - 对话会被异步沉淀回 Mem0。usage-patterns 文档将包装模型中的写入描述为 fire-and-forget(非阻塞)调用,即存储失败只记录日志、不阻断响应。需要留意的是:当前仓库快照的 processMemories 实现把
addMemories包在try/catch中执行,写失败不会影响生成结果,这与文档描述的语义一致;具体是否await、是否阻塞首包,请以你所安装的实际发布版本为准。文档同时提醒:由于写入是异步的,存在"最新一轮对话尚未入库"的短暂窗口期,若追求强一致,应改用下面的独立工具函数并显式await addMemories。
模式三:独立工具函数,全权掌控记忆生命周期
当你希望精确控制"何时检索、何时存储",或 LLM Provider 已单独初始化、不想被包装时,可使用 retrieveMemories 与 addMemories。它们把记忆生命周期拆成三步,见 mem0-utils.ts。
与 OpenAI 配合
import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";
import { retrieveMemories, addMemories } from "@mem0/vercel-ai-provider";
const user_id = "alice";
const prompt = "Suggest a weekend trip";
// Step 1: 检索记忆,返回可直接注入 system 的格式化字符串
const memories = await retrieveMemories(prompt, {
user_id: user_id,
});
// Step 2: 把记忆作为 system 上下文生成回答
const { text } = await generateText({
model: openai("gpt-5-mini"),
prompt,
system: memories,
});
console.log(text);
// Step 3: 将本轮对话沉淀为新的记忆
await addMemories(
[
{ role: "user", content: [{ type: "text", text: prompt }] },
{ role: "assistant", content: [{ type: "text", text }] },
],
{ user_id: user_id }
);
与 Anthropic 配合
模式完全一致,只是 LLM 与模型 ID 不同:
import { anthropic } from "@ai-sdk/anthropic";
import { generateText } from "ai";
import { retrieveMemories, addMemories } from "@mem0/vercel-ai-provider";
const prompt = "Help me plan my exercise routine";
const config = { user_id: "bob" };
const memories = await retrieveMemories(prompt, config);
const { text } = await generateText({
model: anthropic("claude-sonnet-4-20250514"),
prompt,
system: memories,
});
await addMemories(
[
{ role: "user", content: [{ type: "text", text: prompt }] },
{ role: "assistant", content: [{ type: "text", text }] },
],
config
);
值得注意 retrieveMemories 的返回值是"格式化好的 system prompt 字符串"而非数组——示例里直接把它放进了 system 字段。如果空结果,函数返回 ""(见 mem0-utils.ts),不会污染 system 上下文。
四个工具函数如何选
| 函数 | 返回值 | 适用场景 |
|---|---|---|
retrieveMemories(prompt, config?) |
格式化好的 system prompt 字符串 | 直接注入 generateText / streamText 的 system 参数 |
getMemories(prompt, config?) |
原始记忆 数组 | 需要对记忆做程序化处理(过滤、排序、计数等) |
searchMemories(prompt, config?) |
完整搜索 响应(results + relations 等) | 需要相似度分数、关联关系或完整元数据 |
addMemories(messages, config?) |
写入 API 的响应 | 把消息沉淀为新记忆 |
四个函数的第一个参数都是 LanguageModelV3Prompt | string,第二个参数都是可选的 Mem0ConfigSettings(见 memory-utilities.md)。实现上,getMemories 与 searchMemories 会先把 prompt 拍平成纯文本字符串(flattenPrompt),再调用内部的 searchInternalMemories,最终 POST 到 {host}/v3/memories/search/;addMemories 则把消息经 convertToMem0Format 转换后 POST 到 {host}/v3/memories/add/(默认 host 为 https://api.mem0.ai,见 mem0-utils.ts)。
一个实用的代码级细节:flattenPrompt 对多模态内容做占位化处理——file 类型内容在作为检索 query 时被替换为 [PDF document]、[Markdown document]、[Image] 或 [File attachment] 等描述符(mem0-utils.ts),既避免把二进制内容喂给记忆检索,又能让 AI 记住"这轮讨论过一份 PDF"。
模式四:generateObject 结构化输出
记忆增强不限于自由文本。用 generateObject 可拿到带记忆上下文的类型化结构化结果:
import { generateObject } from "ai";
import { createMem0 } from "@mem0/vercel-ai-provider";
import { z } from "zod";
const mem0 = createMem0();
const { object } = await generateObject({
model: mem0("gpt-5-mini", { user_id: "alice" }),
prompt: "Suggest a meal plan for today",
schema: z.object({
breakfast: z.string(),
lunch: z.string(),
dinner: z.string(),
snacks: z.array(z.string()),
notes: z.string().describe("Personalization notes based on known preferences"),
}),
});
console.log(object);
// { breakfast: "Avocado toast (you mentioned loving it)", lunch: "...", ... }
文档说明该集成的 defaultObjectGenerationMode 为 "json",即结构化输出开箱即用。它的记忆注入路径与文本生成完全一致——真正带来差异的是 schema 中的字段约束,例如示例里给 notes 加上了 describe("Personalization notes based on known preferences"),引导模型把检索到的偏好写进结构化字段,让"个性化结论"可被程序直接消费。
模式五:多 Provider 配置
包装模型支持在创建 Provider 时切换底层 LLM。文档与仓库代码给出的 provider 一览:
| Provider | 配置值 | 必需环境变量 |
|---|---|---|
| OpenAI(默认) | "openai" |
OPENAI_API_KEY |
| Anthropic | "anthropic" |
ANTHROPIC_API_KEY |
"google" |
GOOGLE_GENERATIVE_AI_API_KEY |
|
| Groq | "groq" |
GROQ_API_KEY |
| Cohere | "cohere" |
COHERE_API_KEY |
import { createMem0 } from "@mem0/vercel-ai-provider";
// OpenAI(默认,无需传 provider)
const mem0 = createMem0(); // defaults to "openai"
const model = mem0("gpt-5-mini", { user_id: "alice" });
// Anthropic
const mem0A = createMem0({ provider: "anthropic" });
const modelA = mem0A("claude-sonnet-4-20250514", { user_id: "alice" });
// Google
const mem0G = createMem0({ provider: "google" });
const modelG = mem0G("gemini-2.0-flash", { user_id: "alice" });
// Groq
const mem0Gr = createMem0({ provider: "groq" });
const modelGr = mem0Gr("llama-3.3-70b-versatile", { user_id: "alice" });
// Cohere
const mem0C = createMem0({ provider: "cohere" });
const modelC = mem0C("command-r-plus", { user_id: "alice" });
如果不想依赖环境变量,可以显式传 API Key:
const mem0 = createMem0({
provider: "openai",
apiKey: "sk-xxx", // OpenAI API key
mem0ApiKey: "m0-xxx", // Mem0 API key
});
底层切换逻辑在 mem0-provider-selector.ts:Mem0ClassSelector.supportedProviders = ["openai", "anthropic", "cohere", "groq", "google", "gemini"],传入不支持的 provider 会直接抛 Model not supported。注意这里的历史遗留细节:"gemini" 别名仍存在于支持列表中,但推荐值统一用 "google",两套 skill 文档都明确指出使用 "google" 而非 "gemini"。创建出的底层模型由 provider-response-provider.ts(Mem0AITextGenerator)负责实际转发。
模式六:Next.js API Route 实战
将以上模式落地到服务端,最典型的是 Next.js App Router 的聊天接口。
包装模型版本
// app/api/chat/route.ts
import { streamText } from "ai";
import { createMem0 } from "@mem0/vercel-ai-provider";
const mem0 = createMem0();
export async function POST(req: Request) {
const { messages, user_id } = await req.json();
const lastMessage = messages[messages.length - 1];
const result = streamText({
model: mem0("gpt-5-mini", { user_id }),
prompt: lastMessage.content,
});
return result.toDataStreamResponse();
}
代码量很少,因为记忆的检索与存储都被包装模型接管了:取最后一条用户消息作为 prompt,返回 AI SDK 标准的数据流响应(toDataStreamResponse),客户端可无缝对接 useChat。
独立工具函数版本(控制力更强)
当你想在流式响应的同时异步落库、或显式控制检索粒度时:
// app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";
import { retrieveMemories, addMemories } from "@mem0/vercel-ai-provider";
export async function POST(req: Request) {
const { messages, user_id } = await req.json();
const lastMessage = messages[messages.length - 1];
// 检索相关记忆
const memories = await retrieveMemories(lastMessage.content, {
user_id,
});
// 流式返回(把记忆作为 system 注入)
const result = streamText({
model: openai("gpt-5-mini"),
prompt: lastMessage.content,
system: memories,
});
// 后台沉淀本轮对话(fire-and-forget)
result.text.then(async (text) => {
await addMemories(
[
{ role: "user", content: [{ type: "text", text: lastMessage.content }] },
{ role: "assistant", content: [{ type: "text", text }] },
],
{ user_id }
);
});
return result.toDataStreamResponse();
}
这里用 result.text.then(...) 把 addMemories 挂到流式结果完成后执行,是"后台落库不阻塞首包"的典型写法:客户端先收到流,等文本生成完,再把这轮 user/assistant 消息作为新记忆写入。
内部机制:一次带记忆的调用到底发生了什么
understanding 内部流程有助于判断延迟来源与调试记忆命中问题。包装模型的调用路径如下(usage-patterns 文档 + mem0-generic-language-model.ts 相互印证):
1. doGenerate(options) / doStream(options) 被调用
2. processMemories(messagesPrompts, mem0Config):
a. addMemories(messagesPrompts, mem0Config)
--> POST {host}/v3/memories/add/(写入本轮对话)
b. await getMemories(messagesPrompts, mem0Config)
--> POST {host}/v3/memories/search/(用拍平的 prompt 检索)
--> 返回记忆数组
c. 把记忆格式化为 system 消息文本
d. 把 system 消息 prepend 到 messagesPrompts 数组最前面
e. 返回 { memories, messagesPrompts }
3. 通过 Mem0ClassSelector.createProvider() 创建底层 LLM
4. 调用 model.doGenerate(updatedOptions) / model.doStream(updatedOptions)
5. 返回结果
几个值得展开的实现细节:
记忆写入先于检索发生。从当前源码看,processMemories 会先把传入 prompt(含历史对话)addMemories 落库,再执行 getMemories 检索——这保证了"上一轮刚说过的话"在本轮检索时即可命中,代价是文档强调的写入异常被 try/catch 吞掉、只打日志(mem0-generic-language-model.ts)。无论哪种发布形态,对外保证一致:记忆写入失败不会让 LLM 调用失败。
检索端实体标识在 v3 契约中进 filters。源码注释明确写到 "v3: entity IDs go inside the filters object, not as top-level params",user_id / app_id / agent_id / run_id 会被合并进 filters 对象后随 search 请求发送(mem0-utils.ts)。
记忆注入格式。被检索出的记忆会拼成一个 system 消息,插入到 prompt 数组第 0 位,格式为:
{
role: "system",
content: "System Message: These are the memories I have stored. Give more weightage to the question by users and try to answer that first. You have to modify your answer based on the memories I have provided. If the memories are irrelevant you can ignore them. Also don't reply to this section of the prompt, or the memories, they are only for your reference. The System prompt starts after text System Message: \n\n Memory: ... \n\n Memory: ... \n\n"
}
这段模板同时出现在 mem0-generic-language-model.ts 与 retrieveMemories 的实现里(mem0-utils.ts),两条代码路径保持一致。模板本身包含了三条关键行为指令:优先回答用户问题、根据记忆调整答案、记忆无关时可忽略且不要回复记忆部分。此外,只有检索结果非空时才注入 system 消息——无记忆时 processMemories 会原样返回 prompt(mem0-generic-language-model.ts),避免给每次调用都附加无意义的 system 开销。
响应中附带记忆来源(仅非流式路径)。在 doGenerate 中,若命中记忆,返回结果会追加一个 type: "source" 的条目,把命中记忆原文放进 providerMetadata.mem0.memories,方便上层做引用溯源或展示"依据的记忆"(mem0-generic-language-model.ts)。doStream 则直接透传底层流式响应,不追加额外结构。
自定义配置:host、检索质量与配置合并优先级
自定义 Mem0 API host
无论是自托管 Mem0 还是走代理,都可覆盖默认的 https://api.mem0.ai:
// 包装模型级别
const mem0 = createMem0({
mem0Config: {
host: "https://my-mem0-instance.example.com",
},
});
// 或独立工具函数级别
const memories = await retrieveMemories(prompt, {
user_id: "alice",
host: "https://my-mem0-instance.example.com",
});
host 字段会直接影响最终请求 URL(${baseUrl}/v3/memories/search/ 与 ${baseUrl}/v3/memories/add/),同时 Mem0ProviderSettings 也支持自定义 baseURL、headers 与自定义 fetch 实现(mem0-provider.ts),可自由接入代理或做请求级中间件。
记忆过滤与重排
检索质量由三个参数控制,作用于每次模型调用:
const mem0 = createMem0();
const model = mem0("gpt-5-mini", {
user_id: "alice",
top_k: 10, // 最多检索 10 条记忆
threshold: 0.8, // 只保留相似度 score >= 0.8 的记忆
rerank: true, // 对检索结果启用重排
});
对应 mem0-types.ts 中 Mem0ConfigSettings 的 top_k / threshold / rerank 字段:searchInternalMemories 会把 top_k、可选的 threshold、rerank 透传进 search 请求体。usage-patterns 文档注释给出的默认检索条数为 5;作为参考,当前快照中 searchInternalMemories 的签名默认参数为 10(mem0-utils.ts),不同版本间存在默认值差异,显式传 top_k 是最稳妥的做法。threshold 用于过滤低相似度噪音,rerank 用于在初检后做二次精排。
Provider 专属配置(organization / project 等)
SDK 特有的设置通过 config 字段下发:
const mem0 = createMem0({
provider: "openai",
config: {
organization: "org-xxx",
project: "proj-xxx",
},
});
Mem0ProviderSettings.config 的类型是 OpenAI / Anthropic / Cohere / Groq / Google 五家 provider settings 的联合类型(mem0-types.ts),最终随 provider 创建一起传入底层模型。
默认配置与每调用覆盖的合并顺序
可在 Provider 级设置对所有模型生效的默认记忆配置:
const mem0 = createMem0({
mem0Config: {
user_id: "alice",
top_k: 10,
},
});
// 这些调用自动继承 mem0Config 中的 user_id 与 top_k
const { text } = await generateText({
model: mem0("gpt-5-mini"),
prompt: "Hello",
});
而 mem0() 的第二个参数(每调用 settings)会合并覆盖默认值:
// 覆盖本次调用的 user_id
const model = mem0("gpt-5-mini", { user_id: "bob" });
合并顺序为:
config.mem0Config(Provider 默认)< settings(每次调用覆盖)
这一优先级在 mem0-generic-language-model.ts 中以对象展开顺序直接实现:{ mem0ApiKey, ...this.config.mem0Config, ...this.settings },后展开者胜出。所以一个常见的多租户写法是:Provider 级 mem0Config 放通用阈值与 top_k,路由层每次调用只覆盖 user_id。
完整 Mem0ConfigSettings 字段速查
两个模式(包装模型与独立工具函数)共享同一份可选配置结构(mem0-types.ts):
| 字段 | 类型 | 说明 |
|---|---|---|
user_id |
string |
把记忆限定到某个用户 |
app_id |
string |
把记忆限定到某个应用 |
agent_id |
string |
把记忆限定到某个 Agent |
run_id |
string |
把记忆限定到某次会话/运行 |
metadata |
Record<string, any> |
自定义元数据 |
filters |
Record<string, any> |
自定义检索过滤器 |
infer |
boolean |
是否启用推断 |
page |
number |
分页页码 |
page_size |
number |
每页条数 |
mem0ApiKey |
string |
Mem0 API Key,缺省回落到 MEM0_API_KEY 环境变量 |
top_k |
number |
检索条数上限 |
threshold |
number |
最低相似度阈值 |
rerank |
boolean |
是否启用重排 |
host |
string |
自定义 Mem0 API 地址,默认 https://api.mem0.ai |
实践要点与常见坑
综合 usage-patterns.md 与 SKILL.md 的提示,实战中值得注意:
- 始终提供实体标识(
user_id/agent_id/app_id/run_id之一)。缺少实体作用域时记忆无法被正确限定,检索结果会跨实体串味; - 独立工具函数需要显式 API Key:在 config 中传
mem0ApiKey,或设置MEM0_API_KEY环境变量(loadApiKey 会优先取 config、再回落环境变量); - 版本兼容性:本集成面向 Vercel AI SDK v5 的 V3 Provider 契约,当前源码中
specificationVersion/provider.specificationVersion均为v3,实现ProviderV3/LanguageModelV3接口,不兼容 AI SDK v3 / v4。接入前请确认ai包版本; "gemini"别名是历史遗留:provider 选择器虽仍接受它,但请统一使用"google"(见上文多 Provider 一节);- 记忆读写的键不同:写入(add)将实体 ID 作为顶层字段发送,而检索(search)在 v3 契约中将实体 ID 放进
filters,这是 mem0-utils.ts 内部自动处理的两个不同请求形状,不需要你手工拼接; - 多模态消息的检索降级:prompt 中的 PDF / Markdown / 图片内容在作为检索 query 时会被替换成占位描述符,而写入端
convertToMem0Format则会尽力还原为pdf_url/mdx_url/image_url结构,保证记忆库能理解"这段对话涉及的文件类型"。
若想深入验证上述行为,仓库在 integrations/vercel-ai-sdk/tests 下提供了覆盖记忆核心路径、Provider 契约、工具函数与输出生成的测试用例(如 memory-core.test.ts、v3-provider-contract.test.ts 等),可结合测试断言理解每个参数在真实调用链中的生效位置。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00