Skip to content

fix: normalize text-only content arrays to strings for OpenAI-compatible providers#1027

Open
BingqingLyu wants to merge 1 commit into
mainfrom
fork-pr-50152-fix-nvidia-provider-message-format
Open

fix: normalize text-only content arrays to strings for OpenAI-compatible providers#1027
BingqingLyu wants to merge 1 commit into
mainfrom
fork-pr-50152-fix-nvidia-provider-message-format

Conversation

@BingqingLyu

@BingqingLyu BingqingLyu commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Problem: When using an NVIDIA provider (or other third-party OpenAI-compatible providers like vLLM, Ollama, LiteLLM) configured as api: "openai-completions", pi-ai's convertMessages emits 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.
  • Why it matters: Users cannot use NVIDIA or other strict OpenAI-compatible providers for subagent runs or any session where the internal message representation passes through pi-ai's conversion layer.
  • What changed: Added a createOpenAICompatContentNormalizationWrapper stream wrapper that normalizes text-only content arrays to plain strings via the onPayload hook for openai-completions payloads. Applied universally in applyExtraParamsToAgent.
  • What did NOT change (scope boundary): Messages with mixed content (text + images) are left as arrays. Non-openai-completions API 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 convertMessages source to confirm the root cause.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Gateway / orchestration

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)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Any
  • Runtime/container: Node 22+
  • Model/provider: NVIDIA NIM (nvidia/llama-3.1-nemotron-70b-instruct), vLLM, or any strict OpenAI-compatible provider
  • Integration/channel: Any

Steps

  1. Configure an NVIDIA provider with api: "openai-completions"
  2. Spawn a subagent or send a message through the provider
  3. Observe the outbound payload

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

  • Failing test/log before + passing after

9 new tests cover the fix:

  • Flattens text-only user content arrays to plain strings for NVIDIA
  • Flattens multi-block text-only content into a single string
  • Preserves mixed content arrays with images untouched
  • Does not modify already-plain-string content
  • Normalizes system message content arrays to strings
  • Applies to any openai-completions provider, not just NVIDIA
  • Skips normalization for non-openai-completions API types
  • Handles empty content arrays gracefully
  • Flattens assistant text-only content arrays to strings

Human Verification (required)

  • Verified scenarios: All 9 test cases pass. Reviewed pi-ai convertMessages source to confirm root cause at node_modules/@mariozechner/pi-ai/dist/providers/openai-completions.js:420-452.
  • Edge cases checked: Empty arrays (left alone), mixed text+image content (left alone), multi-block text (concatenated), already-string content (pass-through), non-openai-completions API (skipped).
  • What I did not verify: Live NVIDIA API call (no API key available in test environment).

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Revert the single commit. The wrapper is applied in extra-params.ts and can be commented out.
  • Files/config to restore: src/agents/pi-embedded-runner/extra-params.ts, src/agents/pi-embedded-runner/openai-stream-wrappers.ts
  • Known bad symptoms reviewers should watch for: If any OpenAI-compatible provider specifically requires array-format content for text-only messages (unlikely), it would break. Native OpenAI is unaffected since the wrapper only fires for openai-completions API type.

Risks and Mitigations

  • Risk: A provider might rely on the array format for cache_control annotations on text blocks (e.g., OpenRouter Anthropic caching adds cache_control to text blocks).
    • Mitigation: The normalization only flattens arrays where ALL blocks are plain {type:"text"} objects. Blocks with extra properties like cache_control would cause allText to 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.

…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
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.

[Bug]: NVIDIA Provider Sends Anthropic-Style Message Format to OpenAI-Compatible API

1 participant