You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: Switchable streaming and non-streaming model output (#170)
- Updated `run_model` and `run_model_stream` hooks to allow for both synchronous and asynchronous execution.
- Introduced a `stream_output` flag in `ChannelManager` and `BubFramework` to control streaming behavior.
- Modified `process_inbound` to handle streaming output based on the new flag.
- Enhanced `Agent` class to support streaming output through `run_stream` method.
- Updated documentation to reflect changes in streaming capabilities and usage.
- Added tests to verify the correct behavior of streaming and non-streaming executions.
Signed-off-by: Frost Ming <me@frostming.com>
6.For each stream event, call `OutboundChannelRouter.dispatch_event(...)`, which forwards to `channel.on_event(event, message)` when the target channel exists.
19
+
5. Execute `run_model(prompt, session_id, state)` by default, or `run_model_stream(prompt, session_id, state)` when the caller opts into streaming.
20
+
6.In streaming mode, forward each stream event through the outbound router before collecting final text.
21
21
7. Always execute `save_state(...)` in a `finally` block.
22
22
8. Render outbound batches via `render_outbound(...)`, then flatten them.
23
23
9. If no outbound exists, emit one fallback outbound.
24
24
10. Dispatch each outbound via `dispatch_outbound(message)`.
25
25
26
-
If no plugin implements `run_model_stream`, `HookRuntime` falls back to `run_model(prompt, session_id, state)`and adapts the returned text into a stream with a single text chunk.
26
+
`HookRuntime` keeps both directions compatible: `run_model()` can consume a streaming plugin by concatenating text chunks, and `run_model_stream()` can consume a plain `run_model()`plugin by adapting its text into a single-chunk stream.
27
27
28
28
## Hook Priority Semantics
29
29
@@ -50,7 +50,8 @@ If no plugin implements `run_model_stream`, `HookRuntime` falls back to `run_mod
50
50
Builtin `BuiltinImpl` behavior includes:
51
51
52
52
-`build_prompt`: supports comma command mode; non-command text may include `context_str`.
53
-
-`run_model_stream`: delegates to `Agent.run()`.
53
+
-`run_model`: delegates to `Agent.run()`.
54
+
-`run_model_stream`: delegates to `Agent.run_stream()`.
54
55
-`system_prompt`: combines a default prompt with workspace `AGENTS.md`.
55
56
-`register_cli_commands`: installs `run`, `gateway`, `chat`, plus hidden diagnostic commands.
56
57
-`provide_channels`: returns `telegram` and `cli` channel adapters.
@@ -67,6 +68,8 @@ Channels have two different outbound surfaces:
67
68
68
69
If a channel does not implement any special event behavior, it can ignore `on_event` and rely entirely on `send()`.
69
70
71
+
Channel streaming is opt-in through `BUB_STREAM_OUTPUT=true` (used by `ChannelManager`). When disabled, channels only receive the final rendered outbound message.
@@ -43,7 +49,7 @@ Channel adapters can receive outbound data in two forms:
43
49
44
50
Use `on_event` for incremental UX such as live text updates, typing indicators, progress bars, or chunk-level logging. Use `send` for the final durable outbound payload.
45
51
46
-
`on_event` is optional. A channel that does not need streaming behavior can ignore it and only implement `send`.
52
+
`on_event` is optional. A channel that does not need streaming behavior can ignore it and only implement `send`.`ChannelManager` only forwards stream events when `BUB_STREAM_OUTPUT=true`; otherwise channels receive final outbounds only.
Copy file name to clipboardExpand all lines: docs/extension-guide.md
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -100,16 +100,16 @@ Current `process_inbound()` hook usage:
100
100
1.`resolve_session` (`call_first`)
101
101
2.`load_state` (`call_many`, then merged by framework)
102
102
3.`build_prompt` (`call_first`)
103
-
4.`run_model_stream` (`call_first`)
103
+
4.`run_model` / `run_model_stream` (`call_first`)
104
104
5.`save_state` (`call_many`, always executed in `finally`)
105
105
6.`render_outbound` (`call_many`)
106
106
7.`dispatch_outbound` (`call_many`, per outbound)
107
107
108
108
Compatibility note:
109
109
110
-
-`run_model_stream` is the primary model hook.
111
-
-If no plugin implements `run_model_stream`, Bub falls back to `run_model`.
112
-
-The `run_model` return value is wrapped into a stream with exactly one text chunk.
110
+
-Bub can execute either `run_model` or `run_model_stream`, depending on whether the caller requests streaming.
111
+
-`HookRuntime.run_model()` can consume a streaming plugin by concatenating text chunks.
112
+
-`HookRuntime.run_model_stream()` can consume a plain `run_model()` implementation by wrapping it into a one-chunk stream.
113
113
- A plugin should implement one of these hooks, not both.
114
114
115
115
Other hook consumers:
@@ -182,8 +182,8 @@ uv run bub hooks
182
182
uv run bub run "hello"
183
183
```
184
184
185
-
Check that your plugin is listed for `build_prompt`/ `run_model_stream`, and output reflects your override.
186
-
If you intentionally use the legacy compatibility hook, check for `run_model`.
185
+
Check that your plugin is listed for `build_prompt`plus whichever model hook you implement, and output reflects your override.
186
+
If you intentionally use the non-streaming path, check for `run_model`; if you need incremental output, check for `run_model_stream`.
187
187
188
188
## 10) Listen To Parent Stream
189
189
@@ -229,7 +229,7 @@ class StreamTapPlugin:
229
229
230
230
Use this when you need to log chunks, redact text, inject extra events, or measure stream timing without reimplementing the underlying model call.
231
231
232
-
If you also need to support parents that only implement legacy `run_model`, add your own fallback path and wrap that text result into a one-chunk stream.
232
+
If you also need to support parents that only implement `run_model`, add your own fallback path and wrap that text result into a one-chunk stream.
`run_model`remains supported as a compatibility hook and is adapted into a single-chunk stream when `run_model_stream` is absent.
40
+
By default Bub executes `run_model`and expects plain text. Streaming remains available through `run_model_stream`, and `HookRuntime` adapts either hook shape to the other for compatibility.
41
41
42
42
Builtins are plugins registered first. Later plugins override earlier ones. No special cases.
0 commit comments