openai/chatkit-python

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
6df9ee60da4de3f063e752e60bd3ceef920f4e78

Branches

Tags

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

Clone

HTTPS

Download ZIP

chatkit/types.py

870lines · 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 ThreadItemUpdatedEvent(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
295# Type alias for backwards compatibility
296ThreadItemUpdated = ThreadItemUpdatedEvent
297
298
299class ThreadItemDoneEvent(BaseModel):
300 """Event emitted when a thread item is marked complete."""
301
302 type: Literal["thread.item.done"] = "thread.item.done"
303 item: ThreadItem
304
305
306class ThreadItemRemovedEvent(BaseModel):
307 """Event emitted when a thread item is removed."""
308
309 type: Literal["thread.item.removed"] = "thread.item.removed"
310 item_id: str
311
312
313class ThreadItemReplacedEvent(BaseModel):
314 """Event emitted when a thread item is replaced."""
315
316 type: Literal["thread.item.replaced"] = "thread.item.replaced"
317 item: ThreadItem
318
319
320class StreamOptions(BaseModel):
321 """Settings that control runtime stream behavior."""
322
323 allow_cancel: bool
324 """Allow the client to request cancellation mid-stream."""
325
326
327class StreamOptionsEvent(BaseModel):
328 """Event emitted to set stream options at runtime."""
329
330 type: Literal["stream_options"] = "stream_options"
331 stream_options: StreamOptions
332
333
334class ProgressUpdateEvent(BaseModel):
335 """Event providing incremental progress from the assistant."""
336
337 type: Literal["progress_update"] = "progress_update"
338 icon: IconName | None = None
339 text: str
340
341
342class ErrorEvent(BaseModel):
343 """Event indicating an error occurred while processing a thread."""
344
345 type: Literal["error"] = "error"
346 code: ErrorCode | Literal["custom"] = Field(default="custom")
347 message: str | None = None
348 allow_retry: bool = Field(default=False)
349
350
351class NoticeEvent(BaseModel):
352 """Event conveying a user-facing notice."""
353
354 type: Literal["notice"] = "notice"
355 level: Literal["info", "warning", "danger"]
356 message: str
357 """
358 Supports markdown e.g. "You've reached your limit of 100 messages. [Upgrade](https://...) to a paid plan."
359 """
360 title: str | None = None
361
362
363ThreadStreamEvent = Annotated[
364 ThreadCreatedEvent
365 | ThreadUpdatedEvent
366 | ThreadItemDoneEvent
367 | ThreadItemAddedEvent
368 | ThreadItemUpdated
369 | ThreadItemRemovedEvent
370 | ThreadItemReplacedEvent
371 | StreamOptionsEvent
372 | ProgressUpdateEvent
373 | ErrorEvent
374 | NoticeEvent,
375 Field(discriminator="type"),
376]
377"""Union of all streaming events emitted to clients."""
378
379### THREAD ITEM UPDATE TYPES
380
381
382class AssistantMessageContentPartAdded(BaseModel):
383 """Event emitted when new assistant content is appended."""
384
385 type: Literal["assistant_message.content_part.added"] = (
386 "assistant_message.content_part.added"
387 )
388 content_index: int
389 content: AssistantMessageContent
390
391
392class AssistantMessageContentPartTextDelta(BaseModel):
393 """Event carrying incremental assistant text output."""
394
395 type: Literal["assistant_message.content_part.text_delta"] = (
396 "assistant_message.content_part.text_delta"
397 )
398 content_index: int
399 delta: str
400
401
402class AssistantMessageContentPartAnnotationAdded(BaseModel):
403 """Event announcing a new annotation on assistant content."""
404
405 type: Literal["assistant_message.content_part.annotation_added"] = (
406 "assistant_message.content_part.annotation_added"
407 )
408 content_index: int
409 annotation_index: int
410 annotation: Annotation
411
412
413class AssistantMessageContentPartDone(BaseModel):
414 """Event indicating an assistant content part is finalized."""
415
416 type: Literal["assistant_message.content_part.done"] = (
417 "assistant_message.content_part.done"
418 )
419 content_index: int
420 content: AssistantMessageContent
421
422
423class WidgetStreamingTextValueDelta(BaseModel):
424 """Event streaming widget text deltas."""
425
426 type: Literal["widget.streaming_text.value_delta"] = (
427 "widget.streaming_text.value_delta"
428 )
429 component_id: str
430 delta: str
431 done: bool
432
433
434class WidgetRootUpdated(BaseModel):
435 """Event published when the widget root changes."""
436
437 type: Literal["widget.root.updated"] = "widget.root.updated"
438 widget: WidgetRoot
439
440
441class WidgetComponentUpdated(BaseModel):
442 """Event emitted when a widget component updates."""
443
444 type: Literal["widget.component.updated"] = "widget.component.updated"
445 component_id: str
446 component: WidgetComponent
447
448
449class WorkflowTaskAdded(BaseModel):
450 """Event emitted when a workflow task is added."""
451
452 type: Literal["workflow.task.added"] = "workflow.task.added"
453 task_index: int
454 task: Task
455
456
457class WorkflowTaskUpdated(BaseModel):
458 """Event emitted when a workflow task is updated."""
459
460 type: Literal["workflow.task.updated"] = "workflow.task.updated"
461 task_index: int
462 task: Task
463
464
465ThreadItemUpdate = (
466 AssistantMessageContentPartAdded
467 | AssistantMessageContentPartTextDelta
468 | AssistantMessageContentPartAnnotationAdded
469 | AssistantMessageContentPartDone
470 | WidgetStreamingTextValueDelta
471 | WidgetComponentUpdated
472 | WidgetRootUpdated
473 | WorkflowTaskAdded
474 | WorkflowTaskUpdated
475)
476"""Union of possible updates applied to thread items."""
477
478
479### THREAD TYPES
480
481
482class ThreadMetadata(BaseModel):
483 """Metadata describing a thread without its items."""
484
485 title: str | None = None
486 id: str
487 created_at: datetime
488 status: ThreadStatus = Field(default_factory=lambda: ActiveStatus())
489 # TODO - make not client rendered
490 metadata: dict[str, Any] = Field(default_factory=dict)
491
492
493class ActiveStatus(BaseModel):
494 """Status indicating the thread is active."""
495
496 type: Literal["active"] = Field(default="active", frozen=True)
497
498
499class LockedStatus(BaseModel):
500 """Status indicating the thread is locked."""
501
502 type: Literal["locked"] = Field(default="locked", frozen=True)
503 reason: str | None = None
504
505
506class ClosedStatus(BaseModel):
507 """Status indicating the thread is closed."""
508
509 type: Literal["closed"] = Field(default="closed", frozen=True)
510 reason: str | None = None
511
512
513ThreadStatus = Annotated[
514 ActiveStatus | LockedStatus | ClosedStatus,
515 Field(discriminator="type"),
516]
517"""Union of lifecycle states for a thread."""
518
519
520class Thread(ThreadMetadata):
521 """Thread with its paginated items."""
522
523 items: Page[ThreadItem]
524
525
526### THREAD ITEM TYPES
527
528
529class ThreadItemBase(BaseModel):
530 """Base fields shared by all thread items."""
531
532 id: str
533 thread_id: str
534 created_at: datetime
535
536
537class UserMessageItem(ThreadItemBase):
538 """Thread item representing a user message."""
539
540 type: Literal["user_message"] = "user_message"
541 content: list[UserMessageContent]
542 attachments: list[Attachment] = Field(default_factory=list)
543 quoted_text: str | None = None
544 inference_options: InferenceOptions
545
546
547class AssistantMessageItem(ThreadItemBase):
548 """Thread item representing an assistant message."""
549
550 type: Literal["assistant_message"] = "assistant_message"
551 content: list[AssistantMessageContent]
552
553
554class ClientToolCallItem(ThreadItemBase):
555 """Thread item capturing a client tool call."""
556
557 type: Literal["client_tool_call"] = "client_tool_call"
558 status: Literal["pending", "completed"] = "pending"
559 call_id: str
560 name: str
561 arguments: dict[str, Any]
562 output: Any | None = None
563
564
565class WidgetItem(ThreadItemBase):
566 """Thread item containing widget content."""
567
568 type: Literal["widget"] = "widget"
569 widget: WidgetRoot
570 copy_text: str | None = None
571
572
573class TaskItem(ThreadItemBase):
574 """Thread item containing a task."""
575
576 type: Literal["task"] = "task"
577 task: Task
578
579
580class WorkflowItem(ThreadItemBase):
581 """Thread item representing a workflow."""
582
583 type: Literal["workflow"] = "workflow"
584 workflow: Workflow
585
586
587class EndOfTurnItem(ThreadItemBase):
588 """Marker item indicating the assistant ends its turn."""
589
590 type: Literal["end_of_turn"] = "end_of_turn"
591
592
593class HiddenContextItem(ThreadItemBase):
594 """
595 HiddenContext is never sent to the client. It's not officially part of ChatKit.js.
596 It is only used internally to store additional context in a specific place in the thread.
597 """
598
599 type: Literal["hidden_context_item"] = "hidden_context_item"
600 content: Any
601
602
603class SDKHiddenContextItem(ThreadItemBase):
604 """
605 Hidden context that is used by the ChatKit Python SDK for storing additional context
606 for internal operations.
607 """
608
609 type: Literal["sdk_hidden_context"] = "sdk_hidden_context"
610 content: str
611
612
613ThreadItem = Annotated[
614 UserMessageItem
615 | AssistantMessageItem
616 | ClientToolCallItem
617 | WidgetItem
618 | WorkflowItem
619 | TaskItem
620 | HiddenContextItem
621 | SDKHiddenContextItem
622 | EndOfTurnItem,
623 Field(discriminator="type"),
624]
625"""Union of all thread item variants."""
626
627
628### ASSISTANT MESSAGE TYPES
629
630
631class AssistantMessageContent(BaseModel):
632 """Assistant message content consisting of text and annotations."""
633
634 annotations: list[Annotation] = Field(default_factory=list)
635 text: str
636 type: Literal["output_text"] = "output_text"
637
638
639class Annotation(BaseModel):
640 """Reference to supporting context attached to assistant output."""
641
642 type: Literal["annotation"] = "annotation"
643 source: URLSource | FileSource | EntitySource
644 index: int | None = None
645
646
647### USER MESSAGE TYPES
648
649
650class UserMessageInput(BaseModel):
651 """Payload describing a user message submission."""
652
653 content: list[UserMessageContent]
654 attachments: list[str]
655 quoted_text: str | None = None
656 inference_options: InferenceOptions
657
658
659class UserMessageTextContent(BaseModel):
660 """User message content containing plaintext."""
661
662 type: Literal["input_text"] = "input_text"
663 text: str
664
665
666class UserMessageTagContent(BaseModel):
667 """User message content representing an interactive tag."""
668
669 type: Literal["input_tag"] = "input_tag"
670 id: str
671 text: str
672 data: dict[str, Any]
673 group: str | None = None
674 interactive: bool = False
675
676
677UserMessageContent = Annotated[
678 UserMessageTextContent | UserMessageTagContent, Field(discriminator="type")
679]
680"""Union of allowed user message content payloads."""
681
682
683class InferenceOptions(BaseModel):
684 """Model and tool configuration for message processing."""
685
686 tool_choice: ToolChoice | None = None
687 model: str | None = None
688
689
690class ToolChoice(BaseModel):
691 """Explicit tool selection for the assistant to invoke."""
692
693 id: str
694
695
696class AttachmentBase(BaseModel):
697 """Base metadata shared by all attachments."""
698
699 id: str
700 name: str
701 mime_type: str
702 upload_url: AnyUrl | None = None
703 """
704 The URL to upload the file, used for two-phase upload.
705 Should be set to None after upload is complete or when using direct upload where uploading happens when creating the attachment object.
706 """
707
708
709class FileAttachment(AttachmentBase):
710 """Attachment representing a generic file."""
711
712 type: Literal["file"] = "file"
713
714
715class ImageAttachment(AttachmentBase):
716 """Attachment representing an image resource."""
717
718 type: Literal["image"] = "image"
719 preview_url: AnyUrl
720
721
722Attachment = Annotated[
723 FileAttachment | ImageAttachment,
724 Field(discriminator="type"),
725]
726"""Union of supported attachment types."""
727
728
729### WORKFLOW TYPES
730
731
732class Workflow(BaseModel):
733 """Workflow attached to a thread with optional summary."""
734
735 type: Literal["custom", "reasoning"]
736 tasks: list[Task]
737 summary: WorkflowSummary | None = None
738 expanded: bool = False
739
740
741class CustomSummary(BaseModel):
742 """Custom summary for a workflow."""
743
744 title: str
745 icon: IconName | None = None
746
747
748class DurationSummary(BaseModel):
749 """Summary providing total workflow duration."""
750
751 duration: int
752 """The duration of the workflow in seconds"""
753
754
755WorkflowSummary = CustomSummary | DurationSummary
756"""Summary variants available for workflows."""
757
758### TASK TYPES
759
760
761class BaseTask(BaseModel):
762 """Base fields common to all workflow tasks."""
763
764 status_indicator: Literal["none", "loading", "complete"] = "none"
765 """Only used when rendering the task as part of a workflow. Indicates the status of the task."""
766
767
768class CustomTask(BaseTask):
769 """Workflow task displaying custom content."""
770
771 type: Literal["custom"] = "custom"
772 title: str | None = None
773 icon: IconName | None = None
774 content: str | None = None
775
776
777class SearchTask(BaseTask):
778 """Workflow task representing a web search."""
779
780 type: Literal["web_search"] = "web_search"
781 title: str | None = None
782 title_query: str | None = None
783 queries: list[str] = Field(default_factory=list)
784 sources: list[URLSource] = Field(default_factory=list)
785
786
787class ThoughtTask(BaseTask):
788 """Workflow task capturing assistant reasoning."""
789
790 type: Literal["thought"] = "thought"
791 title: str | None = None
792 content: str
793
794
795class FileTask(BaseTask):
796 """Workflow task referencing file sources."""
797
798 type: Literal["file"] = "file"
799 title: str | None = None
800 sources: list[FileSource] = Field(default_factory=list)
801
802
803class ImageTask(BaseTask):
804 """Workflow task rendering image content."""
805
806 type: Literal["image"] = "image"
807 title: str | None = None
808
809
810Task = Annotated[
811 CustomTask | SearchTask | ThoughtTask | FileTask | ImageTask,
812 Field(discriminator="type"),
813]
814"""Union of workflow task variants."""
815
816
817### SOURCE TYPES
818
819
820class SourceBase(BaseModel):
821 """Base class for sources displayed to users."""
822
823 title: str
824 description: str | None = None
825 timestamp: str | None = None
826 group: str | None = None
827
828
829class FileSource(SourceBase):
830 """Source metadata for file-based references."""
831
832 type: Literal["file"] = "file"
833 filename: str
834
835
836class URLSource(SourceBase):
837 """Source metadata for external URLs."""
838
839 type: Literal["url"] = "url"
840 url: str
841 attribution: str | None = None
842
843
844class EntitySource(SourceBase):
845 """Source metadata for entity references."""
846
847 type: Literal["entity"] = "entity"
848 id: str
849 icon: IconName | None = None
850 data: dict[str, Any] = Field(default_factory=dict)
851
852 preview: Literal["lazy"] | None = Field(
853 default=None,
854 deprecated=True,
855 description="This field is ignored. Please use the entities.onRequestPreview ChatKit.js option instead.",
856 )
857
858
859Source = Annotated[
860 URLSource | FileSource | EntitySource,
861 Field(discriminator="type"),
862]
863"""Union of supported source types."""
864
865
866### MISC TYPES
867
868
869FeedbackKind = Literal["positive", "negative"]
870"""Literal type for feedback sentiment."""
871