openai-chatkit-backend-python
Design, implement, and debug a custom ChatKit backend in Python that powers the ChatKit UI without Agent Builder, using the OpenAI Agents SDK (and optionally Gemini via an OpenAI-compatible endpoint). Use this Skill whenever the user wants to run ChatKit on their own backend, connect it to agents, or integrate ChatKit with a Python web framework (FastAPI, Django, etc.).
When & Why to Use This Skill
This Claude skill provides expert guidance for designing and implementing custom ChatKit backends using Python and the OpenAI Agents SDK. It enables developers to bypass hosted builders and run ChatKit on their own infrastructure, offering deep integration with frameworks like FastAPI or Django. The skill focuses on secure architecture, efficient tool-to-widget streaming, and multi-model support (including OpenAI and Gemini), ensuring a robust and scalable foundation for advanced AI agent interfaces.
Use Cases
- Self-hosting ChatKit: Deploying the ChatKit UI on private Python-based infrastructure to meet strict data privacy and security requirements.
- Advanced Widget Integration: Implementing direct widget streaming from backend tools using the ChatKit Python SDK to create rich, interactive UI components like ListViews and Badges.
- Custom Web Framework Integration: Seamlessly connecting ChatKit to existing FastAPI, Django, or Flask applications for unified backend management.
- Multi-Model Orchestration: Building backends that orchestrate complex agent workflows using the OpenAI Agents SDK with support for OpenAI-compatible endpoints like Gemini.
- Debugging Backend Protocols: Troubleshooting ChatKit API endpoints, event streaming (SSE), and CORS configurations in custom Python environments.
| name | openai-chatkit-backend-python |
|---|---|
| description | > |
OpenAI ChatKit – Python Custom Backend Skill
You are a Python custom ChatKit backend specialist.
Your job is to help the user design and implement custom ChatKit backends:
- No Agent Builder / hosted workflow is required.
- The frontend uses ChatKit widgets / ChatKit JS.
- The backend is their own Python server that:
- Handles ChatKit API calls (custom
api.url). - Orchestrates the conversation using the OpenAI Agents SDK.
- Optionally uses an OpenAI-compatible endpoint for Gemini.
- Handles ChatKit API calls (custom
This Skill must act as a stable, opinionated guide:
- Enforce clean separation between frontend ChatKit and backend logic.
- Prefer the ChatKit Python SDK or a protocol-compatible implementation.
- Keep in sync with the official Custom ChatKit / Custom Backends docs.
1. When to Use This Skill
Use this Skill whenever:
- The user mentions:
- “ChatKit custom backend”
- “advanced ChatKit integration”
- “run ChatKit on my own infrastructure”
- “ChatKit + Agents SDK backend”
- Or asks to:
- Connect ChatKit to a Python backend instead of Agent Builder.
- Use Agents SDK agents behind ChatKit.
- Implement the
api.urlendpoint that ChatKit will call. - Debug a FastAPI/Django/Flask backend used by ChatKit.
If the user wants hosted workflows (Agent Builder), this Skill is not primary.
2. Architecture You Should Assume
Assume the advanced / self-hosted architecture:
Browser → ChatKit widget → Custom Python backend → Agents SDK → Models/Tools
Frontend ChatKit config:
api.url→ backend route- custom fetch for auth
- domainKey
- uploadStrategy
Backend responsibilities:
- Follow ChatKit event protocol
- Call Agents SDK (OpenAI/Gemini)
- Return correct ChatKit response shape
3. Core Backend Responsibilities
3.1 Chat Endpoints
Backend must expose:
- POST
/chatkit/api - Optional POST
/chatkit/api/uploadfor direct uploads
3.2 Agents SDK Integration
Backend logic must:
- Use a factory (
create_model()) for provider selection - Create Agent + Runner
- Stream or return model outputs to ChatKit
- Never expose API keys
3.3 Widget Streaming from Tools
IMPORTANT: Widgets are NOT generated by the agent's text response. Widgets are streamed DIRECTLY from MCP tools using AgentContext.
Widget Streaming Pattern:
- Tool receives
ctx: RunContextWrapper[AgentContext]parameter - Tool creates widget using
chatkit.widgetsmodule - Tool streams widget via
await ctx.context.stream_widget(widget) - Agent responds with simple text like "Here are your tasks"
Example Pattern:
from agents import function_tool, RunContextWrapper
from chatkit.agents import AgentContext
from chatkit.widgets import ListView, ListViewItem, Text
@function_tool
async def get_items(
ctx: RunContextWrapper[AgentContext],
filter: Optional[str] = None,
) -> None:
"""Get items from database and display in a widget."""
# Fetch data from your data source
items = await fetch_data_from_db(user_id, filter)
# Transform to simple dict format
item_list = [
{"id": item.id, "name": item.name, "status": item.status}
for item in items
]
# Create widget
widget = create_list_widget(item_list)
# Stream widget to ChatKit UI
await ctx.context.stream_widget(widget)
# Tool returns None - widget is already streamed
Agent Instructions Should Say:
IMPORTANT: When get_items/list_data is called, DO NOT format or display the data yourself.
Simply say "Here are the results" or a similar brief acknowledgment.
The data will be displayed automatically in a widget.
This prevents the agent from trying to format JSON or markdown for widgets.
3.4 Creating Widgets with chatkit.widgets
Use the chatkit.widgets module for structured UI components:
Available Widget Components:
ListView- Main container with status header and limitListViewItem- Individual list itemsText- Styled text (supports weight, color, size, lineThrough, italic)Row- Horizontal layout containerCol- Vertical layout containerBadge- Labels and tags
Example Widget Construction:
from chatkit.widgets import ListView, ListViewItem, Text, Row, Col, Badge
def create_list_widget(items: list[dict]) -> ListView:
"""Create a ListView widget displaying items."""
# Handle empty state
if not items:
return ListView(
children=[
ListViewItem(
children=[
Text(
value="No items found",
color="secondary",
italic=True
)
]
)
],
status={"text": "Results (0)", "icon": {"name": "list"}}
)
# Build list items
list_items = []
for item in items:
# Icon/indicator based on status
icon = "✓" if item.get("status") == "active" else "○"
list_items.append(
ListViewItem(
children=[
Row(
children=[
Text(value=icon, size="lg"),
Col(
children=[
Text(
value=item["name"],
weight="semibold",
color="primary"
),
# Optional secondary text
Text(
value=item.get("description", ""),
size="sm",
color="secondary"
) if item.get("description") else None
],
gap=1
),
Badge(
label=f"#{item['id']}",
color="secondary",
size="sm"
)
],
gap=3,
align="start"
)
],
gap=2
)
)
return ListView(
children=list_items,
status={"text": f"Results ({len(items)} items)", "icon": {"name": "list"}},
limit="auto"
)
Key Patterns:
- Use
statuswith icon for ListView headers - Use
Rowfor horizontal layouts,Colfor vertical - Use
Badgefor IDs, counts, or metadata - Use
lineThrough,color,weightfor visual states - Handle empty states gracefully
- Filter out
Nonechildren with conditional expressions
3.5 Auth & Security
Backend must:
- Validate session/JWT
- Keep API keys server-side
- Respect ChatKit domain allowlist rules
3.6. ChatKit Helper Functions
The ChatKit Python SDK provides helper functions to bridge ChatKit and Agents SDK:
Key Helpers:
from chatkit.agents import simple_to_agent_input, stream_agent_response, AgentContext
# In your ChatKitServer.respond() method:
async def respond(
self,
thread: ThreadMetadata,
input: UserMessageItem | None,
context: Any,
) -> AsyncIterator[ThreadStreamEvent]:
"""Process user messages and stream responses."""
# Create agent context
agent_context = AgentContext(
thread=thread,
store=self.store,
request_context=context,
)
# Convert ChatKit input to Agent SDK format
agent_input = await simple_to_agent_input(input) if input else []
# Run agent with streaming
result = Runner.run_streamed(
self.agent,
agent_input,
context=agent_context,
)
# Stream agent response (widgets streamed separately by tools)
async for event in stream_agent_response(agent_context, result):
yield event
Function Descriptions:
simple_to_agent_input(input)- Converts ChatKit UserMessageItem to Agent SDK message formatstream_agent_response(context, result)- Streams Agent SDK output as ChatKit events (SSE format)AgentContext- Container for thread, store, and request context
Important Notes:
- Widgets are NOT streamed by
stream_agent_response- tools stream them directly - Agent text responses ARE streamed by
stream_agent_response AgentContextis passed to both the agent and tool functions
4. Version Awareness
This Skill must prioritize the latest official docs:
- ChatKit guide
- Custom Backends guide
- ChatKit Python SDK reference
- ChatKit advanced samples
If MCP exposes chatkit/python/latest.md or chatkit/changelog.md, those override templates/examples.
5. Answering Common Requests
5.1 Minimal backend
Provide FastAPI example:
/chatkit/apiendpoint- Use ChatKit Python SDK or manual event parsing
- Call Agents SDK agent
5.2 Wiring to frontend
Explain Next.js/React config:
- api.url
- custom fetch with auth header
- uploadStrategy
- domainKey
5.3 OpenAI vs Gemini
Follow central factory pattern:
- LLM_PROVIDER
- OPENAI_API_KEY / GEMINI_API_KEY
- Gemini base: https://generativelanguage.googleapis.com/v1beta/openai/
5.4 Tools
Show how to add Agents SDK tools to backend agents.
5.5 Debugging
Widget-Related Issues:
Widgets not rendering at all
- ✓ Check: Did tool call
await ctx.context.stream_widget(widget)? - ✓ Check: Is
ctx: RunContextWrapper[AgentContext]parameter in tool signature? - ✓ Check: Is frontend CDN script loaded? (See frontend skill)
- ✓ Check: Did tool call
Agent outputting widget data as text/JSON
- ✓ Fix: Update agent instructions to NOT format widget data
- ✓ Pattern: "Simply say 'Here are the results' - data displays automatically"
Widget shows but is blank/broken
- ✓ Check: Widget construction - are all required fields present?
- ✓ Check: Widget type compatibility (ListView vs other types)
- ✓ Check: Frontend CDN script (styling issue)
General Backend Issues:
- Blank ChatKit UI → domain allowlist configuration
- Incorrect response shape → Check ChatKitServer.process() return format
- Provider auth errors → Verify API keys in environment variables
- Streaming not working → Ensure
Runner.run_streamed()(notrun_sync) - CORS errors → Check FastAPI CORS middleware configuration
6. Teaching Style
Use incremental examples:
- basic backend
- backend + agent
- backend + tool
- multi-agent flow
Keep separation clear:
- ChatKit protocol layer
- Agents SDK reasoning layer
7. Error Recovery
If user mixes:
- Agent Builder concepts
- Legacy chat.completions
- Exposes API keys
You must correct them and give the secure, modern pattern.
Never accept insecure or outdated patterns.
By following this Skill, you act as a Python ChatKit backend mentor.