Appearance
OpenRouter Agent SDK 的动态参数机制允许将 callModel 的任意参数(model、temperature、instructions、maxOutputTokens 等)写成接收 TurnContext 的函数,从而根据对话轮次、当前请求内容、外部数据库等动态决定参数值。本文涵盖函数签名、TurnContext 属性、async 异步函数用法,以及渐进模型升级、自适应温度、上下文感知指令、动态 token 上限、按特性开关等实用模式。
基础用法
callModel 的大多数参数都可以写成函数,而不是静态值。函数在每轮调用前执行,根据当前上下文动态返回参数值:
typescript
import { OpenRouter } from '@openrouter/agent';
const openrouter = new OpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,
});
const result = openrouter.callModel({
// 根据轮次动态选择模型
model: (ctx) => {
return ctx.numberOfTurns > 3 ? 'openai/gpt-5.2' : 'openai/gpt-5-nano';
},
input: 'Hello!',
tools: [myTool],
});函数签名
参数函数接收 TurnContext,返回对应类型的值(支持 async):
typescript
type ParameterFunction<T> = (context: TurnContext) => T | Promise<T>;TurnContext 属性
| 属性 | 类型 | 说明 |
|---|---|---|
numberOfTurns | number | 当前轮次(从 1 开始) |
turnRequest | OpenResponsesRequest | undefined | 当前请求对象,包含消息列表和模型设置 |
toolCall | OpenResponsesFunctionToolCall | undefined | 当前正在执行的工具调用 |
异步函数
参数函数可以是 async,适合从外部系统加载数据:
typescript
const result = openrouter.callModel({
model: 'openai/gpt-5-nano',
// 从数据库读取用户偏好
temperature: async (ctx) => {
const prefs = await fetchUserPreferences(userId);
return prefs.preferredTemperature ?? 0.7;
},
// 动态加载业务规则
instructions: async (ctx) => {
const rules = await fetchBusinessRules();
return `请遵循以下规则:\n${rules.join('\n')}`;
},
input: 'Hello!',
});实用模式
渐进模型升级
前几轮用快速模型,对话变复杂后切换更强模型:
typescript
const result = openrouter.callModel({
model: (ctx) => {
if (ctx.numberOfTurns <= 2) {
return 'openai/gpt-5-nano'; // 轻量快速
}
return 'openai/gpt-5.2'; // 复杂对话升级
},
input: '帮我深入分析这个问题...',
tools: [analysisTool],
});自适应 temperature
根据请求内容的关键词判断任务类型,动态调整创意度:
typescript
const result = openrouter.callModel({
model: 'openai/gpt-5-nano',
temperature: (ctx) => {
const lastMessage = JSON.stringify(ctx.turnRequest?.input).toLowerCase();
if (lastMessage.includes('creative') || lastMessage.includes('brainstorm')) {
return 1.0; // 创意任务,放开随机性
}
if (lastMessage.includes('code') || lastMessage.includes('calculate')) {
return 0.2; // 精确任务,降低随机性
}
return 0.7; // 默认
},
input: '写一个创意故事',
});上下文感知的 instructions
根据对话轮次长度调整系统指令,避免长对话中回复过于冗长:
typescript
const result = openrouter.callModel({
model: 'openai/gpt-5-nano',
instructions: (ctx) => {
const base = '你是一个有帮助的助手。';
const turnInfo = `这是第 ${ctx.numberOfTurns} 轮对话。`;
if (ctx.numberOfTurns > 5) {
return `${base}\n${turnInfo}\n对话较长,请保持回复简洁。`;
}
return `${base}\n${turnInfo}`;
},
input: '继续帮我...',
tools: [helpTool],
});动态 maxOutputTokens
按任务类型调整输出长度上限:
typescript
const result = openrouter.callModel({
model: 'openai/gpt-5-nano',
maxOutputTokens: (ctx) => {
const lastMessage = JSON.stringify(ctx.turnRequest?.input).toLowerCase();
if (lastMessage.includes('summarize') || lastMessage.includes('brief')) {
return 200; // 摘要任务,严格限制
}
if (lastMessage.includes('detailed') || lastMessage.includes('explain')) {
return 2000; // 详细解释,放宽上限
}
return 500; // 默认
},
input: '请详细解释这个概念',
});功能开关(Feature Flags)
在对话深入后才开启推理模式,节省前期成本:
typescript
const result = openrouter.callModel({
model: 'anthropic/claude-sonnet-4.5',
provider: async (ctx) => {
const enableThinking = ctx.numberOfTurns > 2;
return enableThinking ? {
anthropic: {
thinking: { type: 'enabled', budgetTokens: 1000 },
},
} : undefined;
},
input: '解决这个复杂问题',
tools: [analysisTool],
});与工具配合使用
动态参数与工具调用可以同时使用:
typescript
const smartAssistant = openrouter.callModel({
// 检测到工具调用历史后升级模型
model: (ctx) => {
const hasToolUse = JSON.stringify(ctx.turnRequest?.input).includes('function_call');
return hasToolUse ? 'anthropic/claude-sonnet-4.5' : 'openai/gpt-5-nano';
},
// 工具执行完毕后降低随机性(第二轮起)
temperature: (ctx) => {
return ctx.numberOfTurns > 1 ? 0.3 : 0.7;
},
input: '调研并分析这个课题',
tools: [searchTool, analysisTool],
});执行时机
动态参数在每轮开始时执行,参数函数的返回值决定本轮 API 调用的实际配置。
错误处理
async 参数函数中应处理异常,提供 fallback:
typescript
const result = openrouter.callModel({
model: 'openai/gpt-5-nano',
instructions: async (ctx) => {
try {
const rules = await fetchRules();
return `请遵循以下规则:${rules}`;
} catch (error) {
console.error('规则加载失败:', error);
return '你是一个有帮助的助手。'; // fallback
}
},
input: 'Hello!',
});最佳实践
保持函数纯净
参数函数应尽量无副作用:
typescript
// 好:纯函数
model: (ctx) => ctx.numberOfTurns > 3 ? 'gpt-4' : 'gpt-4o-mini',
// 避免:含副作用
model: (ctx) => {
logToDatabase(ctx); // 副作用,可能影响可预测性
return 'gpt-4';
},缓存昂贵的操作
如果每轮都需要读取同一份数据,先缓存结果:
typescript
let cachedRules: string | null = null;
const result = openrouter.callModel({
instructions: async (ctx) => {
if (!cachedRules) {
cachedRules = await fetchExpensiveRules();
}
return cachedRules;
},
input: 'Hello!',
});提供合理默认值
参数函数始终提供 fallback,避免返回 undefined 导致意外行为:
typescript
model: (ctx) => {
const preferredModel = getPreferredModel();
return preferredModel ?? 'openai/gpt-5-nano'; // 必有兜底
},常见问题
Q: 动态参数函数和 nextTurnParams 有什么区别?
A: 动态参数函数在 callModel 层面配置,每轮开始时执行,适合根据对话状态调整全局配置;nextTurnParams 在工具层面配置,在工具 execute 执行完毕后运行,适合工具自己修改下一轮的上下文。两者可以同时使用。
Q: 参数函数会影响 streaming 吗?
A: 不会。动态参数只影响每轮发起 API 请求时的参数,不影响响应的消费方式(streaming 与否由调用方决定)。
Q: ctx.numberOfTurns 从几开始计数?
A: 从 1 开始,第一轮为 1,后续依次递增。