openai/chatkit-python

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.5.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

docs/guides/update-client-during-response.md

164lines · modecode

1# Update the client during a response
2
3Keep your UI responsive while the server is working: stream progress text, trigger client-side effects, and run client tools mid-response without blocking everything else.
4
5This guide covers three patterns:
6
7- Progress updates for lightweight status while tools run
8- Client effects for fire-and-forget UI behavior
9- Client tools for round-trips to the browser/app during inference
10
11## Show progress while tools run
12
13Use `ProgressUpdateEvent` when you need lightweight, real-time status. These updates stream immediately to the client and disappear after the turn—they are not stored in the thread.
14
15### From tools
16
17Inside a tool, use `AgentContext.stream` to enqueue progress events. They are delivered to the client immediately and are not persisted as thread items.
18
19```python
20from agents import RunContextWrapper, function_tool
21from chatkit.agents import AgentContext
22from chatkit.types import ProgressUpdateEvent
23
24
25@function_tool()
26async def ingest_files(ctx: RunContextWrapper[AgentContext], paths: list[str]):
27 await ctx.context.stream(ProgressUpdateEvent(icon="upload", text="Uploading..."))
28 await upload(paths)
29
30 await ctx.context.stream(
31 ProgressUpdateEvent(icon="search", text="Indexing and chunking...")
32 )
33 await index_files(paths)
34
35 await ctx.context.stream(ProgressUpdateEvent(icon="check", text="Done"))
36```
37
38`stream_agent_response` will forward these events for you alongside any assistant text or tool call updates.
39
40### From custom pipelines
41
42If you are not using the Agents SDK, yield `ProgressUpdateEvent` directly from your `respond` or `action` methods while your backend works:
43
44```python
45async def respond(...):
46 yield ProgressUpdateEvent(icon="search", text="Searching tickets...")
47 results = await search_tickets()
48
49 yield ProgressUpdateEvent(icon="code", text="Generating summary...")
50 yield from await stream_summary(results)
51```
52
53Use short, action-oriented messages and throttle updates to meaningful stages instead of every percent to avoid noisy streams.
54
55## Trigger client-side effects without blocking
56
57Send `ClientEffectEvent` to trigger fire-and-forget UI work (such as refreshing a view, opening a modal, or showing a toast) without creating thread items or pausing the model stream.
58
59Client effects are ephemeral: they stream immediately to ChatKit.js, trigger your registered effect handler, and are not persisted to the thread history. Use client tool calls instead when you need a round-trip response from the client.
60
61### Stream a client effect from your server
62
63Yield client effects directly from the `respond` or `action` method:
64
65```python
66async def respond(...):
67 yield ClientEffectEvent(
68 name="highlight_text",
69 data={"index": 142, "length": 35},
70 )
71```
72
73Or from tools, through `AgentContext`:
74
75```python
76from agents import RunContextWrapper, function_tool
77from chatkit.agents import AgentContext
78from chatkit.types import ClientEffectEvent
79
80
81@function_tool()
82async def highlight_text(ctx: RunContextWrapper[AgentContext], index: int, length: int):
83 await ctx.context.stream(
84 ClientEffectEvent(
85 name="highlight_text",
86 data={"index": index, "length": length},
87 )
88 )
89```
90
91### Handle the client effect in ChatKit.js
92
93Register a client effect handler when initializing ChatKit on the client:
94
95```ts
96const chatkit = useChatKit({
97 // ...
98 onEffect: async ({name, data}) => {
99 if (name === "highlight_text") {
100 const {index, length} = data;
101 highlightArticleText({index, length});
102 // No return value needed
103 }
104 },
105});
106```
107
108## Call client tools mid-inference
109
110Client tool calls let the agent invoke browser/app callbacks mid-inference. Register the tool on both client and server; when triggered, ChatKit pauses the model, sends the tool request to the client, and resumes with the returned result.
111
112Use client effects instead when you do not need to wait for the client callback response for the rest of your response.
113
114### Define a client tool in your agent
115
116Set `ctx.context.client_tool_call` inside a tool and configure the agent to stop at that tool. Only one client tool call can run per turn. Include client tools in `stop_at_tool_names` so the model pauses while the client callback runs and returns its result.
117
118```python
119from agents import Agent, RunContextWrapper, StopAtTools, function_tool
120from chatkit.agents import AgentContext, ClientToolCall
121
122
123@function_tool(description_override="Read the user's current canvas selection.")
124async def get_selected_canvas_nodes(ctx: RunContextWrapper[AgentContext]) -> None:
125 ctx.context.client_tool_call = ClientToolCall(
126 name="get_selected_canvas_nodes",
127 arguments={"project": my_project()},
128 )
129
130
131assistant = Agent[AgentContext](
132 ...,
133 tools=[get_selected_canvas_nodes],
134 # StopAtTools pauses model generation so the pending client callback can run and resume the run.
135 tool_use_behavior=StopAtTools(stop_at_tool_names=[get_selected_canvas_nodes.name]),
136)
137```
138
139### Register the client tool in ChatKit.js
140
141Provide a matching callback when initializing ChatKit on the client. The function name must match the `ClientToolCall.name`, and its return value is sent back to the server to resume the run.
142
143```ts
144const chatkit = useChatKit({
145 // ...
146 onClientTool: async ({name, params}) => {
147 if (name === "get_selected_canvas_nodes") {
148 const {project} = params;
149 const nodes = myCanvas.getSelectedNodes(project);
150 return {
151 nodes: nodes.map((node) => ({id: node.id, kind: node.type})),
152 };
153 }
154 },
155});
156```
157
158### Stream and resume
159
160In `respond`, stream via `stream_agent_response` as usual. ChatKit emits a pending client tool call item; the frontend runs your registered client tool, posts the output back, and the server continues the run.
161
162When the client posts the tool result, ChatKit stores it as a `ClientToolCallItem`. The continued inference after the client tool call handler returns the result feeds both the call and its output back to the model through `ThreadItemConverter.client_tool_call_to_input`, which emits a `function_call` plus matching `function_call_output` so the model sees the browser-provided context.
163
164
165