fix(anthropic): omit temperature instead of sending null when it is unset (#1794) - #1797
fix(anthropic): omit temperature instead of sending null when it is unset (#1794)#1797Anai-Guo wants to merge 1 commit into
Conversation
…nset (getzep#1794) The MCP server's config schema defaults llm.temperature to None, and the client factory stores it verbatim on the core LLMConfig, noting that "downstream clients omit temperature when it is None". The OpenAI client does exactly that, but AnthropicClient forwarded self.temperature to messages.create() unconditionally. The Anthropic SDK serializes an explicit None rather than dropping the key, so the request body carries "temperature": null and the API answers 400 invalid_request_error ("temperature: Input should be a valid number"). Build the create() kwargs and add temperature only when it is not None, mirroring OpenAIClient._create_completion. An explicit 0.0 is still sent.
linhongyu510
left a comment
There was a problem hiding this comment.
Verified at 6e51b5c. The change is correctly limited to request construction: temperature is omitted only for None, while explicit 0.0 and 0.7 remain present, with no changes to retries, token accounting, tool parsing, or exception mapping. Independent validation: the full Anthropic client module passes 14 tests; tests/llm_client tests/test_text_utils.py tests/utils passes 319 tests with 2 expected integration skips; Ruff check/format and git diff --check pass. I also performed a red/green check by reverting only anthropic_client.py while retaining the new test: test_temperature_omitted_when_unset fails exactly because the baseline still forwards the key, then all three temperature tests pass again on this HEAD. The separate queue-failure observability concern is appropriately left out of this minimal fix.
Fixes #1794.
Problem
mcp_server/src/config/schema.pydefaultsllm.temperaturetoNone, andmcp_server/src/services/factories.pypasses it straight through to the coreLLMConfig— with the same comment repeated at all five provider branches:OpenAIClienthonors that contract (openai_client.py):AnthropicClientdid not — it forwardedself.temperaturetomessages.create()unconditionally. The Anthropic SDK serializes an explicitNonerather than dropping the key, so the request body carries"temperature": nulland the API rejects it:Which I confirmed at the wire level against a mocked httpx transport
(anthropic 0.120.2):
messages.create(..., temperature=None)nullmessages.create(...)(omitted)messages.create(..., temperature=0.0)0.0Under the MCP server this is invisible to callers: the episode queue catches the
exception, logs it and marks the task done, so every
add_memorystill reportssuccess while nothing lands.
Fix
Build the
messages.create()kwargs and addtemperatureonly when it is notNone, mirroringOpenAIClient._create_completion. The guard isis not None,not truthiness, so an explicit
temperature=0.0is still sent.Verification
Reproduction against the real
AnthropicClientwith a mockedmessages.create, before → after:temperatureunset (None)Nonetemperature=0.00.00.0temperature=0.70.70.7Tests (
tests/llm_client/test_anthropic_client.py, newTestAnthropicClientTemperature) cover all three rows.anthropic_client.pywhile keeping the new testsfails exactly
test_temperature_omitted_when_unset(13 passed, 1 failed).The two guard cases stay green on both sides, which is what they are for.
pytest tests/llm_client tests/test_text_utils.py tests/utils: 297 passed,2 skipped (the skips are the
_inttests, noANTHROPIC_API_KEY).ruff==0.14.11(the version pinned in.github/workflows/lint.yml)checkandformat --checkclean on both files.Deliberately not in scope
The issue reports two things; this PR fixes only the first.
OpenAIGenericClient,GeminiClient,GroqClientalso passtemperature=self.temperaturethrough unconditionally, so the factory's"downstream clients omit temperature when it is
None" comment is not truefor them either. Whether that is actually a bug depends on how each of those
APIs treats an explicit
null, which I have not verified the way I verifiedAnthropic's — so I left them alone rather than guess. Happy to follow up if
you'd like them covered.
that turns hard failures into log lines while the tool call reports success).
That is an MCP server API-shape decision, not a bug fix.
🤖 Generated with Claude Code