В этом туториале мы пройдём путь от простой tool-enabled системы до управляемой multi-agent orchestration. Главное отличие от старых туториалов: мы не будем начинать с текстового “ReAct-театра” и вручную парсить Thought/Action. В 2026 разумнее строить agent stack на актуальных primitives:
Responses API;strict function tools;conversation state;OpenAI Agents SDK для handoffs и orchestration;Начинаем не с “полноценного автономного агента”, а с полезного workflow: модель получает задачу, при необходимости вызывает tool и возвращает ответ.
Почему это хороший старт:
get_ticket_status;search_policy;from openai import OpenAI
import json
client = OpenAI()
TOOLS = [
{
"type": "function",
"name": "get_ticket_status",
"description": "Вернуть текущий статус тикета поддержки.",
"parameters": {
"type": "object",
"properties": {
"ticket_id": {"type": "string"}
},
"required": ["ticket_id"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "search_policy",
"description": "Найти внутреннюю policy по указанной теме.",
"parameters": {
"type": "object",
"properties": {
"topic": {"type": "string"}
},
"required": ["topic"],
"additionalProperties": False,
},
"strict": True,
},
]
def get_ticket_status(ticket_id: str) -> str:
return f"{ticket_id}: waiting for security review"
def search_policy(topic: str) -> str:
if "ticket" in topic.lower():
return "Можно сообщать клиенту статус, но не внутренние комментарии и персональные данные сотрудников."
return "Policy not found."
TOOL_IMPL = {
"get_ticket_status": get_ticket_status,
"search_policy": search_policy,
}
def run_once(user_input: str) -> str:
response = client.responses.create(
model="gpt-5.4",
input=user_input,
tools=TOOLS,
)
outputs = []
for item in response.output:
if item.type == "function_call":
tool_fn = TOOL_IMPL[item.name]
args = json.loads(item.arguments)
result = tool_fn(**args)
outputs.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": result,
})
if not outputs:
return response.output_text
followup = client.responses.create(
model="gpt-5.4",
previous_response_id=response.id,
input=outputs,
)
return followup.output_text
print(run_once("Проверь статус тикета INC-4421 и кратко ответь клиенту."))
Уже на этом шаге у нас не “чат-бот”, а управляемый tool-enabled workflow. Для многих внутренних support и ops задач этого уже достаточно.
Следующий шаг — не memory в широком смысле, а thread continuity. Система должна уметь продолжать работу между turns, не таща весь history вручную.
Самый простой вариант в OpenAI stack:
previous_response_id для продолжения цепочки;conversation_id / sessions, если нужен более явный managed state.from openai import OpenAI
client = OpenAI()
first = client.responses.create(
model="gpt-5.4",
input="Пользователь работает над запуском продукта Альфа. Запомни это в рамках текущего потока.",
)
second = client.responses.create(
model="gpt-5.4",
previous_response_id=first.id,
input="Подготовь короткий план задач на неделю.",
)
print(second.output_text)
Это ещё не long-term memory, но уже нормальная основа для multi-turn workflow и agent runs.
Теперь добавим production-friendly quality loop. Вместо расплывчатой “reflection” сделаем явный evaluator step:
def generate_answer(task: str) -> str:
response = client.responses.create(
model="gpt-5.4",
input=f"Реши задачу:\n{task}",
)
return response.output_text
def evaluate_answer(task: str, answer: str) -> dict:
response = client.responses.create(
model="gpt-5.4-mini",
input=(
"Проверь ответ по трём критериям: factuality, policy compliance, completeness.\n"
f"Задача:\n{task}\n\nОтвет:\n{answer}\n\n"
"Верни JSON вида {pass: bool, feedback: string}."
),
text={"format": {"type": "json_object"}},
)
return json.loads(response.output_text)
def generate_with_gate(task: str, max_rounds: int = 2) -> str:
answer = generate_answer(task)
for _ in range(max_rounds):
verdict = evaluate_answer(task, answer)
if verdict["pass"]:
return answer
answer = generate_answer(
task + "\n\nИсправь предыдущий ответ с учётом feedback:\n" + verdict["feedback"]
)
return answer
Это уже похоже на production pipeline:
Теперь, когда есть рабочий baseline, можно идти в multi-agent orchestration.
В current OpenAI Agents SDK это удобно делать через:
Agent;Runner;function_tool;handoffs.Пример triage + specialists:
from agents import Agent, Runner, function_tool
@function_tool
def get_ticket_status(ticket_id: str) -> str:
return f"{ticket_id}: waiting for security review"
billing_agent = Agent(
name="Billing specialist",
handoff_description="Отвечает на вопросы об оплате, счетах и возвратах.",
instructions="Ты специалист по billing support.",
)
technical_agent = Agent(
name="Technical specialist",
handoff_description="Отвечает на технические вопросы по продукту и тикетам.",
instructions="Ты технический специалист поддержки.",
tools=[get_ticket_status],
)
triage_agent = Agent(
name="Triage agent",
instructions="Маршрутизируй пользователя к правильному специалисту.",
handoffs=[billing_agent, technical_agent],
)
result = Runner.run_sync(
triage_agent,
"Проверь статус тикета INC-4421 и объясни клиенту, что происходит."
)
print(result.final_output)
print(result.last_agent.name)
Handoff полезен, когда specialist должен стать владельцем следующей части разговора. Если specialist решает только bounded subtask, часто лучше manager pattern или agent-as-tool.
Если вам нужен не provider-native runtime, а более явный graph control, тот же tutorial path можно собрать и через LangGraph:
То есть выбор уже не между “агент” и “не агент”, а между:
После этих четырёх шагов у вас есть:
И только после этого имеет смысл думать о более тяжёлых вещах: