Skip to content

fix(agent): fall back to non-streaming on JSONDecodeError - #24532

Open
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:fix/issue-24523-llmgateway-stream-json-error
Open

liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:fix/issue-24523-llmgateway-stream-json-error

Conversation

@liuhao1024

@liuhao1024 liuhao1024 commented May 12, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

When using custom:llmgateway (or similar providers) with streaming enabled, tool-call turns can fail with json.JSONDecodeError: Expecting value: line 1 column 1 (char 0). The agent retries the same streaming path multiple times, then the tool workflow fails entirely.

Root Cause

The streaming error handling in _interruptible_streaming_api_call only recognizes "stream not supported" as a signal to fall back to non-streaming. When a provider returns malformed or empty SSE responses for tool-call turns (raising json.JSONDecodeError), the error is treated as a generic failure and retried with the same streaming path — which fails identically each time.

Related Issue

N/A

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • See commit messages for detailed changes

How to Test

  1. Run pytest tests/ -q — all tests should pass
  2. Verify the specific scenario described above is resolved

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 26.4.1

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture and workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A

Some providers (e.g. custom:llmgateway) return malformed or empty
SSE responses when streaming tool-call turns, raising
json.JSONDecodeError ("Expecting value: line 1 column 1 (char 0)").

Previously, only "stream not supported" errors triggered the
automatic fallback to non-streaming. This adds JSONDecodeError
to the same fallback path so the session switches to non-streaming
for subsequent requests.

Fixes NousResearch#24523
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists labels May 12, 2026
@teknium1

Copy link
Copy Markdown
Collaborator

Thanks for isolating the streaming failure mode. The underlying fallback is still absent on current main: run_agent.py:1093-1109 explicitly excludes JSONDecodeError from the malformed-stream classifier, while agent/chat_completion_helpers.py:2816-2865 only sets _disable_streaming for unsupported-stream or Bedrock-IAM errors.

Problems

  • The production hunk is stale. run_agent.py:4764-4769 is now only a forwarder; commit 0430e71ec moved the streaming caller to agent/chat_completion_helpers.py. The current retry branch is at agent/chat_completion_helpers.py:2619-2865.
  • The added test checks only the flag after a direct helper call. It does not verify the next loop iteration takes the non-streaming branch at agent/conversation_loop.py:1276-1316.
  • The change makes any raw JSONDecodeError session-wide transport state. The linked [Bug]: streaming gets disabled for whole session on one error and causes errors, should be by request #25723 discussion flags session-wide disablement as a possible problem, so the intended scope needs an explicit decision and regression coverage.

Suggested changes

  • Rebase the logic conceptually onto agent/chat_completion_helpers.py and test streaming-to-non-streaming routing through the conversation loop.
  • Narrow and document the incompatibility signature and fallback lifetime.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit area/streaming Streaming responses: gateway delivery, provider wire labels Jul 13, 2026
Expri-commits pushed a commit to Expri-commits/hermes-agent that referenced this pull request Sep 3, 2026
…ssion (NousResearch#25723)

One transient 'stream not supported' error permanently poisoned the
whole session into silent non-streaming calls — fatal behind proxies
that kill idle connections (z.ai edge ~180-210s → HTTP 524, kills
subagent/cron turns that Claude Code/OpenCode never see because they
always stream). Mirrors upstream PR NousResearch#25771 (unmerged); reset placed at
the top of agent.conversation_loop.run_conversation so every entry
point (CLI, gateway, subagent children, review forks) re-attempts
streaming each turn while the within-turn retry fallback (NousResearch#24532)
still works. Local-patches carry until upstream merges.
Expri-commits pushed a commit to Expri-commits/hermes-agent that referenced this pull request Sep 3, 2026
…ssion (NousResearch#25723)

One transient 'stream not supported' error permanently poisoned the
whole session into silent non-streaming calls — fatal behind proxies
that kill idle connections (z.ai edge ~180-210s → HTTP 524, kills
subagent/cron turns that Claude Code/OpenCode never see because they
always stream). Mirrors upstream PR NousResearch#25771 (unmerged); reset placed at
the top of agent.conversation_loop.run_conversation so every entry
point (CLI, gateway, subagent children, review forks) re-attempts
streaming each turn while the within-turn retry fallback (NousResearch#24532)
still works. Local-patches carry until upstream merges.
Expri-commits pushed a commit to Expri-commits/hermes-agent that referenced this pull request Sep 4, 2026
…ssion (NousResearch#25723)

One transient 'stream not supported' error permanently poisoned the
whole session into silent non-streaming calls — fatal behind proxies
that kill idle connections (z.ai edge ~180-210s → HTTP 524, kills
subagent/cron turns that Claude Code/OpenCode never see because they
always stream). Mirrors upstream PR NousResearch#25771 (unmerged); reset placed at
the top of agent.conversation_loop.run_conversation so every entry
point (CLI, gateway, subagent children, review forks) re-attempts
streaming each turn while the within-turn retry fallback (NousResearch#24532)
still works. Local-patches carry until upstream merges.
Expri-commits pushed a commit to Expri-commits/hermes-agent that referenced this pull request Sep 10, 2026
…ssion (NousResearch#25723)

One transient 'stream not supported' error permanently poisoned the
whole session into silent non-streaming calls — fatal behind proxies
that kill idle connections (z.ai edge ~180-210s → HTTP 524, kills
subagent/cron turns that Claude Code/OpenCode never see because they
always stream). Mirrors upstream PR NousResearch#25771 (unmerged); reset placed at
the top of agent.conversation_loop.run_conversation so every entry
point (CLI, gateway, subagent children, review forks) re-attempts
streaming each turn while the within-turn retry fallback (NousResearch#24532)
still works. Local-patches carry until upstream merges.
Expri-commits pushed a commit to Expri-commits/hermes-agent that referenced this pull request Sep 15, 2026
…ssion (NousResearch#25723)

One transient 'stream not supported' error permanently poisoned the
whole session into silent non-streaming calls — fatal behind proxies
that kill idle connections (z.ai edge ~180-210s → HTTP 524, kills
subagent/cron turns that Claude Code/OpenCode never see because they
always stream). Mirrors upstream PR NousResearch#25771 (unmerged); reset placed at
the top of agent.conversation_loop.run_conversation so every entry
point (CLI, gateway, subagent children, review forks) re-attempts
streaming each turn while the within-turn retry fallback (NousResearch#24532)
still works. Local-patches carry until upstream merges.
Expri-commits pushed a commit to Expri-commits/hermes-agent that referenced this pull request Sep 17, 2026
…ssion (NousResearch#25723)

One transient 'stream not supported' error permanently poisoned the
whole session into silent non-streaming calls — fatal behind proxies
that kill idle connections (z.ai edge ~180-210s → HTTP 524, kills
subagent/cron turns that Claude Code/OpenCode never see because they
always stream). Mirrors upstream PR NousResearch#25771 (unmerged); reset placed at
the top of agent.conversation_loop.run_conversation so every entry
point (CLI, gateway, subagent children, review forks) re-attempts
streaming each turn while the within-turn retry fallback (NousResearch#24532)
still works. Local-patches carry until upstream merges.
Expri-commits pushed a commit to Expri-commits/hermes-agent that referenced this pull request Sep 17, 2026
…ssion (NousResearch#25723)

One transient 'stream not supported' error permanently poisoned the
whole session into silent non-streaming calls — fatal behind proxies
that kill idle connections (z.ai edge ~180-210s → HTTP 524, kills
subagent/cron turns that Claude Code/OpenCode never see because they
always stream). Mirrors upstream PR NousResearch#25771 (unmerged); reset placed at
the top of agent.conversation_loop.run_conversation so every entry
point (CLI, gateway, subagent children, review forks) re-attempts
streaming each turn while the within-turn retry fallback (NousResearch#24532)
still works. Local-patches carry until upstream merges.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/streaming Streaming responses: gateway delivery, provider wire comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants