0Pricing
AI Engineering Academy · 课时

在应用中处理工具调用

检测 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 为 API 定义函数模式
  2. 在应用中处理工具调用
  3. 并行调用函数
  4. 构建自然语言数据库接口
← 返回 AI Engineering Academy