fix(dspy): reconcile max_tokens override with aliased max_completion_tokens for reasoning models - #79
Open
detail-app[bot] wants to merge 2 commits into
Conversation
…tokens for reasoning models
Greptile SummaryThis revision makes
Confidence Score: 5/5The PR has no reportable outstanding findings under the review-thread rules and appears safe to merge. The previous cross-model token-alias finding was manually resolved without explanation, so it does not lower merge confidence and cannot be reposted as a new comment. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
A["LM.copy(model, max_tokens)"] --> B{"Target is reasoning model?"}
B -->|Yes| C["Translate override to max_completion_tokens"]
B -->|No| D["Keep override as max_tokens"]
C --> E["BaseLM.copy merges source kwargs"]
D --> E
E --> F["Copied LM"]
Reviews (2): Last reviewed commit: "fix(lm): use target model for token alia..." | Re-trigger Greptile |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Warning
GitHub issue creation failed
Detail attempted to publish this bug to GitHub, but the issue could not be created. This fix PR was created without that issue, and missing tracker references are shown as
Unknown issue.You can review and merge this PR normally. Please review your tracker integration settings before the next publish run.
Detail bug report: View on Detail
📝 Changes Description
reasoning_lm.copy(max_tokens=N)on an OpenAI reasoning-modeldspy.LMcorruptedkwargs: it inserted a spuriousmax_tokens=Nkey next to the stale, init-renamedmax_completion_tokenskey instead of updating it. The dual-key state then flowed into the litellm request on bothmodel_type="chat"andmodel_type="responses"paths (contradictory token-budget params), anddump_state()silently overwrote the user'sNwith the stale value on save/reload.Root cause:
LM.__init__/_get_initial_kwargsaliases the user-facingmax_tokensto the provider keymax_completion_tokensfor reasoning models, but the genericBaseLM.copy()(which bypasses__init__and merges overrides directly intokwargs) never knew about the alias. The compoundingdump_state()site was also asymmetric withload_state()— it unconditionally overwrotemax_tokens, whileload_state()only sets it when absent.Fix (
dspy/clients/lm.py, two small changes):LM.copy()to reconcilemax_tokens→max_completion_tokensfor reasoning models before delegating toBaseLM.copy(), so the copy matches what__init__would produce and the override survivesdump_state().LM.dump_state()to only setmax_tokenswhen absent (and always dropmax_completion_tokens), mirroring the defensiveload_state()— defense in depth against any other code path that might create a dual-key state.Closes Unknown issue
✅ Contributor Checklist
ruff checkpasses on both changed files;pre-commit run --files dspy/clients/lm.py tests/clients/test_lm.pypassesfix(dspy): reconcile max_tokens override with aliased max_completion_tokens for reasoning models{label}(dspy): {message}AI-generated contribution disclosure (per
CONTRIBUTING.md's "AI-Generated Contributions" policy): This change was authored with the opencode CLI agent (powered by the Detail model). Prompts: the bug report itself (which included the failing test, root-cause analysis, and a recommended fix sketch) was provided as the task; I verified the bug independently by reproducing the dual-keykwargsstate and thedump_state()data loss, confirmed the existingcopy()test suite did not exercise the alias interaction, and confirmed viagit blame/git logthat the alias and the generic merge logic became jointly reachable at commit80cdcbef. The fix is deliberately minimal and mirrors DSPy's existing reasoning-model aliasing convention; no new validation surface is added.Testing: Unit tests, lint, and build all pass. Seven focused regression tests were added to
tests/clients/test_lm.py:copy(max_tokens=N)produces a singlemax_completion_tokenskey (not a dual-key state) anddump_state()["max_tokens"]preserves the raised budget.max_completion_tokens(no spuriousmax_tokens), patched viadspy.clients.lm.litellm_completion.max_output_tokens(no leakedmax_completion_tokens/max_tokens), patched vialitellm.responses.dump_state → load_state → dump_statepreserves the budget at every hop.copy(max_tokens=None)removes the aliased key (the standard "None means remove"copy()semantics).max_tokensdirectly (the reconciliation is correctly gated on_is_openai_reasoning_model).dump_state()defense-in-depth: a simulated pre-existing dual-key state keeps the existingmax_tokensand drops the stale alias.Full
tests/clients/test_lm.py(94 tests) passes, as does the broadertests/clients/suite and the DSPy-internallm.copy()callers (tests/predict/test_best_of_n.py,tests/predict/test_refine.py,tests/teleprompt/). A sensitivity check confirmed the new tests fail without the fix (e.g. the primary test fails withunexpected token keys: ['max_completion_tokens', 'max_tokens']) and pass with it.uv buildsucceeds and the built wheel imports cleanly.Not verified end-to-end: The live
tests/clients/test_lm_direct_live.pyprobes (which make real OpenAI API calls to confirm the provider does not reject the single-budget request) could not be run — they require a validOPENAI_API_KEY, which is unavailable in this environment. The mocked chat/responses request-path tests cover the sameLM.forward→ litellm code paths offline; only live-provider acceptance of the reconciled single-key request is unconfirmed.Known scope limitation (not addressed here):
BaseLM.copy()bypassesLM._get_initial_kwargs(), so it does not re-run thetemperature == 1.0/max_tokens >= 16000validation that the constructor enforces for reasoning models. This fix reconciles the alias but does not add validation tocopy(); that is a separate, pre-existing gap.Automatic Fixes PRs can be configured here.