Skip to content

ai: split OpenAI Responses and compatible providers - #475

Open
martinhoyer wants to merge 6 commits into
sashiko-dev:mainfrom
martinhoyer:openai-responses-provider
Open

ai: split OpenAI Responses and compatible providers#475
martinhoyer wants to merge 6 commits into
sashiko-dev:mainfrom
martinhoyer:openai-responses-provider

Conversation

@martinhoyer

Copy link
Copy Markdown

Summary

Split the existing OpenAI integration into two distinct providers:

  • openai uses OpenAI’s native /v1/responses endpoint.
  • openai-compatible retains the Chat Completions implementation.

Motivation

Using OpenAI API with gpt-5.6 models currently fails on:

{
  "error": {
    "message": "Unsupported value: 'temperature' does not support 0.0 with this model. Only the default (1) value is supported.",
    "type": "invalid_request_error",
    "param": "temperature",
    "code": "unsupported_value"
  }
}

Shoehorning special temperature handling into the existing compatible provider did not solve the underlying problem. Modern OpenAI reasoning and tool-calling workflows ultimately need the Responses API, while third-party OpenAI-compatible services generally continue to expose Chat Completions.
Splitting openai and openai-compatible therefore seemed like the most practical and maintainable approach.

Changes

  • Add a dedicated OpenAI Responses API client.
  • Add [ai.openai] configuration, separate from [ai.openai_compat].
  • Clearly reject legacy-only provider = "openai" configurations with migration guidance.
  • Preserve and replay complete Responses output items, including encrypted reasoning state and original function-call identifiers.
  • Classify retryable OpenAI errors correctly.
  • Handle JSON mode requirements and cached-input token accounting.
  • Update settings, examples, provider documentation, and design documentation.

Not sure if there should be separate design doc as well.

Add a dedicated [ai.openai] settings table for the official OpenAI
Responses API, separate from [ai.openai_compat], which remains tied to
Chat Completions compatible services.

Expose optional base URL, context window, output token limit, and
reasoning effort settings while allowing the provider to select
model-specific defaults when values are omitted.

Signed-off-by: Martin Hoyer <mhoyer@redhat.com>
Introduce a dedicated client for OpenAI's /v1/responses endpoint and
select it for provider = "openai", while keeping the existing Chat
Completions client behind provider = "openai-compatible".

Preserve every response output item as opaque, versioned continuation
state so reasoning data and server-issued function call identifiers are
replayed exactly across tool turns. Keep this state out of persisted
review logs and invalidate older cache entries that cannot provide
lossless continuation.

Also classify Responses API retryable errors, account for cached input
tokens, satisfy JSON mode's prompt requirement, use current GPT-5.6
context defaults, and reject legacy-only [ai.openai_compat]
configurations with an actionable migration error.

Signed-off-by: Martin Hoyer <mhoyer@redhat.com>
Document the dedicated OpenAI Responses provider, its configuration,
and the migration from legacy [ai.openai_compat] settings. Update the
checked-in settings and standalone example to use the new [ai.openai]
table.

Describe lossless output-item replay, JSON mode, cached-token
accounting, and function call identifier handling. Refresh GPT-5.6
reasoning effort values and context limits to match the current API
documentation.

Signed-off-by: Martin Hoyer <mhoyer@redhat.com>
@rgushchin

Copy link
Copy Markdown
Member

Note

This review was generated with the assistance of an AI tool.

Overall looks great and well-architected! A few quick observations and potential regression risks:

  1. Default max_tokens (4,096) may truncate reasoning:
    In src/ai/mod.rs, omitting [ai.openai].max_tokens defaults to 4,096. On the Responses API, max_output_tokens bounds the sum of internal reasoning tokens and visible output. Reasoning models like gpt-5.6 with medium effort can easily exhaust 4,096 tokens during the reasoning phase alone, causing premature incomplete (truncated) responses. Consider defaulting to 65536 for the openai provider.

  2. Handle status: "failed" in translate_ai_response:
    translate_ai_response checks resp.status == "incomplete", but if OpenAI returns a 200 OK with status: "failed" and an error object, it currently returns Ok(AiResponse) with empty content and tool calls. Propagating the error message directly would prevent confusing downstream ValidationErrors.

  3. Global cache invalidation:
    Bumping CACHE_KEY_VERSION in src/ai/cache.rs to "sashiko-ai-cache-v2" invalidates cached responses for all providers (Gemini, Claude, Bedrock, Ollama, etc.). Users running benchmarks or workloads with response_cache = true will experience a full cold-cache restart across all backends.

  4. base_url doc & normalization:
    docs/configuration.md lists [ai.openai].base_url as "model-derived", but default_base_url() statically points to https://api.openai.com/v1/responses. Also, OpenAiClient does not normalize paths (e.g. appending /responses), so custom proxy URLs like http://host/v1 without /responses will fail.

  5. temperature suppression:
    translate_ai_request unconditionally passes temperature: None. This fixes gpt-5.6 rejecting 0.0, but prevents tuning temperature for non-reasoning models (like gpt-4o) or future models that support it on /v1/responses.

@martinhoyer

Copy link
Copy Markdown
Author

Consider defaulting to 65536 for the openai provider.

Thanks, already noticed 4096 is too low. Trying 16k and it was quite enough for sol with xhigh reasoning for a patch review. Wouldn't 65k be an overkill?

…actory

Raise the default output budget to 16K and include the provider-reported
reason and output-token usage when a response is incomplete.

Treat failed, nonterminal, and contradictory response states as typed
provider errors, while tolerating null usage accounting in error responses.

Normalize recognized API roots to the Responses endpoint and forward
sampling temperature outside known GPT-5 and o-series reasoning families.

Signed-off-by: Martin Hoyer <mhoyer@redhat.com>
…actory

Keep the legacy request hash for providers whose response representation
did not change, preserving their existing cache entries.

Apply the versioned salt only to the dedicated OpenAI Responses provider,
where older cached tool responses lack lossless continuation metadata. Add
coverage for exact legacy hashes and provider-specific format selection.

Signed-off-by: Martin Hoyer <mhoyer@redhat.com>
…plit

Set the recommended and documented output budget to 16K, explaining that
reasoning and visible output share the Responses API limit.

Document the static default endpoint, proxy-root normalization, model-aware
temperature handling, failed-response errors, incomplete diagnostics, and
provider-scoped cache compatibility behavior.

Signed-off-by: Martin Hoyer <mhoyer@redhat.com>
@martinhoyer

Copy link
Copy Markdown
Author

@rgushchin adressing feedback in fixup commits, thanks.

  • Raised the default output budget from 4K to 16K, validated with Sol/xhigh, and now log incomplete reasons and token usage.
  • Surface failed Responses as provider errors instead of empty responses.
  • Scoped cache invalidation to OpenAI Responses only.
  • Corrected base_url documentation and added /responses normalization.
  • Forward temperature for supported models while omitting it for known reasoning models.

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.

2 participants