Перейти к основному содержимому

Model I/O

После главы вы сможете спроектировать provider-neutral границу model call и не смешивать output модели с выполненным действием или durable state.

Минимальный контракт

На уровне HTTP model service принимает сериализуемый input и возвращает output. Для runtime полезнее более строгая граница:

interface ModelPort {
complete(request: ModelRequest): Promise<AgentDecision>;
}

type AgentDecision =
| {kind: 'final'; content: string}
| {kind: 'tool'; call: ToolCall};

Vendor adapter переводит этот контракт в Responses API, Messages API или локальный inference endpoint. Runtime не должен знать название SDK.

Что входит в request

Полезно различать:

  • instructions: устойчивые правила приложения;
  • conversation: реплики пользователя и видимые ответы;
  • goal: актуальная задача текущего run;
  • task state: структурированная версия мира;
  • tool definitions: capability, schema и effect metadata;
  • observations: результаты уже выполненных действий;
  • retrieved context: выбранные данные с provenance.

Порядок и точная иерархия ролей зависят от API. Не кодируйте policy только порядком строк в одном prompt: authorization должна существовать в обычном коде.

Почему строка END — слабый протокол

assistant: Готово. END

Проблемы:

  • пользовательский документ тоже может содержать END;
  • parser не знает, завершён ли ответ или только процитирован token;
  • невозможно выразить approval_required, budget_exhausted, cancelled;
  • tool arguments остаются невалидированным текстом.

Предпочтительнее discriminated union или native tool-call items. Structured output уменьшает двусмысленность, но всё равно требует runtime validation: output модели остаётся недоверенным вводом программы.

Messages прошлого turn

На второй вопрос модели обычно нужен не «весь массив размышлений», а новая проекция рабочего состояния:

Если provider хранит conversation server-side, он может сам присоединить предыдущие items. Если приложение stateless, нужные items передаются снова. В обоих случаях приложение отвечает за то, чтобы актуальные instructions, policy и domain state не исчезли.

Практическое правило: пользовательский ввод, на который ещё отвечают, должен быть доступен; старые tool payloads можно сжимать; скрытый chain-of-thought прошлого turn хранить как application memory не следует.

Reasoning boundary

Reasoning model может выполнять внутреннее вычисление до выдачи output. Это не тот же объект, что application events.

Храните:

  • final decision;
  • tool call и validated arguments;
  • policy result;
  • environment outcome;
  • краткое объяснение/summary, если оно нужно человеку;
  • token/latency/cost metadata.

Не делайте recovery зависимым от скрытого reasoning trace. Современные API могут переносить provider-specific reasoning items между вызовами, но это механизм inference continuity, не ваш audit log.

Streaming

Streaming меняет доставку output, но не обязан менять семантику шага.

Наивный вариант выполняет tool, как только в потоке встретилось похожее имя. Надёжный вариант:

  1. принимает typed stream events;
  2. собирает полный tool-call item;
  3. проверяет завершение item;
  4. валидирует schema;
  5. выполняет policy/approval;
  6. только затем вызывает effect.

Частичный текст можно показывать пользователю, но он не является committed final answer. UI должен различать in_progress, completed, incomplete, cancelled и failed.

Cancellation и timeout

Отмена model request не откатывает уже запущенный внешний tool. Нужны отдельные механизмы:

  • abort model/network call;
  • cooperative cancellation long-running tool;
  • deadline на каждый слой;
  • durable interrupt record;
  • compensation для уже завершённого effect.

Три стратегии stateful conversation

СтратегияВыигрышЦенаПлохой выбор, когда…
Передавать весь transcriptПростотаРост tokens, stale factsДиалог длинный или sensitive
Provider conversation IDМеньше orchestrationVendor lifecycle/retentionНужен portable/ZDR runtime
Собственный state + projectionКонтроль, portabilityStorage и context policyОдноразовый прототип

Helios и реальный аналог

Helios получает вопрос о доставке. Model adapter может быть любым, но AgentDecision остаётся одинаковым. Аналогично support-бот neobank должен отделять model output от ledger API: модель предлагает get_transaction, runtime выполняет чтение с tenant identity.

Лаборатория

ScriptedModel записывает два запроса. Проверьте, что второй содержит исходную цель, пользовательский вопрос и tool observation:

npm run test:course:pattern -- "preserving the user goal"

Self-check

  • Какие output variants поддерживает ваш adapter?
  • Где валидируется structured output?
  • Сохраняются ли instructions при переходе между turns?
  • Может ли partial stream запустить side effect?
  • Что именно отменяет cancellation?
  • Можно ли заменить provider, не переписав runtime?

Механика и источники

Механика API, проверено 2026-08-30: OpenAI Responses API принимает instructions/input/tools, поддерживает conversation или previous response linkage, streaming, background status и tool calls. Это vendor mapping, не core contract курса: Responses API reference.