NousResearch/hermes-agent
Gemini streaming incompatibility with stream_options argument
Cluster 14387 · agent. Data as of
Nothing open on GitHub. Fix merged in #54653 on 29 Jun 2026. Check it covers your case.
The short answer
0open issues
0open PRs
4items grouped
- Known?
- Yes. 4 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #54653. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #54653. That is separate from the fix event. - Still open
- Nothing open on GitHub.
A bug was identified where stream_options is passed to Gemini's completions API, which does not support that keyword argument. Multiple fix attempts address omitting stream_options specifically for Gemini streaming calls.
Open issues0
Open PRs0
Fixes on record
-
#54653 fix(agent): omit stream_options for native Gemini streaming
files:
agent/chat_completion_helpers.py,tests/run_agent/test_streaming.pyShow the diff
-
agent/chat_completion_helpers.py@@ -28,6 +28,7 @@ from hermes_cli.timeouts import get_provider_request_timeout, get_provider_stale_timeout from hermes_constants import PARTIAL_STREAM_STUB_ID, FINISH_REASON_LENGTH from agent.error_classifier import FailoverReason +from agent.gemini_native_adapter import is_native_gemini_base_url from agent.model_metadata import is_local_endpoint from agent.message_sanitization import ( _sanitize_surrogates, @@ -1911,7 +1912,6 @@ def _call_chat_completions(): stream_kwargs = { **api_kwargs, "stream": True, - "stream_options": {"include_usage": True}, "timeout": _httpx.Timeout( connect=_conn_cap, read=_stream_read_timeout, @@ -1919,6 +1919,14 @@ def _call_chat_completions(): pool=_conn_cap, ), } + # OpenAI's `stream_options={"include_usage": True}` drives usage + # accounting on OpenAI-compatible endpoints (incl. the Gemini OpenAI + # compat shim and aggregators like OpenRouter). Google's *native* + # Gemini REST endpoint rejects the keyword outright + # (`Completions.create() got an unexpected keyword argument + # 'stream_options'`), so omit it only for that endpoint. + if not is_native_gemini_base_url(agent.base_url): + stream_kwargs["stream_options"] = {"include_usage": True} request_client = _set_request_client( agent._create_request_openai_client( reason="chat_completion_stream_request", -
tests/run_agent/test_streaming.py@@ -95,6 +95,99 @@ def test_text_only_response(self, mock_close, mock_create): assert response.usage is not None assert response.usage.completion_tokens == 3 + @patch("run_agent.AIAgent._create_request_openai_client") + @patch("run_agent.AIAgent._close_request_openai_client") + def test_native_gemini_endpoint_omits_stream_options(self, mock_close, mock_create): + """Google's native Gemini REST endpoint rejects OpenAI-only stream_options.""" + from run_agent import AIAgent + + mock_client = MagicMock() + mock_client.chat.completions.create.return_value = iter([ + _make_stream_chunk(content="Paris", finish_reason="stop", model="gemini"), + ]) + mock_create.return_value = mock_client + + agent = AIAgent( + api_key="[redacted:secret-assignment]", + base_url="https://generativelanguage.googleapis.com/v1beta", + model="gemini-3-flash-preview", + provider="gemini", + quiet_mode=True, + skip_context_files=True, + skip_memory=True, + ) + agent.api_mode = "chat_completions" + agent._interrupt_requested = False + + response = agent._interruptible_streaming_api_call({}) + + assert response.choices[0].message.content == "Paris" + call_kwargs = mock_client.chat.completions.create.call_args.kwargs + assert call_kwargs["stream"] is True + assert "stream_options" not in call_kwargs + + @patch("run_agent.AIAgent._create_request_openai_client") + @patch("run_agent.AIAgent._close_request_openai_client") + def test_gemini_openai_compat_shim_keeps_stream_options(self, mock_close, mock_create): + """The Gemini OpenAI-compat shim (.../openai) accepts stream_options.""" + from run_agent import AIAgent + + mock_client = MagicMock() + mock_client.chat.completions.create.return_value = iter([ + _make_stream_chunk(content="ok", finish_reason="stop", model="gemini"), + _make_empty_chunk(usage=SimpleNamespace(prompt_tokens=2, completion_tokens=1)), + ]) + mock_create.return_value = mock_client + + agent = AIAgent( + api_key="[redacted:secret-assignment]", + base_url="https://generativelanguage.googleapis.com/v1beta/openai", + model="gemini-3-flash-preview", + provider="gemini", + quiet_mode=True, + skip_context_files=True, + skip_memory=True, + ) + agent.api_mode = "chat_completions" + agent._interrupt_requested = False + + response = agent._interruptible_streaming_api_call({}) + + assert response.choices[0].message.content == "ok" + call_kwargs = mock_client.chat.completions.create.call_args.kwargs + assert call_kwargs["stream_options"] == {"include_usage": True} + + @patch("run_agent.AIAgent._create_request_openai_client") + @patch("run_agent.AIAgent._close_request_openai_client") + def test_openai_compatible_streaming_keeps_stream_options(self, mock_close, mock_create): + """OpenAI-compatible aggregators still request final usage chunks.""" + from run_agent import AIAgent + + mock_client = MagicMock() + mock_client.chat.completions.create.return_value = iter([ + _make_stream_chunk(content="ok", finish_reason="stop", model="test-model"), + _make_empty_chunk(usage=SimpleNamespace(prompt_tokens=2, completion_tokens=1)), + ]) + mock_create.return_value = mock_client + + agent = AIAgent( + api_key="[redacted:secret-assignment]", + base_url="https://openrouter.ai/api/v1", + model="test/model", + provider="openrouter", + quiet_mode=True, + skip_context_files=True, + skip_memory=True, + ) + agent.api_mode = "chat_completions" + agent._interrupt_requested = False + + response = agent._interruptible_streaming_api_call({}) + + assert response.choices[0].message.content == "ok" + call_kwargs = mock_client.chat.completions.create.call_args.kwargs + assert call_kwargs["stream_options"] == {"include_usage": True} + @patch("run_agent.AIAgent._create_request_openai_client") @patch("run_agent.AIAgent._close_request_openai_client") def test_tool_call_response(self, mock_close, mock_create):
-
2 PRs closed without merging
From the close comments: duplicate 1 · merged via another change 1
Closed issues · 1
- Closed#14387[Bug]: 📝 Error: Completions.create() got an unexpected keyword argument 'stream_options'
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.50: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- agent
- Merged PRs
- #54653
- Closed issues
- 1
- Attempts
- 2 PRs closed without merging
- Fix link checked
- 29 Jun 2026