本文面向:想了解如何用统一接口对接多个 LLM Provider 的开发者。
预计阅读时间:10 分钟
最终效果:理解 Vercel AI SDK 的 generateText / generateObject / embed 核心 API,掌握 Provider 工厂模式和 Zod Schema 结构化输出。
为什么需要统一抽象?
假设你正在开发一个 AI 应用,最开始用 Ollama 本地模型做原型,后来想切到 OpenAI 获得更好的效果,再后来又想试试 Anthropic 的 Claude。如果没有统一抽象,你需要:
- 为每个 Provider 写不同的 API 调用代码
- 处理每个 Provider 不同的请求/响应格式
- 在切换时修改大量业务代码
Vercel AI SDK(ai包)解决了这个问题。它提供了一套统一的generateText、generateObject、embed等 API,底层通过 Provider 适配器对接不同的模型服务。你的业务代码只需要写一次,切换 Provider 只需改配置。
ChatCrystal 项目支持 6 种 Provider——Ollama、OpenAI、Anthropic、Google、Azure、Custom——全部通过 Vercel AI SDK 统一调用。本文以 ChatCrystal 的实际代码为例,讲解 SDK 的核心用法。
核心 API 速览
Vercel AI SDK 提供三个核心函数,覆盖了最常见的 AI 调用场景:
1. generateText — 生成文本
最基本的调用方式,给一个 prompt,返回文本结果:
import{generateText}from'ai';const{text}=awaitgenerateText({model:getLanguageModel(),prompt:'用一句话解释什么是向量数据库',});2. generateObject — 结构化输出
这是 AI SDK 最强大的能力之一。你用 Zod 定义一个 schema,LLM 会返回严格符合该结构的 JSON 对象,无需手动解析:
import{generateObject}from'ai';import{z}from'zod';constschema=z.object({title:z.string().describe('简洁的标题,20字以内'),summary:z.string().describe('2-4 段 markdown 摘要'),key_conclusions:z.array(z.string()).describe('3-5 个关键结论'),tags:z.array(z.string()).describe('3-6 个小写英文标签'),});const{object}=awaitgenerateObject({model:getLanguageModel(),schema,system:'你是一个技术对话分析专家',prompt:transcript,});// object 的类型完全匹配 schema,TypeScript 自动推导ChatCrystal 的对话摘要功能就是这么实现的。generateObject内部会将 Zod schema 转换为 JSON Schema,注入到 prompt 中引导 LLM 输出结构化数据,然后自动校验返回结果。如果校验失败,SDK 会自动重试(默认 3 次)。
3. embed — 生成向量嵌入
用于语义搜索的文本向量化:
import{embed}from'ai';const{embedding}=awaitembed({model,value:query});// embedding 是 number[],维度取决于模型ChatCrystal 在两个地方用到embed:
- 笔记入库时:将笔记文本分块后逐块生成向量,存入 vectra 索引
- 搜索查询时:将用户查询转为向量,在索引中做相似度检索
Provider 工厂模式
ChatCrystal 用工厂模式管理 6 种 Provider。核心是一个ProviderEntry接口:
interfaceProviderEntry{name:string;displayName:string;supportsEmbedding:boolean;requiresApiKey:boolean;requiresBaseURL:boolean;createLanguageModel(config:ProviderConfig):LanguageModel;createEmbeddingModel?(config:ProviderConfig):EmbeddingModel;}每个 Provider 注册到一个 Map 中:
constproviders=newMap<string,ProviderEntry>();providers.set('ollama',{/* ... */});providers.set('openai',{/* ... */});providers.set('anthropic',{/* ... */});// ...获取模型实例只需两步:
functiongetLanguageModel():LanguageModel{const{provider,...config}=appConfig.llm;returngetProvider(provider).createLanguageModel(config);}getProvider(name)从 Map 中查找 Provider,调用其createLanguageModel方法返回一个LanguageModel实例。之后所有业务代码都通过这个实例调用generateText/generateObject,完全不关心底层是哪个 Provider。
6 种 Provider 的实现细节
Ollama — 本地推理
Ollama 没有官方的 Vercel AI SDK 适配器,但它提供 OpenAI 兼容的/v1/端点。所以 ChatCrystal 用@ai-sdk/openai来对接:
providers.set('ollama',{createLanguageModel({baseURL,model}){consturl=baseURL||'http://localhost:11434';constollama=createOpenAI({baseURL:`${url}/v1`,apiKey:'ollama',// Ollama 不需要真实 key,填个占位值name:'ollama',});returnollama(model);},});这个技巧非常实用:任何提供 OpenAI 兼容 API 的服务(比如 vLLM、LiteLLM、LocalAI)都可以用createOpenAI适配。apiKey填一个占位字符串即可,因为本地服务不校验它。
OpenAI / Anthropic / Google — 原生 SDK
这三个 Provider 各有官方适配器,用法几乎一致:
// OpenAIconstopenai=createOpenAI({baseURL,apiKey});returnopenai(model);// Anthropicconstanthropic=createAnthropic({apiKey});returnanthropic(model);// Googleconstgoogle=createGoogleGenerativeAI({apiKey});returngoogle(model);注意 Anthropic 不支持 embedding(supportsEmbedding: false),所以如果你用 Claude 做摘要,embedding 还需要单独配一个支持的 Provider。
Azure — 企业部署
Azure OpenAI 通过@ai-sdk/azure适配,需要提供 Azure 专属的 endpoint URL 和 API key:
constazure=createAzure({baseURL,apiKey});returnazure(model);Custom — 万能兜底
Custom Provider 和 Ollama 的思路一样,用createOpenAI对接任何 OpenAI 兼容的 API:
providers.set('custom',{createLanguageModel({baseURL,apiKey,model}){constcustom=createOpenAI({baseURL,apiKey,name:'custom'});returncustom(model);},});这是最具扩展性的设计——用户不需要等我们新增 Provider 适配,只要服务兼容 OpenAI API 格式,填个 URL 和 key 就能用。
generateObject 的 Zod Schema 验证
generateObject的结构化输出依赖 Zod schema。ChatCrystal 在两个场景中重度使用它:
场景一:对话摘要
constSummarizeSchema=z.object({title:z.string(),summary:z.string(),key_conclusions:z.array(z.string()),code_snippets:z.array(z.object({language:z.string(),code:z.string(),description:z.string(),})),tags:z.array(z.string()),});const{object}=awaitgenerateObject({model:getLanguageModel(),schema:SummarizeSchema,system:SYSTEM_PROMPT,prompt:transcript,maxOutputTokens:4096,maxRetries:3,});.describe()方法可以给每个字段加上自然语言描述,这些描述会被注入到 prompt 中,帮助 LLM 理解每个字段的期望内容。
场景二:笔记关系发现
constRelationElementSchema=z.object({target_note_id:z.number().describe('目标笔记的 ID'),relation_type:z.enum(['CAUSED_BY','LEADS_TO','RESOLVED_BY','SIMILAR_TO','CONTRADICTS','DEPENDS_ON','EXTENDS','REFERENCES',]).describe('关系类型'),confidence:z.number().min(0).max(1).describe('置信度,0.0-1.0'),description:z.string().describe('简短说明关系,20字以内'),});const{object:rawRelations}=awaitgenerateObject({model:getLanguageModel(),output:'array',// 输出是一个数组schema:RelationElementSchema,system:RELATION_SYSTEM_PROMPT,prompt,});这里用了output: 'array'参数,告诉 SDK 返回一个数组而非单个对象。配合z.enum()可以限制字段的合法取值范围——如果 LLM 返回了不在枚举中的关系类型,SDK 会自动重试。
配置驱动的 Provider 切换
ChatCrystal 的配置结构非常直观:
// config.json{"llm":{"provider":"openai","baseURL":"","apiKey":"sk-...","model":"gpt-4o"},"embedding":{"provider":"ollama","baseURL":"http://localhost:11434","apiKey":"","model":"nomic-embed-text"}}LLM 和 Embedding 是独立配置的。你可以用 OpenAI 做摘要、Ollama 做 embedding,反之亦然。切换 Provider 只需要改provider字段,业务代码零修改。
这就是抽象层的价值——generateObject({ model, ... })这行代码不关心model是 GPT-4o、Claude Sonnet 还是 Qwen2.5,SDK 帮你处理了所有差异。
错误处理:自动重试
AI SDK 内置了重试机制。maxRetries参数控制失败后的重试次数:
const{object}=awaitgenerateObject({model:getLanguageModel(),schema:SummarizeSchema,system:SYSTEM_PROMPT,prompt:transcript,maxRetries:3,// 失败后最多重试 3 次});ChatCrystal 在关系发现中设maxRetries: 2,在摘要中设maxRetries: 3。重试对处理 429(Rate Limit)和 5xx 错误特别有用。此外,ChatCrystal 在队列层还做了并发控制(concurrency=1)和 1 req/sec 的限速,与 SDK 的重试机制形成双层保护。
总结
Vercel AI SDK 的核心设计理念是抽象差异、统一接口:
| 概念 | 作用 |
|---|---|
generateText | 生成自由文本 |
generateObject | 生成结构化 JSON,Zod schema 校验 |
embed | 生成向量嵌入 |
| Provider 适配器 | createOpenAI/createAnthropic/createGoogleGenerativeAI等 |
LanguageModel | 统一的模型实例,所有 API 函数的第一个参数 |
maxRetries | 自动重试,处理瞬时错误 |
对于学生来说,理解这个设计模式比记住具体 API 更重要:当你需要对接多个外部服务时,定义一个统一接口,让每个服务实现自己的适配器,业务代码只依赖接口而不依赖具体实现。这就是 Strategy 模式在 AI 领域的典型应用。
下一步
- 阅读 Vercel AI SDK 官方文档 了解更多 API 细节
- 试试用
createOpenAI对接一个兼容 OpenAI 的本地服务(如 vLLM、LM Studio) - 探索
streamText和streamObject实现流式输出 - 了解 AI SDK 的 Tool Calling 能力,让 LLM 调用外部函数
项目地址:github.com/ZengLiangYi/ChatCrystal