tool-calling

阅读约 20 分钟

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),
  });
}

消息数组现在包含:

  1. 我们原来的请求
  2. LLM 的响应(包含工具调用请求)
  3. 工具调用的结果(从 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"]
        }
      }
    }
  ]
}

模型的推理和工具调用:

  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. 最终分析:将所有收集到的信息综合成综合响应。

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 定义工具时,请遵循以下最佳实践:

© 2026 OpenRouter.help
查看官方英文原件