openai/chatkit-python

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
6988ea0a4ea7ee9e1d2f6360544fc21e9eff1717

Branches

Tags

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

Clone

HTTPS

Download ZIP

chatkit/types.py

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