Skip to content

OpenRouter Agent SDK 的动态参数机制允许将 callModel 的任意参数(modeltemperatureinstructionsmaxOutputTokens 等)写成接收 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 属性

属性类型说明
numberOfTurnsnumber当前轮次(从 1 开始)
turnRequestOpenResponsesRequest | undefined当前请求对象,包含消息列表和模型设置
toolCallOpenResponsesFunctionToolCall | 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,后续依次递增。