在应用中处理工具调用
检测 API 响应中的 finish_reason tool_calls,提取函数名称和参数,执行对应的 Python 函数,并将结果发送回模型。
在应用中处理工具调用 是 CoddyKit 上的免费 AI Engineering Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Engineering Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Engineering Academy 课程共包含 4 节课。
工具调用响应对象
模型决定调用函数时,API 响应会在消息对象中包含一个 tool_calls 列表。每次工具调用都有唯一的 id、要调用的 function.name,以及 function.arguments——这是一个 JSON 字符串,其中包含模型希望传入的参数。解析这些内容并执行函数由您的应用程序代码负责。
from openai import OpenAI
import json
client = OpenAI()
response = client.chat.completions.create(
model='gpt-4o',
messages=[{'role': 'user', 'content': 'What is the weather in Paris?'}],
tools=tools # defined previously
)
message = response.choices[0].message
if response.choices[0].finish_reason == 'tool_calls':
for tool_call in message.tool_calls:
print('Call ID:', tool_call.id)
print('Function name:', tool_call.function.name)
print('Arguments (JSON string):', tool_call.function.arguments)解析函数参数
function.arguments 字段是一个经过 JSON 编码的字符串,而不是 Python dict。您必须使用 json.loads() 对其进行解析。请始终将这一步放在 try/except 中:尽管有架构指导,模型偶尔仍会生成格式错误的 JSON,因此您需要妥善处理这种情况。
import json
def parse_tool_call(tool_call) -> dict:
'''Parse a tool call's arguments from JSON string to dict.'''
try:
args = json.loads(tool_call.function.arguments)
return args
except json.JSONDecodeError as e:
print(f'Failed to parse arguments for {tool_call.function.name}: {e}')
print(f'Raw arguments: {tool_call.function.arguments}')
return {}
# Usage
tool_call = message.tool_calls[0]
args = parse_tool_call(tool_call)
print('Parsed args:', args) # {'location': 'Paris', 'unit': 'celsius'}分派到正确的函数
使用 function.name 将调用分派给正确的 Python 函数。一种清晰的做法是将函数存放在一个字典中,并建立名称到可调用对象的映射。这样可以避免脆弱的 if/elif 链,也便于日后添加新工具。
def get_current_weather(location: str, unit: str = 'celsius') -> str:
# Real implementation calls a weather API
return f'{location}: 18{chr(176)}C, partly cloudy'
def create_calendar_event(title: str, start_time: str, duration_minutes: int, **kwargs) -> str:
return f'Event created: {title} at {start_time} for {duration_minutes} minutes'
# Tool registry: maps function names to callables
TOOL_REGISTRY = {
'get_current_weather': get_current_weather,
'create_calendar_event': create_calendar_event
}
def execute_tool_call(tool_call) -> str:
name = tool_call.function.name
args = parse_tool_call(tool_call)
if name not in TOOL_REGISTRY:
return f'Unknown function: {name}'
try:
result = TOOL_REGISTRY[name](**args)
return str(result)
except Exception as e:
return f'Function {name} raised an error: {str(e)}'将结果发送回模型
执行函数后,您必须通过后续 API 调用将结果发送回模型。先将助手消息(其中包含 tool_calls)添加到对话中,然后添加一条新消息,其中包含 role='tool'、tool_call_id 以及作为内容的函数结果。接着再次调用 API。
def run_tool_call_loop(messages: list, tools: list) -> str:
response = client.chat.completions.create(
model='gpt-4o',
messages=messages,
tools=tools
)
message = response.choices[0].message
messages.append(message) # Add assistant's tool_calls message
# Execute all tool calls and collect results
for tool_call in (message.tool_calls or []):
result = execute_tool_call(tool_call)
# Add each tool result as a 'tool' role message
messages.append({
'role': 'tool',
'tool_call_id': tool_call.id,
'content': result
})
# Second API call with results appended
final_response = client.chat.completions.create(
model='gpt-4o',
messages=messages,
tools=tools
)
return final_response.choices[0].message.content完整的一轮对话
一次完整的工具调用交互包含对话历史中的四条消息:用户消息、请求工具调用的助手消息、工具结果消息,以及结合结果生成最终回复的助手消息。理解这种结构对于构建支持多轮工具调用的助手至关重要。
# The full message history for a tool-calling conversation:
conversation = [
{'role': 'user', 'content': 'What is the weather in Tokyo?'},
# Model requests a tool call (added by run_tool_call_loop)
# {'role': 'assistant', 'content': None, 'tool_calls': [...]},
# Application sends tool result back
# {'role': 'tool', 'tool_call_id': 'call_abc123', 'content': 'Tokyo: 22C, sunny'},
# Model produces final human-readable response
# {'role': 'assistant', 'content': 'The weather in Tokyo is 22 degrees Celsius and sunny.'}
]
final_answer = run_tool_call_loop(
[{'role': 'user', 'content': 'What is the weather in Tokyo?'}],
tools
)
print(final_answer)处理未调用工具的情况
有时模型会直接回答而不调用任何工具——此时 finish_reason 会是 'stop',而不是 'tool_calls'。尝试处理工具调用前,请始终检查这种情况。稳健的实现应清晰地处理这两个分支。
def smart_complete(user_message: str) -> str:
messages = [{'role': 'user', 'content': user_message}]
response = client.chat.completions.create(
model='gpt-4o',
messages=messages,
tools=tools
)
choice = response.choices[0]
if choice.finish_reason == 'stop':
# Model answered directly without calling a tool
return choice.message.content
elif choice.finish_reason == 'tool_calls':
# Process tool calls
messages.append(choice.message)
for tc in choice.message.tool_calls:
result = execute_tool_call(tc)
messages.append({'role': 'tool', 'tool_call_id': tc.id, 'content': result})
# Get final answer
final = client.chat.completions.create(model='gpt-4o', messages=messages)
return final.choices[0].message.content
return 'Unexpected finish reason: ' + choice.finish_reason执行前验证参数
模型偶尔可能会传入不符合业务逻辑验证要求的参数,例如负数时长、无效电子邮件地址或过去的日期。请在调用实际函数之前验证参数;如果验证失败,则返回描述性错误字符串。这样,模型就可以在下一轮中修正参数。
from pydantic import BaseModel, ValidationError
from datetime import datetime
class CreateEventArgs(BaseModel):
title: str
start_time: str # ISO 8601
duration_minutes: int
def safe_create_event(tool_call) -> str:
try:
raw_args = json.loads(tool_call.function.arguments)
validated = CreateEventArgs(**raw_args)
# Additional business rule
event_time = datetime.fromisoformat(validated.start_time)
if event_time < datetime.now():
return 'Error: start_time must be in the future.'
return create_calendar_event(**validated.dict())
except ValidationError as e:
return f'Invalid arguments: {e}'记录工具调用交互
请始终记录工具调用交互,以便调试和分析。记录函数名称、参数、结果和执行时间。这些数据可以帮助您确定哪些工具调用最频繁、哪些工具会失败,以及模型会生成哪些参数模式,对改进架构和函数实现非常有价值。
import time
import logging
logger = logging.getLogger('tool_calls')
def logged_execute(tool_call) -> str:
name = tool_call.function.name
args_str = tool_call.function.arguments
start = time.time()
result = execute_tool_call(tool_call)
elapsed = time.time() - start
logger.info(
'Tool call executed',
extra={
'function': name,
'arguments': args_str,
'result_length': len(result),
'elapsed_ms': round(elapsed * 1000)
}
)
return result工具调用的安全注意事项
未经验证,绝不要根据模型输出执行任意函数。请始终在 TOOL_REGISTRY 中明确列出允许的函数名称,验证所有参数,并在执行操作前检查授权。模型是不受信任的调用方——如果分派逻辑过于宽松,恶意提示可能会尝试调用具有破坏性的函数。
- 仅允许调用 TOOL_REGISTRY 中明确列出的函数
- 执行前使用 Pydantic 验证输入
- 写入或删除操作必须经过授权
返回丰富的结构化结果
工具结果不必是纯字符串。您可以返回 JSON 格式的数据、表格或摘要。以 JSON 返回结构化数据,可以让模型解析最终答案中的特定字段并引用这些字段。对于大型结果,请返回包含关键信息的摘要,而不要将所有原始数据直接倾倒到上下文中。
def get_order_status(order_id: str) -> str:
# Fetch from real database
order = {'id': order_id, 'status': 'shipped', 'estimated_delivery': '2024-03-15', 'carrier': 'FedEx', 'tracking': 'FX123456'}
# Return concise summary, not raw DB record
return (
f'Order {order_id}: Status={order["status"]}, '
f'Estimated delivery: {order["estimated_delivery"]}, '
f'Carrier: {order["carrier"]}, Tracking: {order["tracking"]}'
)多轮工具调用对话
一种强大的模式是多轮对话:模型在多条用户消息之间调用工具,逐步构建上下文。请始终保留完整的对话历史,包括过去的工具调用和结果,以便模型能够引用之前的答案,而无需不必要地再次调用工具。
快速检查
测试您对在应用程序中处理工具调用的理解。
课程回顾
在本课中,您学习了:工具调用参数以 JSON 字符串的形式到达,必须使用 json.loads() 解析;TOOL_REGISTRY 字典将函数名称映射到可调用对象,从而实现清晰的分派;以及结果会以 role='tool' 消息的形式返回给模型,并带有相匹配的 tool_call_id。接下来,我们将处理模型同时调用多个函数的情况,也就是并行函数调用。
常见问题解答
「在应用中处理工具调用」课时是免费的吗?
是的 — 「在应用中处理工具调用」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Engineering Academy 课程的其余内容,请升级到 CoddyKit PRO。 AI Engineering Academy 课程共包含 4 节课。
「在应用中处理工具调用」这节课中我会学到什么?
检测 API 响应中的 finish_reason tool_calls,提取函数名称和参数,执行对应的 Python 函数,并将结果发送回模型。 你通过在浏览器中直接运行的动手代码来练习 AI Engineering Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 AI Engineering Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 AI Engineering Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「在应用中处理工具调用」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 AI Engineering Academy 课中编写并运行代码吗?
能。每节 AI Engineering Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 为 API 定义函数模式
- 在应用中处理工具调用
- 并行调用函数
- 构建自然语言数据库接口