OpenAI: Responses API и function_call_output
Для новых проектов OpenAI рекомендует Responses API, и адаптер строится вокруг его модели данных. Ответ - это массив типизированных output items; tool calls находятся среди них, аргументы приходят JSON-строкой, а результат возвращается как function_call_output с тем же call_id. Понять это один раз проще, чем потом отлаживать "почему модель не видит результат".
Начинается адаптер с клиента и перевода нашего определения tool в форму Responses.
import OpenAI from "openai";
const client = new OpenAI();
const model = process.env.OPENAI_MODEL ?? "gpt-5.6";
type OpenAIState = {
previousResponseId: string;
instructions: string;
tools: ToolDefinition[];
};
function toOpenAITool(tool: ToolDefinition) {
return {
type: "function" as const,
name: tool.name,
description: tool.description,
parameters: tool.parameters,
strict: true
};
}Первый ход отправляет сообщение и tools, а состояние продолжения - это response.id.
async function start(input: AgentInput): Promise<ProviderTurn> {
const response = await client.responses.create({
model,
instructions: input.instructions,
input: input.message,
tools: input.tools.map(toOpenAITool),
parallel_tool_calls: true
});
return fromOpenAIResponse(response, {
previousResponseId: response.id,
instructions: input.instructions,
tools: input.tools
});
}Продолжение после tools возвращает результаты как function_call_output по previous_response_id.
async function resume(
rawState: unknown,
results: ToolResult[]
): Promise<ProviderTurn> {
const state = openAIStateSchema.parse(rawState);
const response = await client.responses.create({
model,
previous_response_id: state.previousResponseId,
instructions: state.instructions,
tools: state.tools.map(toOpenAITool),
input: results.map(function toOutput(result) {
return {
type: "function_call_output" as const,
call_id: result.callId,
output: JSON.stringify({
ok: !result.isError,
value: result.output
})
};
})
});
return fromOpenAIResponse(response, {
...state,
previousResponseId: response.id
});
}И нормализация ответа сводит output items к нашему единому ProviderTurn.
function fromOpenAIResponse(response: any, state: OpenAIState): ProviderTurn {
const calls = response.output
.filter(function isCall(item: any) {
return item.type === "function_call";
})
.map(function normalize(item: any): ToolCall {
return {
id: item.call_id,
name: item.name,
arguments: JSON.parse(item.arguments)
};
});
if (calls.length > 0) return { kind: "tool_calls", calls, state };
return { kind: "text", text: response.output_text, state };
}Одна деталь про хранение, о которую легко споткнуться.
Storage и privacy. Responses хранятся по умолчанию. Если политика требует store: false, используйте stateless continuation и передавайте нужные output items, включая reasoning items, вместе с tool outputs. Не смешивайте два режима случайно.OpenAI можно взять и на прямом API, и на готовом harness. Разберём, когда второй выгоднее.