Назад в блог

API и SDKИнструкция

Function calling в Python через OpenAI-совместимый API

Передайте tools, прочитайте tool_calls и верните результат с role tool: двухшаговый цикл function calling на Python с MiniMax-M2.7.

Автор: CheapTokens TeamОпубликовано Обновлено 5 мин чтения

Схема двухшагового цикла вызова инструментов: отправить tools, прочитать tool_calls, вернуть результат сообщением tool
Содержание
  1. Как работает function calling
  2. Настройка клиента
  3. Описание функции и первый запрос
  4. Что вы увидите
  5. Несколько вызовов и длинные циклы
  6. Почему MiniMax-M2.7 подходит для циклов с инструментами
  7. Если не работает
  8. Что дальше

Function calling (вызов инструментов) позволяет модели попросить ваш код выполнить функцию. Вы передаёте в запросе список tools, модель вместо текста отвечает tool_calls, вы сами выполняете функцию и возвращаете её результат в сообщении с role: tool. После этого модель пишет итоговый ответ. В статье весь цикл разобран на Python для OpenAI-совместимого адреса https://api.cheaptokens.dev/v1/chat/completions с моделью MiniMax-M2.7 (id MiniMaxAI/MiniMax-M2.7, контекст 180K, до 8 192 токенов в ответе).

Вызов инструментов указан как поддерживаемый для каждой модели каталога (документация CheapTokens: docs, проверено 22 октября 2026 года). Нужен API-ключ; если вы ещё не делали запросов, начните с первого запроса к API.

Как работает function calling#

Модель ничего не запускает. Она лишь формирует структурированную просьбу: «вызови get_weather с {\"city\": \"Paris\"}». Реальную работу делает ваша программа и сообщает результат. Получается цикл из двух запросов:

  1. Запрос 1. Вы отправляете сообщение пользователя и список tools. Модель отвечает текстом либо возвращает один или несколько tool_calls.
  2. Ваш код. Вы разбираете каждый вызов, запускаете нужную функцию и собираете результат.
  3. Запрос 2. Вы снова отправляете всю переписку: исходные сообщения, сообщение ассистента с tool_calls и по одному сообщению role: tool на каждый вызов. По результатам модель пишет итоговый ответ.

Каждый запрос заново отправляет всю историю, поэтому цикл с инструментами тратит больше входных токенов, чем один вопрос. Если вы ещё не знакомы с этим, прочитайте что такое токен.

Настройка клиента#

Установите OpenAI SDK (pip install openai) и положите ключ в переменную окружения, а не в код:

bash
export CHEAPTOKENS_API_KEY="your-key-here"

Базовый адрес — https://api.cheaptokens.dev/v1. Id модели берите из документации CheapTokens: у разных провайдеров одна и та же модель называется по-разному. В статье используется MiniMaxAI/MiniMax-M2.7.

Описание функции и первый запрос#

Функция описывается в массиве tools. Каждый элемент содержит type: function и объект function с полями name, description и parameters в виде JSON Schema. По description модель решает, когда вызвать функцию, поэтому пишите его коротко и конкретно.

Ниже — полный скрипт. Функция погоды — заглушка с выдуманными данными, поэтому внешние сервисы не нужны:

python
import json
import os

from openai import OpenAI

client = OpenAI(
    base_url='https://api.cheaptokens.dev/v1',
    api_key=os.environ['CHEAPTOKENS_API_KEY'],
)
MODEL = 'MiniMaxAI/MiniMax-M2.7'


def get_weather(city: str) -> dict:
    # Stub with fake data: replace with a real lookup
    fake = {'Paris': 14, 'Tokyo': 19}
    return {'city': city, 'temp_c': fake.get(city, 10)}


FUNCTIONS = {'get_weather': get_weather}

tools = [
    {
        'type': 'function',
        'function': {
            'name': 'get_weather',
            'description': 'Get the current temperature in Celsius for a city.',
            'parameters': {
                'type': 'object',
                'properties': {
                    'city': {'type': 'string', 'description': 'City name, for example Paris'},
                },
                'required': ['city'],
            },
        },
    }
]

messages = [{'role': 'user', 'content': 'What is the weather in Paris right now?'}]

# Step 1: the model decides whether to call a function
first = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
message = first.choices[0].message

if not message.tool_calls:
    print(message.content)
    raise SystemExit

# Step 2: run every requested call and send the results back
messages.append(message)
for call in message.tool_calls:
    args = json.loads(call.function.arguments)
    result = FUNCTIONS[call.function.name](**args)
    messages.append({
        'role': 'tool',
        'tool_call_id': call.id,
        'content': json.dumps(result),
    })

final = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
print(final.choices[0].message.content)

От трёх деталей зависит, заработает ли цикл:

  • call.function.arguments — это строка с JSON, а не словарь. Разберите её через json.loads.
  • Каждое сообщение role: tool должно содержать tool_call_id вызова, на который оно отвечает, то есть call.id.
  • content сообщения-инструмента — строка. Для структурированных результатов используйте json.dumps.

Что вы увидите#

Если после первого запроса напечатать поля, они будут выглядеть так (id вызова при каждом запуске разный):

text
finish_reason: tool_calls
call.id: call_abc123
call.function.name: get_weather
call.function.arguments: {"city": "Paris"}

Затем скрипт печатает итоговый ответ — обычное предложение вроде «Сейчас в Париже 14 °C». Формулировка от запуска к запуску меняется, а число берётся из вашей функции.

Несколько вызовов и длинные циклы#

Модель может вернуть больше одного вызова в одном ответе, например если спросить о погоде в двух городах. Поэтому в примере цикл идёт по message.tool_calls, и на каждый вызов отвечает сообщение с его собственным id. Руководство OpenAI по function calling описывает то же поведение: при tool_choice по умолчанию (auto) модель может вызвать ноль, одну или несколько функций (документация OpenAI, проверено 22 октября 2026 года; у страницы нет даты обновления, и она описывает новую форму Responses API, а в этой статье используется форма Chat Completions для адреса /v1/chat/completions).

Агенты повторяют два шага, пока модель не перестанет просить инструменты. Оберните запросы в цикл и всегда задавайте предел шагов:

python
for _ in range(5):  # safety limit against endless loops
    response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
    message = response.choices[0].message
    if not message.tool_calls:
        print(message.content)
        break
    messages.append(message)
    for call in message.tool_calls:
        args = json.loads(call.function.arguments)
        result = FUNCTIONS[call.function.name](**args)
        messages.append({'role': 'tool', 'tool_call_id': call.id, 'content': json.dumps(result)})

Если хотите выводить ответ по мере генерации, совместите это со стримингом в Python.

Почему MiniMax-M2.7 подходит для циклов с инструментами#

Цикл с инструментами отправляет много запросов с растущей историей. Для такого сценария в CheapTokens есть MiniMax-M2.7: контекст 180K, до 8 192 токенов в ответе, одна цена в любое время суток без пиковых часов и один баланс на все модели. В сумме, с учётом входа и выхода, модель дешевле референсной цены OpenRouter (сравнение цен CheapTokens, проверено 31 октября 2026 года).

Актуальные цены за 1M токенов: вход $0.05, выход $0.15. Для примера нагрузки (это не замер): двухзапросный цикл выше при примерно 2 700 входных и 350 выходных токенах в сумме стоит $0.00019. Полный список цен — на странице цен.

Чтобы попробовать другую модель, поменяйте только id; список id — на странице моделей.

Если не работает#

  • message.tool_calls равен None. Модель ответила текстом. Сделайте description функции конкретнее и проверьте, что вопросу действительно нужна эта функция.
  • json.JSONDecodeError на arguments. Модель выдала некорректный JSON. Оберните json.loads в try/except и верните текст ошибки как результат инструмента, чтобы модель повторила вызов с исправленными аргументами.
  • Второй запрос падает или модель игнорирует результат. Проверьте, что на каждый вызов приходится ровно одно сообщение role: tool с совпадающим tool_call_id и что сообщение ассистента с tool_calls стоит в истории перед ними.
  • KeyError в FUNCTIONS[...]. Модель назвала функцию, которую вы не зарегистрировали. Верните короткую строку с ошибкой как результат инструмента, а не роняйте программу.
  • Ответ обрывается. Посмотрите finish_reason: значение length означает, что достигнут лимит токенов ответа. См. почему ответ ИИ обрывается на полуслове.
  • 401 или ошибки авторизации. Проверьте, что ключ лежит в CHEAPTOKENS_API_KEY, а базовый адрес заканчивается на /v1.

Что дальше#

Замените заглушку настоящей функцией: запросом к базе, поиском или калькулятором. Добавляйте записи в tools и в FUNCTIONS и делайте результат каждой функции коротким: он попадает в историю при каждом следующем запросе. Чтобы получить ключ и сделать первый вызов, откройте документацию CheapTokens.