NVIDIA-NeMo / NVIDIA-NeMo/Switchyard

Responses backend rejects plaintext reasoning after cross-provider routing

Open
#481 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
3.2k
Forks
291
Avg merge
1d 8h
Merged PRs (30d)
182

Description

Problem

When a stage router switches between a local OpenAI Responses-compatible model and a strict OpenAI Responses backend, provider-specific reasoning items can make the next target reject otherwise valid conversation history.

Local to strict OpenAI

Some local servers, including llama.cpp, return reasoning items with plaintext content and an empty encrypted_content. Replaying that history to the strict backend fails with:

Invalid 'input[3].content': array too long. Expected an array with maximum length 0, but got an array with length 1 instead.
Strict OpenAI to local

The reverse handoff has a separate incompatibility. A signed, encrypted reasoning item normalized for strict OpenAI cannot be consumed by llama.cpp:

item['content'] is not an array

Adding an empty array is also rejected:

item['content'] is empty

Expected behavior

Switchyard should normalize Responses reasoning items for the selected target while preserving messages, function calls, and function-call outputs:

  • With the default responses_reasoning = "preserve_encrypted" policy, remove unsigned/plaintext reasoning items and retain signed encrypted reasoning with an empty plaintext content array.
  • With an explicit responses_reasoning = "drop" policy, remove all reasoning items for a local backend that cannot consume another provider's encrypted representation.
  • Preserve the remaining conversation and tool-call history in order.

The policy is explicit rather than inferred from model names, URLs, or authentication, so authenticated local servers and unauthenticated hosted-compatible servers can be configured correctly.

Implementation and validation

A focused implementation is available on the fork branch:

https://github.com/srchandrupatla/Switchyard/tree/fix/responses-reasoning-handoff

Relevant commits:

  • 30c648c2 — sanitize unsigned Responses reasoning handoffs
  • ab9b9e8f — add the explicit, model-agnostic Responses reasoning replay policy
  • 0971862f — keep the existing public backend configuration API source-compatible

Validation completed so far:

  • Formatting and Clippy pass.
  • 55 switchyard-llm-client unit tests pass.
  • 11 client observability tests pass.
  • The full non-PyO3 Rust workspace test suite passes.
  • Dedicated HTTP-level regression tests cover both handoff directions.
  • A release build on macOS passed a real llama.cpp cloud-to-local request with HTTP 200.
  • The reverse local-to-gpt-5.6-sol request also completed successfully.
  • Two isolated 20-request bidirectional stress runs completed with 20/20 successes.

I am continuing production stress testing before opening a PR.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the fix/responses-reasoning-handoff branch and commits 30c648c2, ab9b9e8f, and 0971862f, then review the dedicated HTTP-level regression tests for both handoff directions. Run the Rust workspace tests and verify that the configured policy preserves valid encrypted reasoning or drops incompatible reasoning while retaining messages and tool-call history in order.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend-api-design
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.