openai/chatkit-python

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1b15f7de44765f3dd01a304292b08ffe12c2574b

Branches

Tags

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

Clone

HTTPS

Download ZIP

chatkit/types.py

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