OpenRouter 平台功能

OpenRouter 平台功能

工具调用

8 分钟阅读

工具与函数调用

在提示词中使用工具

工具调用(也称为函数调用)让 LLM 能够使用外部工具。LLM 不会直接调用工具。相反,它建议要调用的工具。随后由用户单独调用该工具,并将结果提供回 LLM。最后,LLM 将响应整理成对用户原始问题的回答。

OpenRouter 在模型和模型服务提供商之间标准化了工具调用接口,从而可以轻松地将外部工具与任何受支持的模型集成。

支持的模型:你可以在 openrouter.ai/models?supported_parameters=tools 上按筛选条件查找支持工具调用的模型。

如果你更喜欢通过完整的端到端示例学习,请继续阅读。

请求体示例

使用 OpenRouter 进行工具调用包含三个关键步骤。以下是每个步骤的核心请求体格式:

步骤 1:带工具的推理请求

{
  "model": "google/gemini-3-flash-preview",
  "messages": [
    {
      "role": "user",
      "content": "詹姆斯·乔伊斯有哪些书的书名?"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_gutenberg_books",
        "description": "在古腾堡计划图书馆中搜索书籍",
        "parameters": {
          "type": "object",
          "properties": {
            "search_terms": {
              "type": "array",
              "items": {"type": "string"},
              "description": "用于查找书籍的搜索词列表"
            }
          },
          "required": ["search_terms"]
        }
      }
    }
  ]
}

步骤 2:执行工具(客户端)

收到带有 tool_calls 的模型响应后,在本地执行所请求的工具并准备结果:

// 模型以 tool_calls 响应,你在本地执行该工具
const toolResult = await searchGutenbergBooks(["James", "Joyce"]);

步骤 3:带工具结果的推理请求

{
  "model": "google/gemini-3-flash-preview",
  "messages": [
    {
      "role": "user",
      "content": "詹姆斯·乔伊斯有哪些书的书名?"
    },
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "id": "call_abc123",
          "type": "function",
          "function": {
            "name": "search_gutenberg_books",
            "arguments": "{\"search_terms\": [\"James\", \"Joyce\"]}"
          }
        }
      ]
    },
    {
      "role": "tool",
      "tool_call_id": "call_abc123",
      "content": "[{\"id\": 4300, \"title\": \"Ulysses\", \"authors\": [{\"name\": \"Joyce, James\"}]}]"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_gutenberg_books",
        "description": "在古腾堡计划图书馆中搜索书籍",
        "parameters": {
          "type": "object",
          "properties": {
            "search_terms": {
              "type": "array",
              "items": {"type": "string"},
              "description": "用于查找书籍的搜索词列表"
            }
          },
          "required": ["search_terms"]
        }
      }
    }
  ]
}

注意:必须在每次请求(步骤 1 和步骤 3)中都包含 tools 参数,以便路由器在每次调用时校验工具 schema。

工具调用示例

下面的 Python 代码让 LLM 能够调用外部 API——这里是古腾堡计划,用于搜索书籍。

首先做一些基础设置:

import { OpenRouter } from '@openrouter/sdk';

const OPENROUTER_API_KEY = "<OPENROUTER_API_KEY>";

// 可以使用任何支持工具调用的模型
const MODEL = "google/gemini-3-flash-preview";

const openRouter = new OpenRouter({
  apiKey: OPENROUTER_API_KEY,
});

const task = "詹姆斯·乔伊斯有哪些书的书名?";

const messages = [
  {
    role: "system",
    content: "你是一名乐于助人的助手。"
  },
  {
    role: "user",
    content: task,
  }
];

定义工具

接下来,定义我们要调用的工具。请记住,工具将由 LLM 请求,但我们在此编写的代码最终负责执行调用,并将结果返回给 LLM。

async function searchGutenbergBooks(searchTerms: string[]): Promise<Book[]> {
  const searchQuery = searchTerms.join(' ');
  const url = 'https://gutendex.com/books';
  const response = await fetch(`${url}?search=${searchQuery}`);
  const data = await response.json();

  return data.results.map((book: any) => ({
    id: book.id,
    title: book.title,
    authors: book.authors,
  }));
}

const tools = [
  {
    type: 'function',
    function: {
      name: 'searchGutenbergBooks',
      description:
        '根据指定搜索词在古腾堡计划图书馆中搜索书籍',
      parameters: {
        type: 'object',
        properties: {
          search_terms: {
            type: 'array',
            items: {
              type: 'string',
            },
            description:
              "在古腾堡图书馆中查找书籍的搜索词列表(例如 ['dickens', 'great'] 可搜索狄更斯所著、标题中含 'great' 的书籍)",
          },
        },
        required: ['search_terms'],
      },
    },
  },
];

const TOOL_MAPPING = {
  searchGutenbergBooks,
};

注意,「工具」只是一个普通函数。然后我们编写与 OpenAI 函数调用参数兼容的 JSON「规范」。我们会把该规范传给 LLM,让它知道此工具可用以及如何使用。需要时它会请求该工具以及任何参数。随后我们在本地整理工具调用、执行函数,并将结果返回给 LLM。

使用工具与工具结果

让我们向模型发出第一次 OpenRouter API 调用:

const result = await openRouter.chat.send({
  model: 'google/gemini-3-flash-preview',
  tools,
  messages,
  stream: false,
});

const response_1 = result.choices[0].message;

LLM 以结束原因 tool_calls 和一个 tool_calls 数组响应。在通用的 LLM 响应处理程序中,你应在处理工具调用之前检查 finish_reason,但这里我们假定情况就是如此。让我们继续处理工具调用:

// 将响应追加到 messages 数组,以便 LLM 拥有完整上下文
// 很容易忘记这一步!
messages.push(response_1);

// 现在处理所请求的工具调用,并使用我们的书籍查找工具
for (const toolCall of response_1.tool_calls) {
  const toolName = toolCall.function.name;
  const { search_params } = JSON.parse(toolCall.function.arguments);
  const toolResponse = await TOOL_MAPPING[toolName](search_params);
  messages.push({
    role: 'tool',
    toolCallId: toolCall.id,
    name: toolName,
    content: JSON.stringify(toolResponse),
  });
}

此时 messages 数组包含:

  1. 我们的原始请求
  2. LLM 的响应(包含一次工具调用请求)
  3. 工具调用的结果(从古腾堡计划 API 返回的 json 对象)

现在,我们可以发出第二次 OpenRouter API 调用,并有望得到结果!

const response_2 = await openRouter.chat.send({
  model: 'google/gemini-3-flash-preview',
  messages,
  tools,
  stream: false,
});

console.log(response_2.choices[0].message.content);

输出会类似:

以下是詹姆斯·乔伊斯的一些作品:

*   *Ulysses*
*   *Dubliners*
*   *A Portrait of the Artist as a Young Man*
*   *Chamber Music*
*   *Exiles: A Play in Three Acts*

成功了!我们已经在提示词中成功使用了工具。

交错思考

交错思考允许模型在工具调用之间进行推理,从而在收到工具结果后做出更复杂的决策。此功能帮助模型在工具调用之间插入推理步骤来串联多次调用,并根据中间结果做出更细致的判断。

重要:交错思考会增加 Token 用量和响应延迟。启用此功能时请考虑预算和性能要求。

交错思考如何工作

借助交错思考,模型可以:

  • 在决定下一步之前,对工具调用的结果进行推理
  • 在工具调用之间插入推理步骤,串联多次调用
  • 根据中间结果做出更细致的决策
  • 为其工具选择过程提供透明的推理

示例:带推理的多步研究

下面的示例展示模型如何使用交错思考,跨多个来源研究一个主题:

初始请求:

{
  "model": "anthropic/claude-sonnet-4.5",
  "messages": [
    {
      "role": "user",
      "content": "研究电动汽车的环境影响,并提供一份全面分析。"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_academic_papers",
        "description": "按给定主题搜索学术论文",
        "parameters": {
          "type": "object",
          "properties": {
            "query": {"type": "string"},
            "field": {"type": "string"}
          },
          "required": ["query"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "get_latest_statistics",
        "description": "获取某主题的最新统计数据",
        "parameters": {
          "type": "object",
          "properties": {
            "topic": {"type": "string"},
            "year": {"type": "integer"}
          },
          "required": ["topic"]
        }
      }
    }
  ]
}

模型的推理与工具调用:

  1. 初始思考:「我需要研究电动汽车的环境影响。先从学术论文入手,获取经过同行评审的研究。」

  2. 第一次工具调用search_academic_papers({"query": "electric vehicle lifecycle environmental impact", "field": "environmental science"})

  3. 第一次工具结果之后:「论文对制造环节影响的结论不一。我需要当前统计数据来补充这项学术研究。」

  4. 第二次工具调用get_latest_statistics({"topic": "electric vehicle carbon footprint", "year": 2024})

  5. 第二次工具结果之后:「现在我同时有了学术研究和当前数据。接着搜索制造相关的专项研究,以填补发现的缺口。」

  6. 第三次工具调用search_academic_papers({"query": "electric vehicle battery manufacturing environmental cost", "field": "materials science"})

  7. 最终分析:将收集到的全部信息综合成一份全面回应。

交错思考的最佳实践

  • 清晰的工具描述:提供详细描述,以便模型推理何时使用每个工具
  • 结构化参数:使用定义良好的参数 schema,帮助模型进行精确的工具调用
  • 保留上下文:在多次工具交互中维持对话上下文
  • 错误处理:设计工具以提供有意义的错误消息,帮助模型调整方法

实现注意事项

实现交错思考时:

  • 由于额外的推理步骤,模型响应可能更慢
  • 由于推理过程,Token 用量会更高
  • 推理质量取决于模型能力
  • 某些模型可能比其他模型更适合这种方法

简单的智能体循环

在上面的示例中,调用是显式、顺序进行的。要处理各种各样的用户输入和工具调用,可以使用智能体循环。

下面是一个简单智能体循环的示例(使用与上面相同的 tools 和初始 messages):

async function callLLM(messages: Message[]): Promise<ChatResponse> {
  const result = await openRouter.chat.send({
    model: 'google/gemini-3-flash-preview',
    tools,
    messages,
    stream: false,
  });

  messages.push(result.choices[0].message);
  return result;
}

async function getToolResponse(response: ChatResponse): Promise<Message> {
  const toolCall = response.choices[0].message.toolCalls[0];
  const toolName = toolCall.function.name;
  const toolArgs = JSON.parse(toolCall.function.arguments);

  // 在本地查找正确的工具,并用提供的参数调用它
  // 可以添加其他工具而无需更改智能体循环
  const toolResult = await TOOL_MAPPING[toolName](toolArgs);

  return {
    role: 'tool',
    toolCallId: toolCall.id,
    content: toolResult,
  };
}

const maxIterations = 10;
let iterationCount = 0;

while (iterationCount < maxIterations) {
  iterationCount++;
  const response = await callLLM(messages);

  if (response.choices[0].message.toolCalls) {
    messages.push(await getToolResponse(response));
  } else {
    break;
  }
}

if (iterationCount >= maxIterations) {
  console.warn("Warning: Maximum iterations reached");
}

console.log(messages[messages.length - 1].content);

最佳实践与高级模式

函数定义指南

为 LLM 定义工具时,请遵循这些最佳实践:

清晰且具描述性的名称:使用能清楚表明工具用途的描述性函数名。

// 好:清晰且具体
{ "name": "get_weather_forecast" }
// 避免:过于含糊
{ "name": "weather" }

全面的描述:提供详细描述,帮助模型理解何时以及如何使用该工具。

{
  "description": "获取特定地点的当前天气状况和 5 日预报。支持城市、邮政编码和坐标。",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "城市名、邮政编码或坐标 (lat,lng)。示例:'New York'、'10001'、'40.7128,-74.0060'"
      },
      "units": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "温度单位偏好",
        "default": "celsius"
      }
    },
    "required": ["location"]
  }
}

流式工具调用

对流式响应使用工具调用时,请适当处理不同的内容类型:

const stream = await fetch('/api/chat/completions', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    model: 'anthropic/claude-sonnet-4.5',
    messages: messages,
    tools: tools,
    stream: true
  })
});

const reader = stream.body.getReader();
let toolCalls = [];

while (true) {
  const { done, value } = await reader.read();
  if (done) {
    break;
  }

  const chunk = new TextDecoder().decode(value);
  const lines = chunk.split('\n').filter(line => line.trim());

  for (const line of lines) {
    if (line.startsWith('data: ')) {
      const data = JSON.parse(line.slice(6));

      if (data.choices[0].delta.tool_calls) {
        toolCalls.push(...data.choices[0].delta.tool_calls);
      }

      if (data.choices[0].delta.finish_reason === 'tool_calls') {
        await handleToolCalls(toolCalls);
      } else if (data.choices[0].delta.finish_reason === 'stop') {
        // 没有工具调用的常规完成
        break;
      }
    }
  }
}

配置 tool_choice

使用 tool_choice 参数控制工具使用:

// 由模型决定(默认)
{ "tool_choice": "auto" }
// 禁用工具使用
{ "tool_choice": "none" }
// 强制使用特定工具
{
  "tool_choice": {
    "type": "function",
    "function": {"name": "search_database"}
  }
}

并行工具调用

使用 parallel_tool_calls 参数控制是否可以同时调用多个工具(对大多数模型默认为 true):

// 禁用并行工具调用——工具将按顺序调用
{ "parallel_tool_calls": false }

parallel_tool_callsfalse 时,模型每次只会请求一次工具调用,而不是可能并行请求多次调用。

多工具工作流

设计能够良好配合的工具:

{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_products",
        "description": "在目录中搜索产品"
      }
    },
    {
      "type": "function",
      "function": {
        "name": "get_product_details",
        "description": "获取特定产品的详细信息"
      }
    },
    {
      "type": "function",
      "function": {
        "name": "check_inventory",
        "description": "检查产品的当前库存水平"
      }
    }
  ]
}

这允许模型自然地串联操作:搜索 → 获取详情 → 检查库存。

可靠性跟踪

OpenRouter 会跟踪每个模型服务提供商完成工具调用的可靠程度,并在每个模型页面的性能选项卡上以 工具调用错误率(Tool Call Error Rate) 展示。同一信号也会驱动工具调用请求上的 Auto Exacto 模型服务提供商排序。关于精确校验器、JSON Schema 草案、正则语义以及按工具调用的分类,参见 工具调用成功率如何衡量

关于 OpenRouter 的消息格式和工具参数的更多细节,参见 API 参考