Skip to content

[Bug] Claude→OpenAI translator passes content as array instead of string, crashing NVIDIA NIM / vLLM backends with "sequence item 0: expected str instance, list found" #3056

@bingo4916

Description

@bingo4916

Title:

[Bug] Claude→OpenAI translator passes content as array instead of string, crashing NVIDIA NIM / vLLM backends with "sequence item 0: expected str instance, list found"

Body:

## Bug Description

When using `openai-compatibility` to route Claude Code (`/v1/messages`) requests to an OpenAI-compatible backend (specifically **NVIDIA NIM** running DeepSeek V4-Pro), the Claude→OpenAI message translator passes `content` as a **Claude-format array** `[{"type":"text","text":"..."}]` instead of flattening it to an **OpenAI-format string** `"..."`.

The upstream backend (Python-based vLLM/NVIDIA NIM) receives the array-typed content and crashes with:

TypeError: sequence item 0: expected str instance, list found


This is related but distinct from #1136 (which covers empty system message arrays). This bug affects **all message roles** (user, assistant, system) when their `content` field contains Claude's array format.

## Environment

- **CLIProxyAPI Version:** 6.9.38 (commit `2c626efc`, built 2026-04-25)
- **OS:** Windows 11 (24H2), x64
- **Upstream:** NVIDIA NIM (`https://integrate.api.nvidia.com/v1`)
- **Model:** `deepseek-ai/deepseek-v4-pro`
- **Client:** Claude Code CLI v2.1.119

## Configuration

```yaml
openai-compatibility:
  - name: nvidia
    base-url: https://integrate.api.nvidia.com/v1
    api-key-entries:
      - api-key: nvap***
    models:
      - name: deepseek-ai/deepseek-v4-pro

payload:
  override:
    - models:
        - name: deepseek-ai/deepseek-v4-pro
          protocol: openai
      params:
        chat_template_kwargs.thinking: false
        max_tokens: 16384

Steps to Reproduce

  1. Configure CLIProxyAPI with openai-compatibility pointing to NVIDIA NIM
  2. Point Claude Code at CLIProxyAPI via ANTHROPIC_BASE_URL=http://localhost:8317
  3. Set model to deepseek-ai/deepseek-v4-pro
  4. Send any message through Claude Code

Actual Behavior

What Claude Code sends (/v1/messages):

{
  "model": "deepseek-ai/deepseek-v4-pro",
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "hello"}
      ]
    }
  ],
  "max_tokens": 8192,
  "stream": true
}

What gets forwarded to NVIDIA NIM (/v1/chat/completions) — BUG HERE:

The translator converts the request but keeps content as an array (or produces malformed nested structures). The NVIDIA Python backend receives non-string content and crashes:

HTTP 500 Internal Server Error: sequence item 0: expected str instance, list found

Error log from CLIProxyAPI v6.9.38:

openai_compat_executor.go:258] request error, error status: 500,
error message: "Internal server error: sequence item 0: expected str instance, list found"

After this error, the credential enters cooldown and all subsequent requests return:

503 auth_unavailable: no auth available (providers=nvidia, model=deepseek-ai/deepseek-v4-pro)

Expected Behavior

The Claude→OpenAI translator should flatten all content arrays to strings before forwarding:

{
  "model": "deepseek-ai/deepseek-v4-pro",
  "messages": [
    {
      "role": "user",
      "content": "hello"
    }
  ],
  "max_tokens": 8192,
  "stream": true
}

The flattening logic should handle:

  • Simple text blocks: [{"type":"text","text":"..."}]"..."
  • Mixed content (text + tool_use + tool_result): concatenate text parts, stringify tool parts
  • System messages with array content
  • Multi-turn conversations with assistant content arrays containing both text and tool_use blocks

Workaround

I wrote a Node.js translation proxy that sits between Claude Code and CLIProxyAPI, correctly flattening content arrays. The chain is:

Claude Code → :8318 (translator-proxy, flattens content) → :8317 (CLIProxyAPI) → NVIDIA NIM

With this workaround in place, requests succeed with HTTP 200 (50-80s response time on DeepSeek V4-Pro). This proves the issue is specifically in CLIProxyAPI's internal/translator/claude/openai/chat-completions/ path.

Key workaround code — the flatten function:

function flattenContent(content) {
  if (typeof content === 'string') return content;
  if (Array.isArray(content)) {
    return content
      .filter(block => block.type === 'text')
      .map(block => block.text || '')
      .join('');
  }
  return String(content || '');
}

Root Cause Analysis

The relevant code is likely in:

  • internal/translator/claude/openai/chat-completions/claude_openai_request.go

The converter needs a flattenContent() function that:

  1. If content is a string → pass through unchanged
  2. If content is an array → extract all type:"text" blocks' .text fields, join them into a single string
  3. For assistant messages with tool_use blocks → convert to OpenAI tool_calls format with function.name / function.arguments
  4. For user messages with tool_result blocks → convert to readable string representation

Additional Context

  • Direct OpenAI-format calls work fine: When calling /v1/chat/completions directly (bypassing the Claude→OpenAI translation), NVIDIA NIM responds correctly with HTTP 200
  • The issue only occurs through the /v1/messages → OpenAI translation path
  • Related issues: #1136 (system empty array), #1043 (assistant array dropped), #1670 (tool_result array handling)
  • Alternative project that handles this correctly: anyllm-proxy (Rust-based proxy purpose-built for Anthropic↔OpenAI translation)

Impact

This blocks all Claude Code users from using NVIDIA NIM (and likely other strict OpenAI-compatible backends such as vLLM, Ollama, TGI) through CLIProxyAPI's openai-compatibility feature. NVIDIA NIM is one of the most popular free-tier LLM providers, so this affects a significant number of users.


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions