mpfaffenberger / mpfaffenberger/code_puppy

model_factory.make_model_settings: settings object constructed up to 3 times with later dict mutations dropped — thinking_level branch downgrades AnthropicModelSettings/ResponsesSettings to base ModelSettings

Open
#417 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
814
Forks
278
Avg merge
2d 5h
Merged PRs (30d)
76

Description

Problem

In code_puppy/model_factory.py::make_model_settings (lines ~120-330), the GLM and Anthropic/GPT-5 branches construct a settings dict, assign model_settings = ..., then keep mutating model_settings_dict afterwards — whether the mutation takes effect depends on subtle dict-recreation ordering:

1. GLM thinking key is silently dropped for GLM models that go down the Anthropic/Copilot/GPT-5 branches

Lines ~175-186:

if \"glm-4.7\" in model_name.lower() or \"glm-5\" in model_name.lower():
    ...
    model_settings_dict[\"thinking\"] = {\"type\": \"enabled\", \"clear_thinking\": clear_thinking}

model_settings: ModelSettings = ModelSettings(**model_settings_dict)

ModelSettings is a TypedDict; \"thinking\" is not one of its keys, so this relies on TypedDict construction being non-validating — fine — but then each subsequent branch (is_copilot, gpt-5, anthropic) re-creates the settings from model_settings_dict, so a GLM model routed through e.g. a custom_openai config gets thinking only if it skips all branches. The flow is impossible to reason about: assignment at line ~188 is overwritten up to three times, and which keys are present depends on branch ordering rather than declared intent.

2. The final thinking_level block re-creates plain ModelSettings, discarding subclass settings

Lines ~318-330:

if model_supports_setting(model_name, \"thinking_level\"):
    if \"thinking_enabled\" not in model_settings_dict:
        model_settings_dict[\"thinking_enabled\"] = True
    ...
    model_settings = ModelSettings(**model_settings_dict)

If a model supports thinking_level and matched an earlier branch (e.g. a Copilot Claude or GPT-5 Responses model), the carefully built OpenAIResponsesModelSettings/AnthropicModelSettings is replaced by a base ModelSettings — losing the class identity that pydantic-ai uses for provider-specific serialization. Today this mostly bites Gemini-3-style configs that declare thinking_level in supported_settings, but it's a landmine.

3. Function shape

~210 lines, 5 mostly-exclusive model-family branches, two construction passes. Splitting into _settings_for_copilot(), _settings_for_gpt5(), _settings_for_anthropic(), _settings_for_gemini_thinking() with a single final construction point would make the drop/overwrite bugs structurally impossible.

Suggested fix

Build model_settings_dict fully first (all branches mutate the dict only), then construct the settings object exactly once at the end, choosing the class via a small mapping. The thinking_level defaults block should mutate the dict before that single construction, not re-create afterwards.

Filed by Zen Reviewer A (code-puppy-60635a)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in code_puppy/model_factory.py at make_model_settings, reading the GLM, Copilot, GPT-5, Anthropic, and thinking_level branches together. Trace how model_settings_dict and the provider-specific settings classes are constructed. Done means the dictionary is fully mutated before one final construction, with GLM thinking retained and subclass settings no longer replaced by base ModelSettings.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
ai
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.