Skip to content

openai provider sends max_tokens/temperature to GPT-5/o-series reasoning models → 400 #430

Description

@initializ-mk

Summary

The OpenAI provider unconditionally sends max_tokens (and temperature) on the /v1/chat/completions request. OpenAI's reasoning-model families (GPT-5, o1, o3, o4-…) reject max_tokens — they require max_completion_tokens, and they also reject a non-default temperature. So any agent that sets a token cap while running one of these models gets a hard HTTP 400 and the turn fails.

Observed live (initializ platform, model gpt-5.4, direct OpenAI):

HTTP 400
{"error":{"message":"Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead.","type":"invalid_request_error","param":"max_tokens","code":"unsupported_parameter"}}

Verified the same key/URL/model succeed once the parameter is corrected:

  • gpt-5.4 + max_completion_tokens200
  • gpt-5.4 + max_tokens400 (above)
  • gpt-4o + max_tokens200 (non-reasoning models are unaffected)

Why it mostly hasn't bitten yet

openaiRequest.MaxTokens is omitempty (forge-core/llm/providers/openai.go:168), so an agent that never sets a token cap omits the field and slips through. The failure surfaces only when ChatRequest.MaxTokens > 0 and the model is a reasoning model — but that combination is a hard failure, not a degradation.

Where it is

forge-core/llm/providers/openai.go:

  • openaiRequest struct — Temperature *float64 json:"temperature,omitempty", MaxTokens int json:"max_tokens,omitempty" (lines ~167-168).
  • Mapping from ChatRequest (lines ~215-220):
    r := openaiRequest{
        ...
        Temperature: req.Temperature,
        MaxTokens:   req.MaxTokens,
    }
  • No reasoning-model awareness exists anywhere in forge-core/llm (grep for max_completion_tokens / o1- / gpt-5 returns nothing).

Proposed fix

  1. Add a max_completion_tokens field to openaiRequest (json:"max_completion_tokens,omitempty").
  2. Detect the reasoning-model families by model-id prefix (conservative, documented): gpt-5*, o1*, o3*, o4*. Consider a small helper isReasoningModel(model string) bool.
  3. When the target model is a reasoning model and req.MaxTokens > 0, serialize it as max_completion_tokens and leave max_tokens unset; otherwise keep today's max_tokens behavior byte-for-byte.
  4. For reasoning models, omit temperature (they only accept the default 1; a non-default value 400s the same way). Non-reasoning path unchanged.
  5. Apply the same treatment on the streaming path if it shares the request builder.

Notes:

  • Detection is by the resolved model id, so custom base_url gateways that pass an OpenAI-family model id still work.
  • Keep it additive and prefix-scoped so non-reasoning models are unaffected (regression-safe).

Acceptance criteria

  • ChatRequest{MaxTokens: N} to gpt-5*/o1*/o3*/o4* serializes max_completion_tokens: N and omits max_tokens.
  • temperature is omitted for those models even if ChatRequest.Temperature is set.
  • Non-reasoning models (e.g. gpt-4o) serialize max_tokens + temperature exactly as before.
  • Unit tests assert the serialized request body for a reasoning model and a non-reasoning model (mock transport capturing the payload) — both non-streaming and streaming.
  • docs note on reasoning-model parameter handling if there's a provider reference page.

References

  • forge-core/llm/providers/openai.go (request struct + mapping)
  • forge-core/llm/types.goChatRequest.MaxTokens / .Temperature
  • OpenAI: reasoning models require max_completion_tokens and default temperature.

Found while debugging a skill-builder failure on the initializ platform — the platform's agent-builder hit this same 400 against gpt-5.4. No code changed in this issue; spec + repro only.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or requestforge-coreAffects the forge-core library (runtime, security, types, llm, mcp, auth)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions