fix: normalize text-only content arrays to strings for OpenAI-compatible providers#1027
Open
BingqingLyu wants to merge 1 commit into
Open
fix: normalize text-only content arrays to strings for OpenAI-compatible providers#1027BingqingLyu wants to merge 1 commit into
BingqingLyu wants to merge 1 commit into
Conversation
…ble providers
pi-ai emits Anthropic-style [{type:"text", text:"..."}] content blocks
for user messages. Many OpenAI-compatible providers (NVIDIA NIM, Ollama,
vLLM, LiteLLM) reject this format with HTTP 400.
Add an onPayload normalization wrapper that flattens text-only content
arrays to plain strings for openai-completions payloads, while preserving
annotated blocks (e.g. cache_control) and mixed content (e.g. image_url).
Fixes openclaw#50107
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.
Summary
api: "openai-completions", pi-ai'sconvertMessagesemits user messages with Anthropic-style content block arrays ([{type:"text", text:"..."}]) instead of plain strings. This causes NVIDIA NIM and similar providers to reject requests with HTTP 400 errors.createOpenAICompatContentNormalizationWrapperstream wrapper that normalizes text-only content arrays to plain strings via theonPayloadhook foropenai-completionspayloads. Applied universally inapplyExtraParamsToAgent.openai-completionsAPI types (Responses, Anthropic, Google) are not affected. The pi-ai library itself is not modified.AI-assisted: This PR was authored with AI assistance. Fully tested with colocated vitest tests. The fix was verified by reading the pi-ai
convertMessagessource to confirm the root cause.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
NVIDIA and other third-party OpenAI-compatible providers now work correctly when configured as
openai-completions. Messages are sent in the standard{"role": "user", "content": "string"}format instead of the Anthropic-style{"role": "user", "content": [{"type": "text", "text": "string"}]}format.Security Impact (required)
NoNoNoNoNoRepro + Verification
Environment
nvidia/llama-3.1-nemotron-70b-instruct), vLLM, or any strict OpenAI-compatible providerSteps
api: "openai-completions"Expected
User messages in the payload use plain string content:
{"role": "user", "content": "Say hello"}Actual
User messages use Anthropic-style content arrays:
{"role": "user", "content": [{"type": "text", "text": "Say hello"}]}, causing HTTP 400 from NVIDIA API.Evidence
9 new tests cover the fix:
Human Verification (required)
convertMessagessource to confirm root cause atnode_modules/@mariozechner/pi-ai/dist/providers/openai-completions.js:420-452.Review Conversations
Compatibility / Migration
YesNoNoFailure Recovery (if this breaks)
extra-params.tsand can be commented out.src/agents/pi-embedded-runner/extra-params.ts,src/agents/pi-embedded-runner/openai-stream-wrappers.tsopenai-completionsAPI type.Risks and Mitigations
cache_controlto text blocks).{type:"text"}objects. Blocks with extra properties likecache_controlwould causeallTextto still be true, but the OpenRouter caching wrapper runs on a separate code path (system/developer roles only) and is applied before this wrapper in the chain. The concatenation preserves all text content.