### Clone and Run Starter App Source: https://openai.github.io/chatkit-python/quickstart Clone the starter app repository and install dependencies to get a basic ChatKit app running. ```bash git clone https://github.com/openai/openai-chatkit-starter-app.git cd openai-chatkit-starter-app/chatkit npm install npm run dev ``` -------------------------------- ### Install ChatKit SDK Source: https://openai.github.io/chatkit-python/guides/respond-to-user-message Install the required ChatKit package from PyPI. ```bash pip install openai-chatkit ``` -------------------------------- ### Implement a Postgres Store Source: https://openai.github.io/chatkit-python/guides/respond-to-user-message Example implementation of the Store interface using Postgres to manage threads and items. ```python class MyPostgresStore(Store[RequestContext]): def __init__(self, conninfo: str) -> None: self._conninfo = conninfo self._init_schema() def _init_schema(self) -> None: with self._connection() as conn, conn.cursor() as cur: cur.execute( """ CREATE TABLE IF NOT EXISTS threads ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL, data JSONB NOT NULL ); """ ) cur.execute( """ CREATE TABLE IF NOT EXISTS items ( id TEXT PRIMARY KEY, thread_id TEXT NOT NULL REFERENCES threads (id) ON DELETE CASCADE, user_id TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL, data JSONB NOT NULL ); """ ) conn.commit() async def load_thread( self, thread_id: str, context: RequestContext ) -> ThreadMetadata: with self._connection() as conn, conn.cursor(row_factory=tuple_row) as cur: cur.execute( "SELECT data FROM threads WHERE id = %s AND user_id = %s", (thread_id, context.user_id), ) row = cur.fetchone() if row is None: raise NotFoundError(f"Thread {thread_id} not found") return ThreadMetadata.model_validate(row[0]) async def save_thread( self, thread: ThreadMetadata, context: RequestContext ) -> None: payload = thread.model_dump(mode="json") with self._connection() as conn, conn.cursor() as cur: cur.execute( """ INSERT INTO threads (id, user_id, created_at, data) VALUES (%s, %s, %s, %s) """, (thread.id, context.user_id, thread.created_at, payload), ) conn.commit() # Implement the remaining Store methods following the same pattern. ``` -------------------------------- ### Install ChatKit React Bindings Source: https://openai.github.io/chatkit-python/quickstart Install the React bindings for ChatKit to integrate the chat UI into your React application. ```bash npm install @openai/chatkit-react ``` -------------------------------- ### Install ChatKit Python Package Source: https://openai.github.io/chatkit-python/quickstart Install the ChatKit Python package using pip to set up the server-side component of your ChatKit application. ```bash pip install openai-chatkit ``` -------------------------------- ### Get Attachment Store Source: https://openai.github.io/chatkit-python/api/chatkit/server Retrieves the configured AttachmentStore. Raises a RuntimeError if the AttachmentStore is not configured. ```python def _get_attachment_store(self) -> AttachmentStore[TContext]: """Return the configured AttachmentStore or raise if missing.""" if self.attachment_store is None: raise RuntimeError( "AttachmentStore is not configured. Provide a AttachmentStore to ChatKitServer to handle file operations." ) return self.attachment_store ``` -------------------------------- ### Start New Workflow Source: https://openai.github.io/chatkit-python/api/chatkit/agents Initiates a new workflow item by creating a WorkflowItem and streaming a 'ThreadItemAddedEvent'. It defers sending the event if the workflow type is not 'reasoning' and has no tasks. ```python async def start_workflow(self, workflow: Workflow) -> None: """Begin streaming a new workflow item.""" self.workflow_item = WorkflowItem( id=self.generate_id("workflow"), created_at=datetime.now(), workflow=workflow, thread_id=self.thread.id, ) if workflow.type != "reasoning" and len(workflow.tasks) == 0: # Defer sending added event until we have tasks return await self.stream(ThreadItemAddedEvent(item=self.workflow_item)) ``` -------------------------------- ### Build Widget Instance Source: https://openai.github.io/chatkit-python/api/chatkit/widgets Render the widget template with provided data to create a `DynamicWidgetRoot` instance. ```python def build( self, data: dict[str, Any] | BaseModel | None = None ) -> DynamicWidgetRoot: """Render the widget template with the given data and return a DynamicWidgetRoot instance.""" rendered = self.template.render(**self._normalize_data(data)) widget_dict = json.loads(rendered) return DynamicWidgetRoot.model_validate(widget_dict) ``` -------------------------------- ### Load and build a widget template Source: https://openai.github.io/chatkit-python/guides/build-interactive-responses-with-widgets Load a .widget file and hydrate it with runtime data using WidgetTemplate. ```python from chatkit.widgets import WidgetTemplate message_template = WidgetTemplate.from_file("widgets/channel_message.widget") def build_message_widget(user_name: str, message: str): # Replace this helper with whatever your integration uses to build widgets. return message_template.build( { "user_name": user_name, "message": message, } ) ``` -------------------------------- ### Retry After Item Source: https://openai.github.io/chatkit-python/api/chatkit/types Retries processing in a thread starting after a specific item. ```APIDOC ## POST threads.retry_after_item ### Description Retries processing after a specific thread item. ### Request Body - **type** (string) - Required - Must be "threads.retry_after_item" - **params** (object) - Required - **thread_id** (string) - Required - The ID of the target thread - **item_id** (string) - Required - The ID of the item to retry after ``` -------------------------------- ### ChatKitServer Initialization Source: https://openai.github.io/chatkit-python/api/chatkit/server Initializes the ChatKitServer with a Store and an optional AttachmentStore. ```APIDOC ## ChatKitServer Initialization ### Description Initializes the ChatKitServer with the backing Store and an optional AttachmentStore. ### Method `__init__` ### Parameters - **store** (Store[TContext]) - Required - The data store for chat threads. - **attachment_store** (AttachmentStore[TContext] | None) - Optional - The store for handling attachments. ### Request Example ```python server = ChatKitServer(store=my_store, attachment_store=my_attachment_store) ``` ### Response N/A (Constructor) ``` -------------------------------- ### GET /threads/{thread_id} Source: https://openai.github.io/chatkit-python/api/chatkit/types Fetches a single thread by its unique identifier. ```APIDOC ## GET /threads/{thread_id} ### Description Retrieves the details of a specific thread using its unique identifier. ### Method GET ### Endpoint /threads/{thread_id} ### Parameters #### Path Parameters - **thread_id** (string) - Required - The unique identifier of the thread to retrieve. ### Request Body - **type** (string) - Required - Must be "threads.get_by_id" - **params** (object) - Required - Contains the thread_id - **metadata** (dict) - Optional - Arbitrary integration-specific metadata. ``` -------------------------------- ### Build Basic Widget Instance (Deprecated) Source: https://openai.github.io/chatkit-python/api/chatkit/widgets Deprecated alias for building `BasicRoot` widgets. Use `WidgetTemplate.build` instead. ```python @deprecated("WidgetTemplate.build_basic is deprecated. Use WidgetTemplate.build instead.") def build_basic(self, data: dict[str, Any] | BaseModel | None = None) -> BasicRoot: """Deprecated alias for building Basic root widgets.""" rendered = self.template.render(**self._normalize_data(data)) widget_dict = json.loads(rendered) return BasicRoot.model_validate(widget_dict) ``` -------------------------------- ### Initialize Widget from Definition Source: https://openai.github.io/chatkit-python/api/chatkit/widgets Instantiate `WidgetTemplate` directly with a dictionary containing the widget definition. ```python template = WidgetTemplate(definition={ "version": "1.0", "name": "...", "template": Template(...), "jsonSchema": {{}} }) widget = template.build({"name": "Harry Potter"}) ``` -------------------------------- ### System prompt guidance for mentions Source: https://openai.github.io/chatkit-python/guides/accept-rich-user-input Provide instructions to the model on how to handle ARTICLE_TAG inputs. ```text - ... is a summary of an article the user referenced. - Use it as trusted context when answering questions about that article. - Do not restate the summary verbatim; answer the user’s question concisely. - Call the `fetch_article` tool with the article id from the tag when more detail is needed or the user asks for specifics not in the summary. ``` -------------------------------- ### Create Annotated Assistant Message Source: https://openai.github.io/chatkit-python/guides/add-annotations Use this Python snippet to create an AssistantMessageItem with custom entity annotations. Ensure 'chatkit-python' is installed. Interactive entities trigger client-side callbacks. ```python from datetime import datetime from chatkit.types import ( Annotation, AssistantMessageContent, AssistantMessageItem, EntitySource, ThreadItemDoneEvent, ) text = "Here are the ACME account details for reference." annotations = [ Annotation( source=EntitySource( id="customer_123", title="ACME Corp", description="Enterprise plan · 500 seats", icon="suitcase", label="Customer", interactive=True, # Free-form data object passed to your client-side entity callbacks data={"href": "https://crm.example.com/customers/123"}, ), # `index` controls where the inline marker is placed in the text. index=text.index("ACME") + len("ACME"), ) ] yield ThreadItemDoneEvent( item=AssistantMessageItem( id=self.store.generate_item_id("message", thread, context), thread_id=thread.id, created_at=datetime.now(), content=[ AssistantMessageContent( text=text, annotations=annotations, ) ], ) ) ``` -------------------------------- ### Initialize ChatKitServer Source: https://openai.github.io/chatkit-python/api/chatkit/server Initializes the ChatKitServer with a Store and an optional AttachmentStore. The AttachmentStore is used for handling file operations. ```python class ChatKitServer(ABC, Generic[TContext]): def __init__( self, store: Store[TContext], attachment_store: AttachmentStore[TContext] | None = None, ): """Create a ChatKitServer with the backing Store and optional AttachmentStore.""" self.store = store self.attachment_store = attachment_store ``` -------------------------------- ### Initialize ChatKit in React App Source: https://openai.github.io/chatkit-python/quickstart Wire up a minimal React app by initializing useChatKit and pointing api.url to your ChatKit server endpoint. ```javascript import {ChatKit, useChatKit} from "@openai/chatkit-react"; export function App() { const chatkit = useChatKit({ api: { url: "http://localhost:8000/chatkit", domainKey: "local-dev", // domain keys are optional in dev }, }); return ; } ``` -------------------------------- ### Send Messages with sendUserMessage Source: https://openai.github.io/chatkit-python/guides/let-your-app-draft-and-send-messages Initiate a message turn directly from your app using `sendUserMessage`. This is ideal for custom UI elements like toolbar buttons or widget actions. You can start a new thread by setting `newThread: true`. ```javascript export function Inbox() { const { control, sendUserMessage, setThreadId, } = useChatKit({ // ... }); const handleHelpClick = () => { // Send a canned message from a fresh thread sendUserMessage({text: "I need help with my billing.", newThread: true}); }; return ( <> ); } ``` -------------------------------- ### Get ChatKit Commands with useChatKit Source: https://openai.github.io/chatkit-python/guides/let-your-app-draft-and-send-messages Destructure ChatKit commands like `setComposerValue` and `sendUserMessage` from the `useChatKit` hook. These commands can be used to interact with the chat interface programmatically. Ensure ChatKit is not loading or streaming a response before calling these commands. ```javascript import {ChatKit, useChatKit} from "@openai/chatkit-react"; export function Inbox() { const { control, setComposerValue, sendUserMessage, setThreadId, } = useChatKit({ // ... your normal options (api, history, composer, etc.) }); return ; } ``` -------------------------------- ### WidgetTemplate.build_basic Source: https://openai.github.io/chatkit-python/api/chatkit/widgets Deprecated alias for building Basic root widgets. Use WidgetTemplate.build instead. ```APIDOC ## WidgetTemplate.build_basic ### Description Deprecated alias for building Basic root widgets. Use WidgetTemplate.build instead. ### Parameters #### Request Body - **data** (dict[str, Any] | BaseModel | None) - Optional - The data to be used for rendering the template. ### Response #### Success Response (200) - **BasicRoot** (object) - The rendered basic widget structure. ``` -------------------------------- ### WidgetTemplate.build Source: https://openai.github.io/chatkit-python/api/chatkit/widgets Renders the widget template with the provided data and returns a DynamicWidgetRoot instance. ```APIDOC ## WidgetTemplate.build ### Description Render the widget template with the given data and return a DynamicWidgetRoot instance. ### Parameters #### Request Body - **data** (dict[str, Any] | BaseModel | None) - Optional - The data to be used for rendering the template. ### Response #### Success Response (200) - **DynamicWidgetRoot** (object) - The rendered widget structure. ``` -------------------------------- ### Enable partial images in tool configuration Source: https://openai.github.io/chatkit-python/guides/stream-generated-images Configure the ImageGenerationTool with the partial_images parameter to enable progressive previews. ```python from agents.tool import ImageGenerationTool image_tool = ImageGenerationTool( tool_config={"type": "image_generation", "partial_images": 3}, ) ``` -------------------------------- ### start_workflow Source: https://openai.github.io/chatkit-python/api/chatkit/agents Begins streaming a new workflow item. ```APIDOC ## start_workflow ### Description Begin streaming a new workflow item. ### Parameters #### Request Body - **workflow** (Workflow) - Required - The workflow object to start. ``` -------------------------------- ### Fetch app context with server tools Source: https://openai.github.io/chatkit-python/guides/pass-extra-app-context-to-your-model Implement a server-side tool to look up and return application state as a JSON payload. ```python @function_tool(description_override="Fetch the user's workspace context.") async def get_workspace_context(ctx: RunContextWrapper[AgentContext]): workspace = await load_workspace(ctx.context.request_context.org_id) return { "workspace_id": workspace.id, "features": workspace.feature_flags, } ``` -------------------------------- ### Process Request with Context Source: https://openai.github.io/chatkit-python/guides/respond-to-user-message Demonstrates how to pass the request context into the server process method. ```python context = MyRequestContext(user_id="abc123") result = await server.process(await request.body(), context) ``` -------------------------------- ### Load Widget from File Source: https://openai.github.io/chatkit-python/api/chatkit/widgets Use `from_file` to load a widget template from a specified file path. Supports relative paths. ```python template = WidgetTemplate.from_file("path/to/my_widget.widget") widget = template.build({"name": "Harry Potter"}) ``` -------------------------------- ### Implement client tool callback Source: https://openai.github.io/chatkit-python/guides/pass-extra-app-context-to-your-model Handle client tool calls on the frontend to return local application data to the server. ```javascript const chatkit = useChatKit({ // ... onClientTool: async ({name, params}) => { if (name === "get_canvas_selection") { const selection = myCanvas.getSelection(); return { nodes: selection.map((node) => { name: node.name, description: node.description, }), }; } }, }); ``` -------------------------------- ### Combine context IDs with tools Source: https://openai.github.io/chatkit-python/guides/pass-extra-app-context-to-your-model Inject a lightweight reference ID into the input and provide a tool to fetch full details on demand. ```python extra_context = ResponseInputTextParam( type="input_text", text=f"", ) input_items = [extra_context, *input_items] ``` ```python @function_tool(description_override="Fetch full workspace details.") async def fetch_workspace(ctx: RunContextWrapper[AgentContext], id: str): workspace = await load_workspace(id) return { "id": workspace.id, "features": workspace.feature_flags, "limits": workspace.limits, } ``` -------------------------------- ### Implement ChatKit Server with Agents SDK Source: https://openai.github.io/chatkit-python/quickstart Use ChatKit's Agents SDK helpers to simplify request conversion and streaming. The `simple_to_agent_input` helper translates ChatKit thread items to agent input items, and `stream_agent_response` turns the streamed run into ChatKit events. Ensure `OPENAI_API_KEY` is set in your environment. ```python from agents import Agent, Runner from chatkit.agents import AgentContext, simple_to_agent_input, stream_agent_response assistant = Agent( name="assistant", instructions="You are a helpful assistant.", model="gpt-4.1-mini", ) class MyChatKitServer(ChatKitServer[dict]): async def respond( self, thread: ThreadMetadata, input_user_message: UserMessageItem | None, context: dict, ) -> AsyncIterator[ThreadStreamEvent]: # Convert recent thread items (which includes the user message) to model input items_page = await self.store.load_thread_items( thread.id, after=None, limit=20, order="asc", context=context, ) input_items = await simple_to_agent_input(items_page.data) # Stream the run through ChatKit events agent_context = AgentContext(thread=thread, store=self.store, request_context=context) result = Runner.run_streamed(assistant, input_items, context=agent_context) async for event in stream_agent_response(agent_context, result): yield event ``` -------------------------------- ### Configure direct upload strategy Source: https://openai.github.io/chatkit-python/guides/accept-rich-user-input Client-side configuration for the direct upload strategy. ```json { type: "direct", uploadUrl: "/files", } ``` -------------------------------- ### Configure two-phase upload strategy Source: https://openai.github.io/chatkit-python/guides/accept-rich-user-input Client-side configuration for the two-phase upload strategy. ```json { type: "two_phase", } ``` -------------------------------- ### Implement ChatKitServer Source: https://openai.github.io/chatkit-python/guides/respond-to-user-message Subclass ChatKitServer and implement the respond method to yield events for user turns. ```python from collections.abc import AsyncIterator from datetime import datetime from chatkit.server import ChatKitServer from chatkit.types import ( AssistantMessageContent, AssistantMessageItem, ThreadItemDoneEvent, ThreadMetadata, ThreadStreamEvent, UserMessageItem, ) class MyChatKitServer(ChatKitServer[MyRequestContext]): async def respond( self, thread: ThreadMetadata, input: UserMessageItem | None, context: MyRequestContext, ) -> AsyncIterator[ThreadStreamEvent]: # Replace this with your inference pipeline. yield ThreadItemDoneEvent( item=AssistantMessageItem( thread_id=thread.id, id=self.store.generate_item_id("message", thread, context), created_at=datetime.now(), content=[AssistantMessageContent(text="Hi there!")], ) ) ``` -------------------------------- ### Initialize ResponseStreamConverter Source: https://openai.github.io/chatkit-python/api/chatkit/agents Initialize the converter, optionally specifying the number of partial image updates for progress normalization. ```python self.partial_images = partial_images ``` -------------------------------- ### Define Stream Options Source: https://openai.github.io/chatkit-python/api/chatkit/types Configuration for runtime stream behavior. ```python class StreamOptions(BaseModel): """Settings that control runtime stream behavior.""" allow_cancel: bool """Allow the client to request cancellation mid-stream.""" ``` ```python allow_cancel: bool ``` ```python class StreamOptionsEvent(BaseModel): """Event emitted to set stream options at runtime.""" type: Literal["stream_options"] = "stream_options" stream_options: StreamOptions ``` -------------------------------- ### Implement client tools for local state Source: https://openai.github.io/chatkit-python/guides/pass-extra-app-context-to-your-model Use client tools to handle state that exists only on the client, such as UI selections. ```python @function_tool(description_override="Read the user's current canvas selection.") async def get_canvas_selection(ctx: RunContextWrapper[AgentContext]) -> None: ctx.context.client_tool_call = ClientToolCall( name="get_canvas_selection", arguments={}, ) ``` -------------------------------- ### Minimal ChatKit Python Server Source: https://openai.github.io/chatkit-python/quickstart Create a minimal Python server using FastAPI and ChatKitServer that forwards requests to a ChatKitServer instance and streams a fixed response. ```python # Other imports omitted for brevity; see the starter repo for a runnable file with all imports. from chatkit.server import ChatKitServer app = FastAPI() class MyChatKitServer(ChatKitServer[dict]): async def respond( self, thread: ThreadMetadata, input_user_message: UserMessageItem | None, context: dict, ) -> AsyncIterator[ThreadStreamEvent]: # Streams a fixed "Hello, world!" assistant message yield ThreadItemDoneEvent( item=AssistantMessageItem( thread_id=thread.id, id=self.store.generate_item_id("message", thread, context), created_at=datetime.now(), content=[AssistantMessageContent(text="Hello, world!")], ), ) # Create your server by passing a store implementation. # MyChatKitStore is defined in the next section. server = MyChatKitServer(store=MyChatKitStore()) @app.post("/chatkit") async def chatkit(request: Request): result = await server.process(await request.body(), context={}) if isinstance(result, StreamingResult): return StreamingResponse(result, media_type="text/event-stream") return Response(content=result.json, media_type="application/json") ``` -------------------------------- ### Register a client tool callback in JavaScript Source: https://openai.github.io/chatkit-python/guides/update-client-during-response Provide an onClientTool handler during ChatKit initialization to execute logic in the browser and return results to the server. ```javascript const chatkit = useChatKit({ // ... onClientTool: async ({name, params}) => { if (name === "get_selected_canvas_nodes") { const {project} = params; const nodes = myCanvas.getSelectedNodes(project); return { nodes: nodes.map((node) => ({id: node.id, kind: node.type})), }; } }, }); ``` -------------------------------- ### Implement two-phase attachment store Source: https://openai.github.io/chatkit-python/guides/accept-rich-user-input Server-side implementation of an AttachmentStore that issues multipart upload URLs for the two-phase strategy. ```python attachment_store = BlobAttachmentStore() server = MyChatKitServer(store=data_store, attachment_store=attachment_store) class BlobAttachmentStore(AttachmentStore[RequestContext]): def generate_attachment_id(self, mime_type: str, context: RequestContext) -> str: return f"att_{uuid4().hex}" async def create_attachment( self, input: AttachmentCreateParams, context: RequestContext ) -> Attachment: att_id = self.generate_attachment_id(input.mime_type, context) upload_url = issue_multipart_upload_url(att_id, input.mime_type) # your blob store attachment = Attachment( id=att_id, mime_type=input.mime_type, name=input.name, upload_url=upload_url, ) await data_store.save_attachment(attachment, context=context) return attachment async def delete_attachment(self, attachment_id: str, context: RequestContext) -> None: await delete_blob(att_id=attachment_id) # your blob store ``` -------------------------------- ### Prepare Model Input Source: https://openai.github.io/chatkit-python/guides/respond-to-user-message Convert thread items into model input using default or custom converters. ```python from agents import Runner from chatkit.agents import AgentContext, ThreadItemConverter, simple_to_agent_input # Option A (defaults): input_items = await simple_to_agent_input(items) # Option B (your integration-specific converter): # input_items = await MyThreadItemConverter().to_agent_input(items) agent_context = AgentContext( thread=thread, store=self.store, request_context=context, ) ``` -------------------------------- ### action Source: https://openai.github.io/chatkit-python/api/chatkit/server Handles a widget or client-dispatched action and yields response events. ```APIDOC ## action ### Description Handle a widget or client-dispatched action and yield response events. ### Parameters - **thread** (ThreadMetadata) - Required - Metadata for the current thread. - **action** (Action[str, Any]) - Required - The action to be performed. - **sender** (WidgetItem | None) - Required - The widget item that triggered the action. - **context** (TContext) - Required - Arbitrary per-request context provided by the caller. ### Response - **AsyncIterator[ThreadStreamEvent]** - Yields response events. ``` -------------------------------- ### Configure composer tools Source: https://openai.github.io/chatkit-python/guides/let-users-pick-tools-and-models Define the tools available in the composer menu by providing an array of tool objects with unique IDs and labels during ChatKit initialization. ```javascript const chatkit = useChatKit({ // ... composer: { tools: [ { id: "summarize", icon: "book-open", label: "Summarize", placeholderOverride: "Summarize the current page or document.", }, { id: "search_tickets", icon: "search", label: "Search tickets", shortLabel: "Search", placeholderOverride: "Search support tickets for similar issues.", }, ], }, }); ``` -------------------------------- ### async widget_to_input Source: https://openai.github.io/chatkit-python/api/chatkit/agents Converts a WidgetItem into input item(s) to send to the model, representing the widget as a text description with its JSON representation. ```APIDOC ## async widget_to_input ### Description Converts a WidgetItem into input item(s) to send to the model. It generates a text description including the widget ID and its JSON-serialized content. ### Parameters #### Request Body - **item** (WidgetItem) - Required - The widget item to be converted. ``` -------------------------------- ### Implement ChatKitServer.transcribe Source: https://openai.github.io/chatkit-python/guides/accept-rich-user-input Handle audio input from the client and return a transcription result using the OpenAI Audio API. ```python async def transcribe(self, audio_input: AudioInput, context: RequestContext) -> TranscriptionResult: ext = { "audio/webm": "webm", "audio/mp4": "m4a", "audio/ogg": "ogg", }.get(audio_input.media_type) if not ext: raise HTTPException(status_code=400, detail="Unexpected audio format") audio_file = io.BytesIO(audio_input.data) audio_file.name = f"audio.{ext}" transcription = client.audio.transcriptions.create( model="gpt-4o-transcribe", file=audio_file ) return TranscriptionResult(text=transcription.text) ``` -------------------------------- ### async sync_action Source: https://openai.github.io/chatkit-python/api/chatkit/server Handles a widget or client-dispatched action and returns a synchronous response. ```APIDOC ## async sync_action ### Description Handle a widget or client-dispatched action and return a SyncCustomActionResponse. ### Parameters - **thread** (ThreadMetadata) - Required - Metadata for the current thread. - **action** (Action[str, Any]) - Required - The action to be performed. - **sender** (WidgetItem | None) - Required - The widget item that triggered the action. - **context** (TContext) - Required - Arbitrary per-request context provided by the caller. ### Response - **SyncCustomActionResponse** - The synchronous response to the action. ``` -------------------------------- ### Client-side ChatKit Configuration with Domain Key Source: https://openai.github.io/chatkit-python/guides/prepare-your-app-for-production Configure the ChatKit client options, including the backend API URL and the domain key obtained from the OpenAI domain allowlist. This key is used to verify the hosting domain. ```javascript const options = { api: { url: "https://your-domain.com/api/chatkit", // Copy this value from the domain allowlist entry. domainKey: "your-domain-key", }, }; ``` -------------------------------- ### Implement client-side action handling Source: https://openai.github.io/chatkit-python/guides/build-interactive-responses-with-widgets Provide an onAction callback to handle client-side logic and optionally forward actions to the server. ```javascript const chatkit = useChatKit({ // ... widgets: { onAction: async (action, widgetItem) => { if (action.type === "save_profile") { const result = await saveProfile(action.payload); // Optionally invoke a server action after client-side work completes. await chatkit.sendCustomAction( { type: "save_profile_complete", payload: {...result, user_id: action.payload.user_id}, }, widgetItem.id, ); } }, }, }); ``` -------------------------------- ### Implement Localization with gettext Source: https://openai.github.io/chatkit-python/guides/prepare-your-app-for-production Configure gettext to handle multi-language support and use the locale from the request context to translate tool output and error messages. ```python from pathlib import Path import gettext from agents import RunContextWrapper, function_tool from chatkit.agents import AgentContext LOCALE_DIR = Path(__file__).with_suffix("").parent / "locales" _translations: dict[str, gettext.NullTranslations] = {} def get_translations(locale: str) -> gettext.NullTranslations: """Return a gettext translation object for the given locale.""" if locale not in _translations: _translations[locale] = gettext.translation( "messages", # your .po/.mo domain localedir=LOCALE_DIR, languages=[locale], fallback=True, ) return _translations[locale] @function_tool() async def load_document( ctx: RunContextWrapper[AgentContext], document_id: str, ): locale = ctx.context.request_context.locale _ = get_translations(locale).gettext await ctx.context.stream_progress( icon="document", text=_("Loading document…"), ) doc = await get_document_by_id(document_id) if not doc: raise ValueError(_("We couldn’t find that document.")) return doc ``` -------------------------------- ### Format Client Tool Call to Input Source: https://openai.github.io/chatkit-python/api/chatkit/agents Converts a ClientToolCallItem into a list of TResponseInputItem. Pending tool calls are filtered out as they cannot be sent to the model. ```python async def client_tool_call_to_input( self, item: ClientToolCallItem ) -> TResponseInputItem | list[TResponseInputItem] | None: if item.status == "pending": # Filter out pending tool calls - they cannot be sent to the model return None return [ ResponseFunctionToolCallParam( type="function_call", call_id=item.call_id, name=item.name, arguments=json.dumps(item.arguments), ), FunctionCallOutput( type="function_call_output", call_id=item.call_id, output=json.dumps(item.output), ), ] ``` -------------------------------- ### Abstract load_threads Method Source: https://openai.github.io/chatkit-python/api/chatkit/store Abstract method to load a paginated list of threads. Requires pagination parameters and context. ```python @abstractmethod async def load_threads( self, limit: int, after: str | None, order: str, context: TContext, ) -> Page[ThreadMetadata]: """Load a page of threads with pagination controls.""" pass ``` -------------------------------- ### Configure model picker Source: https://openai.github.io/chatkit-python/guides/let-users-pick-tools-and-models Expose model selection options in the composer to allow users to choose between different performance or quality tiers. ```javascript const chatkit = useChatKit({ // ... composer: { models: [ { id: "gpt-4.1-mini", label: "Fast", description: "Answers right away", }, { id: "gpt-4.1", label: "Quality", description: "All rounder" default: true, }, ], }, }); ``` -------------------------------- ### diff_widget Source: https://openai.github.io/chatkit-python/api/chatkit/server Compares two WidgetRoot objects and returns a list of deltas representing the changes required to transition from the 'before' state to the 'after' state. ```APIDOC ## diff_widget ### Description Compares two WidgetRoot objects and returns a list of deltas. This function determines if a full replacement is necessary or if incremental updates (streaming text deltas) can be applied. ### Parameters - **before** (WidgetRoot) - Required - The initial state of the widget. - **after** (WidgetRoot) - Required - The new state of the widget. ### Response - **Returns** (list[WidgetStreamingTextValueDelta | WidgetRootUpdated | WidgetComponentUpdated]) - A list of delta objects representing the changes. ### Errors - **ValueError**: Raised if a node ID is missing from the initial render or if a streaming text update is not a cumulative prefix of the previous value. ``` -------------------------------- ### Handle Widget Actions Source: https://openai.github.io/chatkit-python/api/chatkit/server Implement this method to handle widget or client-dispatched actions. It should yield response events asynchronously. ```python def action( self, thread: ThreadMetadata, action: Action[str, Any], sender: WidgetItem | None, context: TContext, ) -> AsyncIterator[ThreadStreamEvent]: """Handle a widget or client-dispatched action and yield response events.""" raise NotImplementedError( "The action() method must be overridden to react to actions. " "See https://github.com/openai/chatkit-python/blob/main/docs/widgets.md#widget-actions" ) ``` -------------------------------- ### Stream Progress Updates from Server Tools Source: https://openai.github.io/chatkit-python/guides/respond-to-user-message Use ctx.context.stream within a function tool to send real-time progress updates to the client during execution. ```python from agents import RunContextWrapper, function_tool from chatkit.agents import AgentContext from chatkit.types import ProgressUpdateEvent @function_tool() async def load_document(ctx: RunContextWrapper[AgentContext], document_id: str): await ctx.context.stream(ProgressUpdateEvent(icon="document", text="Loading document...")) return await get_document_by_id(document_id) ``` -------------------------------- ### Define SourceBase Model Source: https://openai.github.io/chatkit-python/api/chatkit/types Base class for all source metadata displayed to users. ```python class SourceBase(BaseModel): """Base class for sources displayed to users.""" title: str description: str | None = None timestamp: str | None = None group: str | None = None ``` -------------------------------- ### Enable dictation in the client Source: https://openai.github.io/chatkit-python/guides/accept-rich-user-input Configure the composer options within useChatKit to enable the dictation feature. ```javascript const chatkit = useChatKit({ // ... composer: { dictation: { enabled: true, }, }, }); ``` -------------------------------- ### Stream Agent Responses and Handle Guardrails Source: https://openai.github.io/chatkit-python/guides/respond-to-user-message Convert agent runs into streamable events and handle guardrail tripwire exceptions to provide user feedback. ```python from agents import ( InputGuardrailTripwireTriggered, OutputGuardrailTripwireTriggered, Runner, ) from chatkit.agents import stream_agent_response from chatkit.types import ErrorEvent result = Runner.run_streamed( assistant_agent, input_items, context=agent_context, ) try: async for event in stream_agent_response(agent_context, result): yield event except InputGuardrailTripwireTriggered: yield ErrorEvent(message="We blocked that message for safety.") except OutputGuardrailTripwireTriggered: yield ErrorEvent( message="The assistant response was blocked.", allow_retry=False, ) ``` -------------------------------- ### Add ImageGenerationTool to Agent Source: https://openai.github.io/chatkit-python/guides/stream-generated-images Integrate the ImageGenerationTool into your agent's tool list to enable image generation capabilities. This allows the model to create images when prompted. ```python from agents import Agent from agents.tool import ImageGenerationTool agent = Agent( name="designer", instructions="Generate images when asked.", tools=[ImageGenerationTool(tool_config={"type": "image_generation"})], ) ``` -------------------------------- ### Build User Message Item Source: https://openai.github.io/chatkit-python/api/chatkit/server Constructs a UserMessageItem from UserMessageInput, including content, attachments, and quoted text. ```python return UserMessageItem( id=self.store.generate_item_id("message", thread, context), content=input.content, thread_id=thread.id, attachments=[ (await self.store.load_attachment(attachment_id, context)).model_copy( update={"thread_id": thread.id} ) for attachment_id in input.attachments ], quoted_text=input.quoted_text, ) ``` -------------------------------- ### Define FileSource Model Source: https://openai.github.io/chatkit-python/api/chatkit/types Source metadata specifically for file references. ```python class FileSource(SourceBase): """Source metadata for file-based references.""" type: Literal["file"] = "file" filename: str ``` -------------------------------- ### Chatkit Python SDK Overview Source: https://openai.github.io/chatkit-python/api/chatkit Overview of the core modules available in the Chatkit Python library for building chat applications. ```APIDOC ## Chatkit Python SDK Modules ### Description The Chatkit Python SDK is organized into several core modules to handle server communication, data persistence, agent integration, and UI rendering. ### Modules - **chatkit.server**: Contains `ChatKitServer` and helpers for receiving messages, streaming responses, and tool/widget wiring. - **chatkit.store**: Provides interfaces and utilities for persisting threads, items, and metadata. - **chatkit.agents**: Includes helpers for integrating ChatKit with the Agents SDK. - **chatkit.types**: Defines Pydantic models for threads, items, events, and shared data types. - **chatkit.errors**: Defines structured error types for consistent `ErrorEvent` emission. - **chatkit.widgets**: Provides models and helpers like `WidgetTemplate`, `DynamicWidgetRoot`, and `BasicRoot` for rich UI responses. ``` -------------------------------- ### Load ChatKit.js in HTML Source: https://openai.github.io/chatkit-python/quickstart Include the ChatKit.js script in your index.html file to enable ChatKit functionality in the browser. ```html
``` -------------------------------- ### Implement Action Handling Source: https://openai.github.io/chatkit-python/api/chatkit/server Override the `action` method to handle widget or client-dispatched actions. This asynchronous method yields response events. ```python async def action( self, thread: ThreadMetadata, action: Action[str, Any], sender: WidgetItem | None, context: TContext, ) -> AsyncIterator[ThreadStreamEvent]: """Handle a widget or client-dispatched action and yield response events.""" raise NotImplementedError( "The action() method must be overridden to react to actions. " "See https://github.com/openai/chatkit-python/blob/main/docs/widgets.md#widget-actions" ) ``` -------------------------------- ### Implement an in-memory ChatKit store Source: https://openai.github.io/chatkit-python/quickstart Defines a custom store class inheriting from Store to handle thread and item lifecycle. This implementation uses dictionaries for storage and includes a helper for pagination. ```python from collections import defaultdict from chatkit.store import NotFoundError, Store from chatkit.types import Attachment, Page, ThreadItem, ThreadMetadata class MyChatKitStore(Store[dict]): def __init__(self): self.threads: dict[str, ThreadMetadata] = {} self.items: dict[str, list[ThreadItem]] = defaultdict(list) async def load_thread(self, thread_id: str, context: dict) -> ThreadMetadata: if thread_id not in self.threads: raise NotFoundError(f"Thread {thread_id} not found") return self.threads[thread_id] async def save_thread(self, thread: ThreadMetadata, context: dict) -> None: self.threads[thread.id] = thread async def load_threads( self, limit: int, after: str | None, order: str, context: dict ) -> Page[ThreadMetadata]: threads = list(self.threads.values()) return self._paginate( threads, after, limit, order, sort_key=lambda t: t.created_at, cursor_key=lambda t: t.id ) async def load_thread_items( self, thread_id: str, after: str | None, limit: int, order: str, context: dict ) -> Page[ThreadItem]: items = self.items.get(thread_id, []) return self._paginate( items, after, limit, order, sort_key=lambda i: i.created_at, cursor_key=lambda i: i.id ) async def add_thread_item( self, thread_id: str, item: ThreadItem, context: dict ) -> None: self.items[thread_id].append(item) async def save_item( self, thread_id: str, item: ThreadItem, context: dict ) -> None: items = self.items[thread_id] for idx, existing in enumerate(items): if existing.id == item.id: items[idx] = item return items.append(item) async def load_item( self, thread_id: str, item_id: str, context: dict ) -> ThreadItem: for item in self.items.get(thread_id, []): if item.id == item_id: return item raise NotFoundError(f"Item {item_id} not found in thread {thread_id}") async def delete_thread(self, thread_id: str, context: dict) -> None: self.threads.pop(thread_id, None) self.items.pop(thread_id, None) async def delete_thread_item( self, thread_id: str, item_id: str, context: dict ) -> None: self.items[thread_id] = [ item for item in self.items.get(thread_id, []) if item.id != item_id ] def _paginate(self, rows: list, after: str | None, limit: int, order: str, sort_key, cursor_key): sorted_rows = sorted(rows, key=sort_key, reverse=order == "desc") start = 0 if after: for idx, row in enumerate(sorted_rows): if cursor_key(row) == after: start = idx + 1 break data = sorted_rows[start : start + limit] has_more = start + limit < len(sorted_rows) next_after = cursor_key(data[-1]) if has_more and data else None return Page(data=data, has_more=has_more, after=next_after) # Attachments are intentionally not implemented for the quickstart async def save_attachment( self, attachment: Attachment, context: dict ) -> None: raise NotImplementedError() async def load_attachment( self, attachment_id: str, context: dict ) -> Attachment: raise NotImplementedError() async def delete_attachment(self, attachment_id: str, context: dict) -> None: raise NotImplementedError() ``` -------------------------------- ### Add Client Tool Output Source: https://openai.github.io/chatkit-python/api/chatkit/types Records the output of a client-side tool in a thread. ```APIDOC ## POST threads.add_client_tool_output ### Description Records tool output in a thread. ### Request Body - **type** (string) - Required - Must be "threads.add_client_tool_output" - **params** (object) - Required - **thread_id** (string) - Required - The ID of the target thread - **result** (Any) - Required - The tool execution result ``` -------------------------------- ### process Source: https://openai.github.io/chatkit-python/api/chatkit/server Parses an incoming ChatKit request and routes it to either streaming or non-streaming handlers. ```APIDOC ## process ### Description Parse an incoming ChatKit request and route it to streaming or non-streaming handlers. ### Parameters - **request** (str | bytes | bytearray) - Required - The incoming request data. - **context** (TContext) - Required - Arbitrary per-request context provided by the caller. ### Response - **Returns** (StreamingResult | NonStreamingResult) - The result of the processed request. ``` -------------------------------- ### simple_to_agent_input Source: https://openai.github.io/chatkit-python/api/chatkit/agents Converts thread items into agent input using the default converter. ```APIDOC ## simple_to_agent_input ### Description Helper function that converts one or more thread items into agent input using the default ThreadItemConverter. ### Parameters #### Request Body - **thread_items** (Sequence[ThreadItem] | ThreadItem) - Required - The thread item or sequence of thread items to be converted. ``` -------------------------------- ### Route requests based on tool choice Source: https://openai.github.io/chatkit-python/guides/let-users-pick-tools-and-models Branch the inference pipeline by using the tool ID to select the appropriate agent or tool set. ```python if tool_choice == "summarize": agent = summarization_agent elif tool_choice == "search_tickets": agent = ticket_search_agent else: agent = default_agent result = Runner.run_streamed(agent, input_items, context=agent_context) ``` -------------------------------- ### Use custom converter for agent input Source: https://openai.github.io/chatkit-python/guides/stream-generated-images Apply the custom converter instance when building the agent input. ```python input_items = await MyThreadItemConverter().to_agent_input(items) ``` -------------------------------- ### ItemsListParams: Pagination parameters for listing thread items Source: https://openai.github.io/chatkit-python/api/chatkit/types Configure pagination and ordering for listing thread items. Supports limit, order (asc/desc), and after cursor. ```python class ItemsListParams(BaseModel): """Pagination parameters for listing thread items.""" thread_id: str limit: int | None = None order: Literal["asc", "desc"] = "desc" after: str | None = None ``` -------------------------------- ### Implement Synchronous Action Handling Source: https://openai.github.io/chatkit-python/api/chatkit/server Override the `sync_action` method to handle widget or client-dispatched actions synchronously. This method returns a `SyncCustomActionResponse`. ```python async def sync_action( self, thread: ThreadMetadata, action: Action[str, Any], sender: WidgetItem | None, context: TContext, ) -> SyncCustomActionResponse: """Handle a widget or client-dispatched action and return a SyncCustomActionResponse.""" raise NotImplementedError( "The sync_action() method must be overridden to react to sync actions. " "See https://github.com/openai/chatkit-python/blob/main/docs/widgets.md#widget-actions" ) ``` -------------------------------- ### Implement create_attachment Source: https://openai.github.io/chatkit-python/api/chatkit/store Method for creating an attachment record, which must be overridden to support two-phase file uploads. ```python async def create_attachment( self, input: AttachmentCreateParams, context: TContext ) -> Attachment: """Create an attachment record from upload metadata.""" raise NotImplementedError( f"{type(self).__name__} must override create_attachment() to support two-phase file upload" ) ``` -------------------------------- ### Implement FastAPI direct upload endpoint Source: https://openai.github.io/chatkit-python/guides/accept-rich-user-input Server-side implementation for handling direct file uploads via multipart/form-data. ```python @app.post("/files") async def upload_file(request: Request): form_data = await request.form() file = form_data.get("file") # Your blob store upload attachment = await upload_to_blob_store(file) return Response(content=attachment.model_dump_json(), media_type="application/json") ``` -------------------------------- ### Stream a widget from a tool Source: https://openai.github.io/chatkit-python/guides/build-interactive-responses-with-widgets Enqueue a widget from within a function tool using AgentContext. ```python from agents import RunContextWrapper, function_tool from chatkit.agents import AgentContext @function_tool(description_override="Display a sample widget to the user.") async def sample_widget(ctx: RunContextWrapper[AgentContext]): message_widget = build_message_widget(...) await ctx.context.stream_widget(message_widget) ``` -------------------------------- ### Configure entity search in ChatKit.js Source: https://openai.github.io/chatkit-python/guides/accept-rich-user-input Define the onTagSearch callback to return entity objects and enable the composer menu button. ```typescript const chatkit = useChatKit({ // ... entities: { onTagSearch: async (query: string) => { return [ { id: "article_123", title: "The Future of AI", group: "Trending", icon: "globe", data: { type: "article" } }, { id: "article_124", title: "One weird trick to improve your sleep", group: "Trending", icon: "globe", data: { type: "article" } }, ] }, // Optional: show the "@" button in the composer for added discoverability. showComposerMenu: true, }, }) ```