shinychat for Python website

shinychat provides a Shiny toolkit for building generative AI applications like chatbots and streaming content. It works best with chatlas, but also works great with other LLM frameworks such as LangChain, Pydantic AI, and more.

Installation

shinychat is a dependency of the shiny package, so in most cases, you’ll want to just install shiny with:

uv pip install shiny

However, you can also install shinychat separately with:

uv pip install shinychat

Or, install the development version of shinychat from GitHub with:

uv pip install git+https://github.com/posit-dev/shinychat.git

Starter example

With shiny installed, you’re ready to run your first shinychat app. Create a new file named app.py with the following content:

from shinychat.express import Chat

# Create a chat instance and display it
chat = Chat(id="chat")
chat.ui()

# Define a callback to run when the user submits a message
@chat.on_user_submit
async def handle_user_input(user_input: str):
    # Simply echo the user's input back to them
    await chat.append_message(f"You said: {user_input}")

To run the app, execute the following command in your terminal (or via the Shiny extension):

uv shiny run --reload app.py

Stream cancellation

shinychat supports cancelling in-progress AI response streams. Set enable_cancel=True when creating the chat UI to show a stop button during streaming. Users can also press Escape while the chat has focus to cancel.

For cooperative cancellation, chatlas provides a StreamController that you pass to stream_async(). The controller automatically resets itself between streams, so you only need to create it once.

Shiny Express

from shiny import reactive
from shiny.express import input
from chatlas import ChatAnthropic, StreamController
from shinychat.express import Chat

chat = Chat(id="chat")
chat.ui(enable_cancel=True)

client = ChatAnthropic(system_prompt="You are a helpful assistant.")
ctrl = StreamController()

@chat.on_user_submit
async def handle_user_input(user_input: str):
    response = await client.stream_async(user_input, controller=ctrl)
    await chat.append_message_stream(response)

@reactive.effect
@reactive.event(input.chat_cancel)
def handle_cancel():
    ctrl.cancel()

Shiny Core

from shiny import App, reactive, ui
from chatlas import ChatAnthropic, StreamController
from shinychat import Chat, chat_ui

app_ui = ui.page_fillable(
    chat_ui("chat", enable_cancel=True),
)

def server(input, output, session):
    client = ChatAnthropic(system_prompt="You are a helpful assistant.")
    chat = Chat("chat")
    ctrl = StreamController()

    @chat.on_user_submit
    async def handle_user_input(user_input: str):
        response = await client.stream_async(user_input, controller=ctrl)
        await chat.append_message_stream(response)

    @reactive.effect
    @reactive.event(input.chat_cancel)
    def handle_cancel():
        ctrl.cancel()

app = App(app_ui, server)

Key points:

  • enable_cancel=True shows the stop button while a response is streaming.
  • StreamController() is created once and reused — it automatically resets between streams.
  • Pass the controller to stream_async(controller=ctrl) so chatlas can honour the cancellation signal.
  • The cancel input fires as input.<id>_cancel (e.g. input.chat_cancel). Observe it with @reactive.event to call ctrl.cancel(). This is needed in both Express and Core apps.
  • Partial responses are automatically preserved in chat history by chatlas when a stream is cancelled.

Learn more

The official shiny website offers the best starting point for learning about shinychat: