Skip to content

fix(sdk/go): rewrite max_tokens to max_completion_tokens for newer OpenAI models#724

Merged
santoshkumarradha merged 8 commits into
Agent-Field:mainfrom
7vignesh:fix/441-max-tokens-rewrite
Jul 15, 2026
Merged

fix(sdk/go): rewrite max_tokens to max_completion_tokens for newer OpenAI models#724
santoshkumarradha merged 8 commits into
Agent-Field:mainfrom
7vignesh:fix/441-max-tokens-rewrite

Conversation

@7vignesh

@7vignesh 7vignesh commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

The Go SDK always emits max_tokens in API requests, but newer OpenAI models (o1, o3, gpt-4o, gpt-4.x) only accept max_completion_tokens and silently ignore max_tokens. This causes Go agents to lose their output-length
cap with no error. This PR adds a model-aware serialization pass that rewrites the parameter for affected models.

Closes #441

Type of Change

  • Bug fix

Test Plan

  • cd sdk/go && go test ./ai/ -v -run "TestNeedsMaxCompletion|TestMarshalRequest"
  • cd sdk/go && go build ./...
  • cd sdk/go && CGO_ENABLED=0 GOOS=linux go build ./...
  • golangci-lint run ./ai/... no new issues

Test Coverage

  • I ran tests for the surface(s) I changed locally.
  • New code paths are covered by tests in this PR (no bare additions).
  • If I removed code, I updated coverage-baseline.json in this PR only if
    the removal caused a legitimate regression and I called it out in the
    summary above.
  • The coverage gate check is green in CI before requesting review.

Checklist

Related Issues / PRs

Fixes #441

…enAI models (Agent-Field#441)

Newer OpenAI models (o1, o3, gpt-4o, gpt-4.x) dropped support for the
legacy max_tokens parameter in favor of max_completion_tokens. The Go
SDK was always emitting max_tokens, causing the output-length cap to be
silently ignored.

Changes:
- Add model-aware marshalRequest() that detects newer OpenAI models and
  rewrites max_tokens → max_completion_tokens at serialization time
- Detection uses model prefix matching (o1, o3, o4, gpt-4o, gpt-4.)
- Preserves backward compatibility: legacy models still use max_tokens
- Request-level model override takes precedence over client default
- Add 6 focused tests covering new models, legacy models, nil handling,
  and request-level model override

Closes Agent-Field#441
Copilot AI review requested due to automatic review settings July 6, 2026 18:27
@7vignesh
7vignesh requested review from a team and AbirAbbas as code owners July 6, 2026 18:27
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Performance

SDK Memory Δ Latency Δ Tests Status
Go 170 B -39% 0.65 µs -35%

✓ No regressions detected

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the Go SDK’s request serialization to avoid OpenAI silently ignoring max_tokens for newer model families by rewriting it to max_completion_tokens when applicable.

Changes:

  • Added model detection logic to decide when max_tokens must be rewritten to max_completion_tokens.
  • Routed request serialization in Client through a new rewrite-aware marshalRequest method.
  • Added unit tests covering model detection and JSON marshaling behavior for rewritten vs legacy models.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
sdk/go/ai/model_params.go Adds model/base-URL detection and rewrite-aware request marshaling to emit max_completion_tokens for newer OpenAI model families.
sdk/go/ai/model_params_test.go Adds tests validating model detection and ensuring marshaled JSON uses the correct token field.
sdk/go/ai/client.go Switches request serialization to c.marshalRequest(req) for both non-streaming and streaming calls.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread sdk/go/ai/model_params.go Outdated
Comment thread sdk/go/ai/model_params_test.go Outdated
Comment thread sdk/go/ai/model_params_test.go Outdated
Comment thread sdk/go/ai/model_params_test.go Outdated
Comment thread sdk/go/ai/model_params_test.go Outdated
Comment thread sdk/go/ai/model_params_test.go Outdated
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

📊 Coverage gate

Thresholds from .coverage-gate.toml: per-surface ≥ 84%, aggregate ≥ 85%, max per-surface regression ≤ 1.0 pp, max aggregate regression ≤ 0.50 pp.

Surface Current Baseline Δ
control-plane 86.90% 87.40% ↓ -0.50 pp 🟡
sdk-go 92.50% 92.00% ↑ +0.50 pp 🟢
sdk-python 93.76% 93.73% ↑ +0.03 pp 🟢
sdk-typescript 90.42% 90.42% → +0.00 pp 🟢
web-ui 84.84% 84.79% ↑ +0.05 pp 🟡
aggregate 85.60% 85.75% ↓ -0.15 pp 🟡

✅ Gate passed

No surface regressed past the allowed threshold and the aggregate stayed above the floor.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

📐 Patch coverage gate

Threshold: 80% on lines this PR touches vs origin/main (from .coverage-gate.toml:thresholds.min_patch).

Surface Touched lines Patch coverage Status
control-plane 0 ➖ no changes
sdk-go 58 96.00%
sdk-python 0 ➖ no changes
sdk-typescript 0 ➖ no changes
web-ui 0 ➖ no changes

✅ Patch gate passed

Every surface whose lines were touched by this PR has patch coverage at or above the threshold.

@AbirAbbas AbirAbbas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ran a multi-agent review over this (PR-AF on AgentField, findings hand-verified before posting). The fix itself is sound — model-gated rewrite, both request paths covered, shadow-struct trick is correct (outer fields at depth 0 win over the embedded alias). Two things worth addressing before merge, inline below.

Minor notes, no action needed: isOpenAICompatible and the tests never assert the wire format on the tool-calling/streaming paths (only the non-streaming one), and the anthropic/cohere checks are substring matches on the whole URL — fine today, but a dedicated test for isOpenAICompatible would pin all of this down.

Comment thread sdk/go/ai/model_params.go Outdated
Comment thread sdk/go/ai/model_params.go Outdated
7vignesh and others added 3 commits July 15, 2026 00:19
Address reviewer feedback:
- TestIsOpenAICompatible: pin down behavior for OpenAI, Azure, OpenRouter,
  Anthropic, Cohere, and custom/self-hosted endpoints
- TestMarshalRequest_NonOpenAIEndpointKeepsMaxTokens: verify non-OpenAI
  endpoints keep max_tokens even for new model names
- TestMarshalRequest_StreamingPathRewritesMaxTokens: verify the rewrite
  works correctly when stream=true (same marshalRequest path)

@santoshkumarradha santoshkumarradha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Checked this in a clean clone and reran the Go AI tests locally. The model-aware rewrite path and tests look good to me.

…oof model matching

Address review feedback on Agent-Field#724:

- Invert the model check: instead of allowlisting o1/o3/o4/gpt-4o/gpt-4.x
  (which missed gpt-5 and any future family), deny the closed legacy set
  (gpt-4, gpt-4-*, gpt-3*) and rewrite for every other gpt-* model plus
  any o<digit> reasoning model.
- Invert the endpoint default: rewrite max_tokens -> max_completion_tokens
  only for endpoints known to accept it (api.openai.com, *.openai.azure.com,
  openrouter.ai), matched by parsed hostname rather than substring. Unknown
  or self-hosted BaseURLs (Ollama, LM Studio, llama.cpp, vLLM) keep
  max_tokens so servers that silently drop unknown fields never lose the
  output cap (Agent-Field#441).
- Tests: check the NewClient error instead of discarding it, pin BaseURL
  explicitly so env vars cannot flip the endpoint gate, and add coverage
  for gpt-5/o5 rewrites, unknown-host passthrough, host lookalikes, and
  two end-to-end wire-body assertions (recording RoundTripper against
  api.openai.com and a real httptest server for the self-hosted case).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AbirAbbas

Copy link
Copy Markdown
Contributor

Maintainer takeover to address the outstanding review feedback (pushed 5c1c10f to this branch; main was already merged in via 9195709):

  • Model matching future-proofed (thread on needsMaxCompletionTokens): inverted from an allowlist to a closed legacy denylist — gpt-4, gpt-4-*, gpt-3* keep max_tokens; every other gpt-* model and any o<digit> model (gpt-5, o5, and future families) gets max_completion_tokens.
  • Endpoint gating inverted (thread on the default-true fallback): the rewrite now applies only to vouched hosts — api.openai.com/*.openai.com, *.openai.azure.com, openrouter.ai — matched on the parsed hostname (no substring lookalikes). Unknown/self-hosted BaseURLs (Ollama, LM Studio, llama.cpp, vLLM) keep max_tokens, avoiding the silent uncapped-completion failure from [Go SDK] max_tokens not rewritten to max_completion_tokens for newer OpenAI models #441.
  • Copilot test feedback: NewClient errors are now checked with t.Fatalf in every test instead of being discarded.
  • Tests extended: gpt-5/o5 rewrite cases, self-hosted passthrough, host-lookalike rejection, plus two end-to-end wire-body assertions (a recording RoundTripper against api.openai.com and a real httptest server for the unknown-host case). Tests also pin BaseURL explicitly so AI_BASE_URL/OPENROUTER_API_KEY env vars cannot flip the endpoint gate.

Verification: sdk/gogo test ./ai/... passes (170 tests, 0 failures), go vet ./... clean, gofmt -l clean on the touched files. Unrelated pre-existing Windows-only failures in harness/agent (path-separator and subprocess tests) reproduce without this change.

@santoshkumarradha
santoshkumarradha added this pull request to the merge queue Jul 15, 2026
Merged via the queue into Agent-Field:main with commit 99f3e9f Jul 15, 2026
19 checks passed
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.

[Go SDK] max_tokens not rewritten to max_completion_tokens for newer OpenAI models

4 participants