Tool & Function Calling
在提示中使用工具
原文链接:https://openrouter.ai/docs/guides/features/tool-calling
Tool calls(也称为 function calls)让 LLM 访问外部工具。LLM 不直接调用工具,而是建议要调用的工具。然后用户单独调用工具并将结果提供回 LLM。最后,LLM 将响应格式化为用户原始问题的答案。
OpenRouter 标准化了跨模型和 provider 的 tool calling 接口,使得与任何支持的模型集成外部工具变得容易。
支持的模型:你可以通过在 openrouter.ai/models?supported_parameters=tools 上过滤来找到支持 tool calling 的模型。
请求体示例
使用 OpenRouter 进行 tool calling 涉及三个关键步骤。以下是每个步骤的基本请求体格式:
第 1 步:带工具的推理请求
{
"model": "google/gemini-3-flash-preview",
"messages": [
{
"role": "user",
"content": "What are the titles of some James Joyce books?"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "search_gutenberg_books",
"description": "Search for books in the Project Gutenberg library",
"parameters": {
"type": "object",
"properties": {
"search_terms": {
"type": "array",
"items": {"type": "string"},
"description": "List of search terms to find books"
}
},
"required": ["search_terms"]
}
}
}
]
}
第 2 步:工具执行(客户端)
收到模型的 tool_calls 响应后,在本地执行请求的工具并准备结果:
// Model responds with tool_calls, you execute the tool locally
const toolResult = await searchGutenbergBooks(["James", "Joyce"]);
第 3 步:带工具结果的推理请求
{
"model": "google/gemini-3-flash-preview",
"messages": [
{
"role": "user",
"content": "What are the titles of some James Joyce books?"
},
{
"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": "Search for books in the Project Gutenberg library",
"parameters": {
"type": "object",
"properties": {
"search_terms": {
"type": "array",
"items": {"type": "string"},
"description": "List of search terms to find books"
}
},
"required": ["search_terms"]
}
}
}
]
}
注意:tools 参数必须在每个请求(第 1 步和第 3 步)中包含,以便 router 在每次调用时验证工具 schema。
Tool Calling 示例
以下是让 LLM 调用外部 API(在本例中为 Project Gutenberg,用于搜索书籍)的 Python 代码。
首先,进行一些基本设置:
import { OpenRouter } from '@openrouter/sdk';
const OPENROUTER_API_KEY = "{{API_KEY_REF}}";
// You can use any model that supports tool calling
const MODEL = "{{MODEL}}";
const openRouter = new OpenRouter({
apiKey: OPENROUTER_API_KEY,
});
const task = "What are the titles of some James Joyce books?";
const messages = [
{
role: "system",
content: "You are a helpful assistant."
},
{
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:
'Search for books in the Project Gutenberg library based on specified search terms',
parameters: {
type: 'object',
properties: {
search_terms: {
type: 'array',
items: {
type: 'string',
},
description:
"List of search terms to find books in the Gutenberg library (e.g. ['dickens', 'great'] to search for books by Dickens with 'great' in the title)",
},
},
required: ['search_terms'],
},
},
},
];
const TOOL_MAPPING = {
searchGutenbergBooks,
};
请注意,"工具"只是一个普通函数。然后我们编写一个与 OpenAI function calling 参数兼容的 JSON "spec"。我们将把该 spec 传递给 LLM,以便它知道此工具可用以及如何使用它。它会在需要时请求该工具以及任何参数。然后我们会在本地处理工具调用,执行函数调用,并将结果返回给 LLM。
工具使用和工具结果
让我们对模型进行第一次 OpenRouter API 调用:
const result = await openRouter.chat.send({
model: '{{MODEL}}',
tools,
messages,
stream: false,
});
const response_1 = result.choices[0].message;
LLM 以 tool_calls 的 finish reason 和 tool_calls 数组响应。在通用 LLM 响应处理程序中,你需要在处理 tool calls 之前检查 finish_reason,但这里我们假设就是这种情况。让我们继续处理工具调用:
// Append the response to the messages array so the LLM has the full context
// It's easy to forget this step!
messages.push(response_1);
// Now we process the requested tool calls, and use our book lookup tool
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),
});
}
消息数组现在包含:
- 我们原来的请求
- LLM 的响应(包含工具调用请求)
- 工具调用的结果(从 Project Gutenberg API 返回的 JSON 对象)
现在,我们可以进行第二次 OpenRouter API 调用,希望得到结果!
const response_2 = await openRouter.chat.send({
model: '{{MODEL}}',
messages,
tools,
stream: false,
});
console.log(response_2.choices[0].message.content);
输出类似于:
Here are some books by James Joyce:
* *Ulysses*
* *Dubliners*
* *A Portrait of the Artist as a Young Man*
* *Chamber Music*
* *Exiles: A Play in Three Acts*
成功了!我们已经成功地在 prompt 中使用了工具。
Interleaved Thinking
Interleaved thinking 允许模型在工具调用之间进行推理,从而在收到工具结果后实现更复杂的决策。此功能帮助模型将多个工具调用与中间推理步骤链接起来,并根据中间结果做出细致入微的决策。
重要提示:Interleaved thinking 会增加 token 使用量和响应延迟。启用此功能时请考虑你的预算和性能要求。
Interleaved Thinking 的工作原理
通过 interleaved thinking,模型可以:
- 在决定下一步做什么之前对工具调用的结果进行推理
- 将多个工具调用与中间推理步骤链接起来
- 根据中间结果做出更细致的决策
- 为其工具选择过程提供透明的推理
示例:使用推理进行多步骤研究
以下是一个示例,展示模型如何使用 interleaved thinking 研究跨多个来源的主题:
初始请求:
{
"model": "anthropic/claude-sonnet-4.5",
"messages": [
{
"role": "user",
"content": "Research the environmental impact of electric vehicles and provide a comprehensive analysis."
}
],
"tools": [
{
"type": "function",
"function": {
"name": "search_academic_papers",
"description": "Search for academic papers on a given topic",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"field": {"type": "string"}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "get_latest_statistics",
"description": "Get latest statistics on a topic",
"parameters": {
"type": "object",
"properties": {
"topic": {"type": "string"},
"year": {"type": "integer"}
},
"required": ["topic"]
}
}
}
]
}
模型的推理和工具调用:
-
初始推理:"我需要研究电动汽车的环境影响。让我从学术论文开始,获取同行评审的研究。"
-
第一次工具调用:
search_academic_papers({"query": "electric vehicle lifecycle environmental impact", "field": "environmental science"}) -
第一次工具结果后:"论文显示了制造成本影响的混合结果。我需要当前统计数据来补充学术研究。"
-
第二次工具调用:
get_latest_statistics({"topic": "electric vehicle carbon footprint", "year": 2024}) -
第二次工具结果后:"现在我有了学术研究和当前数据。让我搜索制造特定的研究来解决我发现的空白。"
-
第三次工具调用:
search_academic_papers({"query": "electric vehicle battery manufacturing environmental cost", "field": "materials science"}) -
最终分析:将所有收集到的信息综合成综合响应。
Interleaved Thinking 最佳实践
- 清晰的工具描述:提供详细描述,以便模型可以推理何时使用每个工具
- 结构化参数:使用定义良好的参数 schema 来帮助模型进行精确的工具调用
- 上下文保留:在多次工具交互中维护对话上下文
- 错误处理:设计工具以提供有意义的错误消息,帮助模型调整其方法
实现注意事项
实现 interleaved thinking 时:
- 由于额外的推理步骤,模型可能需要更长时间响应
- 由于推理过程,token 使用量会更高
- 推理质量取决于模型的能力
- 某些模型可能比其他模型更适合这种方法
简单的 Agentic Loop
在上面的例子中,调用是明确且顺序进行的。为了处理各种各样的用户输入和工具调用,你可以使用 agentic loop。
以下是一个简单的 agentic loop 示例(使用与上面相同的 tools 和初始 messages):
async function callLLM(messages: Message[]): Promise<ChatResponse> {
const result = await openRouter.chat.send({
model: '{{MODEL}}',
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);
// Look up the correct tool locally, and call it with the provided arguments
// Other tools can be added without changing the agentic loop
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 定义工具时,请遵循以下最佳实践: