openai/chatkit-python

Public

mirrored from https://github.com/openai/chatkit-pythonAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
6646f83af681e14fcdcf69f2fa31dbdb0a9efa2d

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

docs/quickstart.md

261lines · modeblame

b231f471Jiwon Kim7 months ago1# Quick start
2
3To get a basic ChatKit app running—a React chat UI talking to a Python server—clone and run the starter app:
4
5
6```sh
7git clone https://github.com/openai/openai-chatkit-starter-app.git
8cd openai-chatkit-starter-app/chatkit
adf5a3acYOUR NAME6 months ago9npm install
b231f471Jiwon Kim7 months ago10npm run dev
11```
12
13The sections below explain the core components and steps behind the starter app.
14
15## Render chat UI
16
d25aeb54Jiwon Kim6 months ago17!!! note "Quick start uses React"
b231f471Jiwon Kim7 months ago18This section shows the React integration using `@openai/chatkit-react`.
d25aeb54Jiwon Kim6 months ago19If you’re not using React, you can render ChatKit directly with vanilla JavaScript using `@openai/chatkit`. See the [ChatKit.js quick start](https://openai.github.io/chatkit-js/quickstart/) for details.
b231f471Jiwon Kim7 months ago20
21Install the React bindings:
22
23```sh
24npm install @openai/chatkit-react
25```
26
27In your index.html, load ChatKit.js:
28
29```html
30<!doctype html>
31<html lang="en">
32<head>
33<meta charset="UTF-8" />
34<script src="https://cdn.platform.openai.com/deployments/chatkit/chatkit.js"></script>
35</head>
36<body>
37<div id="root"></div>
38</body>
39</html>
40```
41
42Wire up a minimal React app. Point `api.url` at your ChatKit server endpoint and pass the domain key you configured there.
43
44```tsx
45import {ChatKit, useChatKit} from "@openai/chatkit-react";
46
47export function App() {
48const chatkit = useChatKit({
49api: {
50url: "http://localhost:8000/chatkit",
51domainKey: "local-dev", // domain keys are optional in dev
52},
53});
54
55return <ChatKit control={chatkit.control} />;
56}
57```
58
59The chat UI will render, but sending messages will fail until you start the server below and provide a store for threads and messages.
60
61## Run your ChatKit server
62
63Install the ChatKit Python package and expose a single `/chatkit` endpoint that forwards requests to a `ChatKitServer` instance.
64
65```sh
d25aeb54Jiwon Kim6 months ago66pip install openai-chatkit
b231f471Jiwon Kim7 months ago67```
68
e8c87839Jiwon Kim7 months ago69Create `main.py` with a minimal server that is hard-coded to always reply “Hello, world!”—you'll replace this with an actual call to a model in [Respond to a user message](guides/respond-to-user-message.md).
b231f471Jiwon Kim7 months ago70
71```python
72# Other imports omitted for brevity; see the starter repo for a runnable file with all imports.
73from chatkit.server import ChatKitServer
74
75app = FastAPI()
76
77
78class MyChatKitServer(ChatKitServer[dict]):
79async def respond(
80self,
81thread: ThreadMetadata,
82input_user_message: UserMessageItem | None,
83context: dict,
84) -> AsyncIterator[ThreadStreamEvent]:
85# Streams a fixed "Hello, world!" assistant message
86yield ThreadItemDoneEvent(
87item=AssistantMessageItem(
88thread_id=thread.id,
89id=self.store.generate_item_id("message", thread, context),
90created_at=datetime.now(),
91content=[AssistantMessageContent(text="Hello, world!")],
92),
93)
94
95# Create your server by passing a store implementation.
96# MyChatKitStore is defined in the next section.
97server = MyChatKitServer(store=MyChatKitStore())
98
99
100@app.post("/chatkit")
101async def chatkit(request: Request):
102result = await server.process(await request.body(), context={})
103if isinstance(result, StreamingResult):
104return StreamingResponse(result, media_type="text/event-stream")
105return Response(content=result.json, media_type="application/json")
106```
107
108All ChatKit requests go to this single endpoint. Set `api.url` on the React side to match (`/chatkit` here), and `ChatKitServer` routes each request internally.
109
110
111## Store chat data
112
113ChatKit servers require a store to load and save threads, messages, and other items.
114
115For this quickstart, use a small in-memory store so conversations persist while the process is running, without introducing a database. This keeps the example minimal while still matching real ChatKit behavior.
116
117
118```python
119from collections import defaultdict
120from chatkit.store import NotFoundError, Store
121from chatkit.types import Attachment, Page, ThreadItem, ThreadMetadata
122
123
124class MyChatKitStore(Store[dict]):
125def __init__(self):
126self.threads: dict[str, ThreadMetadata] = {}
127self.items: dict[str, list[ThreadItem]] = defaultdict(list)
128
129async def load_thread(self, thread_id: str, context: dict) -> ThreadMetadata:
130if thread_id not in self.threads:
131raise NotFoundError(f"Thread {thread_id} not found")
132return self.threads[thread_id]
133
134async def save_thread(self, thread: ThreadMetadata, context: dict) -> None:
135self.threads[thread.id] = thread
136
137async def load_threads(
138self, limit: int, after: str | None, order: str, context: dict
139) -> Page[ThreadMetadata]:
140threads = list(self.threads.values())
141return self._paginate(
142threads, after, limit, order, sort_key=lambda t: t.created_at, cursor_key=lambda t: t.id
143)
144
145async def load_thread_items(
146self, thread_id: str, after: str | None, limit: int, order: str, context: dict
147) -> Page[ThreadItem]:
148items = self.items.get(thread_id, [])
149return self._paginate(
150items, after, limit, order, sort_key=lambda i: i.created_at, cursor_key=lambda i: i.id
151)
152
153async def add_thread_item(
154self, thread_id: str, item: ThreadItem, context: dict
155) -> None:
156self.items[thread_id].append(item)
157
158async def save_item(
159self, thread_id: str, item: ThreadItem, context: dict
160) -> None:
161items = self.items[thread_id]
162for idx, existing in enumerate(items):
163if existing.id == item.id:
164items[idx] = item
165return
166items.append(item)
167
168async def load_item(
169self, thread_id: str, item_id: str, context: dict
170) -> ThreadItem:
171for item in self.items.get(thread_id, []):
172if item.id == item_id:
173return item
174raise NotFoundError(f"Item {item_id} not found in thread {thread_id}")
175
176async def delete_thread(self, thread_id: str, context: dict) -> None:
177self.threads.pop(thread_id, None)
178self.items.pop(thread_id, None)
179
180async def delete_thread_item(
181self, thread_id: str, item_id: str, context: dict
182) -> None:
183self.items[thread_id] = [
184item for item in self.items.get(thread_id, []) if item.id != item_id
185]
186
187def _paginate(self, rows: list, after: str | None, limit: int, order: str, sort_key, cursor_key):
188sorted_rows = sorted(rows, key=sort_key, reverse=order == "desc")
189start = 0
190if after:
191for idx, row in enumerate(sorted_rows):
192if cursor_key(row) == after:
193start = idx + 1
194break
195data = sorted_rows[start : start + limit]
196has_more = start + limit < len(sorted_rows)
197next_after = cursor_key(data[-1]) if has_more and data else None
198return Page(data=data, has_more=has_more, after=next_after)
199
200# Attachments are intentionally not implemented for the quickstart
201
202async def save_attachment(
203self, attachment: Attachment, context: dict
204) -> None:
205raise NotImplementedError()
206
207async def load_attachment(
208self, attachment_id: str, context: dict
209) -> Attachment:
210raise NotImplementedError()
211
212async def delete_attachment(self, attachment_id: str, context: dict) -> None:
213raise NotImplementedError()
214
215```
216
217This store implements only the methods required for basic chat while the server is running; persistence across restarts and attachments are intentionally omitted.
218
219For production, replace this with a database-backed store (for example, Postgres or MySQL) so threads and items persist across restarts.
220
221
222## Generate model responses
223
224Replace the hardcoded "Hello, World!" reply from [Run your ChatKit server](#run-your-chatkit-server) with an Agents SDK call to generate real responses. Set `OPENAI_API_KEY` in your environment before running.
225
226Use 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:
227
228
229```python
230from agents import Agent, Runner
231from chatkit.agents import AgentContext, simple_to_agent_input, stream_agent_response
232
233assistant = Agent(
234name="assistant",
235instructions="You are a helpful assistant.",
236model="gpt-4.1-mini",
237)
238
239class MyChatKitServer(ChatKitServer[dict]):
240async def respond(
241self,
242thread: ThreadMetadata,
243input_user_message: UserMessageItem | None,
244context: dict,
245) -> AsyncIterator[ThreadStreamEvent]:
246# Convert recent thread items (which includes the user message) to model input
247items_page = await self.store.load_thread_items(
248thread.id,
249after=None,
250limit=20,
251order="asc",
252context=context,
253)
254input_items = await simple_to_agent_input(items_page.data)
255
256# Stream the run through ChatKit events
257agent_context = AgentContext(thread=thread, store=self.store, request_context=context)
258result = Runner.run_streamed(assistant, input_items, context=agent_context)
259async for event in stream_agent_response(agent_context, result):
260yield event
261```