unique_toolkit 0.0.1__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. unique_toolkit/__init__.py +0 -3
  2. unique_toolkit/{observability/log_config.py → app/init_logging.py} +10 -10
  3. unique_toolkit/app/init_sdk.py +41 -0
  4. unique_toolkit/{performance → app/performance}/async_executor.py +44 -17
  5. unique_toolkit/app/performance/async_wrapper.py +28 -0
  6. unique_toolkit/{event/schema.py → app/schemas.py} +17 -10
  7. unique_toolkit/app/verification.py +58 -0
  8. unique_toolkit/chat/schemas.py +30 -0
  9. unique_toolkit/chat/service.py +285 -87
  10. unique_toolkit/chat/state.py +22 -10
  11. unique_toolkit/chat/utils.py +25 -0
  12. unique_toolkit/content/schemas.py +90 -0
  13. unique_toolkit/content/service.py +356 -0
  14. unique_toolkit/content/utils.py +188 -0
  15. unique_toolkit/embedding/schemas.py +5 -0
  16. unique_toolkit/embedding/service.py +89 -0
  17. unique_toolkit/language_model/infos.py +305 -0
  18. unique_toolkit/language_model/schemas.py +168 -0
  19. unique_toolkit/language_model/service.py +261 -0
  20. unique_toolkit/language_model/utils.py +44 -0
  21. {unique_toolkit-0.0.1.dist-info → unique_toolkit-0.5.0.dist-info}/LICENSE +1 -1
  22. unique_toolkit-0.5.0.dist-info/METADATA +135 -0
  23. unique_toolkit-0.5.0.dist-info/RECORD +24 -0
  24. unique_toolkit/chat/__init__.py +0 -3
  25. unique_toolkit/chat/messages.py +0 -24
  26. unique_toolkit/event/__init__.py +0 -1
  27. unique_toolkit/event/constants.py +0 -1
  28. unique_toolkit/llm/__init__.py +0 -2
  29. unique_toolkit/llm/json_parser.py +0 -21
  30. unique_toolkit/llm/models.py +0 -37
  31. unique_toolkit/llm/service.py +0 -163
  32. unique_toolkit/performance/__init__.py +0 -1
  33. unique_toolkit/performance/async_wrapper.py +0 -13
  34. unique_toolkit/sdk/init.py +0 -19
  35. unique_toolkit/search/service.py +0 -118
  36. unique_toolkit/security/verify.py +0 -29
  37. unique_toolkit-0.0.1.dist-info/METADATA +0 -33
  38. unique_toolkit-0.0.1.dist-info/RECORD +0 -23
  39. {unique_toolkit-0.0.1.dist-info → unique_toolkit-0.5.0.dist-info}/WHEEL +0 -0
@@ -1,9 +1,14 @@
1
- import unique_sdk
2
- from unique_sdk.utils.chat_history import load_history
1
+ import logging
2
+ import re
3
+ from typing import Optional
3
4
 
4
- from unique_toolkit.performance.async_wrapper import to_async
5
+ import unique_sdk
5
6
 
6
- from .state import ChatState
7
+ from unique_toolkit.app.performance.async_wrapper import async_warning, to_async
8
+ from unique_toolkit.chat.schemas import ChatMessage, ChatMessageRole
9
+ from unique_toolkit.chat.state import ChatState
10
+ from unique_toolkit.content.schemas import ContentReference
11
+ from unique_toolkit.content.utils import count_tokens
7
12
 
8
13
 
9
14
  class ChatService:
@@ -12,171 +17,364 @@ class ChatService:
12
17
 
13
18
  Attributes:
14
19
  state (ChatState): The chat state.
20
+ logger (Optional[logging.Logger]): The logger. Defaults to None.
15
21
  """
16
22
 
17
- def __init__(self, state: ChatState):
23
+ def __init__(self, state: ChatState, logger: Optional[logging.Logger] = None):
18
24
  self.state = state
25
+ self.logger = logger or logging.getLogger(__name__)
19
26
 
20
27
  def modify_assistant_message(
21
28
  self,
22
- *,
23
- text: str,
24
- references: list = [],
29
+ content: str,
30
+ references: list[ContentReference] = [],
25
31
  debug_info: dict = {},
26
- ) -> None:
32
+ message_id: Optional[str] = None,
33
+ ) -> ChatMessage:
27
34
  """
28
35
  Modifies a message in the chat session synchronously.
29
36
 
30
37
  Args:
31
- text (str): The new text for the message.
32
- references (Optional[list[dict[str, Any]]], optional): list of references. Defaults to None.
33
- debug_info (Optional[dict[str, Any]]], optional): Debug information. Defaults to None.
38
+ content (str): The new content for the message.
39
+ references (list[ContentReference]): list of ContentReference objects. Defaults to [].
40
+ debug_info (dict[str, Any]]]): Debug information. Defaults to {}.
41
+ message_id (Optional[str]): The message ID. Defaults to None.
42
+
43
+ Returns:
44
+ ChatMessage: The modified message.
45
+
46
+ Raises:
47
+ Exception: If the modification fails.
34
48
  """
35
- self._trigger_modify_assistant_message(text, references, debug_info)
49
+ return self._trigger_modify_assistant_message(
50
+ content=content,
51
+ message_id=message_id,
52
+ references=references,
53
+ debug_info=debug_info,
54
+ )
36
55
 
37
56
  @to_async
57
+ @async_warning
38
58
  def async_modify_assistant_message(
39
59
  self,
40
- *,
41
- text: str,
42
- references: list = [],
60
+ content: str,
61
+ references: list[ContentReference] = [],
43
62
  debug_info: dict = {},
44
- ) -> None:
63
+ message_id: Optional[str] = None,
64
+ ) -> ChatMessage:
45
65
  """
46
66
  Modifies a message in the chat session asynchronously.
47
67
 
48
68
  Args:
49
- text (str): The new text for the message.
50
- references (Optional[list[dict[str, Any]]], optional): list of references. Defaults to None.
69
+ content (str): The new content for the message.
70
+ message_id (str, optional): The message ID. Defaults to None, then the ChatState assistant message id is used.
71
+ references (list[ContentReference]): list of ContentReference objects. Defaults to None.
51
72
  debug_info (Optional[dict[str, Any]]], optional): Debug information. Defaults to None.
73
+
74
+ Returns:
75
+ ChatMessage: The modified message.
76
+
77
+ Raises:
78
+ Exception: If the modification fails.
79
+ """
80
+ return self._trigger_modify_assistant_message(
81
+ content,
82
+ message_id,
83
+ references,
84
+ debug_info,
85
+ )
86
+
87
+ def get_full_history(self) -> list[ChatMessage]:
88
+ """
89
+ Loads the full chat history for the chat session synchronously.
90
+
91
+ Returns:
92
+ list[ChatMessage]: The full chat history.
93
+
94
+ Raises:
95
+ Exception: If the loading fails.
52
96
  """
53
- self._trigger_modify_assistant_message(text, references, debug_info)
97
+ return self._get_full_history()
54
98
 
55
- def get_history(
99
+ @to_async
100
+ @async_warning
101
+ def async_get_full_history(self) -> list[ChatMessage]:
102
+ """
103
+ Loads the full chat history for the chat session asynchronously.
104
+
105
+ Returns:
106
+ list[ChatMessage]: The full chat history.
107
+
108
+ Raises:
109
+ Exception: If the loading fails.
110
+ """
111
+ return self._get_full_history()
112
+
113
+ def get_full_and_selected_history(
56
114
  self,
57
- *,
58
- max_tokens: int,
115
+ token_limit: int,
59
116
  percent_of_max_tokens: float,
60
117
  max_messages: int,
61
- ):
118
+ ) -> tuple[list[ChatMessage], list[ChatMessage]]:
62
119
  """
63
120
  Loads the chat history for the chat session synchronously.
64
121
 
65
122
  Args:
66
- max_tokens (int): The maximum number of tokens to load.
123
+ token_limit (int): The maximum number of tokens to load.
67
124
  percent_of_max_tokens (float): The percentage of the maximum tokens to load.
68
125
  max_messages (int): The maximum number of messages to load.
69
126
 
70
127
  Returns:
71
- list[dict[str, Any]]: The chat history.
128
+ tuple[list[ChatMessage], list[ChatMessage]]: The selected and full chat history.
129
+
130
+ Raises:
131
+ Exception: If the loading fails.
72
132
  """
73
- return self._trigger_load_history(
74
- max_tokens, percent_of_max_tokens, max_messages
133
+ return self._get_full_and_selected_history(
134
+ token_limit=token_limit,
135
+ percent_of_max_tokens=percent_of_max_tokens,
136
+ max_messages=max_messages,
75
137
  )
76
138
 
77
139
  @to_async
78
- def async_get_history(
140
+ @async_warning
141
+ def async_get_full_and_selected_history(
79
142
  self,
80
- *,
81
- max_tokens: int,
143
+ token_limit: int,
82
144
  percent_of_max_tokens: float,
83
145
  max_messages: int,
84
- ):
146
+ ) -> tuple[list[ChatMessage], list[ChatMessage]]:
85
147
  """
86
148
  Loads the chat history for the chat session asynchronously.
87
149
 
88
150
  Args:
89
- max_tokens (int): The maximum number of tokens to load.
151
+ token_limit (int): The maximum number of tokens to load.
90
152
  percent_of_max_tokens (float): The percentage of the maximum tokens to load.
91
153
  max_messages (int): The maximum number of messages to load.
92
154
 
93
155
  Returns:
94
- list[dict[str, Any]]: The chat history.
156
+ tuple[list[ChatMessage], list[ChatMessage]]: The selected and full chat history.
157
+
158
+ Raises:
159
+ Exception: If the loading fails.
95
160
  """
96
- return self._trigger_load_history(
97
- max_tokens, percent_of_max_tokens, max_messages
161
+ return self._get_full_and_selected_history(
162
+ token_limit=token_limit,
163
+ percent_of_max_tokens=percent_of_max_tokens,
164
+ max_messages=max_messages,
98
165
  )
99
166
 
100
167
  def create_assistant_message(
101
168
  self,
102
- *,
103
- text: str,
104
- references: list = [],
169
+ content: str,
170
+ references: list[ContentReference] = [],
105
171
  debug_info: dict = {},
106
172
  ):
107
173
  """
108
174
  Creates a message in the chat session synchronously.
109
175
 
110
176
  Args:
111
- text (str): The text for the message.
112
- references (Optional[list[dict[str, Any]]], optional): list of references. Defaults to None.
113
- debug_info (Optional[dict[str, Any]]], optional): Debug information. Defaults to None.
177
+ content (str): The content for the message.
178
+ references (list[ContentReference]): list of ContentReference objects. Defaults to None.
179
+ debug_info (dict[str, Any]]): Debug information. Defaults to None.
180
+
181
+ Returns:
182
+ ChatMessage: The created message.
183
+
184
+ Raises:
185
+ Exception: If the creation fails.
114
186
  """
115
- return self._trigger_create_assistant_message(text, references, debug_info)
187
+ return self._trigger_create_assistant_message(
188
+ content=content,
189
+ references=references,
190
+ debug_info=debug_info,
191
+ )
116
192
 
117
193
  @to_async
194
+ @async_warning
118
195
  def async_create_assistant_message(
119
196
  self,
120
- *,
121
- text: str,
122
- references: list = [],
197
+ content: str,
198
+ references: list[ContentReference] = [],
123
199
  debug_info: dict = {},
124
200
  ):
125
201
  """
126
202
  Creates a message in the chat session asynchronously.
127
203
 
128
204
  Args:
129
- text (str): The text for the message.
130
- references (Optional[list[dict[str, Any]]], optional): list of references. Defaults to None.
131
- debug_info (Optional[dict[str, Any]]], optional): Debug information. Defaults to None.
205
+ content (str): The content for the message.
206
+ references (list[ContentReference]): list of references. Defaults to None.
207
+ debug_info (dict[str, Any]]): Debug information. Defaults to None.
208
+
209
+ Returns:
210
+ ChatMessage: The created message.
211
+
212
+ Raises:
213
+ Exception: If the creation fails.
132
214
  """
133
- return self._trigger_create_assistant_message(text, references, debug_info)
215
+
216
+ return self._trigger_create_assistant_message(
217
+ content=content,
218
+ references=references,
219
+ debug_info=debug_info,
220
+ )
134
221
 
135
222
  def _trigger_modify_assistant_message(
136
223
  self,
137
- text: str,
138
- references: list = [],
139
- debug_info: dict = {},
140
- ) -> None:
141
- unique_sdk.Message.modify(
142
- user_id=self.state.user_id,
143
- company_id=self.state.company_id,
144
- id=self.state.assistant_message_id,
145
- chatId=self.state.chat_id,
146
- text=text,
147
- references=references or [],
148
- debugInfo=debug_info or {},
149
- )
224
+ content: str,
225
+ message_id: Optional[str],
226
+ references: list[ContentReference],
227
+ debug_info: dict,
228
+ ) -> ChatMessage:
229
+ message_id = message_id or self.state.assistant_message_id
230
+
231
+ try:
232
+ message = unique_sdk.Message.modify(
233
+ user_id=self.state.user_id,
234
+ company_id=self.state.company_id,
235
+ id=message_id, # type: ignore
236
+ chatId=self.state.chat_id,
237
+ text=content,
238
+ references=self._map_references(references), # type: ignore
239
+ debugInfo=debug_info or {},
240
+ )
241
+ except Exception as e:
242
+ self.logger.error(f"Failed to modify assistant message: {e}")
243
+ raise e
244
+ return ChatMessage(**message)
150
245
 
151
- def _trigger_load_history(
246
+ def _trigger_create_assistant_message(
152
247
  self,
153
- max_tokens: int,
154
- percent_of_max_tokens: float,
155
- max_messages: int,
248
+ content: str,
249
+ references: list[ContentReference],
250
+ debug_info: dict,
251
+ ) -> ChatMessage:
252
+ try:
253
+ message = unique_sdk.Message.create(
254
+ user_id=self.state.user_id,
255
+ company_id=self.state.company_id,
256
+ chatId=self.state.chat_id,
257
+ assistantId=self.state.assistant_id,
258
+ text=content,
259
+ role=ChatMessageRole.ASSISTANT.name,
260
+ references=self._map_references(references), # type: ignore
261
+ debugInfo=debug_info,
262
+ )
263
+ except Exception as e:
264
+ self.logger.error(f"Failed to create assistant message: {e}")
265
+ raise e
266
+ return ChatMessage(**message)
267
+
268
+ @staticmethod
269
+ def _map_references(references: list[ContentReference]):
270
+ return [
271
+ {
272
+ "name": ref.name,
273
+ "url": ref.url,
274
+ "sequenceNumber": ref.sequence_number,
275
+ "sourceId": ref.source_id,
276
+ "source": ref.source,
277
+ }
278
+ for ref in references
279
+ ]
280
+
281
+ def _get_full_and_selected_history(
282
+ self,
283
+ token_limit,
284
+ percent_of_max_tokens=0.15,
285
+ max_messages=4,
156
286
  ):
157
- return load_history(
158
- self.state.user_id,
159
- self.state.company_id,
160
- self.state.chat_id,
161
- max_tokens,
162
- percent_of_max_tokens,
287
+ full_history = self._get_full_history()
288
+ selected_history = self._get_selection_from_history(
289
+ full_history,
290
+ int(round(token_limit * percent_of_max_tokens)),
163
291
  max_messages,
164
292
  )
165
293
 
166
- # TODO throws error at the moment
167
- def _trigger_create_assistant_message(
294
+ return full_history, selected_history
295
+
296
+ def _get_full_history(self):
297
+ SYSTEM_MESSAGE_PREFIX = "[SYSTEM] "
298
+
299
+ messages = self._trigger_list_messages(self.state.chat_id)
300
+
301
+ # Remove the last two messages
302
+ messages = messages["data"][:-2] # type: ignore
303
+ filtered_messages = []
304
+ for message in messages:
305
+ if message["text"] is None:
306
+ continue
307
+ elif SYSTEM_MESSAGE_PREFIX in message["text"]:
308
+ continue
309
+ else:
310
+ filtered_messages.append(message)
311
+
312
+ return self._map_to_chat_messages(filtered_messages)
313
+
314
+ def _trigger_list_messages(self, chat_id: str):
315
+ try:
316
+ messages = unique_sdk.Message.list(
317
+ user_id=self.state.user_id,
318
+ company_id=self.state.company_id,
319
+ chatId=chat_id,
320
+ )
321
+ return messages
322
+ except Exception as e:
323
+ self.logger.error(f"Failed to list chat history: {e}")
324
+ raise e
325
+
326
+ @staticmethod
327
+ def _map_to_chat_messages(messages: list[dict]):
328
+ return [ChatMessage(**msg) for msg in messages]
329
+
330
+ def _get_selection_from_history(
168
331
  self,
169
- text: str,
170
- references: list = [],
171
- debug_info: dict = {},
332
+ full_history: list[ChatMessage],
333
+ max_tokens: int,
334
+ max_messages=4,
172
335
  ):
173
- return unique_sdk.Message.create(
174
- user_id=self.state.user_id,
175
- company_id=self.state.company_id,
176
- chatId=self.state.chat_id,
177
- assistantId=self.state.assistant_id,
178
- text=text,
179
- role="ASSISTANT",
180
- references=references,
181
- debugInfo=debug_info,
336
+ messages = full_history[-max_messages:]
337
+ filtered_messages = [m for m in messages if m.content]
338
+ mapped_messages = []
339
+
340
+ for m in filtered_messages:
341
+ m.content = re.sub(r"<sup>\d+</sup>", "", m.content)
342
+ m.role = (
343
+ ChatMessageRole.ASSISTANT
344
+ if m.role == ChatMessageRole.ASSISTANT
345
+ else ChatMessageRole.USER
346
+ )
347
+ mapped_messages.append(m)
348
+
349
+ return self._pick_messages_in_reverse_for_token_window(
350
+ messages=mapped_messages,
351
+ limit=max_tokens,
182
352
  )
353
+
354
+ def _pick_messages_in_reverse_for_token_window(
355
+ self,
356
+ messages: list[ChatMessage],
357
+ limit: int,
358
+ ) -> list[ChatMessage]:
359
+ if len(messages) < 1 or limit < 1:
360
+ return []
361
+
362
+ last_index = len(messages) - 1
363
+ token_count = count_tokens(messages[last_index].content)
364
+ while token_count > limit:
365
+ self.logger.debug(
366
+ f"Limit too low for the initial message. Last message TokenCount {token_count} available tokens {limit} - cutting message in half until it fits"
367
+ )
368
+ content = messages[last_index].content
369
+ messages[last_index].content = content[: len(content) // 2] + "..."
370
+ token_count = count_tokens(messages[last_index].content)
371
+
372
+ while token_count <= limit and last_index > 0:
373
+ token_count = count_tokens(
374
+ "".join([msg.content for msg in messages[:last_index]])
375
+ )
376
+ if token_count <= limit:
377
+ last_index -= 1
378
+
379
+ last_index = max(0, last_index)
380
+ return messages[last_index:]
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import Self
3
3
 
4
- from unique_toolkit.event.schema import Event
4
+ from unique_toolkit.app.schemas import Event
5
5
 
6
6
 
7
7
  @dataclass
@@ -13,6 +13,8 @@ class ChatState:
13
13
  company_id (str): The company ID.
14
14
  user_id (str): The user ID.
15
15
  chat_id (str): The chat ID.
16
+ scope_ids (list[str] | None): The scope IDs.
17
+ chat_only (bool): The chat only flag.
16
18
  user_message_text (str): The user message text.
17
19
  user_message_id (str): The user message ID.
18
20
  assistant_message_id (str): The assistant message ID.
@@ -20,29 +22,39 @@ class ChatState:
20
22
 
21
23
  company_id: str
22
24
  user_id: str
23
- assistant_id: str = ""
24
- chat_id: str = ""
25
- user_message_text: str = ""
26
- user_message_id: str = ""
27
- assistant_message_id: str = ""
25
+ assistant_id: str
26
+ chat_id: str
27
+ scope_ids: list[str] | None = None
28
+ chat_only: bool = False
29
+ user_message_text: str | None = None
30
+ user_message_id: str | None = None
31
+ assistant_message_id: str | None = None
32
+ module_name: str | None = None
28
33
 
29
34
  @classmethod
30
35
  def from_event(cls, event: Event) -> Self:
31
36
  """
32
- Creates a ChatState instance from an event dictionary.
37
+ Creates a ChatState instance from the Event.
33
38
 
34
39
  Args:
35
- event (dict): The event dictionary.
40
+ event (Event): The Event object.
36
41
 
37
42
  Returns:
38
- ChatState: The ChatState instance.
43
+ ChatManager: The ChatManager instance.
39
44
  """
40
- # Chat only flag
45
+ config = event.payload.configuration
46
+
47
+ scope_ids = config.get("scopeIds") or None
48
+ chat_only = config.get("scopeToChatOnUpload", False)
41
49
  return cls(
42
50
  user_id=event.user_id,
43
51
  chat_id=event.payload.chat_id,
44
52
  company_id=event.company_id,
53
+ assistant_id=event.payload.assistant_id,
54
+ scope_ids=scope_ids,
55
+ chat_only=chat_only,
45
56
  user_message_text=event.payload.user_message.text,
46
57
  user_message_id=event.payload.user_message.id,
47
58
  assistant_message_id=event.payload.assistant_message.id,
59
+ module_name=event.payload.name,
48
60
  )
@@ -0,0 +1,25 @@
1
+ from unique_toolkit.chat.schemas import ChatMessage
2
+ from unique_toolkit.content.utils import count_tokens
3
+
4
+
5
+ def convert_chat_history_to_injectable_string(
6
+ history: list[ChatMessage],
7
+ ) -> tuple[list[str], int]:
8
+ """
9
+ Converts chat history to a string that can be injected into the model.
10
+
11
+ Args:
12
+ history (list[ChatMessage]): The chat history.
13
+
14
+ Returns:
15
+ tuple[list[str], int]: The chat history and the token length of the chat context.
16
+ """
17
+ chatHistory = []
18
+ for msg in history:
19
+ if msg.role.value == "assistant":
20
+ chatHistory.append(f"previous_answer: {msg.content}")
21
+ else:
22
+ chatHistory.append(f"previous_question: {msg.content}")
23
+ chatContext = "\n".join(chatHistory)
24
+ chatContextTokenLength = count_tokens(chatContext)
25
+ return chatHistory, chatContextTokenLength
@@ -0,0 +1,90 @@
1
+ from datetime import datetime
2
+ from enum import StrEnum
3
+ from typing import Optional
4
+
5
+ from humps import camelize
6
+ from pydantic import BaseModel, ConfigDict
7
+
8
+ # set config to convert camelCase to snake_case
9
+ model_config = ConfigDict(
10
+ alias_generator=camelize,
11
+ populate_by_name=True,
12
+ arbitrary_types_allowed=True,
13
+ )
14
+
15
+
16
+ class ContentMetadata(BaseModel):
17
+ model_config = model_config
18
+ key: str
19
+ mime_type: str
20
+
21
+
22
+ class ContentChunk(BaseModel):
23
+ model_config = model_config
24
+ id: str
25
+ text: str
26
+ order: int
27
+ key: str | None = None
28
+ chunk_id: str | None = None
29
+ url: str | None = None
30
+ title: str | None = None
31
+ start_page: int | None = None
32
+ end_page: int | None = None
33
+
34
+ object: str | None = None
35
+ metadata: ContentMetadata | None = None
36
+ internally_stored_at: datetime | None = None
37
+ created_at: datetime | None = None
38
+ updated_at: datetime | None = None
39
+
40
+
41
+ class Content(BaseModel):
42
+ model_config = model_config
43
+ id: str
44
+ key: str
45
+ title: str | None = None
46
+ url: str | None = None
47
+ chunks: list[ContentChunk] = []
48
+ write_url: str | None = None
49
+ read_url: str | None = None
50
+
51
+
52
+ class ContentReference(BaseModel):
53
+ model_config = model_config
54
+ id: str
55
+ message_id: str
56
+ name: str
57
+ sequence_number: int
58
+ source: str
59
+ source_id: str
60
+ url: str
61
+
62
+
63
+ class ContentSearchType(StrEnum):
64
+ COMBINED = "COMBINED"
65
+ VECTOR = "VECTOR"
66
+
67
+
68
+ class ContentSearchResult(BaseModel):
69
+ """Schema corresponding to unique_sdk.SearchResult"""
70
+
71
+ id: str
72
+ text: str
73
+ order: int
74
+ chunkId: str | None = None
75
+ key: str | None = None
76
+ title: str | None = None
77
+ url: str | None = None
78
+ startPage: int | None = None
79
+ endPage: int | None = None
80
+ object: str | None = None
81
+
82
+
83
+ class ContentUploadInput(BaseModel):
84
+ key: str
85
+ title: str
86
+ mime_type: str
87
+
88
+ owner_type: Optional[str] = None
89
+ owner_id: Optional[str] = None
90
+ byte_size: Optional[int] = None