AI Chat Panel¶
The AI chat panel adds an LLM-powered chat interface to any Panel application or Panelini dashboard. It supports multiple providers, tool execution, streaming responses, and a live preview pane.
Overview¶
graph TB
subgraph ai [" AI Chat Panel "]
direction TB
frontend(["AiChat"])
backend(["AiBackend"])
iface(["AiInterface"])
config(["Config Loader"])
tools(["Tools"])
end
subgraph panelini [" Panelini "]
sidebar(["Left Sidebar"])
main(["Main Content"])
end
subgraph providers [" LLM Providers "]
anthropic(["Anthropic"])
azure(["Azure OpenAI"])
end
frontend -- "sidebar_objects" --> sidebar
frontend -- "main_objects" --> main
frontend --> backend
backend --> iface
backend --> config
backend --> tools
iface --> anthropic
iface --> azure
classDef aiNode fill:#0d7377,stroke:#095c5f,color:#ffffff
classDef paneliniNode fill:#1e293b,stroke:#334155,color:#f8fafc
classDef providerNode fill:#6366f1,stroke:#4f46e5,color:#ffffff
class frontend,backend,iface,config,tools aiNode
class sidebar,main paneliniNode
class anthropic,azure providerNode
The panel is structured in layers:
AiChat – UI widgets (chat interface, sidebar controls, preview pane)
AiBackend – Business logic (provider management, tool execution, message routing)
AiInterface – Provider-agnostic LLM wrapper (LangChain-based)
Config – YAML configuration with environment variable resolution
Tools – Extensible LangChain tool system
Installation¶
The AI chat panel is an optional extra:
uv add panelini[ai]
or with pip:
pip install panelini[ai]
This installs the required dependencies: langchain, langchain-anthropic, langchain-openai, pyyaml, and python-dotenv.
Quick Start¶
from panelini import Panelini
app = Panelini(title="My AI App", use_ai=True)
app.servable()
That’s it. The use_ai=True flag adds the chat interface to the main area and provider/model controls to the left sidebar.
Standalone Usage (without Panelini)¶
import panel as pn
from panelini.panels.ai import AiChat
chat = AiChat(
system_message="You are a helpful assistant.",
)
# Use sidebar_objects and main_objects in any Panel layout
app = pn.Row(*chat.main_objects)
app.servable()
Configuration Parameters¶
app = Panelini(
title="My AI App",
use_ai=True,
ai_system_message="You are a data analysis assistant.",
ai_welcome_message="Hello! How can I help you today?",
ai_config_path="/path/to/config.yml",
)
Parameter |
Type |
Description |
|---|---|---|
|
|
Enable the AI chat panel (default: |
|
|
System message for the AI backend (default: |
|
|
Optional greeting posted into a new chat. |
|
|
Show the markdown preview pane next to the chat (default: |
|
|
Path to a custom |
Provider Configuration¶
Providers and models are defined in a YAML configuration file. The panel searches for configuration in this order:
PANELINI_AI_CONFIG_PATHenvironment variableWalk upward from the working directory looking for
config.ymlorconfig.yamlFall back to the bundled default configuration
Configuration Format¶
providers:
anthropic:
display_name: "Anthropic"
client_type: "anthropic"
env_vars:
api_key: "${ANTHROPIC_API_KEY}"
endpoint: "${ANTHROPIC_ENDPOINT}"
models:
- name: "Claude Sonnet 4.5"
value: "anthropic/claude-sonnet-4-5"
- name: "Claude Haiku 4.5"
value: "anthropic/claude-haiku-4-5"
azure_openai:
display_name: "Azure OpenAI"
client_type: "azure_openai"
env_vars:
api_key: "${AZURE_OPENAI_API_KEY}"
endpoint: "${AZURE_OPENAI_ENDPOINT}"
api_version: "${AZURE_OPENAI_API_VERSION}"
models:
- name: "GPT-4o"
value: "azure_openai/gpt-4o-2024-11-20"
Model values use the LiteLLM naming convention: provider_prefix/model-id. The prefix is stripped before passing the model name to LangChain. When client_type is omitted, it is derived directly from the first model’s prefix. Bare model names (without a prefix) still work when client_type is set explicitly.
Environment variables referenced with ${VAR_NAME} are resolved at load time. A ValueError is raised if a referenced variable is not set.
Supported Providers¶
Provider |
|
Required Environment Variables |
|---|---|---|
Anthropic |
|
|
Azure OpenAI |
|
|
UI Layout¶
When use_ai=True, the panel injects two areas into the Panelini dashboard:
Main Content¶
The main area receives the chat interface with streaming responses. With
ai_show_preview=True (show_preview=True on AiChat) it turns into a
two-column layout:
Chat (left) – The chat interface with streaming responses
Preview (right) – A markdown preview pane updated by the
update_previewtool
Conversation History¶
Every chat keeps its conversations per user. Without configuration they live
in memory for the lifetime of the process; set PANELINI_HISTORY_DB to a file
path to persist them in SQLite, pass your own store as ai_history_store, or
pass ai_history_store="browser" to keep each user’s history in their
browser’s localStorage (per-browser persistence across reloads and restarts,
no server-side database, ~5MB quota).
ai_history_view picks the initial sidebar style: "tree" (a
drag-and-drop folder tree, the default: organize chats like a filesystem)
or "list" (date-grouped); an icon in the New Chat row switches between
the two at runtime (per session; a reload starts from ai_history_view
again, and a typed search filter carries over the switch). Conversations
are owned by the resolved user id (see user_resolver); anonymous
visitors get a cookie-backed id.
Folder tree¶
A chat can be filed in several folders at once, so it appears as one row under each of them. Dragging a row moves that filing; holding Ctrl or Alt over the drop files the chat in the target as well and leaves the row it came from. Taking a row to the top level takes that filing out of its folder and leaves the chat’s other rows alone, the top level being the absence of a folder rather than another folder to file into. A row is one filing, so moving it never changes a row left behind. Rename, delete, outdent and indent are in the row’s context menu, on F2, Delete and Alt+Arrow, and delete is a button on the row as well, at its trailing edge and dimmed until the row is hovered. New chat and new folder are the header buttons. Outdent takes the selected row out of its folder without touching the chat, which is the distinction deleting a row cannot make, and indent files it in the folder above; both are reported as the move a drag is. Deleting a folder that holds chats kept nowhere else asks for a second Delete before removing the folder, its subfolders and those chats; the folder turns red while it waits, and a Cancel appears beside the warning for anyone who changes their mind.
Document model¶
Each conversation is stored as one JSON document with embedded messages,
defined by the bundled
chat_history_schema.json.
The schema is an OO-LD document: a
plain JSON-Schema carrying a JSON-LD @context that maps properties to
vocabulary terms (schema.org where a term exists). Its root is a oneOf
over two document kinds sharing that context: a conversation and a folder.
The same document is the import/export format of the sidebar icons, so a
downloaded chat re-imports losslessly here or in any other store.
Three parts of the document the panel carries but does not yet author, so
that a chat exported from elsewhere survives the round trip: folder_ids
(the set of folders a chat is filed in), parent_id with
forked_from_message_id (which chat a fork came from and where it
diverged), and per-message attachments. An attachment carries its
payload in url, a data: URI when inline and a reference otherwise; a
store that dropped the bytes for size reasons says so with omitted
rather than leaving the reader to guess.
All backends implement a shared document contract
(DocumentHistoryStore): SQLite keeps one documents row per conversation
or folder, the in-memory store keeps plain dicts, and the shape maps 1:1
onto a Postgres JSONB column or a browser object store.
Tools¶
The panel includes a tool system based on LangChain’s BaseTool. Tools are toggled via sidebar checkboxes.
Built-in Tools¶
Tool |
Description |
|---|---|
|
Returns the current date and time with optional timezone support. Enabled by default. |
|
Renders markdown content in the preview pane. Supports headers, tables, code blocks, and images. |
Streaming vs Tool Mode¶
No tools selected – Responses stream token-by-token inside a collapsible
<details>block, then expand when complete.Tools selected – The model runs a tool execution loop (up to 10 iterations). The final text response is displayed after all tool calls complete.
Adding Custom Tools¶
Custom tools can be passed directly to AiChat via the tools parameter. They appear as additional checkboxes in the sidebar alongside the built-in tools:
from panelini.panels.ai import AiChat
chat = AiChat(tools=[MyTool()])
To create a custom tool, subclass LangChain’s BaseTool:
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field
class MyToolInput(BaseModel):
query: str = Field(description="The search query")
class MyTool(BaseTool):
name: str = "my_tool"
description: str = "Describe what this tool does."
args_schema: type[BaseModel] = MyToolInput
def _run(self, query: str) -> str:
return f"Result for: {query}"
async def _arun(self, query: str) -> str:
return self._run(query=query)
See examples/panels/ai/ai_chat_custom_tool.py for a complete working example with a LocalStorage tool.
Module Structure¶
panelini/panels/ai/
├── __init__.py
├── frontend.py # UI layer (AiChat class)
├── backend.py # Business logic (AiBackend class)
├── default_config.yml # Bundled default provider config
├── history/
│ ├── __init__.py
│ ├── store.py # Records + ChatHistoryStore interface
│ ├── document.py # Document layer + in-memory backend
│ ├── sqlite_store.py # SQLite backend
│ ├── local_storage_store.py # Browser localStorage backend
│ ├── default.py # Shared default store
│ ├── panel.py # Date-grouped sidebar list
│ ├── tree.py # TanstackTable folder tree
│ ├── chat_history_schema.json # Conversation + folder document schema (OO-LD)
│ └── chat_history_schema.sql # SQLite document table DDL
├── tools/
│ ├── __init__.py
│ └── basic_tools.py # Built-in tools
└── utils/
├── __init__.py
├── ai_interface.py # Provider-agnostic LLM interface
└── config.py # YAML config loader
API Reference¶
AiChat¶
Standalone AI chat panel. Can be used independently in any Panel app or integrated into Panelini. Exposes widget lists for integration.
Property / Method |
Description |
|---|---|
|
Constructor. All parameters are optional. Pass custom |
|
Property returning a list of Panel viewables for the sidebar. |
|
Property returning a list of Panel viewables for the main area. |
AiBackend¶
Business logic layer managing providers, models, tools, and message processing.
Method |
Description |
|---|---|
|
Constructor. Loads config and creates the initial AI interface. |
|
Returns |
|
Returns |
|
Switch provider, reset model, clear history. |
|
Switch model, preserve history. |
|
Update sampling temperature, preserve history. |
|
Update available tools, preserve history. |
|
Process a user message. Returns |
|
Async generator yielding response token chunks. |
|
Clear conversation history. |
|
Export the active conversation as a v2 conversation document. |
|
Restore the model context from a v2 document (or legacy export). |
AiInterface¶
Low-level provider-agnostic LLM interface built on LangChain.
Method |
Description |
|---|---|
|
Constructor. Initializes the LLM client and binds tools. |
|
Get a response (streaming or non-streaming). |
|
Get a response that may include tool calls. |
|
Dynamically add a tool to the interface. |
|
Clear conversation history. |
Configuration Classes¶
Class / Function |
Description |
|---|---|
|
Top-level config dataclass. Contains a |
|
Frozen dataclass for a provider (key, display_name, client_type, env_vars, models). |
|
Frozen dataclass for a model (name, value). |
|
Load and validate a YAML config file. Auto-discovers if path is |
Data Flow¶
sequenceDiagram
participant User as User
participant Chat as ChatInterface
participant FE as AiChat
participant BE as AiBackend
participant AI as AiInterface
participant LLM as LLM Provider
User->>Chat: Type message
Chat->>FE: _handle_message()
FE->>BE: process_message() or stream_message()
BE->>AI: get_response() or get_response_with_tools()
AI->>LLM: ainvoke() / astream()
LLM-->>AI: Response / chunks
AI-->>BE: Text + tool_calls
BE->>BE: Execute tools (if any)
BE-->>FE: {"response", "preview_updates"}
FE-->>Chat: Yield response
Chat-->>User: Display message