Skip to content

Commit 799d42f

Browse files
authored
Merge pull request #74 from openai/concepts-pages
[docs] Concepts pages; fix missing api references
2 parents 3a44ad4 + 3b20c3d commit 799d42f

13 files changed

Lines changed: 268 additions & 56 deletions

chatkit/agents.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class AgentContext(BaseModel, Generic[TContext]):
110110
def generate_id(
111111
self, type: StoreItemType, thread: ThreadMetadata | None = None
112112
) -> str:
113+
"""Generate a new store-backed id for the given item type."""
113114
if type == "thread":
114115
return self.store.generate_thread_id(self.request_context)
115116
return self.store.generate_item_id(
@@ -121,6 +122,7 @@ async def stream_widget(
121122
widget: WidgetRoot | AsyncGenerator[WidgetRoot, None],
122123
copy_text: str | None = None,
123124
) -> None:
125+
"""Stream a widget into the thread by enqueueing widget events."""
124126
async for event in stream_widget(
125127
self.thread,
126128
widget,
@@ -134,6 +136,7 @@ async def stream_widget(
134136
async def end_workflow(
135137
self, summary: WorkflowSummary | None = None, expanded: bool = False
136138
) -> None:
139+
"""Finalize the active workflow item, optionally attaching a summary."""
137140
if not self.workflow_item:
138141
# No workflow to end
139142
return
@@ -150,6 +153,7 @@ async def end_workflow(
150153
self.workflow_item = None
151154

152155
async def start_workflow(self, workflow: Workflow) -> None:
156+
"""Begin streaming a new workflow item."""
153157
self.workflow_item = WorkflowItem(
154158
id=self.generate_id("workflow"),
155159
created_at=datetime.now(),
@@ -161,9 +165,10 @@ async def start_workflow(self, workflow: Workflow) -> None:
161165
# Defer sending added event until we have tasks
162166
return
163167

164-
await self.stream(ThreadItemAddedEvent(item=self.workflow_item))
168+
await self.stream(ThreadItemAddedEvent(item=self.workflow_item))
165169

166170
async def update_workflow_task(self, task: Task, task_index: int) -> None:
171+
"""Update an existing workflow task and stream the delta."""
167172
if self.workflow_item is None:
168173
raise ValueError("Workflow is not set")
169174
# ensure reference is updated in case task is a copy
@@ -179,6 +184,7 @@ async def update_workflow_task(self, task: Task, task_index: int) -> None:
179184
)
180185

181186
async def add_workflow_task(self, task: Task) -> None:
187+
"""Append a workflow task and stream the appropriate event."""
182188
self.workflow_item = self.workflow_item or WorkflowItem(
183189
id=self.generate_id("workflow"),
184190
created_at=datetime.now(),
@@ -202,6 +208,7 @@ async def add_workflow_task(self, task: Task) -> None:
202208
)
203209

204210
async def stream(self, event: ThreadStreamEvent) -> None:
211+
"""Enqueue a ThreadStreamEvent for downstream processing."""
205212
await self._events.put(event)
206213

207214
def _complete(self):
@@ -352,6 +359,7 @@ class StreamingThoughtTracker(BaseModel):
352359
async def stream_agent_response(
353360
context: AgentContext, result: RunResultStreaming
354361
) -> AsyncIterator[ThreadStreamEvent]:
362+
"""Convert a streamed Agents SDK run into ChatKit ThreadStreamEvents."""
355363
current_item_id = None
356364
current_tool_call = None
357365
ctx = context
@@ -1003,4 +1011,5 @@ async def to_agent_input(
10031011

10041012

10051013
def simple_to_agent_input(thread_items: Sequence[ThreadItem] | ThreadItem):
1014+
"""Helper that converts thread items using the default ThreadItemConverter."""
10061015
return _DEFAULT_CONVERTER.to_agent_input(thread_items)

chatkit/server.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ async def stream_widget(
197197
copy_text: str | None = None,
198198
generate_id: Callable[[StoreItemType], str] = default_generate_id,
199199
) -> AsyncIterator[ThreadStreamEvent]:
200+
"""Stream a widget root (or async sequence of roots) as ThreadStreamEvents."""
200201
item_id = generate_id("message")
201202

202203
if not isinstance(widget, AsyncGenerator):
@@ -277,6 +278,7 @@ def __init__(
277278
store: Store[TContext],
278279
attachment_store: AttachmentStore[TContext] | None = None,
279280
):
281+
"""Create a ChatKitServer with the backing Store and optional AttachmentStore."""
280282
self.store = store
281283
self.attachment_store = attachment_store
282284

@@ -314,6 +316,7 @@ async def add_feedback( # noqa: B027
314316
feedback: FeedbackKind,
315317
context: TContext,
316318
) -> None:
319+
"""Persist user feedback for one or more thread items."""
317320
pass
318321

319322
def action(
@@ -323,6 +326,7 @@ def action(
323326
sender: WidgetItem | None,
324327
context: TContext,
325328
) -> AsyncIterator[ThreadStreamEvent]:
329+
"""Handle a widget or client-dispatched action and yield response events."""
326330
raise NotImplementedError(
327331
"The action() method must be overridden to react to actions. "
328332
"See https://github.com/openai/chatkit-python/blob/main/docs/widgets.md#widget-actions"
@@ -381,6 +385,7 @@ async def handle_stream_cancelled(
381385
async def process(
382386
self, request: str | bytes | bytearray, context: TContext
383387
) -> StreamingResult | NonStreamingResult:
388+
"""Parse an incoming ChatKit request and route it to streaming or non-streaming handlers."""
384389
parsed_request = TypeAdapter[ChatKitReq](ChatKitReq).validate_json(request)
385390
logger.info(f"Received request op: {parsed_request.type}")
386391

chatkit/store.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,13 @@ class NotFoundError(Exception):
4848
class AttachmentStore(ABC, Generic[TContext]):
4949
@abstractmethod
5050
async def delete_attachment(self, attachment_id: str, context: TContext) -> None:
51+
"""Delete an attachment by id."""
5152
pass
5253

5354
async def create_attachment(
5455
self, input: AttachmentCreateParams, context: TContext
5556
) -> Attachment:
57+
"""Create an attachment record from upload metadata."""
5658
raise NotImplementedError(
5759
f"{type(self).__name__} must override create_attachment() to support two-phase file upload"
5860
)
@@ -78,10 +80,12 @@ def generate_item_id(
7880

7981
@abstractmethod
8082
async def load_thread(self, thread_id: str, context: TContext) -> ThreadMetadata:
83+
"""Load a thread's metadata by id."""
8184
pass
8285

8386
@abstractmethod
8487
async def save_thread(self, thread: ThreadMetadata, context: TContext) -> None:
88+
"""Persist thread metadata (title, status, etc.)."""
8589
pass
8690

8791
@abstractmethod
@@ -93,20 +97,24 @@ async def load_thread_items(
9397
order: str,
9498
context: TContext,
9599
) -> Page[ThreadItem]:
100+
"""Load a page of thread items with pagination controls."""
96101
pass
97102

98103
@abstractmethod
99104
async def save_attachment(self, attachment: Attachment, context: TContext) -> None:
105+
"""Persist attachment metadata."""
100106
pass
101107

102108
@abstractmethod
103109
async def load_attachment(
104110
self, attachment_id: str, context: TContext
105111
) -> Attachment:
112+
"""Load attachment metadata by id."""
106113
pass
107114

108115
@abstractmethod
109116
async def delete_attachment(self, attachment_id: str, context: TContext) -> None:
117+
"""Delete attachment metadata by id."""
110118
pass
111119

112120
@abstractmethod
@@ -117,32 +125,38 @@ async def load_threads(
117125
order: str,
118126
context: TContext,
119127
) -> Page[ThreadMetadata]:
128+
"""Load a page of threads with pagination controls."""
120129
pass
121130

122131
@abstractmethod
123132
async def add_thread_item(
124133
self, thread_id: str, item: ThreadItem, context: TContext
125134
) -> None:
135+
"""Persist a newly created thread item."""
126136
pass
127137

128138
@abstractmethod
129139
async def save_item(
130140
self, thread_id: str, item: ThreadItem, context: TContext
131141
) -> None:
142+
"""Upsert a thread item by id."""
132143
pass
133144

134145
@abstractmethod
135146
async def load_item(
136147
self, thread_id: str, item_id: str, context: TContext
137148
) -> ThreadItem:
149+
"""Load a thread item by id."""
138150
pass
139151

140152
@abstractmethod
141153
async def delete_thread(self, thread_id: str, context: TContext) -> None:
154+
"""Delete a thread and its items."""
142155
pass
143156

144157
@abstractmethod
145158
async def delete_thread_item(
146159
self, thread_id: str, item_id: str, context: TContext
147160
) -> None:
161+
"""Delete a thread item by id."""
148162
pass

docs/concepts/actions.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Actions
2+
3+
ChatKit actions are interaction events triggered by widgets or client code that let the client and server run logic or start a model response independently of user messages.
4+
5+
## Widget actions
6+
7+
Widget actions are specified in the widget definition itself (for example, `Button.onClickAction`), so every interaction carries a typed action payload plus the widget item that fired it. By default actions are routed to the server, but you can set `handler: "client"` when you want to intercept the action in the browser first.
8+
9+
### Server-handled actions
10+
11+
If you leave the handler unset, the action is delivered to `ChatKitServer.action(thread, action, sender, context)`, where `sender` is the widget item that triggered it when that item is available. Server handling is the right choice when you need to mutate thread state, stream widget or message updates, or start an agent response without a new user message. Record important interactions as hidden context so the model can react on the next turn (for example, “user clicked confirm”), and treat `action.payload` as untrusted input that must be validated and authorized before you persist anything.
12+
13+
### Client-handled actions
14+
15+
When you set `handler: "client"`, the action flows into the client SDK’s `widgets.onAction` callback so you can do immediate UI work such as opening dialogs, navigating, or running local validation. Client handlers can still forward a follow-up action to the server with `chatkit.sendCustomAction()` after local logic finishes. The server thread stays unchanged unless you explicitly send that follow-up action or a message.
16+
17+
## Client-sent actions using the chatkit.sendCustomAction() command
18+
19+
Your client integration can also initiate actions directly with `chatkit.sendCustomAction(action, itemId?)`, optionally namespaced to a specific widget item. The server receives these in `ChatKitServer.action` just like a widget-triggered action and can stream widgets, messages, or client effects in response. This pattern is useful when a flow starts outside a widget—or after a client-handled action—but you still want the server to persist results or involve the model.
20+
21+
## Related guides
22+
- [Handle widget actions](../guides/add-features/handle-widget-actions.md)

docs/concepts/entities.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Entities
2+
3+
Entities are structured pieces of information your system can recognize during a conversation, such as names, dates, IDs, or product-specific objects.
4+
5+
They represent meaningful objects in your app’s domain. For example:
6+
7+
- In a notes app, entities might be documents.
8+
- In a news site, they might be articles.
9+
- In an online store, they might be products.
10+
11+
When referenced, entities can link messages to real data and power richer actions and previews.
12+
13+
## Entity sources for assistant messages
14+
15+
Entities can be used as cited sources in assistant responses.
16+
17+
**References:**
18+
19+
- The [EntitySource](../../api/chatkit/types/#chatkit.types.EntitySource) Pydantic model definition
20+
- [Add annotations in assistant messages](../guides/add-features/add-annotations.md#annotating-with-custom-entities).
21+
22+
## Entity tags as @-mentions in user messages
23+
24+
Users can tag your entities in the composer using @-mentions.
25+
26+
**References**:
27+
28+
- The [Entity](https://openai.github.io/chatkit-js/api/openai/chatkit-react/type-aliases/entity/) TypeScript type definition
29+
- The [UserMessageTagContent](../../api/chatkit/types/#chatkit.types.UserMessageTagContent) Pydantic model definition
30+
- [Allow @-mentions in user messages](../guides/add-features/allow-mentions.md).

docs/concepts/thread-items.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Thread items
2+
3+
Thread items are the individual records that make up a thread. This include user and assistant messages, widgets, workflows, and internal markers that guide processing. ChatKit orders and paginates them through your store implementation.
4+
5+
They drive two core experiences:
6+
7+
- **Model input**: Your server's [`respond`](../../api/chatkit/server/#chatkit.server.ChatKitServer.respond) logic will read items to build model input so the model sees the full conversation during an active turn and when resuming past threads. See [Compose model input](../guides/compose-model-input.md).
8+
- **UI rendering**: ChatKit.js renders items incrementally for the active thread during streaming, and re-renders the persisted items when past threads are loaded.
9+
10+
## User messages
11+
12+
[`UserMessageItem`](../../api/chatkit/types/#chatkit.types.UserMessageItem)s represent end-user input. A user message can include the entered text, optional `quoted_text` for reply-style UI, and attachment metadata. User text is plain (no Markdown rendering) but can include @-mentions/tags; see [Allow @-mentions in user messages](../guides/add-features/allow-mentions.md).
13+
14+
## Assistant messages
15+
16+
[`AssistantMessageItem`](../../api/chatkit/types/#chatkit.types.AssistantMessageItem)s represent assistant responses. Content can include text, tool call outputs, widgets, and annotations. Text is Markdown-rendered and can carry inline annotations; see [Add annotations in assistant messages](../guides/add-features/add-annotations.md).
17+
18+
### Markdown support
19+
20+
Markdown in assistant messages supports:
21+
22+
- GitHub-flavored Markdown: Lists, headings, code fences, inline code, blockquotes, links—all with streaming-friendly layout.
23+
- Lists: Ordered/unordered lists stay stable while streaming (Safari-safe markers, no reflow glitches).
24+
- Line breaks: Single newlines render as `<br>` when `breakNewLines` is enabled.
25+
- Code blocks: Syntax-highlighted, copyable, and streamed smoothly; copy buttons are always present.
26+
- Math: LaTeX via remark/rehype math plugins for inline and block equations.
27+
- Tables: Automatic sizing with horizontal scroll for wide outputs.
28+
- Inline annotations: Markdown directives spawn interactive annotations wired into ChatKit handlers.
29+
30+
## Hidden context items
31+
32+
Hidden context items serve as model input but are not rendered in the chat UI. Use them to pass non-visible signals (for example, widget actions or system context) so the model can respond to what the user did, not just what they typed.
33+
34+
- [`HiddenContextItem`](../../api/chatkit/types/#chatkit.types.HiddenContextItem): Your integration’s hidden context; you control the schema and how it is converted for the model.
35+
- [`SDKHiddenContextItem`](../../api/chatkit/types/#chatkit.types.SDKHiddenContextItem): Hidden context inserted by the ChatKit Python SDK for its own operations; you normally leave it alone unless you override conversion behavior.
36+
37+
38+
## ThreadItemConverter
39+
40+
[`ThreadItemConverter`](../../api/chatkit/agents/#chatkit.agents.ThreadItemConverter) maps stored thread items into model-ready input items. Defaults cover messages, widgets, workflows, and tasks; override it to handle attachments, tags, or hidden context in the format your model expects. Combine converter tweaks with prompting so the model sees a coherent view of rich items (for example, summarizing widgets or tasks into text the model can consume).
41+
42+
## Thread item actions
43+
44+
Thread item actions are quick action buttons attached to an assistant turn that let users act on the output, such as retrying, copying, or submitting feedback.
45+
46+
They can be configured client-side with the [threadItemActions option](https://openai.github.io/chatkit-js/api/openai/chatkit-react/type-aliases/threaditemactionsoption/).
47+
48+
49+
## Related guides
50+
- [Persist ChatKit threads and messages](../guides/persist-chatkit-data.md)
51+
- [Compose model inputs](../guides/compose-model-input.md)
52+
- [Add annotations in assistant messages](../guides/add-features/add-annotations.md)
53+
- [Allow @-mentions in user messages](../guides/add-features/allow-mentions.md)
54+
- [Handle feedback](../guides/add-features/handle-feedback.md)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Thread stream events
2+
3+
[`ThreadStreamEvent`](../../api/chatkit/types/#chatkit.types.ThreadStreamEvent)s are the Server-Sent Event (SSE) payloads streamed by ChatKitServer while responding to a user message or action. They keep the client UI in sync with server-side processing and drive persistence in your store.
4+
5+
## Thread metadata updates
6+
7+
ChatKitServer emits these after it creates a thread or notices metadata changes (title, status, etc.) so the UI stays in sync.
8+
9+
- [`ThreadCreatedEvent`](../../api/chatkit/types/#chatkit.types.ThreadCreatedEvent): introduce a new thread
10+
- [`ThreadUpdatedEvent`](../../api/chatkit/types/#chatkit.types.ThreadUpdatedEvent): update the current thread metadata such as title or status
11+
12+
## Thread item events
13+
14+
Thread item events drive the conversation state. ChatKitServer processes these events to persist conversation state before streaming them back to the client.
15+
16+
- [`ThreadItemAddedEvent`](../../api/chatkit/types/#chatkit.types.ThreadItemAddedEvent): introduce a new item (message, tool call, workflow, widget, etc).
17+
- [`ThreadItemUpdatedEvent`](../../api/chatkit/types/#chatkit.types.ThreadItemUpdatedEvent): mutate a pending item (e.g., stream text deltas, workflow task updates).
18+
- [`ThreadItemDoneEvent`](../../api/chatkit/types/#chatkit.types.ThreadItemDoneEvent): mark an item complete and persist it.
19+
- [`ThreadItemRemovedEvent`](../../api/chatkit/types/#chatkit.types.ThreadItemRemovedEvent): delete an item by id.
20+
- [`ThreadItemReplacedEvent`](../../api/chatkit/types/#chatkit.types.ThreadItemReplacedEvent): swap an item in place.
21+
22+
Note: `ThreadItemAddedEvent` does not persist the item. `ChatKitServer` saves on `ThreadItemDoneEvent`/`ThreadItemReplacedEvent`, tracks pending items in between, and handles store writes for all `ThreadItem*Event`s.
23+
24+
## Errors
25+
26+
Stream [`ErrorEvent`](../../api/chatkit/types/#chatkit.types.ErrorEvent)s for user-facing errors in the chat UI. You can configure a custom message and whether a retry button is shown to the user.
27+
28+
## Progress updates
29+
30+
Stream [`ProgressUpdateEvent`](../../api/chatkit/types/#chatkit.types.ProgressUpdateEvent)s to show the user transient status while work is in flight.
31+
32+
See [Show progress for long-running tools](../guides/add-features/show-progress-for-long-running-tools.md) for more info.
33+
34+
## Client effects
35+
36+
Use [`ClientEffectEvent`](../../api/chatkit/types/#chatkit.types.ClientEffectEvent) to trigger fire-and-forget behavior on the client such as opening a dialog or pushing updates.
37+
38+
See [Send client effects](../guides/add-features/send-client-effects.md) for more info.
39+
40+
## Stream options
41+
42+
[`StreamOptionsEvent`](../../api/chatkit/types/#chatkit.types.StreamOptionsEvent) configures runtime stream behavior (for example, allowing user cancellation). `ChatKitServer` emits one at the start of every stream using `get_stream_options`; override that method to change defaults such as `allow_cancel`.
43+
44+
45+
## Related guides
46+
- [Stream responses back to your user](../guides/stream-thread-events.md)

docs/concepts/threads.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Threads
2+
3+
Threads are the core unit of ChatKit: a single conversation timeline that groups messages, tool calls, widgets, and related metadata.
4+
5+
## Lifecycle
6+
- When a user submits a message and no thread exists, `ChatKitServer` creates one by calling your store's [`save_thread`](../../api/chatkit/store/#chatkit.store.Store.save_thread).
7+
- As responses stream back, `ChatKitServer` automatically persists thread items as they are completed—see [Thread items](thread-items.md) and [Stream responses back to your user](../guides/stream-thread-events.md) for how events drive storage.
8+
- Update titles or metadata intentionally in your integration (e.g., after summarizing a topic) by calling [`store.save_thread`](../../api/chatkit/store/#chatkit.store.Store.save_thread) with the new values.
9+
- When history is enabled client-side, ChatKit retrieves past threads. The user can continue any previous thread by default.
10+
- Archive or close threads according to your policies: mark them read-only (e.g., [disable new messages](../guides/add-features/disable-new-messages.md)) or delete them if you no longer want them discoverable.
11+
12+
13+
## Related guides
14+
- [Persist ChatKit threads and messages](../guides/persist-chatkit-data.md)
15+
- [Save thread titles](../guides/add-features/save-thread-titles.md)
16+
- [Disable new messages for a thread](../guides/add-features/disable-new-messages.md)

0 commit comments

Comments
 (0)