providers: coerce numeric config knobs to numbers at send time#191
Merged
Conversation
The admin Settings panel round-trips temperature / max_tokens / top_p through agentSettingsConfig.value, which inherits the string-typed values pg returns for numeric columns. The save persists them into agent_configuration.configuration as strings (e.g. "temperature":"0.7"), then each provider passes them straight through to the upstream API. OpenRouter / Llama coerce on their end so the bug stayed latent there. Anthropic does not — Claude Haiku 4.5 returned HTTP 400 'temperature: Input should be a valid number' on every salem-visitor call, so visitors couldn't act after arriving at the tavern. Storage stays whatever the user typed (no migration). Each provider enforces its own type contract by calling asNumber on numeric body fields. asNumber lives in a new providers/coerce.js module to avoid the circular require chain that would result from putting it in providers/index.js (which loads each provider by name). Coverage: temperature in anthropic, openai, openrouter, perplexity, xai; max_tokens / max_completion_tokens / max_output_tokens across the same set; top_p / top_k / thinking_budget in google.
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.
Symptom
salem-visitor (anthropic / claude-haiku-4-5) chat fails on every dispatch:
```
Anthropic API error 400: {"type":"error","error":{"type":"invalid_request_error",
"message":"temperature: Input should be a valid number"}}
```
Engine logs the failure, visitor never decides anything, sits forever outside the tavern.
Cause
The admin Settings panel (agents.js:449) round-trips numeric knobs through `agentSettingsConfig.value`, which inherits the string-typed values `pg` returns for `numeric` columns. The save persists them into `agent_configuration.configuration` as strings (e.g. `{"temperature":"0.7"}`), then each provider passes them straight through to the upstream API.
OpenRouter / Llama happen to coerce strings on their end, so the bug stayed latent there. Anthropic does not — it rejects with HTTP 400. OpenAI, Google, Perplexity, xAI would all fail the same way for any agent saved through the Settings panel; it just hadn't been exercised yet.
Fix
New `providers/coerce.js` exports `asNumber(v)`: returns a finite number for valid inputs, `undefined` for null / empty / NaN / Infinity / non-numeric strings.
Lives in its own module rather than `providers/index.js` to avoid the circular require chain (index.js loads each provider by name; providers can't require index.js back).
Each provider calls `asNumber` on numeric body fields before assigning. Storage stays whatever the user typed (no migration needed); each provider enforces its own type contract at send time.
Coverage:
Blast radius
Test plan
— Home