openai/chatkit-python

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
c9f5f09d50d3fa3029f1576bc8750f0f01286b16

Branches

Tags

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

Clone

HTTPS

Download ZIP

chatkit/types.py

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