0Pricing
AI Engineering Academy · Урок

Обработка вызовов инструментов в приложении

Обнаружьте tool_calls в поле finish_reason ответа API, извлеките имена функций и аргументы, выполните соответствующую функцию Python и отправьте результат обратно модели.

«Обработка вызовов инструментов в приложении» — бесплатный урок AI Engineering Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения 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. Необходимо разобрать её с помощью 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, проверяйте все аргументы и удостоверяйтесь в наличии прав перед выполнением действий. Модель является недоверенным вызывающим объектом: вредоносная подсказка может попытаться вызвать опасные функции, если логика выбора функции допускает слишком многое.

  • Разрешайте только функции, явно перечисленные в реестре инструментов
  • Проверяйте входные данные с помощью 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_ids. Далее мы рассмотрим случай, когда модель одновременно вызывает несколько функций, используя параллельные вызовы функций.

Часто задаваемые вопросы

Урок «Обработка вызовов инструментов в приложении» бесплатный?

Да — полный текст урока «Обработка вызовов инструментов в приложении» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Engineering Academy, подпишись на CoddyKit PRO. Курс AI Engineering Academy содержит 4 уроков всего.

Чему я научусь в уроке «Обработка вызовов инструментов в приложении»?

Обнаружьте tool_calls в поле finish_reason ответа API, извлеките имена функций и аргументы, выполните соответствующую функцию Python и отправьте результат обратно модели. Ты практикуешь AI Engineering Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать AI Engineering Academy?

Предыдущий опыт не требуется. AI Engineering Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Обработка вызовов инструментов в приложении»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке AI Engineering Academy?

Да. Каждый урок AI Engineering Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Определение схем функций для API
  2. Обработка вызовов инструментов в приложении
  3. Параллельный вызов функций
  4. Создание интерфейса базы данных на естественном языке
← Назад к AI Engineering Academy