Skip to content

Commit ee998f9

Browse files
cpsievertgadenbuie
andauthored
Replace .get_latest_stream_result() with a more general .get_message_stream() (#1880)
Co-authored-by: Garrick Aden-Buie <garrick@posit.co>
1 parent c65ca17 commit ee998f9

File tree

4 files changed

+32
-26
lines changed

4 files changed

+32
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2525
* The `ui.Chat()` component also gains the following:
2626
* The `.on_user_submit()` decorator method now passes the user input to the decorated function. This makes it a bit easier to access the user input. See the new templates (mentioned below) for examples. (#1801)
2727
* The assistant icon is now configurable via `ui.chat_ui()` (or the `ui.Chat.ui()` method in Shiny Express) or for individual messages in the `.append_message()` and `.append_message_stream()` methods of `ui.Chat()`. (#1853)
28-
* A new `get_latest_stream_result()` method was added for an easy way to access the final result of the stream when it completes. (#1846)
28+
* A new `latest_message_stream` property was added for an easy way to reactively read the stream's status, result, and also cancel an in progress stream. (#1846)
2929
* The `.append_message_stream()` method now returns the `reactive.extended_task` instance that it launches. (#1846)
3030
* The `ui.Chat()` component's `.update_user_input()` method gains `submit` and `focus` options that allow you to submit the input on behalf of the user and to choose whether the input receives focus after the update. (#1851)
3131

shiny/ui/_chat.py

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,13 @@ def __init__(
210210
reactive.Value(None)
211211
)
212212

213-
self._latest_stream: reactive.Value[
214-
reactive.ExtendedTask[[], str] | None
215-
] = reactive.Value(None)
213+
@reactive.extended_task
214+
async def _mock_task() -> str:
215+
return ""
216+
217+
self._latest_stream: reactive.Value[reactive.ExtendedTask[[], str]] = (
218+
reactive.Value(_mock_task)
219+
)
216220

217221
# TODO: deprecate messages once we start promoting managing LLM message
218222
# state through other means
@@ -669,32 +673,34 @@ async def _handle_error():
669673

670674
return _stream_task
671675

672-
def get_latest_stream_result(self) -> str | None:
676+
@property
677+
def latest_message_stream(self) -> reactive.ExtendedTask[[], str]:
673678
"""
674-
Reactively read the latest message stream result.
679+
React to changes in the latest message stream.
680+
681+
Reactively reads for the :class:`~shiny.reactive.ExtendedTask` behind the
682+
latest message stream.
675683
676-
This method reads a reactive value containing the result of the latest
677-
`.append_message_stream()`. Therefore, this method must be called in a reactive
678-
context (e.g., a render function, a :func:`~shiny.reactive.calc`, or a
679-
:func:`~shiny.reactive.effect`).
684+
From the return value (i.e., the extended task), you can then:
685+
686+
1. Reactively read for the final `.result()`.
687+
2. `.cancel()` the stream.
688+
3. Check the `.status()` of the stream.
680689
681690
Returns
682691
-------
683692
:
684-
The result of the latest stream (a string).
693+
An extended task that represents the streaming task. The `.result()` method
694+
of the task can be called in a reactive context to get the final state of the
695+
stream.
685696
686-
Raises
687-
------
688-
:
689-
A silent exception if no stream has completed yet.
697+
Note
698+
----
699+
If no stream has yet been started when this method is called, then it returns an
700+
extended task with `.status()` of `"initial"` and that it status doesn't change
701+
state until a message is streamed.
690702
"""
691-
stream = self._latest_stream()
692-
if stream is None:
693-
from .. import req
694-
695-
req(False)
696-
else:
697-
return stream.result()
703+
return self._latest_stream()
698704

699705
async def _append_message_stream(
700706
self,

tests/playwright/shiny/components/chat/stream-result/app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ async def _(message: str):
2020

2121

2222
@render.code
23-
async def stream_result_ui():
24-
return chat.get_latest_stream_result()
23+
async def stream_result():
24+
return chat.latest_message_stream.result()

tests/playwright/shiny/components/chat/stream-result/test_chat_stream_result.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def test_validate_chat_stream_result(page: Page, local_app: ShinyAppProc) -> Non
1212
page.goto(local_app.url)
1313

1414
chat = controller.Chat(page, "chat")
15-
stream_result_ui = controller.OutputCode(page, "stream_result_ui")
15+
stream_result = controller.OutputCode(page, "stream_result")
1616

1717
expect(chat.loc).to_be_visible(timeout=10 * 1000)
1818

@@ -34,4 +34,4 @@ def test_validate_chat_stream_result(page: Page, local_app: ShinyAppProc) -> Non
3434
chat.expect_messages(re.compile(r"\s*".join(messages)), timeout=30 * 1000)
3535

3636
# Verify that the stream result is as expected
37-
stream_result_ui.expect.to_contain_text("Message 9")
37+
stream_result.expect.to_contain_text("Message 9")

0 commit comments

Comments
 (0)