openai/chatkit-python

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.3.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

chatkit/types.py

831lines · modecode

1from __future__ import annotations
2
3from datetime import datetime
4from typing import Any, Generic, Literal
5
6from pydantic import AnyUrl, BaseModel, Field
7from typing_extensions import Annotated, TypeIs, TypeVar
8
9from chatkit.errors import ErrorCode
10
11from .actions import Action
12from .icons import IconName
13from .widgets import WidgetComponent, WidgetRoot
14
15T = TypeVar("T")
16
17
18class Page(BaseModel, Generic[T]):
19 """Paginated collection of records returned from the API."""
20
21 data: list[T] = []
22 has_more: bool = False
23 after: str | None = None
24
25
26### REQUEST TYPES
27
28
29class BaseReq(BaseModel):
30 """Base class for all request payloads."""
31
32 metadata: dict[str, Any] = Field(default_factory=dict)
33 """Arbitrary integration-specific metadata."""
34
35
36class ThreadsGetByIdReq(BaseReq):
37 """Request to fetch a single thread by its identifier."""
38
39 type: Literal["threads.get_by_id"] = "threads.get_by_id"
40 params: ThreadGetByIdParams
41
42
43class ThreadGetByIdParams(BaseModel):
44 """Parameters for retrieving a thread by id."""
45
46 thread_id: str
47
48
49class ThreadsCreateReq(BaseReq):
50 """Request to create a new thread from a user message."""
51
52 type: Literal["threads.create"] = "threads.create"
53 params: ThreadCreateParams
54
55
56class ThreadCreateParams(BaseModel):
57 """User input required to create a thread."""
58
59 input: UserMessageInput
60
61
62class ThreadListParams(BaseModel):
63 """Pagination parameters for listing threads."""
64
65 limit: int | None = None
66 order: Literal["asc", "desc"] = "desc"
67 after: str | None = None
68
69
70class ThreadsListReq(BaseReq):
71 """Request to list threads."""
72
73 type: Literal["threads.list"] = "threads.list"
74 params: ThreadListParams
75
76
77class ThreadsAddUserMessageReq(BaseReq):
78 """Request to append a user message to a thread."""
79
80 type: Literal["threads.add_user_message"] = "threads.add_user_message"
81 params: ThreadAddUserMessageParams
82
83
84class ThreadAddUserMessageParams(BaseModel):
85 """Parameters for adding a user message to a thread."""
86
87 input: UserMessageInput
88 thread_id: str
89
90
91class ThreadsAddClientToolOutputReq(BaseReq):
92 """Request to add a client tool's output to a thread."""
93
94 type: Literal["threads.add_client_tool_output"] = "threads.add_client_tool_output"
95 params: ThreadAddClientToolOutputParams
96
97
98class ThreadAddClientToolOutputParams(BaseModel):
99 """Parameters for recording tool output in a thread."""
100
101 thread_id: str
102 result: Any
103
104
105class ThreadsCustomActionReq(BaseReq):
106 """Request to execute a custom action within a thread."""
107
108 type: Literal["threads.custom_action"] = "threads.custom_action"
109 params: ThreadCustomActionParams
110
111
112class ThreadCustomActionParams(BaseModel):
113 """Parameters describing the custom action to execute."""
114
115 thread_id: str
116 item_id: str | None = None
117 action: Action[str, Any]
118
119
120class ThreadsRetryAfterItemReq(BaseReq):
121 """Request to retry processing after a specific thread item."""
122
123 type: Literal["threads.retry_after_item"] = "threads.retry_after_item"
124 params: ThreadRetryAfterItemParams
125
126
127class ThreadRetryAfterItemParams(BaseModel):
128 """Parameters specifying which item to retry."""
129
130 thread_id: str
131 item_id: str
132
133
134class ItemsFeedbackReq(BaseReq):
135 """Request to submit feedback on specific items."""
136
137 type: Literal["items.feedback"] = "items.feedback"
138 params: ItemFeedbackParams
139
140
141class ItemFeedbackParams(BaseModel):
142 """Parameters describing feedback targets and sentiment."""
143
144 thread_id: str
145 item_ids: list[str]
146 kind: FeedbackKind
147
148
149class AttachmentsDeleteReq(BaseReq):
150 """Request to remove an attachment."""
151
152 type: Literal["attachments.delete"] = "attachments.delete"
153 params: AttachmentDeleteParams
154
155
156class AttachmentDeleteParams(BaseModel):
157 """Parameters identifying an attachment to delete."""
158
159 attachment_id: str
160
161
162class AttachmentsCreateReq(BaseReq):
163 """Request to register a new attachment."""
164
165 type: Literal["attachments.create"] = "attachments.create"
166 params: AttachmentCreateParams
167
168
169class AttachmentCreateParams(BaseModel):
170 """Metadata needed to initialize an attachment."""
171
172 name: str
173 size: int
174 mime_type: str
175
176
177class ItemsListReq(BaseReq):
178 """Request to list items inside a thread."""
179
180 type: Literal["items.list"] = "items.list"
181 params: ItemsListParams
182
183
184class ItemsListParams(BaseModel):
185 """Pagination parameters for listing thread items."""
186
187 thread_id: str
188 limit: int | None = None
189 order: Literal["asc", "desc"] = "desc"
190 after: str | None = None
191
192
193class ThreadsUpdateReq(BaseReq):
194 """Request to update thread metadata."""
195
196 type: Literal["threads.update"] = "threads.update"
197 params: ThreadUpdateParams
198
199
200class ThreadUpdateParams(BaseModel):
201 """Parameters for updating a thread's properties."""
202
203 thread_id: str
204 title: str
205
206
207class ThreadsDeleteReq(BaseReq):
208 """Request to delete a thread."""
209
210 type: Literal["threads.delete"] = "threads.delete"
211 params: ThreadDeleteParams
212
213
214class ThreadDeleteParams(BaseModel):
215 """Parameters identifying a thread to delete."""
216
217 thread_id: str
218
219
220StreamingReq = (
221 ThreadsCreateReq
222 | ThreadsAddUserMessageReq
223 | ThreadsAddClientToolOutputReq
224 | ThreadsRetryAfterItemReq
225 | ThreadsCustomActionReq
226)
227"""Union of request types that produce streaming responses."""
228
229
230NonStreamingReq = (
231 ThreadsGetByIdReq
232 | ThreadsListReq
233 | ItemsListReq
234 | ItemsFeedbackReq
235 | AttachmentsCreateReq
236 | AttachmentsDeleteReq
237 | ThreadsUpdateReq
238 | ThreadsDeleteReq
239)
240"""Union of request types that yield immediate responses."""
241
242
243ChatKitReq = Annotated[
244 StreamingReq | NonStreamingReq,
245 Field(discriminator="type"),
246]
247
248
249def is_streaming_req(request: ChatKitReq) -> TypeIs[StreamingReq]:
250 """Return True if the given request should be processed as streaming."""
251 return isinstance(
252 request,
253 (
254 ThreadsCreateReq,
255 ThreadsAddUserMessageReq,
256 ThreadsRetryAfterItemReq,
257 ThreadsAddClientToolOutputReq,
258 ThreadsCustomActionReq,
259 ),
260 )
261
262
263### THREAD STREAM EVENT TYPES
264
265
266class ThreadCreatedEvent(BaseModel):
267 """Event emitted when a thread is created."""
268
269 type: Literal["thread.created"] = "thread.created"
270 thread: Thread
271
272
273class ThreadUpdatedEvent(BaseModel):
274 """Event emitted when a thread is updated."""
275
276 type: Literal["thread.updated"] = "thread.updated"
277 thread: Thread
278
279
280class ThreadItemAddedEvent(BaseModel):
281 """Event emitted when a new item is added to a thread."""
282
283 type: Literal["thread.item.added"] = "thread.item.added"
284 item: ThreadItem
285
286
287class ThreadItemUpdated(BaseModel):
288 """Event describing an update to an existing thread item."""
289
290 type: Literal["thread.item.updated"] = "thread.item.updated"
291 item_id: str
292 update: ThreadItemUpdate
293
294
295class ThreadItemDoneEvent(BaseModel):
296 """Event emitted when a thread item is marked complete."""
297
298 type: Literal["thread.item.done"] = "thread.item.done"
299 item: ThreadItem
300
301
302class ThreadItemRemovedEvent(BaseModel):
303 """Event emitted when a thread item is removed."""
304
305 type: Literal["thread.item.removed"] = "thread.item.removed"
306 item_id: str
307
308
309class ThreadItemReplacedEvent(BaseModel):
310 """Event emitted when a thread item is replaced."""
311
312 type: Literal["thread.item.replaced"] = "thread.item.replaced"
313 item: ThreadItem
314
315
316class ProgressUpdateEvent(BaseModel):
317 """Event providing incremental progress from the assistant."""
318
319 type: Literal["progress_update"] = "progress_update"
320 icon: IconName | None = None
321 text: str
322
323
324class ErrorEvent(BaseModel):
325 """Event indicating an error occurred while processing a thread."""
326
327 type: Literal["error"] = "error"
328 code: ErrorCode | Literal["custom"] = Field(default="custom")
329 message: str | None = None
330 allow_retry: bool = Field(default=False)
331
332
333class NoticeEvent(BaseModel):
334 """Event conveying a user-facing notice."""
335
336 type: Literal["notice"] = "notice"
337 level: Literal["info", "warning", "danger"]
338 message: str
339 """
340 Supports markdown e.g. "You've reached your limit of 100 messages. [Upgrade](https://...) to a paid plan."
341 """
342 title: str | None = None
343
344
345ThreadStreamEvent = Annotated[
346 ThreadCreatedEvent
347 | ThreadUpdatedEvent
348 | ThreadItemDoneEvent
349 | ThreadItemAddedEvent
350 | ThreadItemUpdated
351 | ThreadItemRemovedEvent
352 | ThreadItemReplacedEvent
353 | ProgressUpdateEvent
354 | ErrorEvent
355 | NoticeEvent,
356 Field(discriminator="type"),
357]
358"""Union of all streaming events emitted to clients."""
359
360### THREAD ITEM UPDATE TYPES
361
362
363class AssistantMessageContentPartAdded(BaseModel):
364 """Event emitted when new assistant content is appended."""
365
366 type: Literal["assistant_message.content_part.added"] = (
367 "assistant_message.content_part.added"
368 )
369 content_index: int
370 content: AssistantMessageContent
371
372
373class AssistantMessageContentPartTextDelta(BaseModel):
374 """Event carrying incremental assistant text output."""
375
376 type: Literal["assistant_message.content_part.text_delta"] = (
377 "assistant_message.content_part.text_delta"
378 )
379 content_index: int
380 delta: str
381
382
383class AssistantMessageContentPartAnnotationAdded(BaseModel):
384 """Event announcing a new annotation on assistant content."""
385
386 type: Literal["assistant_message.content_part.annotation_added"] = (
387 "assistant_message.content_part.annotation_added"
388 )
389 content_index: int
390 annotation_index: int
391 annotation: Annotation
392
393
394class AssistantMessageContentPartDone(BaseModel):
395 """Event indicating an assistant content part is finalized."""
396
397 type: Literal["assistant_message.content_part.done"] = (
398 "assistant_message.content_part.done"
399 )
400 content_index: int
401 content: AssistantMessageContent
402
403
404class WidgetStreamingTextValueDelta(BaseModel):
405 """Event streaming widget text deltas."""
406
407 type: Literal["widget.streaming_text.value_delta"] = (
408 "widget.streaming_text.value_delta"
409 )
410 component_id: str
411 delta: str
412 done: bool
413
414
415class WidgetRootUpdated(BaseModel):
416 """Event published when the widget root changes."""
417
418 type: Literal["widget.root.updated"] = "widget.root.updated"
419 widget: WidgetRoot
420
421
422class WidgetComponentUpdated(BaseModel):
423 """Event emitted when a widget component updates."""
424
425 type: Literal["widget.component.updated"] = "widget.component.updated"
426 component_id: str
427 component: WidgetComponent
428
429
430class WorkflowTaskAdded(BaseModel):
431 """Event emitted when a workflow task is added."""
432
433 type: Literal["workflow.task.added"] = "workflow.task.added"
434 task_index: int
435 task: Task
436
437
438class WorkflowTaskUpdated(BaseModel):
439 """Event emitted when a workflow task is updated."""
440
441 type: Literal["workflow.task.updated"] = "workflow.task.updated"
442 task_index: int
443 task: Task
444
445
446ThreadItemUpdate = (
447 AssistantMessageContentPartAdded
448 | AssistantMessageContentPartTextDelta
449 | AssistantMessageContentPartAnnotationAdded
450 | AssistantMessageContentPartDone
451 | WidgetStreamingTextValueDelta
452 | WidgetComponentUpdated
453 | WidgetRootUpdated
454 | WorkflowTaskAdded
455 | WorkflowTaskUpdated
456)
457"""Union of possible updates applied to thread items."""
458
459
460### THREAD TYPES
461
462
463class ThreadMetadata(BaseModel):
464 """Metadata describing a thread without its items."""
465
466 title: str | None = None
467 id: str
468 created_at: datetime
469 status: ThreadStatus = Field(default_factory=lambda: ActiveStatus())
470 # TODO - make not client rendered
471 metadata: dict[str, Any] = Field(default_factory=dict)
472
473
474class ActiveStatus(BaseModel):
475 """Status indicating the thread is active."""
476
477 type: Literal["active"] = Field(default="active", frozen=True)
478
479
480class LockedStatus(BaseModel):
481 """Status indicating the thread is locked."""
482
483 type: Literal["locked"] = Field(default="locked", frozen=True)
484 reason: str | None = None
485
486
487class ClosedStatus(BaseModel):
488 """Status indicating the thread is closed."""
489
490 type: Literal["closed"] = Field(default="closed", frozen=True)
491 reason: str | None = None
492
493
494ThreadStatus = Annotated[
495 ActiveStatus | LockedStatus | ClosedStatus,
496 Field(discriminator="type"),
497]
498"""Union of lifecycle states for a thread."""
499
500
501class Thread(ThreadMetadata):
502 """Thread with its paginated items."""
503
504 items: Page[ThreadItem]
505
506
507### THREAD ITEM TYPES
508
509
510class ThreadItemBase(BaseModel):
511 """Base fields shared by all thread items."""
512
513 id: str
514 thread_id: str
515 created_at: datetime
516
517
518class UserMessageItem(ThreadItemBase):
519 """Thread item representing a user message."""
520
521 type: Literal["user_message"] = "user_message"
522 content: list[UserMessageContent]
523 attachments: list[Attachment] = Field(default_factory=list)
524 quoted_text: str | None = None
525 inference_options: InferenceOptions
526
527
528class AssistantMessageItem(ThreadItemBase):
529 """Thread item representing an assistant message."""
530
531 type: Literal["assistant_message"] = "assistant_message"
532 content: list[AssistantMessageContent]
533
534
535class ClientToolCallItem(ThreadItemBase):
536 """Thread item capturing a client tool call."""
537
538 type: Literal["client_tool_call"] = "client_tool_call"
539 status: Literal["pending", "completed"] = "pending"
540 call_id: str
541 name: str
542 arguments: dict[str, Any]
543 output: Any | None = None
544
545
546class WidgetItem(ThreadItemBase):
547 """Thread item containing widget content."""
548
549 type: Literal["widget"] = "widget"
550 widget: WidgetRoot
551 copy_text: str | None = None
552
553
554class TaskItem(ThreadItemBase):
555 """Thread item containing a task."""
556
557 type: Literal["task"] = "task"
558 task: Task
559
560
561class WorkflowItem(ThreadItemBase):
562 """Thread item representing a workflow."""
563
564 type: Literal["workflow"] = "workflow"
565 workflow: Workflow
566
567
568class EndOfTurnItem(ThreadItemBase):
569 """Marker item indicating the assistant ends its turn."""
570
571 type: Literal["end_of_turn"] = "end_of_turn"
572
573
574class HiddenContextItem(ThreadItemBase):
575 """HiddenContext is never sent to the client. It's not officially part of ChatKit. It is only used internally to store additional context in a specific place in the thread."""
576
577 type: Literal["hidden_context_item"] = "hidden_context_item"
578 content: Any
579
580
581ThreadItem = Annotated[
582 UserMessageItem
583 | AssistantMessageItem
584 | ClientToolCallItem
585 | WidgetItem
586 | WorkflowItem
587 | TaskItem
588 | HiddenContextItem
589 | EndOfTurnItem,
590 Field(discriminator="type"),
591]
592"""Union of all thread item variants."""
593
594
595### ASSISTANT MESSAGE TYPES
596
597
598class AssistantMessageContent(BaseModel):
599 """Assistant message content consisting of text and annotations."""
600
601 annotations: list[Annotation] = Field(default_factory=list)
602 text: str
603 type: Literal["output_text"] = "output_text"
604
605
606class Annotation(BaseModel):
607 """Reference to supporting context attached to assistant output."""
608
609 type: Literal["annotation"] = "annotation"
610 source: URLSource | FileSource | EntitySource
611 index: int | None = None
612
613
614### USER MESSAGE TYPES
615
616
617class UserMessageInput(BaseModel):
618 """Payload describing a user message submission."""
619
620 content: list[UserMessageContent]
621 attachments: list[str]
622 quoted_text: str | None = None
623 inference_options: InferenceOptions
624
625
626class UserMessageTextContent(BaseModel):
627 """User message content containing plaintext."""
628
629 type: Literal["input_text"] = "input_text"
630 text: str
631
632
633class UserMessageTagContent(BaseModel):
634 """User message content representing an interactive tag."""
635
636 type: Literal["input_tag"] = "input_tag"
637 id: str
638 text: str
639 data: dict[str, Any]
640 interactive: bool = False
641
642
643UserMessageContent = Annotated[
644 UserMessageTextContent | UserMessageTagContent, Field(discriminator="type")
645]
646"""Union of allowed user message content payloads."""
647
648
649class InferenceOptions(BaseModel):
650 """Model and tool configuration for message processing."""
651
652 tool_choice: ToolChoice | None = None
653 model: str | None = None
654
655
656class ToolChoice(BaseModel):
657 """Explicit tool selection for the assistant to invoke."""
658
659 id: str
660
661
662class AttachmentBase(BaseModel):
663 """Base metadata shared by all attachments."""
664
665 id: str
666 name: str
667 mime_type: str
668 upload_url: AnyUrl | None = None
669 """
670 The URL to upload the file, used for two-phase upload.
671 Should be set to None after upload is complete or when using direct upload where uploading happens when creating the attachment object.
672 """
673
674
675class FileAttachment(AttachmentBase):
676 """Attachment representing a generic file."""
677
678 type: Literal["file"] = "file"
679
680
681class ImageAttachment(AttachmentBase):
682 """Attachment representing an image resource."""
683
684 type: Literal["image"] = "image"
685 preview_url: AnyUrl
686
687
688Attachment = Annotated[
689 FileAttachment | ImageAttachment,
690 Field(discriminator="type"),
691]
692"""Union of supported attachment types."""
693
694
695### WORKFLOW TYPES
696
697
698class Workflow(BaseModel):
699 """Workflow attached to a thread with optional summary."""
700
701 type: Literal["custom", "reasoning"]
702 tasks: list[Task]
703 summary: WorkflowSummary | None = None
704 expanded: bool = False
705
706
707class CustomSummary(BaseModel):
708 """Custom summary for a workflow."""
709
710 title: str
711 icon: IconName | None = None
712
713
714class DurationSummary(BaseModel):
715 """Summary providing total workflow duration."""
716
717 duration: int
718 """The duration of the workflow in seconds"""
719
720
721WorkflowSummary = CustomSummary | DurationSummary
722"""Summary variants available for workflows."""
723
724### TASK TYPES
725
726
727class BaseTask(BaseModel):
728 """Base fields common to all workflow tasks."""
729
730 status_indicator: Literal["none", "loading", "complete"] = "none"
731 """Only used when rendering the task as part of a workflow. Indicates the status of the task."""
732
733
734class CustomTask(BaseTask):
735 """Workflow task displaying custom content."""
736
737 type: Literal["custom"] = "custom"
738 title: str | None = None
739 icon: IconName | None = None
740 content: str | None = None
741
742
743class SearchTask(BaseTask):
744 """Workflow task representing a web search."""
745
746 type: Literal["web_search"] = "web_search"
747 title: str | None = None
748 title_query: str | None = None
749 queries: list[str] = Field(default_factory=list)
750 sources: list[URLSource] = Field(default_factory=list)
751
752
753class ThoughtTask(BaseTask):
754 """Workflow task capturing assistant reasoning."""
755
756 type: Literal["thought"] = "thought"
757 title: str | None = None
758 content: str
759
760
761class FileTask(BaseTask):
762 """Workflow task referencing file sources."""
763
764 type: Literal["file"] = "file"
765 title: str | None = None
766 sources: list[FileSource] = Field(default_factory=list)
767
768
769class ImageTask(BaseTask):
770 """Workflow task rendering image content."""
771
772 type: Literal["image"] = "image"
773 title: str | None = None
774
775
776Task = Annotated[
777 CustomTask | SearchTask | ThoughtTask | FileTask | ImageTask,
778 Field(discriminator="type"),
779]
780"""Union of workflow task variants."""
781
782
783### SOURCE TYPES
784
785
786class SourceBase(BaseModel):
787 """Base class for sources displayed to users."""
788
789 title: str
790 description: str | None = None
791 timestamp: str | None = None
792 group: str | None = None
793
794
795class FileSource(SourceBase):
796 """Source metadata for file-based references."""
797
798 type: Literal["file"] = "file"
799 filename: str
800
801
802class URLSource(SourceBase):
803 """Source metadata for external URLs."""
804
805 type: Literal["url"] = "url"
806 url: str
807 attribution: str | None = None
808
809
810class EntitySource(SourceBase):
811 """Source metadata for entity references."""
812
813 type: Literal["entity"] = "entity"
814 id: str
815 icon: IconName | None = None
816 preview: Literal["lazy"] | None = None
817 data: dict[str, Any] = Field(default_factory=dict)
818
819
820Source = Annotated[
821 URLSource | FileSource | EntitySource,
822 Field(discriminator="type"),
823]
824"""Union of supported source types."""
825
826
827### MISC TYPES
828
829
830FeedbackKind = Literal["positive", "negative"]
831"""Literal type for feedback sentiment."""
832