fix: accept system-role messages in /v1/messages (#2193)#2201
Open
oliver0006 wants to merge 1 commit into
Open
fix: accept system-role messages in /v1/messages (#2193)#2201oliver0006 wants to merge 1 commit into
oliver0006 wants to merge 1 commit into
Conversation
The Anthropic-compatible endpoint rejected any request with a system-role entry inside messages with a 422, which made it unusable for Claude Code pointed at exo via ANTHROPIC_BASE_URL. ClaudeRole now accepts "system" and such messages are folded into the system prompt (joined after the top-level system field) before conversion, as suggested in the issue. Fixes exo-explore#2193 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Motivation
Fixes #2193.
/v1/messagesrejected any request carrying a system-role entry insidemessageswith a 422 (ClaudeRole = Literal["user", "assistant"]). Strictly correct per Anthropic's spec, but real-world Anthropic-format clients — notably Claude Code pointed at exo viaANTHROPIC_BASE_URL, the most common reason this endpoint exists — do send system-role entries inmessages, so the endpoint was unusable for exactly its target clients. This implements the lenient-adapter direction proposed by @batmunkh0612 in the issue (thanks for the precise report and repro!).Changes
ClaudeRoleaccepts"system"(request-side only — response types have their own literals).claude_request_to_text_generation()folds system-role messages into the system prompt: the top-levelsystemfield keeps its exact existing semantics, in-band system messages are appended after it (joined with blank lines), and the combined text goes through the existing_strip_volatile_headers()(which was already built for Claude Code's volatile billing headers). System entries are removed from the conversation before conversion, preserving message order.Why It Works
The 422 came from pydantic validation of the request model, before any handler logic ran. Widening the request literal makes validation pass, and folding happens entirely inside the request→
TextGenerationTaskParamsadapter, so nothing downstream changes: the internalMessageRolealready includes"system", and a request without in-band system messages produces byte-identical results to before.Test Plan
Manual Testing
Hardware: Fedora Linux (16-thread Ryzen). Launched
uv run exoand replayed the exact curl from the issue:422 {"detail":[{"type":"literal_error",..."msg":"Input should be 'user' or 'assistant'","input":"system"}]}404 No instance found for model ...with no model loaded — i.e. it would generate normally with one).Automated Testing
New
TestSystemRoleMessagesclass intest_claude_api.py:instructionsand is excluded frominputsystemfieldAll 20 tests in
test_claude_api.pypass (including the pre-existing top-level-systemblock-joining semantics, which are unchanged); fulluv run pytest src→ 344 passed.uv run basedpyright0 errors,ruff checkandruff format --checkpass.Disclosure: co-written by a human (@oliver0006) directing the work and Claude AI (Fable 5) writing code — which makes this a fix Claude wrote so Claude Code can run on exo. The loop closes itself.
🤖 Generated with Claude Code