工具和函数调用
工具调用(也称为函数调用)为 LLM 提供访问外部工具的能力。LLM 不直接调用工具。相反,它建议要调用的工具。然后用户单独调用工具并将结果提供回 LLM。最后,LLM 将响应格式化为对用户原始问题的答案。
OpenRoute 标准化了跨模型和提供商的工具调用接口,使得将外部工具与任何支持的模型集成变得容易。
支持的模型:您可以通过在 openroute.cn/models?supported_parameters=tools 上过滤来找到支持工具调用的模型。
如果您更喜欢从完整的端到端示例中学习,请继续阅读。
请求体示例
使用 OpenRoute 进行工具调用涉及三个关键步骤。以下是每个步骤的基本请求体格式:
步骤 1:带工具的推理请求
{
"model": "google/gemini-2.0-flash-001",
"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-2.0-flash-001",
"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"]
}
}
}
]
}
The tools
parameter must be included in every request (Steps 1 and 3) so the router can validate the tool schema on each call.
工具调用示例
以下是让 LLM 能够调用外部 API 的 Python 代码——在这个例子中是 Project Gutenberg,用于搜索书籍。
首先,让我们进行一些基本设置:
import json, requests
from openai import OpenAI
OPENROUTE_API_KEY = f"{YOUR_API_KEY}"
# 您可以使用任何支持工具调用的模型
MODEL = "google/gemini-2.0-flash-001"
openai_client = OpenAI(
base_url="https://openroute.cn/api/v1",
api_key=OPENROUTE_API_KEY,
)
task = "詹姆斯·乔伊斯的一些书籍标题是什么?"
messages = [
{
"role": "system",
"content": "您是一个有用的助手。"
},
{
"role": "user",
"content": task,
}
]
const response = await fetch('https://openroute.cn/api/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer {YOUR_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'google/gemini-2.0-flash-001',
messages: [
{ role: 'system', content: '您是一个有用的助手。' },
{
role: 'user',
content: '詹姆斯·乔伊斯的一些书籍标题是什么?',
},
],
}),
});
定义工具
接下来,我们定义要调用的工具。记住,工具将被 LLM 请求,但我们在这里编写的代码最终负责执行调用并将结果返回给 LLM。
def search_gutenberg_books(search_terms):
search_query = " ".join(search_terms)
url = "https://gutendex.com/books"
response = requests.get(url, params={"search": search_query})
simplified_results = []
for book in response.json().get("results", []):
simplified_results.append({
"id": book.get("id"),
"title": book.get("title"),
"authors": book.get("authors")
})
return simplified_results
tools = [
{
"type": "function",
"function": {
"name": "search_gutenberg_books",
"description": "根据指定的搜索词在古腾堡项目图书馆中搜索书籍",
"parameters": {
"type": "object",
"properties": {
"search_terms": {
"type": "array",
"items": {
"type": "string"
},
"description": "在古腾堡图书馆中查找书籍的搜索词列表(例如 ['dickens', 'great'] 搜索狄更斯的标题中包含 'great' 的书籍)"
}
},
"required": ["search_terms"]
}
}
}
]
TOOL_MAPPING = {
"search_gutenberg_books": search_gutenberg_books
}
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,
};
Note that the "tool" is just a normal function. We then write a JSON "spec" compatible with the OpenAI function calling parameter. We'll pass that spec to the LLM so that it knows this tool is available and how to use it. It will request the tool when needed, along with any arguments. We'll then marshal the tool call locally, make the function call, and return the results to the LLM.
工具使用和工具结果
让我们向模型进行第一次 OpenRoute API 调用:
request_1 = {
"model": google/gemini-2.0-flash-001,
"tools": tools,
"messages": messages
}
response_1 = openai_client.chat.completions.create(**request_1).message
const request_1 = await fetch('https://openroute.cn/api/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer {YOUR_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'google/gemini-2.0-flash-001',
tools,
messages,
}),
});
const data = await request_1.json();
const response_1 = data.choices[0].message;
LLM 以 tool_calls
的完成原因和 tool_calls
数组响应。在通用的 LLM 响应处理程序中,您会希望在处理工具调用之前检查 finish_reason
,但在这里我们假设情况就是这样。让我们继续处理工具调用:
# 将响应附加到消息数组,以便 LLM 具有完整上下文
# 很容易忘记这一步!
messages.append(response_1)
# 现在我们处理请求的工具调用,并使用我们的书籍查找工具
for tool_call in response_1.tool_calls:
'''
在这种情况下,我们只提供了一个工具,所以我们知道要调用什么函数。
当提供多个工具时,您可以检查 `tool_call.function.name`
来找出您需要在本地调用什么函数。
'''
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
tool_response = TOOL_MAPPING[tool_name](**tool_args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(tool_response),
})
// 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 的响应(包含工具调用请求)
- 工具调用的结果(从古腾堡项目 API 返回的 json 对象)
现在,我们可以进行第二次 OpenRoute API 调用,希望得到我们的结果!
request_2 = {
"model": MODEL,
"messages": messages,
"tools": tools
}
response_2 = openai_client.chat.completions.create(**request_2)
print(response_2.choices[0].message.content)
const response = await fetch('https://openroute.cn/api/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer {YOUR_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'google/gemini-2.0-flash-001',
messages,
tools,
}),
});
const data = await response.json();
console.log(data.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-3.5-sonnet",
"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"})
-
最终分析:将所有收集的信息综合成全面的响应。
交错思考的最佳实践
- 清晰的工具描述:提供详细描述,以便模型能够推理何时使用每个工具
- 结构化参数:使用定义良好的参数模式来帮助模型进行精确的工具调用
- 上下文保持:在多个工具交互中保持对话上下文
- 错误处理:设计工具以提供有意义的错误消息,帮助模型调整其方法
实现考虑
在实现交错思考时:
- 由于额外的推理步骤,模型可能需要更长时间来响应
- 由于推理过程,token 使用量会更高
- 推理质量取决于模型的能力
- 某些模型可能比其他模型更适合这种方法
简单的代理循环
在上面的示例中,调用是显式和顺序进行的。为了处理各种用户输入和工具调用,您可以使用代理循环。
以下是一个简单的代理循环示例(使用与上面相同的 tools
和初始 messages
):
def call_llm(msgs):
resp = openai_client.chat.completions.create(
model=google/gemini-2.0-flash-001,
tools=tools,
messages=msgs
)
msgs.append(resp.choices[0].message.dict())
return resp
def get_tool_response(response):
tool_call = response.choices[0].message.tool_calls[0]
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.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
tool_result = TOOL_MAPPING[tool_name](**tool_args)
return {
"role": "tool",
"tool_call_id": tool_call.id,
"content": tool_result,
}
max_iterations = 10
iteration_count = 0
while iteration_count < max_iterations:
iteration_count += 1
resp = call_llm(_messages)
if resp.choices[0].message.tool_calls is not None:
messages.append(get_tool_response(resp))
else:
break
if iteration_count >= max_iterations:
print("Warning: Maximum iterations reached")
print(messages[-1]['content'])
async function callLLM(messages: Message[]): Promise<Message> {
const response = await fetch(
'https://openroute.cn/api/v1/chat/completions',
{
method: 'POST',
headers: {
Authorization: `Bearer {YOUR_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'google/gemini-2.0-flash-001',
tools,
messages,
}),
},
);
const data = await response.json();
messages.push(data.choices[0].message);
return data;
}
async function getToolResponse(response: Message): Promise<Message> {
const toolCall = response.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.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": "Get current weather conditions and 5-day forecast for a specific location. Supports cities, zip codes, and coordinates.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, zip code, or coordinates (lat,lng). Examples: 'New York', '10001', '40.7128,-74.0060'"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit preference",
"default": "celsius"
}
},
"required": ["location"]
}
}
流式工具调用
在使用流式响应进行工具调用时,请适当处理不同的内容类型:
const stream = await fetch('/api/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'anthropic/claude-3.5-sonnet',
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') {
// Regular completion without tool calls
break;
}
}
}
}
工具选择配置
使用 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_calls
为 false
时,模型一次只会请求一个工具调用,而不是可能并行进行多个调用。
多工具工作流
设计能够很好协同工作的工具:
{
"tools": [
{
"type": "function",
"function": {
"name": "search_products",
"description": "Search for products in the catalog"
}
},
{
"type": "function",
"function": {
"name": "get_product_details",
"description": "Get detailed information about a specific product"
}
},
{
"type": "function",
"function": {
"name": "check_inventory",
"description": "Check current inventory levels for a product"
}
}
]
}
这允许模型自然地链接操作:搜索 → 获取详情 → 检查库存。
有关 OpenRoute 消息格式和工具参数的更多详细信息,请参阅 API 参考。
Last updated on