介绍
# Integrate AI SDK with You.com Tools
交互式工作流,使用 `@youdotcom-oss/ai-sdk-plugin` 将 You.com 工具添加到您的 Vercel AI SDK 应用程序中。
## 工作流
1. **询问:包管理器** * 使用哪种包管理器?(npm, bun, yarn, pnpm) * 使用他们选择的工具安装包: ```bash npm install @youdotcom-oss/ai-sdk-plugin # or bun add @youdotcom-oss/ai-sdk-plugin # or yarn add @youdotcom-oss/ai-sdk-plugin # or pnpm add @youdotcom-oss/ai-sdk-plugin ```
2. **询问:环境变量名称** * 使用标准的 `YDC_API_KEY`? * 还是自定义名称?(如果是自定义,获取名称) * 他们是否已经在环境中设置了它? * 如果否:引导他们从 https://you.com/platform/api-keys 获取密钥
3. **询问:使用哪些 AI SDK 函数?** * 他们使用 `generateText()` 吗? * 他们使用 `streamText()` 吗? * 两者都用?
4. **询问:现有文件还是新文件?** * 现有:询问要编辑哪些文件 * 新建:询问在哪里创建文件以及如何命名
5. **对于每个文件,询问:** * 添加哪些工具? - `youSearch` (网络搜索) - `youExpress` (AI 代理) - `youContents` (内容提取) - 多个?(哪种组合?) * 此文件中使用 `generateText()` 还是 `streamText()`? * 哪个 AI 提供商模型?(以确定是否需要 stopWhen)
6. **参考集成示例**
请参阅下面的“集成示例”部分以获取完整的代码模式: * generateText() - 使用工具的基本文本生成 * streamText() - 与 Web 框架的流式响应 (Next.js, Express, React)
7. **更新/创建文件**
对于每个文件: * 参考集成示例(根据他们的回答选择 generateText 或 streamText) * 添加所选工具的导入 * 如果是现有文件:找到他们的 generateText/streamText 调用并添加 tools 对象 * 如果是新文件:使用示例结构创建文件 * 基于环境变量名称的工具调用模式: - 标准 `YDC_API_KEY`: `youSearch()` - 自定义名称: `youSearch({ apiKey: process.env.CUSTOM_NAME })` * 将所选工具添加到 tools 对象 * 如果是 streamText + Anthropic:添加 stopWhen 参数
## 集成示例
### generateText() - 基本文本生成
**环境变量设置:** ```typescript import { anthropic } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; import { youContents, youExpress, youSearch } from '@youdotcom-oss/ai-sdk-plugin';
// Reads YDC_API_KEY from environment automatically const result = await generateText({ model: anthropic('claude-sonnet-4-5-20250929'), tools: { search: youSearch(), }, prompt: 'What are the latest developments in quantum computing?', });
console.log(result.text); ```
**多工具:** ```typescript const result = await generateText({ model: anthropic('claude-sonnet-4-5-20250929'), tools: { search: youSearch(), // Web search with citations agent: youExpress(), // AI answers with web context extract: youContents(), // Content extraction from URLs }, prompt: 'Research quantum computing and summarize the key papers', }); ```
**自定义 API 密钥:** ```typescript const result = await generateText({ model: anthropic('claude-sonnet-4-5-20250929'), tools: { search: youSearch({ apiKey: 'your-custom-key' }), }, prompt: 'Your prompt here', }); ```
**完整示例:** ```typescript import { anthropic } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; import { youSearch } from '@youdotcom-oss/ai-sdk-plugin';
const main = async () => { try { const result = await generateText({ model: anthropic('claude-sonnet-4-5-20250929'), tools: { search: youSearch(), }, maxSteps: 5, prompt: 'What are the latest developments in quantum computing?', });
console.log('Generated text:', result.text); console.log('\nTool calls:', result.steps.flatMap(s => s.toolCalls)); } catch (error) { console.error('Error:', error); process.exit(1); } };
main(); ```
### streamText() - 流式响应
**使用 stopWhen 模式的基本流式传输:** ```typescript import { anthropic } from '@ai-sdk/anthropic'; import { streamText, type StepResult } from 'ai'; import { youSearch } from '@youdotcom-oss/ai-sdk-plugin';
// CRITICAL: Always use stopWhen for Anthropic streaming // Anthropic's SDK requires explicit stop conditions const stepCountIs = (n: number) => (stepResult: StepResult<any>) => stepResult.stepNumber >= n;
const result = streamText({ model: anthropic('claude-sonnet-4-5-20250929'), tools: { search: youSearch() }, stopWhen: stepCountIs(3), // Required for Anthropic prompt: 'What are the latest AI developments?', });
// Consume stream for await (const chunk of result.textStream) { process.stdout.write(chunk); } ```
**Next.js 集成 (App Router):** ```typescript // app/api/chat/route.ts import { anthropic } from '@ai-sdk/anthropic'; import { streamText, type StepResult } from 'ai'; import { youSearch } from '@youdotcom-oss/ai-sdk-plugin';
const stepCountIs = (n: number) => (stepResult: StepResult<any>) => stepResult.stepNumber >= n;
export async function POST(req: Request) { const { prompt } = await req.json();
const result = streamText({ model: anthropic('claude-sonnet-4-5-20250929'), tools: { search: youSearch() }, stopWhen: stepCountIs(5), prompt, });
return result.toDataStreamResponse(); } ```
**Express.js 集成:** ```typescript // server.ts import express from 'express'; import { anthropic } from '@ai-sdk/anthropic'; import { streamText, type StepResult } from 'ai'; import { youSearch } from '@youdotcom-oss/ai-sdk-plugin';
const app = express(); app.use(express.json());
const stepCountIs = (n: number) => (stepResult: StepResult<any>) => stepResult.stepNumber >= n;
app.post('/api/chat', async (req, res) => { const { prompt } = req.body;
const result = streamText({ model: anthropic('claude-sonnet-4-5-20250929'), tools: { search: youSearch() }, stopWhen: stepCountIs(5), prompt, });
res.setHeader('Content-Type', 'text/plain; charset=utf-8'); res.setHeader('Transfer-Encoding', 'chunked');
for await (const chunk of result.textStream) { res.write(chunk); }
res.end(); });
app.listen(3000); ```
**React 客户端 (配合 Next.js):** ```typescript // components/Chat.tsx 'use client';
import { useChat } from 'ai/react';
export default function Chat() { const { messages, input, handleInputChange, handleSubmit } = useChat({ api: '/api/chat', });
return ( <div> {messages.map(m => ( <div key={m.id}> <strong>{m.role}:</strong> {m.content} </div> ))}
<form onSubmit={handleSubmit}> <input value={input} onChange={handleInputChange} /> <button type="submit">Send</button> </form> </div> ); } ```
**完整流式传输示例:** ```typescript import { anthropic } from '@ai-sdk/anthropic'; import { streamText, type StepResult } from 'ai'; import { youSearch } from '@youdotcom-oss/ai-sdk-plugin';
const stepCountIs = (n: number) => (stepResult: StepResult<any>) => stepResult.stepNumber >= n;
const main = async () => { try { const result = streamText({ model: anthropic('claude-sonnet-4-5-20250929'), tools: { search: youSearch(), }, stopWhen: stepCountIs(3), prompt: 'What are the latest AI developments?', });
// Stream to stdout console.log('Streaming response:\n'); for await (const chunk of result.textStream) { process.stdout.write(chunk); } console.log('\n\nDone!'); } catch (error) { console.error('Error:', error); process.exit(1); } };
main(); ```
## 工具调用模式
基于步骤 2 中的环境变量名称:
**标准 YDC_API_KEY:** ```typescript import { youSearch } from '@youdotcom-oss/ai-sdk-plugin';
tools: { search: youSearch(), } ```
**自定义环境变量:** ```typescript import { youSearch } from '@youdotcom-oss/ai-sdk-plugin';
const apiKey = process.env.THEIR_CUSTOM_NAME;
tools: { search: youSearch({ apiKey }), } ```
**使用标准环境变量的多工具:** ```typescript import { youSearch, youExpress, youContents } from '@youdotcom-oss/ai-sdk-plugin';
tools: { search: youSearch(), agent: youExpress(), extract: youContents(), } ```
**使用自定义环境变量的多工具:** ```typescript import { youSearch, youExpress, youContents } from '@youdotcom-oss/ai-sdk-plugin';
const apiKey = process.env.THEIR_CUSTOM_NAME;
tools: { search: youSearch({ apiKey }), agent: youExpress({ apiKey }), extract: youContents({ apiKey }), } ```
## 可用工具
### youSearch 网络和新闻搜索 - 模型决定参数(查询、数量、国家等)
### youExpress 具有网络上下文的 AI 代理 - 模型决定参数(输入、工具)
### youContents 网页内容提取 - 模型决定参数(url、格式)
## 关键集成模式
上面的示例演示了: * 导入语句 (AI SDK + 提供商 + You.com 工具) * 环境变量验证(对于新文件是可选的) * 基于环境变量的工具配置 * 结合工具使用 generateText/streamText * 结果处理(特别是 streamText 的 textStream 解构) * Anthropic 流式传输模式 (stopWhen: stepCountIs(3)) * Web 框架集成 (Next.js, Express, React)
## 实现清单
对于每个正在更新/创建的文件:
- [ ] 添加了所选工具的导入 - [ ] 如果是自定义环境变量:声明了正确名称的变量 - [ ] 将 tools 对象添加到 generateText/streamText - [ ] 每个选定的工具都被正确调用: - 标准环境:`toolName()` - 自定义环境:`toolName({ apiKey })` - [ ] 如果是 streamText:解构了 `const { textStream } = ...` - [ ] 如果是 Anthropic + streamText:添加了 `stopWhen: stepCountIs(3)`
全局清单:
- [ ] 使用他们的包管理器安装了包 - [ ] 在他们的环境中设置了环境变量 - [ ] 所有文件已更新/创建 - [ ] 准备好测试
## 常见问题
**问题**: "Cannot find module @youdotcom-oss/ai-sdk-plugin" **解决方法**: 使用他们的包管理器安装
**问题**: "YDC_API_KEY (or custom name) environment variable is required" **解决方法**: 在他们的环境中设置(获取密钥:https://you.com/platform/api-keys)
**问题**: "Tool execution fails with 401" **解决方法**: 验证 API 密钥是否有效
**问题**: "Incomplete or missing response" **解决方法**: 如果使用 streamText,增加步骤计数。从 3 开始并根据需要向上迭代(请参阅 README 故障排除)
**问题**: "textStream is not iterable" **解决方法**: 解构:`const { textStream } = streamText(...)`
**问题**: "Custom env var not working" **解决方法**: 传递给每个工具:`youSearch({ apiKey })`
## 高级:工具开发模式
对于创建自定义 AI SDK 工具或为 @youdotcom-oss/ai-sdk-plugin 做贡献的开发人员:
### 工具函数结构
每个工具函数遵循此模式:
```typescript export const youToolName = (config: YouToolsConfig = {}) => { const apiKey = config.apiKey ?? process.env.YDC_API_KEY;
return tool({ description: 'Tool description for AI model', inputSchema: ZodSchema, execute: async (params) => { if (!apiKey) { throw new Error('YDC_API_KEY is required'); }
const response = await callApiUtility({ params, YDC_API_KEY: apiKey, getUserAgent, });
// Return raw API response for maximum flexibility return response; }, }); }; ```
### 输入模式启用智能查询
始终使用 `@youdotcom-oss/mcp` 中的模式:
```typescript // ✅ Import from @youdotcom-oss/mcp import { SearchQuerySchema } from '@youdotcom-oss/mcp';
export const youSearch = (config: YouToolsConfig = {}) => { return tool({ description: '...', inputSchema: SearchQuerySchema, // Enables AI to use all search parameters execute: async (params) => { ... }, }); };
// ❌ Don't duplicate or simplify schemas const MySearchSchema = z.object({ query: z.string() }); // Missing filters! ```
**为什么这很重要:** - 丰富的模式使 AI 能够使用高级查询参数(过滤器、时效性、国家等) - AI 可以根据用户意图构建更智能的查询 - 防止在包之间重复模式定义 - 确保与 MCP 服务器模式的一致性
### API 密钥处理
始终提供环境变量后备并在 API 调用之前进行验证:
```typescript // ✅ Automatic environment variable fallback const apiKey = config.apiKey ?? process.env.YDC_API_KEY;
// ✅ Check API key in execute function execute: async (params) => { if (!apiKey) { throw new Error('YDC_API_KEY is required'); } const response = await callApi(...); } ```
### 响应格式
始终返回原始 API 响应以获得最大灵活性:
```typescript // ✅ Return raw API response execute: async (params) => { const response = await fetchSearchResults({ searchQuery: params, YDC_API_KEY: apiKey, getUserAgent, });
return response; // Raw response for maximum flexibility }
// ❌ Don't format or transform responses return { text: formatResponse(response), data: response, }; ```
**为什么使用原始响应?** - 为 AI SDK 处理结果提供最大的灵活性 - 不会因格式化而丢失信息 - AI SDK 处理表示层 - 更易于调试(查看实际的 API 响应)
### 工具描述
编写引导 AI 行为的描述:
```typescript // ✅ Clear guidance for AI model description: 'Search the web for current information, news, articles, and content using You.com. Returns web results with snippets and news articles. Use this when you need up-to-date information or facts from the internet.'
// ❌ Too brief description: 'Search the web' ```
## 其他资源
* 包 README: https://github.com/youdotcom-oss/dx-toolkit/tree/main/packages/ai-sdk-plugin * Vercel AI SDK 文档: https://ai-sdk.dev/docs * You.com API: https://you.com/platform/api-keys