feat(llm): opt-in LLM_DISABLE_STREAMING for non-streaming OpenAI-compatible endpoints - #938
Merged
Conversation
…atible endpoints Some OpenAI-compatible gateways don't support Server-Sent Events (or deliver them unreliably), but the SDK run loop Strix uses only issues streamed requests, so such a gateway fails every turn. Add an opt-in LLM_DISABLE_STREAMING setting that wraps the resolved model in _NonStreamingModel: each turn makes one non-streaming get_response and replays the completed result as a single terminal stream event, so tool calls, usage, and the rest of the agent loop are unchanged. Subscription (ChatGPT) models are always streamed and are not wrapped.
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Contributor
Greptile SummaryAdds an opt-in non-streaming adapter for OpenAI-compatible endpoints while preserving the SDK’s streamed agent loop.
Confidence Score: 5/5The PR appears safe to merge. The previous run-loop testing gap is resolved by exercising the real streamed runner, executing the synthetic tool call, continuing to a second model turn, and asserting the final output; no blocking failure remains. Important Files Changed
Reviews (2): Last reviewed commit: "test(llm): cover the full run loop again..." | Re-trigger Greptile |
…rop README note Adds an integration test that drives Runner.run_streamed against a non-streaming gateway through _NonStreamingModel: the synthetic terminal event feeds the runner, which executes the tool call and continues to a final answer over two non-streaming turns. Removes the README env-var note.
Contributor
Author
0xallam
approved these changes
Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Some OpenAI-compatible gateways don't support streaming (Server-Sent Events), or deliver it unreliably — dropping structured
tool_callsdeltas, or stalling mid-stream so the whole turn waits out the read timeout. The SDK run loop Strix uses (Runner.run_streamed) only ever issues streamed requests, so against such a gateway every turn fails, with no way to opt out.This adds an opt-in
LLM_DISABLE_STREAMING=truethat makes Strix talk to the model without streaming, while keeping the streamed run loop (and therefore tool execution, usage accounting, steering, and the TUI event flow) unchanged.How:
StrixProvider.get_modelwraps the resolved model in a small_NonStreamingModeladapter. Itsstream_responseissues one non-streamingget_response(stream:falseon the wire) and replays the completed result as a single terminalresponse.completedevent:This is safe because the run loop builds its authoritative per-turn
ModelResponsesolely from theresponse.completedevent and then runs the normalget_single_step_result_from_response(...)path — which executes tools and emits run items fromfinal_response.outputexactly as it would for a real stream. So a single completed event carrying the full output + usage is sufficient; the only difference is that per-token deltas no longer stream to the UI (inherent when the gateway can't stream).This mirrors the approach the wider ecosystem converged on for the same problem (e.g. the AI SDK's
simulateStreamingMiddleware: call the non-streaming generate, replay as a simulated stream) — an explicit opt-in rather than auto-detection.Notes / scope:
disable_streaming=False); streaming remains the default.get_response(the non-streamed path, e.g. dedupe) is a plain pass-through.Changes
strix/config/settings.py: newLlmSettings.disable_streamingfield (LLM_DISABLE_STREAMING).strix/config/models.py:_NonStreamingModeladapter +_completed_stream_event/_response_usagehelpers; wired intoStrixProvider.get_model.tests/test_disable_streaming.py: a local gateway that rejectsstream:truebut answers non-streamed requests (including a structured tool call), proving the stock model fails against it while the wrapper succeeds — surfacing the tool call, propagating usage, and never sendingstream:true. Plus a fullRunner.run_streamedintegration test (tool call → tool result → final answer over two non-streaming turns) and unit tests for the get_model wrap/no-wrap and subscription-not-wrapped cases.Testing
pytest tests/test_disable_streaming.py— 8 passed.strix/interface/tui/app.pyare unrelated and present onmain.)Link to Devin session: https://app.devin.ai/sessions/54414ea3ecb34ade9189534e5c690969
Requested by: @0xallam