litellm - 💡(How to fix) Fix [Bug]: Responses API → Chat Completions bridge drops tool names and forwards unsupported tool types (custom, shell) [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
BerriAI/litellm#27276Fetched 2026-05-07 03:33:25
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
0
Participants
Timeline (top)
labeled ×1

Error Message

BadRequestError: DeepseekException - Invalid 'tools[0].function.name': empty string

Code Example

# Current code (line ~1411)
else:
    chat_completion_tools.append(
        cast(Union[ChatCompletionToolParam, OpenAIMcpServerTool], tool)
    )

---

# Current code (line ~1394)
"name": typed_tool.get("name") or "",  # Returns "" when name is nested under "function"

---

# config.yaml
model_list:
  - model_name: deepseek-v4-pro
    litellm_params:
      model: deepseek/deepseek-v4-pro
      api_key: os.environ/DEEPSEEK_API_KEY
      drop_params: true
      use_chat_completions_api: true

---

curl -X POST http://localhost:4000/v1/responses \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4-pro",
    "input": "list files",
    "max_output_tokens": 20,
    "tools": [
      {"type": "custom", "name": "shell", "description": "run shell"},
      {"type": "function", "function": {"name": "read", "description": "read file", "parameters": {"type": "object", "properties": {"path": {"type": "string"}}}}}
    ]
  }'

---

BadRequestError: DeepseekException - Invalid 'tools[0].function.name': empty string

---

# Fix 1: Drop unknown tool types instead of forwarding
else:
    pass  # Drop custom/shell/other unsupported types

# Fix 2: Read function name from both formats
elif tool.get("type") == "function":
    typed_tool = cast(FunctionToolParam, tool)
    _fn = tool.get("function") or {}
    parameters = dict(_fn.get("parameters") or typed_tool.get("parameters") or {})
    if not parameters or "type" not in parameters:
        parameters["type"] = "object"
    chat_completion_tool: Dict[str, Any] = {
        "type": "function",
        "function": {
            "name": _fn.get("name") or typed_tool.get("name") or "",
            "description": _fn.get("description") or typed_tool.get("description") or "",
            "parameters": parameters,
            "strict": _fn.get("strict") or typed_tool.get("strict") or False,
        },
    }
RAW_BUFFERClick to expand / collapse

What happened?

When using use_chat_completions_api: true to bridge the Responses API to Chat Completions for providers that don't support /v1/responses (e.g., DeepSeek, Z.AI/GLM, MiniMax), two bugs in transform_responses_api_tools_to_chat_completion_tools cause failures:

Bug 1: Unsupported tool types forwarded as-is

When Codex or other clients send tools with type: "custom" (a valid Responses API tool type per the OpenAI SDK's CustomToolParam), the else branch in transform_responses_api_tools_to_chat_completion_tools passes them through unchanged:

# Current code (line ~1411)
else:
    chat_completion_tools.append(
        cast(Union[ChatCompletionToolParam, OpenAIMcpServerTool], tool)
    )

Providers like DeepSeek reject this: tools[0].type: unknown variant 'custom', expected 'function'.

Bug 2: Function tool names lost during transformation

When clients send tools in Chat Completion format nested under the Responses API (which Codex does), the transformation only reads name from the top level of the tool dict:

# Current code (line ~1394)
"name": typed_tool.get("name") or "",  # Returns "" when name is nested under "function"

The Responses API FunctionToolParam has name at the top level ({"type": "function", "name": "read"}), but Codex sends Chat Completion-style tools even in Responses API requests: {"type": "function", "function": {"name": "read", ...}}. The transformation ignores the nested function.name, resulting in tools[0].function.name: empty string.

Expected behavior

  1. Non-standard tool types (custom, shell, etc.) should be dropped during Responses API → Chat Completions transformation, not forwarded.
  2. Function tool names should be read from both tool.name (Responses API format) AND tool.function.name (Chat Completion format) to handle both styles.

Reproduction

# config.yaml
model_list:
  - model_name: deepseek-v4-pro
    litellm_params:
      model: deepseek/deepseek-v4-pro
      api_key: os.environ/DEEPSEEK_API_KEY
      drop_params: true
      use_chat_completions_api: true
curl -X POST http://localhost:4000/v1/responses \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4-pro",
    "input": "list files",
    "max_output_tokens": 20,
    "tools": [
      {"type": "custom", "name": "shell", "description": "run shell"},
      {"type": "function", "function": {"name": "read", "description": "read file", "parameters": {"type": "object", "properties": {"path": {"type": "string"}}}}}
    ]
  }'

Results in:

BadRequestError: DeepseekException - Invalid 'tools[0].function.name': empty string

Proposed fix

In litellm/responses/litellm_completion_transformation/transformation.py, transform_responses_api_tools_to_chat_completion_tools:

# Fix 1: Drop unknown tool types instead of forwarding
else:
    pass  # Drop custom/shell/other unsupported types

# Fix 2: Read function name from both formats
elif tool.get("type") == "function":
    typed_tool = cast(FunctionToolParam, tool)
    _fn = tool.get("function") or {}
    parameters = dict(_fn.get("parameters") or typed_tool.get("parameters") or {})
    if not parameters or "type" not in parameters:
        parameters["type"] = "object"
    chat_completion_tool: Dict[str, Any] = {
        "type": "function",
        "function": {
            "name": _fn.get("name") or typed_tool.get("name") or "",
            "description": _fn.get("description") or typed_tool.get("description") or "",
            "parameters": parameters,
            "strict": _fn.get("strict") or typed_tool.get("strict") or False,
        },
    }

Why drop_params: true doesn't fix this

drop_params strips unsupported top-level request parameters. It does not traverse the tools array to filter tool types or fix tool field mapping.

LiteLLM version

v1.83.14

Related issues

  • #23825 (custom tool fields forwarded to Bedrock — same class of bug, different provider path)

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

  1. Non-standard tool types (custom, shell, etc.) should be dropped during Responses API → Chat Completions transformation, not forwarded.
  2. Function tool names should be read from both tool.name (Responses API format) AND tool.function.name (Chat Completion format) to handle both styles.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

litellm - 💡(How to fix) Fix [Bug]: Responses API → Chat Completions bridge drops tool names and forwards unsupported tool types (custom, shell) [1 participants]