Skip to content

fix(responses): keep flattened namespace tool names within 64 characters - #46243

Open
Matt17BR wants to merge 3 commits into
BerriAI:mainfrom
Matt17BR:fix/responses-namespace-tool-name-length
Open

Matt17BR wants to merge 3 commits into
BerriAI:mainfrom
Matt17BR:fix/responses-namespace-tool-name-length

Conversation

@Matt17BR

@Matt17BR Matt17BR commented Oct 13, 2026 •

Copy link
Copy Markdown

TLDR

Problem this solves:

  • /v1/responses bridged to chat completions fails with a 400 on long namespace tool names
  • Bedrock Converse and OpenAI chat completions cap function names at 64 characters
  • Codex MCP and app connector tools flatten to names like mcp__codex_apps__atlassian_rovo__legacy__atlassian_rovo_legacy_addcommenttojiraissue (84 characters)

How it solves it:

  • One helper builds every flattened namespace__tool name the bridge sends or maps back
  • Over 64 characters, it shortens the namespace to a prefix plus an 8 character sha256 digest of the full name and keeps the tool name, e.g. mcp__codex_394c900c__atlassian_rovo_legacy_addcommenttojiraissue
  • A tool name too long on its own is truncated before the digest instead
  • Outbound tools, replayed history, the collision check and the reverse map all use the helper, so tool calls still come back with their original namespace and name

User Flow

Before: a developer running Codex through a LiteLLM proxy on a Bedrock Converse model gets a 400 on every turn once an MCP server or app connector with a long name is loaded

  1. They point Codex at the proxy (wire_api = "responses") with a model served by bedrock/converse or any chat completions provider
  2. They register an MCP server whose name makes mcp__<server>__<tool> longer than 64 characters, or Codex loads its app connectors
  3. They send any prompt, Codex sends POST http://localhost:4000/v1/responses with the namespaced tools
  4. The turn fails with HTTP 400 Member must have length less than or equal to 64 naming the tool

After: the same turn works and the model can call the long-named tool

  1. They point Codex at the proxy (wire_api = "responses") with a model served by bedrock/converse or any chat completions provider
  2. They register an MCP server whose name makes mcp__<server>__<tool> longer than 64 characters, or Codex loads its app connectors
  3. They send any prompt, Codex sends POST http://localhost:4000/v1/responses with the namespaced tools
  4. The model calls the tool, the proxy returns it under its original namespace and name, and Codex runs it

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/unit/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • Every automated review on my PR's latest commit has finished, and I have addressed or replied to each of its findings before requesting a maintainer review

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Shared setup. The proxy runs from this branch with python litellm/proxy/proxy_cli.py --config config.yaml --port 4000. The upstream is an OpenAI compatible gateway serving GLM 5 from Bedrock Converse (real calls, the 64 character check comes from Bedrock), reached through custom_openai/ so the request goes through the Responses to chat completions bridge

model_list:
  - model_name: glm-5
    litellm_params:
      model: custom_openai/glm-5
      api_base: https://<openai-compatible-gateway>/v1
      api_key: os.environ/UPSTREAM_API_KEY
general_settings:
  master_key: sk-local-qa

body.json (the streaming case is the same with "stream": true)

{
  "model": "glm-5",
  "input": "Add the comment 'hi' to Jira issue ABC-1 using the tool.",
  "tools": [
    {
      "type": "namespace",
      "name": "mcp__codex_apps__atlassian_rovo__legacy",
      "description": "Atlassian tools",
      "tools": [
        {
          "type": "function",
          "name": "atlassian_rovo_legacy_addcommenttojiraissue",
          "description": "Add a comment to a Jira issue",
          "parameters": {
            "type": "object",
            "properties": {"issue": {"type": "string"}, "body": {"type": "string"}},
            "required": ["issue", "body"]
          }
        }
      ]
    }
  ]
}

For the Codex case, a one tool stdio MCP server (FastMCP, get_build_status(pipeline) returning a fixed string, annotated read only) is registered under a long server name, so Codex sends mcp__celonis_internal_build_and_release_dashboard_server__get_build_status (74 characters). Codex 0.162.1 runs interactively in a terminal and is pointed at the proxy with

codex \
  -c 'model_providers.localqa={name="local litellm",base_url="http://localhost:4000/v1",wire_api="responses",env_key="LOCAL_LITELLM_KEY",supports_websockets=false}' \
  -c model_provider=localqa -c features.apps=false \
  -c 'mcp_servers.celonis_internal_build_and_release_dashboard_server={command="uv",args=["run","--no-project","--with","mcp<2","python","mcp_server.py"]}' \
  -m glm-5

The prompt typed into Codex is Use the build dashboard MCP tool to get the status of the ai-gateway pipeline and reply with the build number only. The Codex output below is a text capture of the terminal pane (tmux capture-pane)

Before (79b93aa)

Non-streaming /v1/responses

  1. curl -sS http://localhost:4000/v1/responses -H "Authorization: Bearer sk-local-qa" -H "Content-Type: application/json" -d @body.json -w '\nHTTP %{http_code}\n'
  2. Output (truncated)
{"error":{"message":"litellm.BadRequestError: Custom_openaiException - litellm.BadRequestError: BedrockException - {\"message\":\"1 validation error detected: Value 'mcp__codex_apps__atlassian_rovo__legacy__atlassian_rovo_legacy_addcommenttojiraissue' at 'toolConfig.tools.1.member.toolSpec.name' failed to satisfy constraint: Member must have length less than or equal to 64\"}\n\nLiteLLM: model group 'glm-5' failed wi...
HTTP 400

Streaming /v1/responses

  1. curl -sS -N http://localhost:4000/v1/responses -H "Authorization: Bearer sk-local-qa" -H "Content-Type: application/json" -d @body_stream.json -w '\nHTTP %{http_code}\n'
  2. Output (truncated)
{"error":{"message":"litellm.BadRequestError: Custom_openaiException - litellm.BadRequestError: BedrockException - {\"message\":\"1 validation error detected: Value 'mcp__codex_apps__atlassian_rovo__legacy__atlassian_rovo_legacy_addcommenttojiraissue' at 'toolConfig.tools.1.member.toolSpec.name' fai...
HTTP 400

Codex with a long-named MCP server

  1. Start Codex with the command above and type the prompt
  2. Codex pane
› Use the build dashboard MCP tool to get the status of the ai-gateway pipeline and reply with the build number only.
■ {"error":{"message":"litellm.BadRequestError: Custom_openaiException - litellm.BadRequestError: BedrockException - {\"message\":\"1 validation error detected:
Value 'mcp__celonis_internal_build_and_release_dashboard_server__get_build_status' at 'toolConfig.tools.62.member.toolSpec.name' failed to satisfy constraint:
Member must have length less than or equal to 64\"}\n\nLiteLLM: model group 'glm-5' failed with the error above. Fallback to glm-4.7 also failed:
litellm.BadRequestError: BedrockException - {\"message\":\"1 validation error detected: Value
'mcp__celonis_internal_build_and_release_dashboard_server__get_build_status' at 'toolConfig.tools.62.member.toolSpec.name' failed to satisfy constraint: Member
must have length less than or equal to 64\"}\n\nLiteLLM: model group 'glm-5' failed with the error above. No fallback was
attempted.","type":"invalid_request_error","param":null,"code":"400"}}

After (1f0d979)

Non-streaming /v1/responses

  1. curl -sS http://localhost:4000/v1/responses -H "Authorization: Bearer sk-local-qa" -H "Content-Type: application/json" -d @body.json -w '\nHTTP %{http_code}\n'
  2. Output, function_call items of output
HTTP 200
{"type": "function_call", "namespace": "mcp__codex_apps__atlassian_rovo__legacy", "name": "atlassian_rovo_legacy_addcommenttojiraissue", "arguments": "{\"issue\": \"ABC-1\", \"body\": \"hi\"}"}

Streaming /v1/responses

  1. curl -sS -N http://localhost:4000/v1/responses -H "Authorization: Bearer sk-local-qa" -H "Content-Type: application/json" -d @body_stream.json -w '\nHTTP %{http_code}\n'
  2. Output, the function_call item of the response.output_item.done event
HTTP 200
{"type": "function_call", "namespace": "mcp__codex_apps__atlassian_rovo__legacy", "name": "atlassian_rovo_legacy_addcommenttojiraissue", "arguments": "{\"issue\": \"ABC-1\", \"body\": \"hi\"}"}

Codex with a long-named MCP server

  1. Start Codex with the command above and type the prompt
  2. Codex pane, the model called the tool under its shortened name and Codex ran it under the original one
› Use the build dashboard MCP tool to get the status of the ai-gateway pipeline and reply with the build number only.
• Called celonis_internal_build_and_release_dashboard_server.get_build_status
  └ pipeline ai-gateway: build #4217 passed at 18:02 UTC
    + Show details
• 4217
  Worked for 32s • 7:18 PM

Unit test mutation check, as tests/AGENTS.md asks. With the helper reverted to a plain f"{namespace}__{tool_name}", test_long_namespace_tool_names_fit_limit_and_round_trip fails on the 64 character assertion. With a version that cuts the flattened name from the end, it fails on the assertion that the tool name survives. tests/unit/responses, tests/unit/llms/bedrock/responses, tests/unit/llms/openai/responses and tests/unit/llms/bedrock_mantle give 1474 passed at the merge base and 1475 passed here, with the same single failure on both sides (tests/unit/responses/test_dispatch.py::test_responses_with_retries_uses_the_dispatch_entrypoint, unrelated)

Type

🐛 Bug Fix

Caveats (if any)

Low

  • Providers that accept longer names (Anthropic allows 128) also get the shortened name above 64 characters
  • The model sees a digest inside the namespace part for long names, the tool name itself stays readable
  • Upstream reached through an OpenAI compatible gateway in front of Bedrock, not direct Bedrock credentials

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

The Responses to chat completions bridge flattens namespace tools to
"<namespace>__<tool>". Codex app connector tools produce names like
mcp__codex_apps__atlassian_rovo__legacy__atlassian_rovo_legacy_addcommenttojiraissue
(84 characters), and Bedrock Converse and OpenAI chat completions reject
function names over 64 characters, so the whole request fails with a 400

Names over the limit now keep a 55 character prefix plus an 8 character
sha256 digest of the full name. The same helper builds outbound tools,
replayed history, the collision check, and the reverse map, so tool calls
still restore to their original namespace and name
… tool names

Shortening from the end cut every Codex Rovo legacy tool down to the same
55 character prefix plus a digest, so a model could not tell them apart by
name. Shorten the namespace instead and keep the tool name, falling back to
truncating a tool name that is too long on its own
@CLAassistant

CLAassistant commented Oct 13, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@codspeed

codspeed Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

⚠️ 1 benchmark spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 31 untouched benchmarks


Comparing Matt17BR:fix/responses-namespace-tool-name-length (1f0d979) with main (22c9e38)

Open in CodSpeed

@codecov

codecov Bot commented Oct 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@VANDRANKI VANDRANKI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Community review (does not clear the merge gate).

I read the full diff and ran flatten_namespace_tool_name and the Responses to chat tool transformation on Python 3.13 from this branch. I did not run an actual Bedrock or OpenAI call.

What I checked:

  • Boundary: names up to 64 characters are returned unchanged (mcp__codex_apps__atlassian_rovo__legacy__short stays as is), so existing short tools keep their names. On main the namespace mcp__codex_apps__atlassian_rovo__legacy plus either of the two atlassian_rovo_legacy_add... tool names in the PR test flattens to 84 characters (computed from the f"{namespace}__{tool_name}" format), over the 64 limit.
  • On this branch those two names become mcp__codex_394c900c__atlassian_rovo_legacy_addcommenttojiraissue and mcp__codex_410c820b__...addworklogtojiraissue, both exactly 64 characters and distinct.
  • A 60-character tool name, a 63, 64 and 70-character tool name, and two namespaces sharing a long prefix with the same tool name all produced names of at most 64 characters and different digests where inputs differed (the fallback branch drops the namespace and keeps a truncated tool name plus digest).
  • Every place that built f"{namespace}__{tool_name}" now goes through the helper: outbound tools, replayed function_call history, _validate_namespace_name_collisions, and namespace_tool_name_map. I grepped litellm/responses and litellm/llms/*/responses and found no other site. The collision check still fires on the flattened long name: a top-level function with that exact flattened name raises ValueError: Top-level function names conflict with flattened namespace tools.
  • tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py: 226 passed on the branch. The directory also contains test_google_ai_studio_responses_wire.py, which errors at collection in my environment; I did not look into it and it is not related to this diff.

Observations, not blockers:

  • The namespace is cut to a short prefix for long names (mcp__codex in the example), so the model sees less of the original namespace. The tool name stays readable, which the docstring says is the intent.
  • tool_choice handling (_transform_tool_choice) has no namespace awareness, before or after this change, so I did not test it with a flattened name.

The change looks correct to me.

This branch has not been deployed

No deployments
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.

3 participants