Skip to content

Serve tool-using OpenAI deployments through the stateless Responses API - #117

Merged
nlundee merged 5 commits into
mainfrom
feat/openai-responses
Oct 29, 2026
Merged

nlundee merged 5 commits into
mainfrom
feat/openai-responses

Conversation

@bdb-dd

@bdb-dd bdb-dd commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

What

OpenAI deployments can be served through the stateless Responses API (/v1/responses) behind the same ModelPort contract (chat, chatStream). That includes tools, tool results, streaming, JSON schema, usage and reasoning. ADR 0040 describes it.

  • Selection. The openai-chat endpoint registration stays as it is. A per-model options.api chooses the API:
    • responses uses the Responses API;
    • chat-completions pins the existing API;
    • left out, a deployment starts on Chat Completions. On the specific 400 that names the deployment and asks for /v1/responses for function tools, it retries the request through Responses and remembers that per deployment and endpoint.
      Discovered deployments therefore work without hand configuration. The choice is visible as transport: { api, source: default | configured | learned } in the model description, the operator listing and OpenAPI.
  • Mapping:
    • the full history goes in input, and system messages become developer messages;
    • tool calls become function_call items keyed by call_id, and tool results become function_call_output;
    • tools become {type: 'function', strict: false};
    • reasoning: { effort }, max_output_tokens;
    • JSON schema becomes text.format;
    • temperature refusals are learned per API and deployment.
      store: false, and no previous_response_id: the platform owns the history. Encrypted reasoning content is requested and replayed through the existing opaque continuation, with no strategy contract change. Usage maps input, output and cached tokens.
  • Streaming maps output-text deltas, function-call item and argument events (assembled by output index, with done snapshots winning), final usage, and the completed and incomplete terminal events. A failed or truncated stream never yields a successful result.

Why

Foundry's gpt-6.1-sol refuses function tools on Chat Completions with every reasoning_effort it accepts (none is rejected too), and answers: "To use function tools, use /v1/responses or set reasoning_effort to 'none'." Every agent-loop or tool-using run on it failed.

Tests

  • responses.test.ts (fake HTTP) and the port-model contract suite for both APIs cover:
    • a full tool round trip;
    • reasoning continuation and effort;
    • JSON schema;
    • cached usage;
    • streaming call assembly;
    • the exact-refusal switch, remembered per deployment;
    • explicit pinning;
    • Chat Completions models unchanged.
  • 10 sabotage legs, each caught:
    • storage on;
    • the tool-output call id broken;
    • reasoning replay dropped;
    • effort changed;
    • the schema format broken;
    • cached tokens zeroed;
    • argument overwrite instead of concatenation;
    • a truncated stream accepted;
    • the learned switch off;
    • pinning ignored.
  • Verified live on Azure AI Foundry with a discovered gpt-6.1-sol. The first tool request learned the switch (transport: {api: responses, source: learned}) and returned the tool call. The tool-result round trip answered correctly, non-streaming and streaming, with usage reported.
  • 150 tests pass across providers-openai, port-model, plugins and the API model and schema suites. Typecheck, lint, format and boundaries are clean.

Draft, for review.

Independent verification

A separate verifier found two issues. Assistant phases and output order were lost when history was replayed. And a cancelled stream could surface as unavailable, or as a metered success. Both are fixed with regression tests. Re-verified live on Foundry with gpt-6.1-sol after the fixes, non-streaming and streaming.

@nlundee nlundee left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bdb-dd I'll take this one over too and get it in, unless you'd rather finish it yourself.

The approach is right: one openai-chat registration, options.api to pin, and a learned switch only on the exact refusal, so discovered deployments just work. store: false with the full history, reasoning carried in the existing continuation, and the switch kept per endpoint connection. I merged it with today's main (22 commits ahead): the only conflict is the ADR index, and the provider, port-model, plugins and model-admin tests (124) pass with tsc -b clean.

What I'll change:

  1. ADR number. 0040 went to Langfuse (#100), and 0041 to 0043 are taken by #179, #125 and #185, so this becomes 0044, with the links in docs/providers.md and the index.
  2. Where the reasoning lives. One line in the ADR that the encrypted reasoning in continuation stays in the run's messages and is never written to the task's events or the database.
  3. ADR wording. Rules in present tense like the other records ("The provider sends", not "Keep"/"Add"), and drop "The orchestrator verifies the deployment live before release", which doesn't match how we release.
  4. Rebase, mark it ready, and smoke test it live on Foundry with gpt-6.1-sol (a tool round trip, streaming and non-streaming) before merging.

@nlundee
nlundee force-pushed the feat/openai-responses branch from d3dfe63 to 37c5ae3 Compare October 29, 2026 13:11
@nlundee
nlundee marked this pull request as ready for review October 29, 2026 13:12
bdb-dd and others added 5 commits October 29, 2026 14:23
Map full model history, tools, reasoning continuation, schemas and streaming through the Responses API. Learn the specific Foundry tools/reasoning refusal per deployment and expose transport selection to operators.

Verify both APIs against the model port contract, 150 focused tests and ten sabotage mutations. Document catalog options and ADR 0039.
Replay ordered Responses output through the opaque continuation while accepting older reasoning-only continuations. Cover streamed and non-streamed tool turns and document the replay contract.
Honor the abort signal when the SDK ends a stream silently and before yielding terminal chunks. Cover cancellation after both streamed text and terminal-snapshot text without successful metering.
0040 went to the Langfuse record, and 0041 to 0043 are taken by open pull
requests. The record now states its rules in the present tense, says that
encrypted reasoning stays in the run's messages and is never stored, names
gpt-5.6-sol, the deployment that refuses tools on Chat Completions on
Digdir's Foundry resource, and drops the release step that does not match
how we release.
@nlundee
nlundee force-pushed the feat/openai-responses branch from 37c5ae3 to 552cb3b Compare October 29, 2026 13:24
@nlundee
nlundee merged commit 8a62b1b into main Oct 29, 2026
13 checks passed
@nlundee
nlundee deleted the feat/openai-responses branch October 29, 2026 13:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants