本文面向想了解如何用统一接口对接多个 LLM Provider 的开发者。预计阅读时间10 分钟最终效果理解 Vercel AI SDK 的 generateText / generateObject / embed 核心 API掌握 Provider 工厂模式和 Zod Schema 结构化输出。为什么需要统一抽象假设你正在开发一个 AI 应用最开始用 Ollama 本地模型做原型后来想切到 OpenAI 获得更好的效果再后来又想试试 Anthropic 的 Claude。如果没有统一抽象你需要为每个 Provider 写不同的 API 调用代码处理每个 Provider 不同的请求/响应格式在切换时修改大量业务代码Vercel AI SDKai包解决了这个问题。它提供了一套统一的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}fromai;const{text}awaitgenerateText({model:getLanguageModel(),prompt:用一句话解释什么是向量数据库,});2. generateObject — 结构化输出这是 AI SDK 最强大的能力之一。你用 Zod 定义一个 schemaLLM 会返回严格符合该结构的 JSON 对象无需手动解析import{generateObject}fromai;import{z}fromzod;constschemaz.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 的类型完全匹配 schemaTypeScript 自动推导ChatCrystal 的对话摘要功能就是这么实现的。generateObject内部会将 Zod schema 转换为 JSON Schema注入到 prompt 中引导 LLM 输出结构化数据然后自动校验返回结果。如果校验失败SDK 会自动重试默认 3 次。3. embed — 生成向量嵌入用于语义搜索的文本向量化import{embed}fromai;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 中constprovidersnewMapstring,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}){consturlbaseURL||http://localhost:11434;constollamacreateOpenAI({baseURL:${url}/v1,apiKey:ollama,// Ollama 不需要真实 key填个占位值name:ollama,});returnollama(model);},});这个技巧非常实用任何提供 OpenAI 兼容 API 的服务比如 vLLM、LiteLLM、LocalAI都可以用createOpenAI适配。apiKey填一个占位字符串即可因为本地服务不校验它。OpenAI / Anthropic / Google — 原生 SDK这三个 Provider 各有官方适配器用法几乎一致// OpenAIconstopenaicreateOpenAI({baseURL,apiKey});returnopenai(model);// AnthropicconstanthropiccreateAnthropic({apiKey});returnanthropic(model);// GoogleconstgooglecreateGoogleGenerativeAI({apiKey});returngoogle(model);注意 Anthropic 不支持 embeddingsupportsEmbedding: false所以如果你用 Claude 做摘要embedding 还需要单独配一个支持的 Provider。Azure — 企业部署Azure OpenAI 通过ai-sdk/azure适配需要提供 Azure 专属的 endpoint URL 和 API keyconstazurecreateAzure({baseURL,apiKey});returnazure(model);Custom — 万能兜底Custom Provider 和 Ollama 的思路一样用createOpenAI对接任何 OpenAI 兼容的 APIproviders.set(custom,{createLanguageModel({baseURL,apiKey,model}){constcustomcreateOpenAI({baseURL,apiKey,name:custom});returncustom(model);},});这是最具扩展性的设计——用户不需要等我们新增 Provider 适配只要服务兼容 OpenAI API 格式填个 URL 和 key 就能用。generateObject 的 Zod Schema 验证generateObject的结构化输出依赖 Zod schema。ChatCrystal 在两个场景中重度使用它场景一对话摘要constSummarizeSchemaz.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 理解每个字段的期望内容。场景二笔记关系发现constRelationElementSchemaz.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.5SDK 帮你处理了所有差异。错误处理自动重试AI SDK 内置了重试机制。maxRetries参数控制失败后的重试次数const{object}awaitgenerateObject({model:getLanguageModel(),schema:SummarizeSchema,system:SYSTEM_PROMPT,prompt:transcript,maxRetries:3,// 失败后最多重试 3 次});ChatCrystal 在关系发现中设maxRetries: 2在摘要中设maxRetries: 3。重试对处理 429Rate Limit和 5xx 错误特别有用。此外ChatCrystal 在队列层还做了并发控制concurrency1和 1 req/sec 的限速与 SDK 的重试机制形成双层保护。总结Vercel AI SDK 的核心设计理念是抽象差异、统一接口概念作用generateText生成自由文本generateObject生成结构化 JSONZod 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