Skip to content

fix: drop deprecated sampling params and response_format from Anthropic provider config - #377

Open
ayo0la wants to merge 2 commits into
interviewstreet:mainfrom
ayo0la:fix/anthropic-provider-config
Open

fix: drop deprecated sampling params and response_format from Anthropic provider config#377
ayo0la wants to merge 2 commits into
interviewstreet:mainfrom
ayo0la:fix/anthropic-provider-config

Conversation

@ayo0la

@ayo0la ayo0la commented Jul 27, 2026

Copy link
Copy Markdown

Summary

The shipped anthropic entry in providers.json cannot complete a single request against the real API. Two independent problems, both config-level:

  1. temperature / top_p were removed on Claude Opus 4.7 and later. Sending either returns 400 "temperature is deprecated for this model". This affects claude-opus-4-8 and claude-sonnet-5. claude-haiku-4-5 predates the removal and still accepts both, so it keeps them.
  2. response_format: {"type": "json_object"} is rejected by the endpoint, which accepts only "json_schema" — and that in turn requires a strict flag plus additionalProperties: false set recursively, which EvaluationData.model_json_schema() does not emit.

Fix

Four lines in providers.json. No code change is needed:

  • OpenAICompatibleProvider.chat() adds temperature / top_p only when they are present in options, so omitting them from the per-model config omits them from the request body.
  • The same method already skips response_format entirely when structured_output is "none". The prompt-level JSON instructions plus extract_json_from_response's fence stripping already cover that path.

Keeping the fix declarative avoids threading a new provider-level boolean through config.provider_for()llm_utils.initialize_llm_provider()OpenAICompatibleProvider.

The per-model keys are kept as empty objects rather than deleted. pdf.py:73 and github.py:377 fall back to a hardcoded {"temperature": 0.1, "top_p": 0.9} when a model is absent from MODEL_PARAMETERS, so removing the entries would reintroduce the identical 400.

Testing

Captured the outgoing chat/completions body for every configured model by stubbing requests.post, so the request shape is observed rather than inferred:

model temperature top_p response_format
claude-opus-4-8 (absent) (absent) (absent)
claude-sonnet-5 (absent) (absent) (absent)
claude-haiku-4-5 0.1 0.9 (absent)
gemini-2.5-flash 0.1 0.9 json_schema
gemma3:4b 0.1 0.9 json_schema

The gemini and ollama entries are unchanged, confirming no regression to the working providers. providers.json validates as JSON.

I do not have an Anthropic key to run score.py end to end; @sahiee-dev confirmed in #373 that a full run completes against real claude-sonnet-5 once these two changes are in place.

Credit

Diagnosis, the live 400 traces, and the end-to-end confirmation are @sahiee-dev's in #373. I confirmed the sampling-parameter half independently and implemented it as a config-only change. Claimed in this comment before starting.

Fixes #373

The shipped anthropic provider entry cannot complete a single request
against the real API. Two independent problems, both in providers.json:

1. temperature and top_p were removed on Claude Opus 4.7 and later.
   Sending either returns 400 "`temperature` is deprecated for this
   model". This affected claude-opus-4-8 and claude-sonnet-5.
   claude-haiku-4-5 still accepts both, so it keeps them.

2. response_format {"type": "json_object"} is rejected by the endpoint,
   which accepts only "json_schema" — and that in turn requires a strict
   flag plus additionalProperties: false set recursively, which
   EvaluationData.model_json_schema() does not emit. Setting
   structured_output to "none" sends no response_format at all; the
   prompt-level JSON instructions and extract_json_from_response's
   fence stripping already cover this path.

No code change is needed. OpenAICompatibleProvider.chat() adds
temperature and top_p only when present in options, and skips
response_format when structured_output is "none", so both fixes are
declarative. The per-model keys are kept (as empty objects) rather than
removed because pdf.py and github.py fall back to a hardcoded
{"temperature": 0.1, "top_p": 0.9} when a model is absent from
MODEL_PARAMETERS, which would reintroduce the same 400.

Verified by capturing the outgoing request body for every configured
model: claude-opus-4-8 and claude-sonnet-5 now send neither sampling
parameter nor response_format, claude-haiku-4-5 still sends both
sampling parameters, and the gemini and ollama entries are byte-for-byte
unchanged.

Diagnosis and the live 400 traces are from @sahiee-dev in interviewstreet#373.

Fixes interviewstreet#373

@sahiee-dev sahiee-dev left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for picking this up quickly. The providers.json change is the right direction, but I don't think it's sufficient on its own, the two call sites that actually build the request options don't read the config the same way, so this doesn't fully fix the bug. (Both lines below are outside this diff, which is why I can't leave inline comments on them, flagging here instead.)

pdf.py:94-95 (_call_llm_for_section), indexes directly instead of using .get():

"temperature": model_params["temperature"],
"top_p": model_params["top_p"],

With "claude-opus-4-8": {} / "claude-sonnet-5": {} in this PR, MODEL_PARAMETERS["claude-sonnet-5"] is {}, and indexing a missing key on an empty dict raises KeyError. Every per-section PDF extraction call (basics/work/education/skills/projects/awards) would crash locally before it even reaches the API, for exactly the two models this PR targets.

evaluator.py:68-69 (evaluate_resume : the final scoring call, arguably the most important one):

"temperature": self.model_params.get("temperature", 0.5),
"top_p": self.model_params.get("top_p", 0.9),

Both .get() calls fall back to non-empty defaults when the key is missing. Since self.model_params is {} for the two affected models, options still ends up with temperature: 0.5, top_p: 0.9 no matter what providers.json says, and OpenAICompatibleProvider.chat() forwards them into the body regardless. I'd expect this to reproduce the exact `temperature` is deprecated for this model 400 on the evaluation call itself.

github.py's generate_projects_json (~line 458) passes model_params straight through as options, so that one call site is correctly fixed by this PR, it's just the other two that don't share that pattern.

Given three call sites read providers.json three different ways (direct index, .get-with-default, passthrough), I'd lean toward moving the guard into OpenAICompatibleProvider.chat() itself in models.py, one choke point where the HTTP body is actually built, so it can't be defeated by whatever a given call site happens to default to upstream. Happy to be wrong if I'm missing something in how MODEL_PARAMETERS gets consumed elsewhere, but the pdf.py crash in particular seems straightforward to repro.

@ayo0la

ayo0la commented Aug 8, 2026

Copy link
Copy Markdown
Author

Re-verified against the live API today (2026-08-08), in case it helps triage.

Native Messages API, sending temperature: 0.1:

model status message
claude-opus-4-8 400 `temperature` is deprecated for this model.
claude-sonnet-5 400 `temperature` is deprecated for this model.
claude-haiku-4-5 200 accepted

OpenAI-compatible endpoint (POST https://api.anthropic.com/v1/chat/completions), which is the path OpenAICompatibleProvider actually takes:

response_format status message
omitted 200 accepted
{"type": "json_object"} 400 response_format.type: Input should be 'json_schema'
{"type": "json_schema", ...} without strict 400 response_format.json_schema.strict: Field required
{"type": "json_schema", ..., "strict": true} 200 accepted

One note on why this sets structured_output to none rather than switching Anthropic to json_schema: strict: true additionally requires the schema to set additionalProperties: false on every object, and to drop numeric and array constraints. The schema from build_evaluation_model currently fails all three, in sequence:

as-is                           -> 400  For 'object' type, 'additionalProperties' must be explicitly set to false
+ additionalProperties: false   -> 400  For 'number' type, properties maximum, minimum are not supported
+ numeric constraints stripped  -> 400  For 'array' type, property 'maxItems' is not supported

So getting Anthropic onto structured outputs needs a schema-normalisation pass, which seemed like a separate change from this config fix. Glad to open that as a follow-up if you would rather go that route.

@baghdme

baghdme commented Aug 10, 2026

Copy link
Copy Markdown

Hi

I have an Anthropic key so I ran the branch's config on current master (actual score.py runs, not stubbed).

@sahiee-dev's review is right on both of his claims. On an uncached resume every section extraction dies immediately with KeyError: 'temperature' (shows up in the logs as ❌ Error calling LLM for basics section: 'temperature'), the direct indexing in pdf.py:94-95 hitting the empty {} params. And with a cached resume (no extraction) the eval call itself comes back 400, because evaluator.py:68-69 re-adds temperature: 0.5, top_p: 0.9 through its .get() defaults no matter what the config says.

Also found something not mentioned yet: the haiku entry this PR keeps is broken too. haiku does still accept sampling params, but only one at a time, when I sent both, this was the outcome:

400: `temperature` and `top_p` cannot both be specified for this model. Please use only one.

Tried all four combinations on /v1/chat/completions: both → 400, temperature only → 200, top_p only → 200, neither → 200. The re-verification on the 8th (of august) tested haiku with temperature alone on the native messages API, which is why it looked fine there. So the haiku line needs to drop one of the two ({"temperature": 0.1} works) regardless of which fix approach lands.

FWIW I also tried the chokepoint idea from the review, skip temperature/top_p in OpenAICompatibleProvider.chat() when the base_url is api.anthropic.com, and a full score.py run completes fine on claude-sonnet-5 after that, real key, no other changes.

The providers.json-only change was not sufficient. Three call sites read
MODEL_PARAMETERS three different ways, so config alone cannot fix this:

- pdf.py:94-95 indexed model_params directly, raising KeyError for any
  model with no sampling params configured. Every per-section extraction
  crashed before reaching the API.
- evaluator.py:68-69 used .get() with non-empty defaults, re-adding
  temperature 0.5 and top_p 0.9 regardless of what providers.json said,
  so the scoring call still 400d.
- github.py passed model_params straight through and was already correct.

Move the guard into OpenAICompatibleProvider.chat(), the one chokepoint
where the HTTP body is assembled, so no call site can reintroduce the
pair by defaulting. top_p is dropped rather than temperature because
temperature is what keeps scoring runs reproducible. Non-Anthropic
providers are untouched and still receive both.

claude-haiku-4-5 shipped both params too and 400s the same way, so all
three Anthropic entries now carry temperature only. Verified against the
live API by @baghdme: both -> 400, either alone -> 200.

Also fixes the two call sites so they forward only configured keys,
which removes the KeyError independently of provider choice.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Bug: shipped Anthropic provider config in providers.json is incompatible with real Claude models

3 participants