openai/chatkit-python

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
12dd5dd57bef297f877d3f3b78337b28b1b473fd

Branches

Tags

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

Clone

HTTPS

Download ZIP

chatkit/types.py

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