express.Chat
express.Chat(
id,
*,
client=None,
greeting=None,
messages=(),
on_error='auto',
tokenizer=None,
)Attributes
| Name | Description |
|---|---|
| latest_message_stream | React to changes in the latest message stream. |
Methods
| Name | Description |
|---|---|
| append_message | Append a message to the chat. |
| append_message_stream | Append a message as a stream of message chunks. |
| clear_messages | Clear all chat messages. |
| destroy | Destroy the chat instance. |
| enable_bookmarking | Enable bookmarking for the chat instance. |
| message_stream_context | Message stream context manager. |
| messages | Reactively read chat messages |
| on_user_submit | Define a function to invoke when user input is submitted. |
| set_greeting | Set or clear the chat greeting. |
| set_user_message | Deprecated. Use update_user_input(value=value) instead. |
| transform_assistant_response | Deprecated. Assistant response transformation features will be removed in a future version. |
| transform_user_input | Deprecated. User input transformation features will be removed in a future version. |
| ui | Create a UI element for this Chat. |
| update_user_input | Update the user input. |
| user_input | Reactively read the user’s message. |
append_message
express.Chat.append_message(message, *, icon=None)Append a message to the chat.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| message | Any | A given message can be one of the following: * A string, which is interpreted as markdown and rendered to HTML on the client. * To prevent interpreting as markdown, mark the string as :class:~shiny.ui.HTML. * A UI element (specifically, a :class:~shiny.ui.TagChild). * This includes :class:~shiny.ui.TagList, which take UI elements (including strings) as children. In this case, strings are still interpreted as markdown as long as they’re not inside HTML. * A dictionary with content and role keys. The content key can contain content as described above, and the role key can be “assistant” or “user”. * More generally, any type registered with :func:shinychat.message_content. NOTE: content may include specially formatted input suggestion links (see note below). |
required |
| icon | HTML | Tag | TagList | None | An optional icon to display next to the message, currently only used for assistant messages. The icon can be any HTML element (e.g., an :func:~shiny.ui.img tag) or a string of HTML. |
None |
Note
Input suggestions are special links that send text to the user input box when clicked (or accessed via keyboard). They can be created in the following ways:
<span class='suggestion'>Suggestion text</span>: An inline text link that places ‘Suggestion text’ in the user input box when clicked.<img data-suggestion='Suggestion text' src='image.jpg'>: An image link with the same functionality as above.<span data-suggestion='Suggestion text'>Actual text</span>: An inline text link that places ‘Suggestion text’ in the user input box when clicked.
A suggestion can also be submitted automatically by doing one of the following:
- Adding a
submitCSS class or adata-suggestion-submit="true"attribute to the suggestion element. - Holding the
Ctrl/Cmdkey while clicking the suggestion link.
Note that a user may also opt-out of submitting a suggestion by holding the Alt/Option key while clicking the suggestion link.
A markdown list (<ul> or <ol>) in which every item contains a single suggestion element is automatically rendered as a grid of clickable cards instead of inline chips. Each suggestion accepts an optional title attribute (plain text), which becomes the card heading; the suggestion’s body becomes the card description. For ordered lists (<ol>), the list-item number is included in the heading.
Use .append_message_stream() instead of this method when stream=True (or similar) is specified in model’s completion method.
append_message_stream
express.Chat.append_message_stream(message, *, icon=None)Append a message as a stream of message chunks.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| message | Iterable[Any] | AsyncIterable[Any] | An (async) iterable of message chunks. Each chunk can be one of the following: * A string, which is interpreted as markdown and rendered to HTML on the client. * To prevent interpreting as markdown, mark the string as :class:~shiny.ui.HTML. * A UI element (specifically, a :class:~shiny.ui.TagChild). * This includes :class:~shiny.ui.TagList, which take UI elements (including strings) as children. In this case, strings are still interpreted as markdown as long as they’re not inside HTML. * A dictionary with content and role keys. The content key can contain content as described above, and the role key can be “assistant” or “user”. * More generally, any type registered with :func:shinychat.message_content_chunk. NOTE: content may include specially formatted input suggestion links (see note below). |
required |
| icon | HTML | Tag | None | An optional icon to display next to the message, currently only used for assistant messages. The icon can be any HTML element (e.g., an :func:~shiny.ui.img tag) or a string of HTML. |
None |
Note
Input suggestions are special links that send text to the user input box when
clicked (or accessed via keyboard). They can be created in the following ways:
* `<span class='suggestion'>Suggestion text</span>`: An inline text link that
places 'Suggestion text' in the user input box when clicked.
* `<img data-suggestion='Suggestion text' src='image.jpg'>`: An image link with
the same functionality as above.
* `<span data-suggestion='Suggestion text'>Actual text</span>`: An inline text
link that places 'Suggestion text' in the user input box when clicked.
A suggestion can also be submitted automatically by doing one of the following:
* Adding a `submit` CSS class or a `data-suggestion-submit="true"` attribute to
the suggestion element.
* Holding the `Ctrl/Cmd` key while clicking the suggestion link.
Note that a user may also opt-out of submitting a suggestion by holding the
`Alt/Option` key while clicking the suggestion link.
A markdown list (`<ul>` or `<ol>`) in which every item contains a single
suggestion element is automatically rendered as a grid of clickable cards instead
of inline chips. Each suggestion accepts an optional `title` attribute (plain
text), which becomes the card heading; the suggestion's body becomes the card
description. For ordered lists (`<ol>`), the list-item number is included in the
heading.
Use this method (over `.append_message()`) when `stream=True` (or similar) is
specified in model's completion method.
Returns
| Name | Type | Description |
|---|---|---|
An extended task that represents the streaming task. The .result() method of the task can be called in a reactive context to get the final state of the stream. |
clear_messages
express.Chat.clear_messages(greeting=False)Clear all chat messages.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| greeting | bool | If True, also clears the greeting in addition to conversation messages. Clearing the greeting causes the {id}_greeting_requested input to fire again (if the chat is visible with no greeting and no messages), enabling a regenerate pattern: clear the greeting, then react to the request to generate a new one via :meth:~shinychat.Chat.set_greeting. |
False |
destroy
express.Chat.destroy()Destroy the chat instance.
enable_bookmarking
express.Chat.enable_bookmarking(
client,
/,
*,
bookmark_store=None,
bookmark_on='response',
)Enable bookmarking for the chat instance.
This method registers on_bookmark and on_restore hooks on session.bookmark (:class:shiny.bookmark.Bookmark) to save/restore chat state on both the Chat and client= instances. In order for this method to actually work correctly, a bookmark_store= must be specified in shiny.express.app_opts().
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| client | 'ClientWithState | chatlas.Chat[Any, Any]' | The chat client instance to use for bookmarking. This can be a Chat model provider from chatlas, or more generally, an instance following the ClientWithState protocol. |
required |
| bookmark_store | 'Optional[BookmarkStore]' | A convenience parameter to set the shiny.express.app_opts(bookmark_store=) which is required for bookmarking (and .enable_bookmarking()). If None, no value will be set. |
None |
| bookmark_on | Optional[Literal['response']] | The event to trigger the bookmarking on. Supported values include: - "response" (the default): a bookmark is triggered when the assistant is done responding. - None: no bookmark is triggered When this method triggers a bookmark, it also updates the URL query string to reflect the bookmarked state. |
'response' |
Raises
| Name | Type | Description |
|---|---|---|
| ValueError | If the Shiny App does have bookmarking enabled. |
Returns
| Name | Type | Description |
|---|---|---|
| CancelCallback | A callback to cancel the bookmarking hooks. |
message_stream_context
express.Chat.message_stream_context() Message stream context manager.
A context manager for appending streaming messages into the chat. This context
manager can:
1. Be used in isolation to append a new streaming message to the chat.
* Compared to `.append_message_stream()` this method is more flexible but
isn't non-blocking by default (i.e., it doesn't launch an extended task).
2. Be nested within itself
* Nesting is primarily useful for making checkpoints to `.replace()` back
to (see the example below).
3. Be used from within a `.append_message_stream()`
* Useful for inserting additional content from another context into the
stream (e.g., see the note about tool calls below).
Yields
:
A `MessageStream` class instance, which has a method for `.append()`ing
message content chunks to as well as a `.replace()` method to reset the
stream back to its initial state (via `.replace("")`). Note that
`.append()` supports the same message content types as `.append_message()`.
Example
```python
import asyncio
from shiny import reactive
from shiny.express import ui
chat = ui.Chat(id="my_chat")
chat.ui()
@reactive.effect
async def _():
async with chat.message_stream_context() as msg:
await msg.append("Starting stream...
Progress:“) async with chat.message_stream_context() as progress: for x in [0, 50, 100]: await progress.append(f” {x}%“) await asyncio.sleep(1) await progress.replace(”“) await msg.replace(”“) await msg.append(”Completed stream”) ```
Note
A useful pattern for displaying tool calls in a chatbot is for the tool to
display using `.message_stream_context()` while the the response generation is
happening through `.append_message_stream()`. This allows the tool to display
things like progress updates (or other "ephemeral" content) and optionally
`.replace("")` the stream back to it's initial state when ready to display the
"final" content.
Note
`.replace()` resets the stream to the checkpoint captured when this context was
entered. It raises `ValueError` if the stream's content since that checkpoint
spans multiple content types (e.g. thinking followed by markdown), because the
replace wire action carries a single content type. Open a fresh
`.message_stream_context()` before the mixed content if you need a clean
checkpoint to replace back to.
messages
express.Chat.messages(format=MISSING, token_limits=None)Reactively read chat messages
Obtain chat messages within a reactive context.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| format | 'MISSING_TYPE | ProviderMessageFormat' | Deprecated. Provider-specific message formatting will be removed in a future version. | MISSING |
| token_limits | tuple[int, int] | None | Deprecated. Token counting and message trimming features will be removed in a future version. | None |
Note
Messages are listed in the order they were added. As a result, when this method is called in a .on_user_submit() callback (as it most often is), the last message will be the most recent one submitted by the user.
Returns
| Name | Type | Description |
|---|---|---|
| tuple[ChatMessage, …] | A tuple of chat messages. |
on_user_submit
express.Chat.on_user_submit(fn=None)Define a function to invoke when user input is submitted.
Apply this method as a decorator to a function (fn) that should be invoked when the user submits a message. This function can take an optional argument, which will be the user input message.
In many cases, the implementation of fn should also do the following:
- Generate a response based on the user input.
- If the response should be aware of chat history, use a package like chatlas to manage the chat state, or use the
.messages()method to get the chat history.
- Append that response to the chat component using
.append_message()( or.append_message_stream()if the response is streamed).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| fn | UserSubmitFunction | None | A function to invoke when user input is submitted. | None |
Note
This method creates a reactive effect that only gets invalidated when the user submits a message. Thus, the function fn can read other reactive dependencies, but it will only be re-invoked when the user submits a message.
set_greeting
express.Chat.set_greeting(greeting)Set or clear the chat greeting.
A greeting is displayed at the top of the chat before any conversation messages. It can be static content, streaming content from an async iterator, or None to remove an existing greeting.
If the greeting has already been dismissed, calling this method updates the greeting content but does not make it visible again. To show a new greeting after dismissal, first clear the chat with await chat.clear_messages(greeting=True).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| greeting | 'str | HTML | Tag | TagList | ChatGreeting | None' | The greeting content. Can be: * None: clears the current greeting entirely (distinct from dismissal). Use this before setting a new greeting when implementing a regenerate pattern. * A markdown string, :class:~htmltools.HTML, :class:~htmltools.Tag, or :class:~htmltools.TagList: displayed as a stand-alone greeting. * A :func:~shinychat.chat_greeting object with options such as dismissible. * A :func:~shinychat.chat_greeting wrapping an :class:~typing.AsyncIterable of strings: streams the greeting content chunk-by-chunk. |
required |
Notes
When no greeting is set and the chat is visible with no messages, an input named {id}_greeting_requested fires (where {id} is the chat’s ID). Use @reactive.event(input.{id}_greeting_requested) to generate a greeting on demand. This input fires on first load and again after :meth:~shinychat.Chat.clear_messages is called with greeting=True.
Examples
Static greeting (stand-alone, dismissible by default):
@reactive.effect
async def _():
await chat.set_greeting("## Welcome!\n\nHow can I help you today?")Static greeting with custom options:
from shinychat import chat_greeting
@reactive.effect
async def _():
greeting = chat_greeting(
"## Welcome!",
dismissible=True,
)
await chat.set_greeting(greeting)Streaming greeting from an async iterator:
@reactive.effect
async def _():
async def token_stream():
for token in ["Hello", " there", "!"]:
yield token
await chat.set_greeting(chat_greeting(token_stream()))LLM-generated greeting using greeting_requested:
import chatlas
from shinychat import Chat, chat_greeting
chat_model = chatlas.ChatOpenAI(model="gpt-4o")
chat = Chat(id="chat")
@reactive.effect
@reactive.event(input.chat_greeting_requested)
async def _():
response = await chat_model.stream_async(
"Write a short, friendly welcome message."
)
await chat.set_greeting(chat_greeting(response))Regenerate pattern (clear and re-request):
@reactive.effect
@reactive.event(input.regenerate)
async def _():
await chat.clear_messages(greeting=True)
# greeting_requested fires again after clear_messages(greeting=True),
# so the LLM-generated greeting handler above will run again.Clear the greeting (e.g., before setting a new one):
await chat.set_greeting(None)set_user_message
express.Chat.set_user_message(value)Deprecated. Use update_user_input(value=value) instead.
transform_assistant_response
express.Chat.transform_assistant_response(fn=None)Deprecated. Assistant response transformation features will be removed in a future version.
transform_user_input
express.Chat.transform_user_input(fn=None)Deprecated. User input transformation features will be removed in a future version.
ui
express.Chat.ui(
messages=None,
greeting=None,
placeholder='Enter a message...',
width='min(680px, 100%)',
height='auto',
fill=True,
icon_assistant=None,
enable_cancel=MISSING,
footer=None,
**kwargs,
)Create a UI element for this Chat.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| messages | Optional[Iterable[str | TagChild | ChatMessageDict | ChatMessage | Any]] | A sequence of messages to display in the chat. Each message can be either a string or a dictionary with content and role keys. The content key should contain the message text, and the role key can be “assistant” or “user”. |
None |
| greeting | Optional[Union[str, HTML, Tag, TagList, ChatGreeting]] | An optional greeting to display at the top of the chat before any conversation messages. Can be a markdown string or a :func:~shinychat.chat_greeting object. |
None |
| placeholder | str | Placeholder text for the chat input. | 'Enter a message...' |
| width | 'CssUnit' | The width of the UI element. | 'min(680px, 100%)' |
| height | 'CssUnit' | The height of the UI element. | 'auto' |
| fill | bool | Whether the chat should vertically take available space inside a fillable container. | True |
| icon_assistant | HTML | Tag | TagList | None | The icon to use for the assistant chat messages. Can be a HTML or a tag in the form of :class:~htmltools.HTML or :class:~htmltools.Tag. If None, a default robot icon is used. |
None |
| enable_cancel | 'bool | MISSING_TYPE' | Whether to show a stop button during streaming that allows the user to cancel the in-progress response. When True, the chat UI shows a stop button in place of the send button while streaming. You must observe input.<id>_cancel on the server and call ctrl.cancel() on a chatlas StreamController to actually stop the stream. Defaults to True when a client= was provided to :class:~shinychat.Chat, False otherwise. |
MISSING |
| footer | Optional[TagChild] | Optional HTML content to display below the chat input. This can be any HTML content (tags, tag lists, or strings). Useful for adding disclaimers, attribution, or other information. The footer text is styled slightly smaller and lighter than body text by default. Customize with CSS properties --shiny-chat-footer-font-size and --shiny-chat-footer-color on the chat container or footer element. |
None |
| kwargs | TagAttrValue | Additional attributes for the chat container element. | {} |
update_user_input
express.Chat.update_user_input(
value=None,
placeholder=None,
submit=False,
focus=False,
)Update the user input.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| value | str | None | The value to set the user input to. | None |
| placeholder | str | None | The placeholder text for the user input. | None |
| submit | bool | Whether to automatically submit the text for the user. Requires value. |
False |
| focus | bool | Whether to move focus to the input element. Requires value. |
False |
user_input
express.Chat.user_input()Reactively read the user’s message.
Returns
| Name | Type | Description |
|---|---|---|
| str | None | The user input message. |
Note
Most users shouldn’t need to use this method directly since the last item in .messages() contains the most recent user input. It can be useful for:
- Taking a reactive dependency on the user’s input outside of a
.on_user_submit()callback. - Maintaining message state separately from
.messages().