Skip to content

fix: handle OpenClaw message format to resolve 422 validation errors#41

Draft
bhargav18 wants to merge 1 commit intoRichardAtCT:mainfrom
bhargav18:fix/openclaw-message-compatibility
Draft

fix: handle OpenClaw message format to resolve 422 validation errors#41
bhargav18 wants to merge 1 commit intoRichardAtCT:mainfrom
bhargav18:fix/openclaw-message-compatibility

Conversation

@bhargav18
Copy link

@bhargav18 bhargav18 commented Feb 28, 2026

Summary

OpenClaw sends message formats that go beyond the strict OpenAI Chat Completions spec the wrapper previously enforced, resulting in 422 Unprocessable Content errors. This PR makes the Message and ContentPart models tolerant of those formats while preserving existing behaviour for standard OpenAI clients.

Root cause

OpenClaw (and other agentic clients) send:

  • Messages with role: "tool" — tool call result messages returned to the model
  • Messages with content: null — assistant messages that made a tool call and have no text content yet
  • Content arrays containing non-text block types (e.g. tool_use, tool_result, image)
  • Extra top-level fields such as store or service_tier from the OpenAI Responses API

Changes

src/models.py

Field Before After
Message.role Literal["system", "user", "assistant"] Literal["system", "user", "assistant", "tool"]
Message.content Union[str, List[ContentPart]] Optional[Union[str, List[Any]]]
ContentPart.type Literal["text"] str
ContentPart.text str (required) Optional[str]
Unknown fields error silently ignored (extra="ignore")
  • normalize_content now handles None content (returns "") and extracts text from tool_result content blocks
  • model_config = {"extra": "ignore"} added to both ContentPart and Message

Test plan

  • poetry run python tests/test_endpoints.py → 4/4 passed
  • poetry run python tests/test_basic.py → 4/4 passed
  • No regression for standard OpenAI SDK requests
  • Verify OpenClaw requests no longer return 422 with a live OpenClaw instance

🤖 Generated with Claude Code

OpenClaw sends message formats that don't match the strict OpenAI spec
the wrapper previously enforced, causing 422 Unprocessable Content errors.

- Accept role 'tool' in Message (tool call result messages)
- Allow null/None content (assistant messages mid-tool-call)
- Loosen ContentPart to accept any content block type beyond just 'text'
- Extract text from tool_result content blocks during normalization
- Add extra='ignore' to drop unknown fields (e.g. store, service_tier)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant