openai/chatkit-python

Public

mirrored fromhttps://github.com/openai/chatkit-pythonAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
df03dc0b6ac612bd24b460b3f3543c6ef7f69953

Branches

Tags

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

Clone

HTTPS

Download ZIP

docs/quickstart.md

260lines · modecode

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