Function Calling в GPT-6 Astra — это движок AI-агентов: модель решает, какой инструмент вызвать и с какими аргументами; ваш runtime исполняет побочный эффект; результат возвращается модели для продолжения рассуждений. Когда этот цикл реализован правильно, Astra может самостоятельно запрашивать API, читать базы данных и выполнять пользовательскую логику. Когда цикл ошибочен — он зависает, либо права инструментов выходят за допустимые рамки. Что такое Function Calling описано в сравнении протоколов; как получить API-ключ — в туториале по API. В этой статье разбираются три шаблона инструментов, переводящих агента из состояния «работает» в «безопасно деплоится».
Шаг 1: Определить схему инструмента
Схема инструмента — JSON-объект, который сообщает модели имя инструмента, что он делает и какие параметры принимает. Пример: инструмент, вызывающий HTTP API погоды.
import json
from openai import OpenAI
client = OpenAI()
tools = [
{
"type": "function",
"name": "get_weather",
"description": "Get current weather for a city via HTTP API.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name, e.g. Tokyo, London, Shanghai",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit. Default celsius.",
},
},
"required": ["city"],
"additionalProperties": False,
},
}
]
description должен описывать, что инструмент делает — не просто повторять его имя. Модель читает это поле, чтобы решить, вызывать ли инструмент. Обязательные поля перечисляются в required; необязательные — в properties, но не в required. additionalProperties: false снижает вероятность передачи неожиданных ключей. Несколько инструментов кладут в один список; модель выбирает один или несколько. При параллельных вызовах цикл должен обработать все результаты перед продолжением.
Шаг 2: Полный tool loop
Паттерн: отправить запрос → проверить function_call в output → выполнить → добавить результаты к input → отправить снова, пока не останется новых function_call. Цикл должен быть while True — одиночный if не получит финальный текстовый ответ модели.
import json
import requests
from openai import OpenAI
client = OpenAI()
def call_weather_api(city: str, unit: str = "celsius") -> dict:
"""Replace with your real HTTP endpoint."""
url = "https://api.example.com/weather"
r = requests.get(url, params={"city": city, "unit": unit}, timeout=5)
r.raise_for_status()
return r.json() # e.g. {"city": "Tokyo", "temp": 22, "condition": "sunny"}
# Initial request — model may call tools
response = client.responses.create(
model="gpt-6-astra",
input="What is the weather in Tokyo right now?",
tools=tools,
reasoning={"effort": "low"},
)
# Tool loop: keep going until model stops calling tools
while True:
tool_calls = [o for o in response.output if o.type == "function_call"]
if not tool_calls:
break # model is done — final answer is in response.output_text
tool_results = []
for call in tool_calls:
args = json.loads(call.arguments)
if call.name == "get_weather":
result = call_weather_api(**args)
else:
result = {"error": f"Unknown tool: {call.name}"}
tool_results.append({
"type": "function_call_output",
"call_id": call.call_id,
"output": json.dumps(result, ensure_ascii=False),
})
# Append history + results and continue
response = client.responses.create(
model="gpt-6-astra",
input=response.output + tool_results,
tools=tools,
reasoning={"effort": "low"},
)
print(response.output_text)
Детали реализации. ① response.output — список смешанных типов; фильтруем только type == "function_call". ② При продолжении передавать response.output + tool_results как input; только tool_results теряет историю разговора. ③ call_id в результате должен совпадать с call_id вызова. ④ Сериализовать возвращаемые значения через json.dumps — не передавать сырой Python-dict.
name по allowlist, проверять типы аргументов, добавлять timeout и allowlist доменов для HTTP-инструментов. DB-инструменты выполняют только предопределённые SQL-запросы — никогда не конкатенировать вывод модели в SQL. Инструменты, пишущие на диск или запускающие shell-команды, запускать на удалённом, одноразовом Mac.
Шаг 3: Read-only инструмент для базы данных
Три требования к безопасному DB-инструменту: соединение только на чтение (без INSERT, UPDATE, DELETE), параметризованные запросы (без SQL injection), SQL allowlist (без произвольного SQL).
import sqlite3
from typing import Any
# Only these parameterized queries can be executed — no raw user SQL
ALLOWED_QUERIES: dict[str, str] = {
"get_order": "SELECT id, status, total, created_at FROM orders WHERE id = ?",
"list_orders": "SELECT id, status, total FROM orders WHERE user_id = ? LIMIT 20",
}
def query_db(query_name: str, params: list[Any]) -> list[dict]:
"""Execute an allowlisted, parameterized read-only query.
Never accepts raw SQL strings from the model.
"""
sql = ALLOWED_QUERIES.get(query_name)
if sql is None:
raise ValueError(f"Query '{query_name}' not in allowlist")
con = sqlite3.connect("orders.db", check_same_thread=False)
con.row_factory = sqlite3.Row
try:
rows = con.execute(sql, params).fetchall()
return [dict(r) for r in rows]
finally:
con.close()
# Schema exposed to the model
db_tool = {
"type": "function",
"name": "query_db",
"description": "Query the orders DB. Only read-only allowlisted queries.",
"parameters": {
"type": "object",
"properties": {
"query_name": {
"type": "string",
"enum": list(ALLOWED_QUERIES.keys()),
"description": "Name of the allowlisted query",
},
"params": {
"type": "array",
"items": {"type": ["string", "number"]},
"description": "Positional parameters for the query",
},
},
"required": ["query_name", "params"],
},
}
В продакшне добавить: пул соединений, таймаут запроса, жёсткий лимит строк в каждом SQL, маскировку чувствительных полей. Результат, возвращаемый модели, должен быть резюме, а не сырым дампом таблицы — каждая строка потребляет токены ввода в следующем запросе.
Функциональные vs хостинговые инструменты
| Измерение | Функциональные инструменты (type: function) | Хостинговые инструменты (OpenAI) |
|---|---|---|
| Место выполнения | Ваш runtime (локально / удалённый Mac) | Инфраструктура OpenAI |
| Лучше для | Приватные API, внутренние БД, кастомная логика | Веб-поиск, code interpreter, shell, Computer Use |
| Усилия на разработку | Схема + функция выполнения + цикл | Указать имя инструмента, OpenAI выполнит |
| Оплата | Только токены | Токены + поштучная плата за инструмент |
| Требования к изоляции | Пишущие инструменты требуют изолированной среды | OpenAI управляет sandbox; данные — ваша ответственность |
Оба типа можно смешивать в одном запросе. Результаты хостинговых инструментов автоматически попадают в историю разговора — ручная подача не нужна. Результаты функциональных инструментов нужно подавать вручную через function_call_output.
Матрица решений
| Если нужно | Использовать | Почему |
|---|---|---|
| Вызвать внутренний REST API | Функциональный инструмент | Приватная сеть недоступна из инфры OpenAI |
| Запросить внутреннюю БД (только чтение) | Функциональный инструмент + SQL allowlist | Приватные данные; права доступа управляются самостоятельно |
| Поиск в публичном вебе в реальном времени | Хостинговый web_search_preview | Обход и ранжирование — задача OpenAI |
| Модель запускает Python | Хостинговый code_interpreter | Выполнение в sandbox OpenAI, без локальных ресурсов |
| Модель управляет десктопом/браузером | Хостинговый computer_use + изолированный Mac | Нужен видимый рабочий стол; изоляция от случайных изменений |
| Shell-команды с записью на диск | Функциональный инструмент + удалённый Mac | Права на запись только на одноразовом хосте |
Рекомендуемые конфигурации
A — Одиночный разработчик/прототип: локальная машина, функциональные инструменты для HTTP API (без записи на диск). Написать схему, стабилизировать цикл, логировать call_id. Переходить на несколько инструментов только после стабильной работы одного.
B — Небольшая команда: функциональные инструменты за внутренними сервисами с token-авторизацией. DB-инструмент: только чтение, пул соединений, таймаут. Хостинговые инструменты активировать по типу задачи. Логику выполнения покрывать unit-тестами.
C — Enterprise: все вызовы инструментов через API-шлюз с аудит-логом. БД — read-only реплика + row-level security. Computer Use — на одноразовых изолированных Mac-нодах. Учётные данные и условия доставки — в центре поддержки; стоимость нодов — на странице цен.
Распространённые ошибки
- Цикл выполняется один раз (if вместо while): финальный ответ модели никогда не приходит.
- Результаты подаются без истории: передача только tool_results в input лишает модель контекста.
- Вывод модели напрямую конкатенируется в SQL/shell: allowlist + параметризованные запросы устраняют класс атак.
- Нет таймаута у HTTP-инструментов: зависший downstream-сервис замораживает всю сессию агента.
- Инструменты возвращают большие дампы: строки потребляют токены и могут запустить апселл 272K.
План действий: 7 шагов
- Сначала убедиться, что текстовый путь Responses работает (см. туториал по API).
- Написать первую схему инструмента. description — глагольная фраза, required заполнен, additionalProperties: false.
- Реализовать функцию выполнения. Для HTTP — timeout. Для DB — allowlist + параметризованные запросы.
- Отправить Responses-запрос с параметром tools; распечатать response.output и убедиться в наличии function_call.
- Реализовать цикл while True. Сопоставить call_id, подать function_call_output, включить историю в input.
- При необходимости добавить хостинговые инструменты (web_search, code_interpreter). Их результаты не нужно подавать вручную.
- Инструменты с записью на диск, shell-командами или управлением браузером перевести на изолированный удалённый Mac; уничтожить workspace по окончании сессии.
FAQ
Обязателен ли Responses API для Function Calling?
Да. Вызовы инструментов, хостинговые инструменты и async tool loop требуют Responses API.
Сколько инструментов можно определить на запрос?
Нет строгого малого лимита, но больше инструментов = больше токенов промпта. Передавать только нужные для текущей задачи.
Как предотвратить SQL-инъекцию?
Параметризованные запросы с ? в сочетании с SQL allowlist. Никогда не конкатенировать вывод модели в SQL.
Когда функциональный, а когда хостинговый инструмент?
Приватный API, внутренняя БД, кастомная логика → функциональный. Веб-поиск, code interpreter, Computer Use → хостинговый.
Можно ли стримить финальный ответ после tool calls?
Да. Сначала стабилизировать нестримящий цикл, затем добавить stream=True.
Заключение
Три основы Function Calling в GPT-6 Astra: схема с ясным description, цикл while, подающий полную историю с каждым результатом, и DB-инструмент на allowlist с параметризованными запросами — пишущие инструменты на изолированном Mac. Удалённые Mac-ноды на страницах аренды и цен; вопросы по аккаунту — в центре поддержки.
Читать дальше
- Как вызвать GPT-6 Astra API: Python от ключа до первого запроса →
- Что такое Function Calling: сравнение OpenAI, Gemini, Claude →
- Что такое GPT-6 Astra: релиз, цена, агенты →
Инструменты вызывают реальные API? Запускайте на изолированном Cloud Mac
Вызывать публичный HTTP-эндпоинт локально — нормально. Инструмент, пишущий на диск, редактирующий репозиторий или управляющий браузером, не должен делить повседневный рабочий стол. Удалённые Mac-ноды изолируют рабочее пространство на сессию.