Skip to content

Commit 1d06e8a

Browse files
committed
Add more add-feature pages
1 parent a723531 commit 1d06e8a

6 files changed

Lines changed: 350 additions & 47 deletions

File tree

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Allow @-mentions in user messages
2+
3+
Mentions travel through ChatKit as structured tags so the model can resolve entities instead of guessing from free text. Send `input_tag` parts from the client and translate them into model-readable context on the server.
4+
5+
## Enable as-you-type entity lookup in the composer
6+
7+
To enable entity tagging as @-mentions in the composer, configure [`entities.onTagSearch`](https://openai.github.io/chatkit-js/api/openai/chatkit/type-aliases/entitiesoption/#ontagsearch) as a ChatKit.js option.
8+
9+
It should return a list of [Entity](https://openai.github.io/chatkit-js/api/openai/chatkit/type-aliases/entity/) objects that match the query string.
10+
11+
12+
```ts
13+
const chatkit = useChatKit({
14+
// ...
15+
entities: {
16+
onTagSearch: async (query: string) => {
17+
return [
18+
{
19+
id: "article_123",
20+
title: "The Future of AI",
21+
group: "Trending",
22+
icon: "globe",
23+
data: { type: "article" }
24+
},
25+
{
26+
id: "article_124",
27+
title: "One weird trick to improve your sleep",
28+
group: "Trending",
29+
icon: "globe",
30+
data: { type: "article" }
31+
},
32+
]
33+
},
34+
},
35+
})
36+
```
37+
38+
## Convert tags into model input in your server
39+
40+
Override `ThreadItemConverter.tag_to_message_content` to describe what each tag refers to.
41+
42+
Example converter method that wraps the tagged entity details in custom markup:
43+
44+
```python
45+
from chatkit.agents import ThreadItemConverter
46+
from chatkit.types import UserMessageTagContent
47+
from openai.types.responses import ResponseInputTextParam
48+
49+
class MyThreadItemConverter(ThreadItemConverter):
50+
async def tag_to_message_content(
51+
self, tag: UserMessageTagContent
52+
) -> ResponseInputTextParam:
53+
if tag.type == "article":
54+
# Load or unpack the entity the tag refers to
55+
summary = await fetch_article_summary(tag.id)
56+
return ResponseInputTextParam(
57+
type="input_text",
58+
text=(
59+
"<ARTICLE_TAG>\n"
60+
f"ID: {tag.id}\n"
61+
f"Title: {tag.text}\n"
62+
f"Summary: {summary}\n"
63+
"</ARTICLE_TAG>"
64+
),
65+
)
66+
```
67+
68+
69+
## Pair mentions with retrieval tool calls
70+
71+
When the referenced content is too large to inline, keep the tag lean (id + short summary) and let the model fetch details via a tool. In your system prompt, tell the assistant to call the retrieval tool when it sees an `ARTICLE_TAG`.
72+
73+
Example tool paired with the converter above:
74+
75+
```python
76+
from agents import Agent, StopAtTools, RunContextWrapper, function_tool
77+
from chatkit.agents import AgentContext
78+
79+
@function_tool(description_override="Fetch full article content by id.")
80+
async def fetch_article(ctx: RunContextWrapper[AgentContext], article_id: str):
81+
article = await load_article_content(article_id)
82+
return {
83+
"title": article.title,
84+
"content": article.body,
85+
"url": article.url,
86+
}
87+
88+
assistant = Agent[AgentContext](
89+
...,
90+
tools=[fetch_article],
91+
)
92+
```
93+
94+
In `tag_to_message_content`, include the id the tool expects (for example, `tag.id` or `tag.data["article_id"]`). The model can then decide to call `fetch_article` to pull the full text instead of relying solely on the brief summary in the tag.
95+
96+
## Prompt the model about mentions
97+
98+
Add short system guidance to help the assistant understand the input item that adds details about the @-mention.
99+
100+
For example:
101+
102+
```
103+
- <ARTICLE_TAG>...</ARTICLE_TAG> is a summary of an article the user referenced.
104+
- Use it as trusted context when answering questions about that article.
105+
- Do not restate the summary verbatim; answer the user’s question concisely.
106+
- Call the `fetch_article` tool with the article id from the tag when more
107+
detail is needed or the user asks for specifics not in the summary.
108+
```
109+
110+
Combined with the converter above, the model receives explicit, disambiguated entity context while users keep a rich mention UI.
111+
112+
113+
## Handle clicks and previews
114+
115+
Clicks and hover previews apply to the tagged entities shown in past user messages. Mark an entity as interactive when you return it from `onTagSearch` so the client knows to wire these callbacks:
116+
117+
```ts
118+
{
119+
id: "article_123",
120+
title: "The Future of AI",
121+
group: "Trending",
122+
icon: "globe",
123+
interactive: true, // clickable/previewable
124+
data: { type: "article" }
125+
}
126+
```
127+
128+
- `entities.onClick` fires when a user clicks a tag in the transcript. Handle navigation or open a detail view. See the [onClick option](https://openai.github.io/chatkit-js/api/openai/chatkit/type-aliases/entitiesoption/#onclick).
129+
- `entities.onRequestPreview` runs when the user hovers or taps a tag that has `interactive: true`. Return a `BasicRoot` widget; you can build one with `WidgetTemplate.build_basic(...)` if you are building the preview widgets server-side. See the [onRequestPreview option](https://openai.github.io/chatkit-js/api/openai/chatkit/type-aliases/entitiesoption/#onrequestpreview).
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Disable new messages for a thread
2+
3+
There are two ways to stop new user messages: temporarily lock a thread or permanently close it when the conversation is finished.
4+
5+
| State | When to use | Input UI | What the user sees |
6+
|---------|------------------------------------------------|------------------------------------------------|--------------------|
7+
| Locked | Temporary pause for moderation or admin action | Composer stays on screen but is disabled; the placeholder shows the lock reason. | The reason for the lock in the disabled composer. |
8+
| Closed | Final state when the conversation is done | The input UI is replaced with an informational banner. | A static default message or a custom reason, if provided. |
9+
10+
## Update thread status (lock, close, or re-open)
11+
12+
Update `thread.status`—whether moving between active, locked, or closed—and persist it.
13+
14+
```python
15+
from chatkit.types import ActiveStatus, LockedStatus, ClosedStatus
16+
17+
# lock
18+
thread.status = LockedStatus(reason="Escalated to support.")
19+
await store.save_thread(thread, context=context)
20+
21+
# close (final)
22+
thread.status = ClosedStatus(reason="Resolved.")
23+
await store.save_thread(thread, context=context)
24+
25+
# re-open
26+
thread.status = ActiveStatus()
27+
await store.save_thread(thread, context=context)
28+
```
29+
30+
If you update the thread status within the `respond` method, ChatKit will emit a `ThreadUpdatedEvent` so connected clients update immediately.
31+
32+
You can also update the thread status from a custom client-facing endpoint that updates the store directly (outside of the ChatKit server request flow). If the user is currently viewing the thread, have the client call `chatkit.fetchUpdates()` after the status is persisted so the UI picks up the latest thread state.
33+
34+
## Block server-side work when locked or closed
35+
36+
Thread status only affects the composer UI; ChatKitServer does not automatically reject actions, tool calls, or imperative message adds. Your integration should short-circuit handlers when a thread is disabled:
37+
38+
```python
39+
class MyChatKitServer(...):
40+
async def respond(thread, input_user_message, context):
41+
if thread.status.type in {"locked", "closed"}:
42+
return
43+
# normal processing
44+
45+
async def action(thread, action, sender, context):
46+
if thread.status.type in {"locked", "closed"}:
47+
return
48+
# normal processing
49+
```
50+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Handle feedback
2+
3+
## Enable feedback actions on the client
4+
5+
Collect thumbs up/down feedback so you can flag broken answers, retrain on good ones, or alert humans. Enable the message actions in the client by setting [`threadItemActions.feedback`](https://openai.github.io/chatkit-js/api/openai/chatkit/type-aliases/threaditemactionsoption/); ChatKit.js renders the controls and sends an `items.feedback` request when a user clicks them.
6+
7+
```tsx
8+
const chatkit = useChatKit({
9+
// ...
10+
threadItemActions: {
11+
feedback: true,
12+
},
13+
})
14+
```
15+
16+
## Implement `add_feedback` on your server
17+
18+
Override the `add_feedback` method on your server to persist the signal anywhere you like.
19+
20+
```python
21+
from chatkit.server import ChatKitServer
22+
from chatkit.types import FeedbackKind
23+
24+
class MyChatKitServer(ChatKitServer[RequestContext]):
25+
async def add_feedback(
26+
self,
27+
thread_id: str,
28+
item_ids: list[str],
29+
feedback: FeedbackKind,
30+
context: RequestContext,
31+
) -> None:
32+
# Example: write to your analytics/QA store
33+
await record_feedback(
34+
thread_id=thread_id,
35+
item_ids=item_ids,
36+
sentiment=feedback,
37+
user_id=context.user_id,
38+
)
39+
```
40+
41+
`item_ids` can include assistant messages, tool calls, or widgets. If you need to ignore certain items (for example, hidden system prompts), filter them here before recording.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Show progress for long-running tools
2+
3+
Long-running tools can feel stalled without feedback. Use progress updates for lightweight status pings and workflows for structured, persisted task checklists.
4+
5+
| | Progress updates (`ProgressUpdateEvent`) | Workflow items (`Workflow`, `WorkflowTask*`) |
6+
|---------------------|------------------------------------------|----------------------------------------------|
7+
| Purpose | Quick, ephemeral status text | Structured list of tasks with statuses |
8+
| Persistence | Not saved to the thread | Persisted as thread items |
9+
| UI | Inline, transient shimmer text | Collapsible checklist widget |
10+
| When new content streams | Automatically cleared and replaced by streamed content | Remains visible above the streamed content |
11+
| Best for | Reporting current phase ("Indexing…") | Multi-step plans users may revisit later |
12+
| How to emit | `ctx.context.stream(ProgressUpdateEvent(...))` | `start_workflow`, `add_workflow_task`, `update_workflow_task`, `end_workflow` |
13+
14+
## Progress updates
15+
16+
Emit `ProgressUpdateEvent` when you need lightweight, real-time status. They stream immediately to the client and disappear after the turn—they are not stored in the thread.
17+
18+
### From tools
19+
20+
Inside a tool, use `AgentContext.stream` to enqueue progress events. They are delivered to the client immediately and are not persisted as thread items.
21+
22+
```python
23+
from agents import RunContextWrapper, function_tool
24+
from chatkit.agents import AgentContext
25+
from chatkit.types import ProgressUpdateEvent
26+
27+
@function_tool()
28+
async def ingest_files(ctx: RunContextWrapper[AgentContext], paths: list[str]):
29+
await ctx.context.stream(ProgressUpdateEvent(icon="upload", text="Uploading..."))
30+
await upload(paths)
31+
32+
await ctx.context.stream(
33+
ProgressUpdateEvent(icon="search", text="Indexing and chunking...")
34+
)
35+
await index_files(paths)
36+
37+
await ctx.context.stream(ProgressUpdateEvent(icon="check", text="Done"))
38+
```
39+
40+
`stream_agent_response` will forward these events for you alongside any assistant text or tool call updates.
41+
42+
### From custom pipelines
43+
44+
If you are not using the Agents SDK, yield `ProgressUpdateEvent` directly from the `respond` or `action` methods while your backend works:
45+
46+
```python
47+
async def respond(...):
48+
yield ProgressUpdateEvent(icon="search", text="Searching tickets...")
49+
results = await search_tickets()
50+
51+
yield ProgressUpdateEvent(icon="code", text="Generating summary...")
52+
yield from await stream_summary(results)
53+
```
54+
55+
Use short, action-oriented messages and throttle updates to meaningful stages instead of every percent to avoid noisy streams.
56+
57+
## Workflow items
58+
59+
Use workflows when you want a persisted, user-visible checklist of tasks. They render as a widget in the transcript and survive after the turn. Combine with progress updates if you need both a checklist and lightweight status text.
60+
61+
Workflows support multiple task variants (custom, search, thought, file, image); see [`Task`](../../../api/chatkit/types/#chatkit.types.Task). Summaries shown when closing a workflow use [`WorkflowSummary`](../../../api/chatkit/types/#chatkit.types.WorkflowSummary) (for example, `CustomSummary` in the snippet below).
62+
63+
Example streaming workflow updates using `AgentContext` helpers:
64+
65+
```python
66+
from agents import RunContextWrapper, function_tool
67+
from chatkit.agents import AgentContext
68+
from chatkit.types import CustomSummary, CustomTask, Workflow
69+
70+
@function_tool()
71+
async def long_running_tool_with_steps(ctx: RunContextWrapper[AgentContext]):
72+
# Create an empty workflow container
73+
await ctx.context.start_workflow(Workflow(type="custom", tasks=[]))
74+
75+
# Add and update the first task
76+
discovery = CustomTask(title="Search data sources", status_indicator="loading")
77+
await ctx.context.add_workflow_task(discovery)
78+
79+
# Run the first task
80+
await search_my_data_sources()
81+
82+
await ctx.context.update_workflow_task(
83+
discovery.model_copy(update={"status_indicator": "complete"}), task_index=0
84+
)
85+
86+
# Add a follow-up task
87+
summary = CustomTask(title="Summarize findings", status_indicator="loading")
88+
await ctx.context.add_workflow_task(summary)
89+
90+
# Run the second task
91+
await summarize_my_findings()
92+
93+
await ctx.context.update_workflow_task(
94+
summary.model_copy(update={"status_indicator": "complete"}), task_index=1
95+
)
96+
97+
# Close the workflow and collapse it in the UI
98+
await ctx.context.end_workflow(
99+
summary=CustomSummary(title="Analysis complete"),
100+
expanded=False,
101+
)
102+
```
103+
104+
Workflows are saved as thread items by `stream_agent_response` when you yield the associated events; they show up for all participants and remain visible in history.

0 commit comments

Comments
 (0)