Appearance
OpenRouter SDK 的 tool() 函数让你用 Zod schema 创建类型安全的 tool,支持自动执行(SDK 自动处理多轮循环)、generator tool(边执行边产出进度)、manual tool(手动处理调用)三种模式。本页涵盖 tool 定义、类型推导、execute 上下文、tool 间共享状态,以及并发执行和错误处理的最佳实践。
tool() 函数
tool() 函数创建带 Zod schema 验证的类型安全 tool:
typescript
import { OpenRouter, tool } from '@openrouter/agent';
import { z } from 'zod';
const weatherTool = tool({
name: 'get_weather',
description: 'Get the current weather for a location',
inputSchema: z.object({
location: z.string().describe('City name, e.g., "San Francisco, CA"'),
}),
outputSchema: z.object({
temperature: z.number(),
conditions: z.string(),
}),
execute: async (params) => {
// params 类型自动推导为 { location: string }
const weather = await fetchWeather(params.location);
return {
temperature: weather.temp,
conditions: weather.description,
};
},
});Tool 类型
SDK 支持三种 tool,由配置自动判断:
普通 Tool
带 execute 函数的标准 tool:
typescript
const calculatorTool = tool({
name: 'calculate',
description: 'Perform a mathematical calculation',
inputSchema: z.object({
expression: z.string().describe('Math expression like "2 + 2"'),
}),
outputSchema: z.object({ result: z.number() }),
execute: async (params) => {
const result = eval(params.expression); // 生产环境请用更安全的实现
return { result };
},
});Generator Tool
执行过程中持续产出进度更新。添加 eventSchema 即启用 generator 模式:
typescript
const searchTool = tool({
name: 'search_database',
description: 'Search documents with progress updates',
inputSchema: z.object({
query: z.string(),
limit: z.number().default(10),
}),
// eventSchema 触发 generator 模式
eventSchema: z.object({
progress: z.number().min(0).max(100),
message: z.string(),
}),
outputSchema: z.object({
results: z.array(z.string()),
totalFound: z.number(),
}),
// execute 变成 async generator
execute: async function* (params) {
yield { progress: 0, message: 'Starting search...' };
const results = [];
for (let i = 0; i < 5; i++) {
yield { progress: (i + 1) * 20, message: `Searching batch ${i + 1}...` };
results.push(...await searchBatch(params.query, i));
}
return {
results: results.slice(0, params.limit),
totalFound: results.length,
};
},
});进度事件通过 getToolStream() 和 getFullResponsesStream() 推送给消费者。
Manual Tool
不自动执行,由你处理 tool 调用:
typescript
const manualTool = tool({
name: 'send_email',
description: 'Send an email (requires user confirmation)',
inputSchema: z.object({
to: z.string().email(),
subject: z.string(),
body: z.string(),
}),
execute: false, // 手动处理
});通过 getToolCalls() 获取 manual tool 的调用信息。
类型推导
SDK 提供工具类型提取:
typescript
import type { InferToolInput, InferToolOutput, InferToolEvent } from '@openrouter/agent';
type WeatherInput = InferToolInput<typeof weatherTool>;
// { location: string }
type WeatherOutput = InferToolOutput<typeof weatherTool>;
// { temperature: number; conditions: string }
type SearchEvent = InferToolEvent<typeof searchTool>;
// { progress: number; message: string }在 callModel 中使用 Tool
单个 Tool
typescript
const openrouter = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
const result = openrouter.callModel({
model: 'openai/gpt-5-nano',
input: 'What is the weather in Tokyo?',
tools: [weatherTool],
});
// Tool 自动执行
const text = await result.getText();
// "The weather in Tokyo is 22°C and sunny."使用 as const 获得完整类型推导
typescript
const result = openrouter.callModel({
model: 'openai/gpt-5-nano',
input: 'What is the weather?',
tools: [weatherTool, searchTool] as const,
maxToolRounds: 0,
});
for await (const toolCall of result.getToolCallsStream()) {
if (toolCall.name === 'get_weather') {
// toolCall.arguments 类型自动推导为 { location: string }
console.log('Weather for:', toolCall.arguments.location);
}
}Execute 上下文
tool execute 函数的第二个参数包含运行上下文:
typescript
const contextAwareTool = tool({
name: 'context_tool',
inputSchema: z.object({ data: z.string() }),
outputSchema: z.object({ result: z.string() }),
execute: async (params, context) => {
console.log('Turn:', context.numberOfTurns);
console.log('History:', context.turnRequest?.input);
console.log('Model:', context.turnRequest?.model);
return { result: `Processed on turn ${context.numberOfTurns}` };
},
});上下文属性
| 属性 | 类型 | 说明 |
|---|---|---|
numberOfTurns | number | 当前轮次(从 1 开始) |
turnRequest | OpenResponsesRequest | undefined | 当前请求对象 |
toolCall | OpenResponsesFunctionToolCall | undefined | 当前 tool 调用 |
local | Readonly | 本 tool 的上下文(只读) |
setContext | (partial) => void | 修改本 tool 的上下文 |
shared | Readonly | 所有 tool 共享的上下文 |
setSharedContext | (partial) => void | 修改共享上下文 |
Tool 上下文(contextSchema)
用 contextSchema 声明 tool 需要的类型化持久上下文:
typescript
const weatherTool = tool({
name: 'get_weather',
inputSchema: z.object({ location: z.string() }),
outputSchema: z.object({ temperature: z.number() }),
contextSchema: z.object({
apiKey: z.string(),
units: z.enum(['celsius', 'fahrenheit']),
}),
execute: async (params, context) => {
const { apiKey, units } = context.local;
const weather = await fetchWeather(params.location, apiKey, units);
return { temperature: weather.temp };
},
});
// 在 callModel 中传入上下文
const result = openrouter.callModel({
model: 'openai/gpt-5-nano',
input: 'What is the weather in Tokyo?',
tools: [weatherTool, dbTool] as const,
context: {
get_weather: { apiKey: 'sk-...', units: 'celsius' },
db_query: { connectionString: 'postgres://...' },
},
});用 setContext 修改上下文
上下文变更在 tool 轮次间持久保留:
typescript
const authTool = tool({
name: 'auth',
inputSchema: z.object({ action: z.string() }),
contextSchema: z.object({
token: z.string(),
refreshCount: z.number(),
}),
execute: async (params, context) => {
const { token } = context.local;
if (isExpired(token)) {
const newToken = await refreshToken(token);
context.setContext({
token: newToken,
refreshCount: context.local.refreshCount + 1,
});
}
return { success: true };
},
});Tool 执行机制
自动执行流程
- 模型接收 prompt,生成 tool 调用
- SDK 提取 tool 调用并验证参数
- 执行 tool 的 execute 函数
- 将结果格式化后返回模型
- 模型生成最终回复(或继续调用 tool)
- 重复直到模型完成
控制执行轮次
typescript
// 限制最多 3 轮
const result = openrouter.callModel({
model: 'openai/gpt-5-nano',
input: 'Research this topic',
tools: [searchTool, analyzeTool],
maxToolRounds: 3,
});
// 动态控制
const result = openrouter.callModel({
model: 'openai/gpt-5-nano',
input: 'Research and analyze',
tools: [searchTool],
maxToolRounds: (context) => context.numberOfTurns < 5,
});maxToolRounds: 0 禁用自动执行,只获取 tool 调用。
并行 Tool 执行
模型同时调用多个 tool 时,SDK 并行执行:
typescript
const result = openrouter.callModel({
model: 'openai/gpt-5-nano',
input: 'Get weather in Paris, Tokyo, and New York simultaneously',
tools: [weatherTool],
});
// 三个城市的请求并行执行
const text = await result.getText();错误处理
execute 函数中的异常会被捕获并发回模型:
typescript
const riskyTool = tool({
name: 'risky_operation',
inputSchema: z.object({ input: z.string() }),
outputSchema: z.object({ result: z.string() }),
execute: async (params) => {
if (params.input === 'fail') {
throw new Error('Operation failed: invalid input');
}
return { result: 'success' };
},
});
// 模型收到错误信息后能做出相应处理
const text = await result.getText();
// "I tried the operation but it failed with: Operation failed: invalid input"最佳实践
清晰的 name 和 description——让模型知道何时调用:
typescript
const tool1 = tool({
name: 'search_knowledge_base',
description: 'Search the company knowledge base for documents, FAQs, and policies. Returns relevant articles with snippets.',
// ...
});用 .describe() 帮模型理解参数:
typescript
const inputSchema = z.object({
query: z.string().describe('Natural language search query'),
maxResults: z.number().min(1).max(100).default(10)
.describe('Maximum number of results to return (1-100)'),
});常见问题
Q: generator tool 和普通 tool 在费用上有差异吗?
A: 没有。generator tool 只是在本地执行过程中 yield 进度更新,不会产生额外的 API 调用费用。
Q: 多个 tool 能共享状态吗?
A: 可以。通过 sharedSchema 和 setSharedContext() 在 tool 间共享类型化的状态,在多工具协作场景(如维持一个 session ID)非常实用。
Q: as const 是必须的吗?
A: 不是必须的,但强烈推荐。不加 as const 时,toolCall.arguments 的类型会退化为 unknown,失去精确类型推导。