unique_toolkit 0.7.9__py3-none-any.whl → 1.33.3__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 (190) hide show
  1. unique_toolkit/__init__.py +36 -3
  2. unique_toolkit/_common/api_calling/human_verification_manager.py +357 -0
  3. unique_toolkit/_common/base_model_type_attribute.py +303 -0
  4. unique_toolkit/_common/chunk_relevancy_sorter/config.py +49 -0
  5. unique_toolkit/_common/chunk_relevancy_sorter/exception.py +5 -0
  6. unique_toolkit/_common/chunk_relevancy_sorter/schemas.py +46 -0
  7. unique_toolkit/_common/chunk_relevancy_sorter/service.py +374 -0
  8. unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +275 -0
  9. unique_toolkit/_common/default_language_model.py +12 -0
  10. unique_toolkit/_common/docx_generator/__init__.py +7 -0
  11. unique_toolkit/_common/docx_generator/config.py +12 -0
  12. unique_toolkit/_common/docx_generator/schemas.py +80 -0
  13. unique_toolkit/_common/docx_generator/service.py +225 -0
  14. unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
  15. unique_toolkit/_common/endpoint_builder.py +368 -0
  16. unique_toolkit/_common/endpoint_requestor.py +480 -0
  17. unique_toolkit/_common/exception.py +24 -0
  18. unique_toolkit/_common/experimental/endpoint_builder.py +368 -0
  19. unique_toolkit/_common/experimental/endpoint_requestor.py +488 -0
  20. unique_toolkit/_common/feature_flags/schema.py +9 -0
  21. unique_toolkit/_common/pydantic/rjsf_tags.py +936 -0
  22. unique_toolkit/_common/pydantic_helpers.py +174 -0
  23. unique_toolkit/_common/referencing.py +53 -0
  24. unique_toolkit/_common/string_utilities.py +140 -0
  25. unique_toolkit/_common/tests/test_referencing.py +521 -0
  26. unique_toolkit/_common/tests/test_string_utilities.py +506 -0
  27. unique_toolkit/_common/token/image_token_counting.py +67 -0
  28. unique_toolkit/_common/token/token_counting.py +204 -0
  29. unique_toolkit/_common/utils/__init__.py +1 -0
  30. unique_toolkit/_common/utils/files.py +43 -0
  31. unique_toolkit/_common/utils/image/encode.py +25 -0
  32. unique_toolkit/_common/utils/jinja/helpers.py +10 -0
  33. unique_toolkit/_common/utils/jinja/render.py +18 -0
  34. unique_toolkit/_common/utils/jinja/schema.py +65 -0
  35. unique_toolkit/_common/utils/jinja/utils.py +80 -0
  36. unique_toolkit/_common/utils/structured_output/__init__.py +1 -0
  37. unique_toolkit/_common/utils/structured_output/schema.py +5 -0
  38. unique_toolkit/_common/utils/write_configuration.py +51 -0
  39. unique_toolkit/_common/validators.py +101 -4
  40. unique_toolkit/agentic/__init__.py +1 -0
  41. unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +28 -0
  42. unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +278 -0
  43. unique_toolkit/agentic/evaluation/config.py +36 -0
  44. unique_toolkit/{evaluators → agentic/evaluation}/context_relevancy/prompts.py +25 -0
  45. unique_toolkit/agentic/evaluation/context_relevancy/schema.py +80 -0
  46. unique_toolkit/agentic/evaluation/context_relevancy/service.py +273 -0
  47. unique_toolkit/agentic/evaluation/evaluation_manager.py +218 -0
  48. unique_toolkit/agentic/evaluation/hallucination/constants.py +61 -0
  49. unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +112 -0
  50. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/prompts.py +1 -1
  51. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/service.py +20 -16
  52. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/utils.py +32 -21
  53. unique_toolkit/{evaluators → agentic/evaluation}/output_parser.py +20 -2
  54. unique_toolkit/{evaluators → agentic/evaluation}/schemas.py +27 -7
  55. unique_toolkit/agentic/evaluation/tests/test_context_relevancy_service.py +253 -0
  56. unique_toolkit/agentic/evaluation/tests/test_output_parser.py +87 -0
  57. unique_toolkit/agentic/history_manager/history_construction_with_contents.py +298 -0
  58. unique_toolkit/agentic/history_manager/history_manager.py +241 -0
  59. unique_toolkit/agentic/history_manager/loop_token_reducer.py +484 -0
  60. unique_toolkit/agentic/history_manager/utils.py +96 -0
  61. unique_toolkit/agentic/message_log_manager/__init__.py +5 -0
  62. unique_toolkit/agentic/message_log_manager/service.py +93 -0
  63. unique_toolkit/agentic/postprocessor/postprocessor_manager.py +212 -0
  64. unique_toolkit/agentic/reference_manager/reference_manager.py +103 -0
  65. unique_toolkit/agentic/responses_api/__init__.py +19 -0
  66. unique_toolkit/agentic/responses_api/postprocessors/code_display.py +71 -0
  67. unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +297 -0
  68. unique_toolkit/agentic/responses_api/stream_handler.py +15 -0
  69. unique_toolkit/agentic/short_term_memory_manager/persistent_short_term_memory_manager.py +141 -0
  70. unique_toolkit/agentic/thinking_manager/thinking_manager.py +103 -0
  71. unique_toolkit/agentic/tools/__init__.py +1 -0
  72. unique_toolkit/agentic/tools/a2a/__init__.py +36 -0
  73. unique_toolkit/agentic/tools/a2a/config.py +17 -0
  74. unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +15 -0
  75. unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +66 -0
  76. unique_toolkit/agentic/tools/a2a/evaluation/config.py +55 -0
  77. unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +260 -0
  78. unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2 +9 -0
  79. unique_toolkit/agentic/tools/a2a/manager.py +55 -0
  80. unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +21 -0
  81. unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py +240 -0
  82. unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +84 -0
  83. unique_toolkit/agentic/tools/a2a/postprocessing/config.py +78 -0
  84. unique_toolkit/agentic/tools/a2a/postprocessing/display.py +264 -0
  85. unique_toolkit/agentic/tools/a2a/postprocessing/references.py +101 -0
  86. unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display.py +421 -0
  87. unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +2103 -0
  88. unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py +603 -0
  89. unique_toolkit/agentic/tools/a2a/prompts.py +46 -0
  90. unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py +6 -0
  91. unique_toolkit/agentic/tools/a2a/response_watcher/service.py +91 -0
  92. unique_toolkit/agentic/tools/a2a/tool/__init__.py +4 -0
  93. unique_toolkit/agentic/tools/a2a/tool/_memory.py +26 -0
  94. unique_toolkit/agentic/tools/a2a/tool/_schema.py +9 -0
  95. unique_toolkit/agentic/tools/a2a/tool/config.py +158 -0
  96. unique_toolkit/agentic/tools/a2a/tool/service.py +393 -0
  97. unique_toolkit/agentic/tools/agent_chunks_hanlder.py +65 -0
  98. unique_toolkit/agentic/tools/config.py +128 -0
  99. unique_toolkit/agentic/tools/factory.py +44 -0
  100. unique_toolkit/agentic/tools/mcp/__init__.py +4 -0
  101. unique_toolkit/agentic/tools/mcp/manager.py +71 -0
  102. unique_toolkit/agentic/tools/mcp/models.py +28 -0
  103. unique_toolkit/agentic/tools/mcp/tool_wrapper.py +234 -0
  104. unique_toolkit/agentic/tools/openai_builtin/__init__.py +11 -0
  105. unique_toolkit/agentic/tools/openai_builtin/base.py +46 -0
  106. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +8 -0
  107. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +88 -0
  108. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +250 -0
  109. unique_toolkit/agentic/tools/openai_builtin/manager.py +79 -0
  110. unique_toolkit/agentic/tools/schemas.py +145 -0
  111. unique_toolkit/agentic/tools/test/test_mcp_manager.py +536 -0
  112. unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +445 -0
  113. unique_toolkit/agentic/tools/tool.py +187 -0
  114. unique_toolkit/agentic/tools/tool_manager.py +492 -0
  115. unique_toolkit/agentic/tools/tool_progress_reporter.py +285 -0
  116. unique_toolkit/agentic/tools/utils/__init__.py +19 -0
  117. unique_toolkit/agentic/tools/utils/execution/__init__.py +1 -0
  118. unique_toolkit/agentic/tools/utils/execution/execution.py +286 -0
  119. unique_toolkit/agentic/tools/utils/source_handling/__init__.py +0 -0
  120. unique_toolkit/agentic/tools/utils/source_handling/schema.py +21 -0
  121. unique_toolkit/agentic/tools/utils/source_handling/source_formatting.py +207 -0
  122. unique_toolkit/agentic/tools/utils/source_handling/tests/test_source_formatting.py +216 -0
  123. unique_toolkit/app/__init__.py +9 -0
  124. unique_toolkit/app/dev_util.py +180 -0
  125. unique_toolkit/app/fast_api_factory.py +131 -0
  126. unique_toolkit/app/init_sdk.py +32 -1
  127. unique_toolkit/app/schemas.py +206 -31
  128. unique_toolkit/app/unique_settings.py +367 -0
  129. unique_toolkit/app/webhook.py +77 -0
  130. unique_toolkit/chat/__init__.py +8 -1
  131. unique_toolkit/chat/deprecated/service.py +232 -0
  132. unique_toolkit/chat/functions.py +648 -78
  133. unique_toolkit/chat/rendering.py +34 -0
  134. unique_toolkit/chat/responses_api.py +461 -0
  135. unique_toolkit/chat/schemas.py +134 -2
  136. unique_toolkit/chat/service.py +115 -767
  137. unique_toolkit/content/functions.py +353 -8
  138. unique_toolkit/content/schemas.py +128 -15
  139. unique_toolkit/content/service.py +321 -45
  140. unique_toolkit/content/smart_rules.py +301 -0
  141. unique_toolkit/content/utils.py +10 -3
  142. unique_toolkit/data_extraction/README.md +96 -0
  143. unique_toolkit/data_extraction/__init__.py +11 -0
  144. unique_toolkit/data_extraction/augmented/__init__.py +5 -0
  145. unique_toolkit/data_extraction/augmented/service.py +93 -0
  146. unique_toolkit/data_extraction/base.py +25 -0
  147. unique_toolkit/data_extraction/basic/__init__.py +11 -0
  148. unique_toolkit/data_extraction/basic/config.py +18 -0
  149. unique_toolkit/data_extraction/basic/prompt.py +13 -0
  150. unique_toolkit/data_extraction/basic/service.py +55 -0
  151. unique_toolkit/embedding/service.py +103 -12
  152. unique_toolkit/framework_utilities/__init__.py +1 -0
  153. unique_toolkit/framework_utilities/langchain/__init__.py +10 -0
  154. unique_toolkit/framework_utilities/langchain/client.py +71 -0
  155. unique_toolkit/framework_utilities/langchain/history.py +19 -0
  156. unique_toolkit/framework_utilities/openai/__init__.py +6 -0
  157. unique_toolkit/framework_utilities/openai/client.py +84 -0
  158. unique_toolkit/framework_utilities/openai/message_builder.py +229 -0
  159. unique_toolkit/framework_utilities/utils.py +23 -0
  160. unique_toolkit/language_model/__init__.py +3 -0
  161. unique_toolkit/language_model/_responses_api_utils.py +93 -0
  162. unique_toolkit/language_model/builder.py +27 -11
  163. unique_toolkit/language_model/default_language_model.py +3 -0
  164. unique_toolkit/language_model/functions.py +345 -43
  165. unique_toolkit/language_model/infos.py +1288 -46
  166. unique_toolkit/language_model/reference.py +242 -0
  167. unique_toolkit/language_model/schemas.py +481 -49
  168. unique_toolkit/language_model/service.py +229 -28
  169. unique_toolkit/protocols/support.py +145 -0
  170. unique_toolkit/services/__init__.py +7 -0
  171. unique_toolkit/services/chat_service.py +1631 -0
  172. unique_toolkit/services/knowledge_base.py +1094 -0
  173. unique_toolkit/short_term_memory/service.py +178 -41
  174. unique_toolkit/smart_rules/__init__.py +0 -0
  175. unique_toolkit/smart_rules/compile.py +56 -0
  176. unique_toolkit/test_utilities/events.py +197 -0
  177. unique_toolkit-1.33.3.dist-info/METADATA +1145 -0
  178. unique_toolkit-1.33.3.dist-info/RECORD +205 -0
  179. unique_toolkit/evaluators/__init__.py +0 -1
  180. unique_toolkit/evaluators/config.py +0 -35
  181. unique_toolkit/evaluators/constants.py +0 -1
  182. unique_toolkit/evaluators/context_relevancy/constants.py +0 -32
  183. unique_toolkit/evaluators/context_relevancy/service.py +0 -53
  184. unique_toolkit/evaluators/context_relevancy/utils.py +0 -142
  185. unique_toolkit/evaluators/hallucination/constants.py +0 -41
  186. unique_toolkit-0.7.9.dist-info/METADATA +0 -413
  187. unique_toolkit-0.7.9.dist-info/RECORD +0 -64
  188. /unique_toolkit/{evaluators → agentic/evaluation}/exception.py +0 -0
  189. {unique_toolkit-0.7.9.dist-info → unique_toolkit-1.33.3.dist-info}/LICENSE +0 -0
  190. {unique_toolkit-0.7.9.dist-info → unique_toolkit-1.33.3.dist-info}/WHEEL +0 -0
@@ -3,7 +3,9 @@ import os
3
3
  import re
4
4
  import tempfile
5
5
  from pathlib import Path
6
+ from typing import Any
6
7
 
8
+ import httpx
7
9
  import requests
8
10
  import unique_sdk
9
11
 
@@ -12,8 +14,12 @@ from unique_toolkit.content.constants import DEFAULT_SEARCH_LANGUAGE
12
14
  from unique_toolkit.content.schemas import (
13
15
  Content,
14
16
  ContentChunk,
17
+ ContentInfo,
15
18
  ContentRerankerConfig,
16
19
  ContentSearchType,
20
+ DeleteContentResponse,
21
+ FolderInfo,
22
+ PaginatedContentInfos,
17
23
  )
18
24
  from unique_toolkit.content.utils import map_contents, map_to_content_chunks
19
25
 
@@ -33,6 +39,7 @@ def search_content_chunks(
33
39
  chat_only: bool | None = None,
34
40
  metadata_filter: dict | None = None,
35
41
  content_ids: list[str] | None = None,
42
+ score_threshold: float | None = None,
36
43
  ) -> list[ContentChunk]:
37
44
  """
38
45
  Performs a synchronous search for content chunks in the knowledge base.
@@ -47,6 +54,7 @@ def search_content_chunks(
47
54
  chat_only (bool | None): Whether to search only in the current chat. Defaults to None.
48
55
  metadata_filter (dict | None): UniqueQL metadata filter. If unspecified/None, it tries to use the metadata filter from the event. Defaults to None.
49
56
  content_ids (list[str] | None): The content IDs to search. Defaults to None.
57
+ score_threshold (float | None): The minimum score threshold for results. Defaults to 0.
50
58
  Returns:
51
59
  list[ContentChunk]: The search results.
52
60
  """
@@ -72,6 +80,7 @@ def search_content_chunks(
72
80
  chatOnly=chat_only,
73
81
  metaDataFilter=metadata_filter,
74
82
  contentIds=content_ids,
83
+ scoreThreshold=score_threshold,
75
84
  )
76
85
  return map_to_content_chunks(searches)
77
86
  except Exception as e:
@@ -92,6 +101,7 @@ async def search_content_chunks_async(
92
101
  chat_only: bool | None = None,
93
102
  metadata_filter: dict | None = None,
94
103
  content_ids: list[str] | None = None,
104
+ score_threshold: float | None = None,
95
105
  ):
96
106
  """
97
107
  Performs an asynchronous search for content chunks in the knowledge base.
@@ -120,6 +130,7 @@ async def search_content_chunks_async(
120
130
  chatOnly=chat_only,
121
131
  metaDataFilter=metadata_filter,
122
132
  contentIds=content_ids,
133
+ scoreThreshold=score_threshold,
123
134
  )
124
135
  return map_to_content_chunks(searches)
125
136
  except Exception as e:
@@ -132,7 +143,8 @@ def search_contents(
132
143
  company_id: str,
133
144
  chat_id: str,
134
145
  where: dict,
135
- ):
146
+ include_failed_content: bool = False,
147
+ ) -> list[Content]:
136
148
  """
137
149
  Performs an asynchronous search for content files in the knowledge base by filter.
138
150
 
@@ -155,6 +167,7 @@ def search_contents(
155
167
  chatId=chat_id,
156
168
  # TODO add type parameter in SDK
157
169
  where=where, # type: ignore
170
+ includeFailedContent=include_failed_content,
158
171
  )
159
172
  return map_contents(contents)
160
173
  except Exception as e:
@@ -167,6 +180,7 @@ async def search_contents_async(
167
180
  company_id: str,
168
181
  chat_id: str,
169
182
  where: dict,
183
+ include_failed_content: bool = False,
170
184
  ):
171
185
  """Asynchronously searches for content in the knowledge base."""
172
186
  if where.get("contentId"):
@@ -178,6 +192,7 @@ async def search_contents_async(
178
192
  company_id=company_id,
179
193
  chatId=chat_id,
180
194
  where=where, # type: ignore
195
+ includeFailedContent=include_failed_content,
181
196
  )
182
197
  return map_contents(contents)
183
198
  except Exception as e:
@@ -204,6 +219,73 @@ def _upsert_content(
204
219
  ) # type: ignore
205
220
 
206
221
 
222
+ async def _upsert_content_async(
223
+ user_id: str,
224
+ company_id: str,
225
+ input_data: dict,
226
+ scope_id: str | None = None,
227
+ chat_id: str | None = None,
228
+ file_url: str | None = None,
229
+ ):
230
+ return await unique_sdk.Content.upsert_async(
231
+ user_id=user_id,
232
+ company_id=company_id,
233
+ input=input_data, # type: ignore
234
+ scopeId=scope_id,
235
+ chatId=chat_id,
236
+ fileUrl=file_url,
237
+ )
238
+
239
+
240
+ async def upload_content_from_bytes_async(
241
+ user_id: str,
242
+ company_id: str,
243
+ content: bytes,
244
+ content_name: str,
245
+ mime_type: str,
246
+ scope_id: str | None = None,
247
+ chat_id: str | None = None,
248
+ skip_ingestion: bool = False,
249
+ ingestion_config: unique_sdk.Content.IngestionConfig | None = None,
250
+ metadata: dict[str, Any] | None = None,
251
+ ) -> Content:
252
+ """
253
+ Asynchronously uploads content to the knowledge base.
254
+
255
+ Args:
256
+ user_id (str): The user ID.
257
+ company_id (str): The company ID.
258
+ content (bytes): The content to upload.
259
+ content_name (str): The name of the content.
260
+ mime_type (str): The MIME type of the content.
261
+ scope_id (str | None): The scope ID. Defaults to None.
262
+ chat_id (str | None): The chat ID. Defaults to None.
263
+ skip_ingestion (bool): Whether to skip ingestion. Defaults to False.
264
+ ingestion_config (unique_sdk.Content.IngestionConfig | None): The ingestion configuration. Defaults to None.
265
+ metadata ( dict[str, Any] | None): The metadata for the content. Defaults to None.
266
+
267
+ Returns:
268
+ Content: The uploaded content.
269
+ """
270
+
271
+ try:
272
+ return await _trigger_upload_content_async(
273
+ user_id=user_id,
274
+ company_id=company_id,
275
+ content=content,
276
+ content_name=content_name,
277
+ mime_type=mime_type,
278
+ scope_id=scope_id,
279
+ chat_id=chat_id,
280
+ skip_ingestion=skip_ingestion,
281
+ ingestion_config=ingestion_config,
282
+ metadata=metadata,
283
+ )
284
+ except Exception as e:
285
+ logger.error(f"Error while uploading content: {e}")
286
+ raise e
287
+
288
+
207
289
  def upload_content_from_bytes(
208
290
  user_id: str,
209
291
  company_id: str,
@@ -213,7 +295,9 @@ def upload_content_from_bytes(
213
295
  scope_id: str | None = None,
214
296
  chat_id: str | None = None,
215
297
  skip_ingestion: bool = False,
216
- ):
298
+ ingestion_config: unique_sdk.Content.IngestionConfig | None = None,
299
+ metadata: dict[str, Any] | None = None,
300
+ ) -> Content:
217
301
  """
218
302
  Uploads content to the knowledge base.
219
303
 
@@ -226,6 +310,8 @@ def upload_content_from_bytes(
226
310
  scope_id (str | None): The scope ID. Defaults to None.
227
311
  chat_id (str | None): The chat ID. Defaults to None.
228
312
  skip_ingestion (bool): Whether to skip ingestion. Defaults to False.
313
+ ingestion_config (unique_sdk.Content.IngestionConfig | None): The ingestion configuration. Defaults to None.
314
+ metadata ( dict[str, Any] | None): The metadata for the content. Defaults to None.
229
315
 
230
316
  Returns:
231
317
  Content: The uploaded content.
@@ -241,6 +327,8 @@ def upload_content_from_bytes(
241
327
  scope_id=scope_id,
242
328
  chat_id=chat_id,
243
329
  skip_ingestion=skip_ingestion,
330
+ ingestion_config=ingestion_config,
331
+ metadata=metadata,
244
332
  )
245
333
  except Exception as e:
246
334
  logger.error(f"Error while uploading content: {e}")
@@ -256,7 +344,10 @@ def upload_content(
256
344
  scope_id: str | None = None,
257
345
  chat_id: str | None = None,
258
346
  skip_ingestion: bool = False,
259
- ):
347
+ skip_excel_ingestion: bool = False,
348
+ ingestion_config: unique_sdk.Content.IngestionConfig | None = None,
349
+ metadata: dict[str, Any] | None = None,
350
+ ) -> Content:
260
351
  """
261
352
  Uploads content to the knowledge base.
262
353
 
@@ -269,6 +360,9 @@ def upload_content(
269
360
  scope_id (str | None): The scope ID. Defaults to None.
270
361
  chat_id (str | None): The chat ID. Defaults to None.
271
362
  skip_ingestion (bool): Whether to skip ingestion. Defaults to False.
363
+ skip_excel_ingestion (bool): Whether to skip excel ingestion. Defaults to False.
364
+ ingestion_config (unique_sdk.Content.IngestionConfig | None): The ingestion configuration. Defaults to None.
365
+ metadata ( dict[str, Any] | None): The metadata for the content. Defaults to None.
272
366
 
273
367
  Returns:
274
368
  Content: The uploaded content.
@@ -284,6 +378,9 @@ def upload_content(
284
378
  scope_id=scope_id,
285
379
  chat_id=chat_id,
286
380
  skip_ingestion=skip_ingestion,
381
+ skip_excel_ingestion=skip_excel_ingestion,
382
+ ingestion_config=ingestion_config,
383
+ metadata=metadata,
287
384
  )
288
385
  except Exception as e:
289
386
  logger.error(f"Error while uploading content: {e}")
@@ -299,7 +396,10 @@ def _trigger_upload_content(
299
396
  scope_id: str | None = None,
300
397
  chat_id: str | None = None,
301
398
  skip_ingestion: bool = False,
302
- ):
399
+ skip_excel_ingestion: bool = False,
400
+ ingestion_config: unique_sdk.Content.IngestionConfig | None = None,
401
+ metadata: dict[str, Any] | None = None,
402
+ ) -> Content:
303
403
  """
304
404
  Uploads content to the knowledge base.
305
405
 
@@ -312,6 +412,9 @@ def _trigger_upload_content(
312
412
  scope_id (str | None): The scope ID. Defaults to None.
313
413
  chat_id (str | None): The chat ID. Defaults to None.
314
414
  skip_ingestion (bool): Whether to skip ingestion. Defaults to False.
415
+ skip_excel_ingestion (bool): Whether to skip excel ingestion. Defaults to False.
416
+ ingestion_config (unique_sdk.Content.IngestionConfig | None): The ingestion configuration. Defaults to None.
417
+ metadata (dict[str, Any] | None): The metadata for the content. Defaults to None.
315
418
 
316
419
  Returns:
317
420
  Content: The uploaded content.
@@ -368,16 +471,23 @@ def _trigger_upload_content(
368
471
  logger.error(error_msg)
369
472
  raise ValueError(error_msg)
370
473
 
474
+ if ingestion_config is None:
475
+ ingestion_config = {}
476
+
477
+ if skip_excel_ingestion:
478
+ ingestion_config["uniqueIngestionMode"] = "SKIP_EXCEL_INGESTION"
479
+ elif skip_ingestion:
480
+ ingestion_config["uniqueIngestionMode"] = "SKIP_INGESTION"
481
+
371
482
  input_dict = {
372
483
  "key": content_name,
373
484
  "title": content_name,
374
485
  "mimeType": mime_type,
375
486
  "byteSize": byte_size,
487
+ "ingestionConfig": ingestion_config,
488
+ "metadata": metadata,
376
489
  }
377
490
 
378
- if skip_ingestion:
379
- input_dict["ingestionConfig"] = {"uniqueIngestionMode": "SKIP_INGESTION"}
380
-
381
491
  if chat_id:
382
492
  _upsert_content(
383
493
  user_id=user_id,
@@ -395,7 +505,131 @@ def _trigger_upload_content(
395
505
  scope_id=scope_id,
396
506
  ) # type: ignore
397
507
 
398
- return Content(**created_content)
508
+ return Content.model_validate(created_content, by_alias=True, by_name=True)
509
+
510
+
511
+ async def _trigger_upload_content_async(
512
+ user_id: str,
513
+ company_id: str,
514
+ content: str | Path | bytes,
515
+ content_name: str,
516
+ mime_type: str,
517
+ scope_id: str | None = None,
518
+ chat_id: str | None = None,
519
+ skip_ingestion: bool = False,
520
+ skip_excel_ingestion: bool = False,
521
+ ingestion_config: unique_sdk.Content.IngestionConfig | None = None,
522
+ metadata: dict[str, Any] | None = None,
523
+ ):
524
+ """
525
+ Asynchronously uploads content to the knowledge base.
526
+
527
+ Args:
528
+ user_id (str): The user ID.
529
+ company_id (str): The company ID.
530
+ content (str | Path | bytes): The content to upload. If string or Path, file will be read from disk.
531
+ content_name (str): The name of the content.
532
+ mime_type (str): The MIME type of the content.
533
+ scope_id (str | None): The scope ID. Defaults to None.
534
+ chat_id (str | None): The chat ID. Defaults to None.
535
+ skip_ingestion (bool): Whether to skip ingestion. Defaults to False.
536
+ skip_excel_ingestion (bool): Whether to skip excel ingestion. Defaults to False.
537
+ ingestion_config (unique_sdk.Content.IngestionConfig | None): The ingestion configuration. Defaults to None.
538
+ metadata (dict[str, Any] | None): The metadata for the content. Defaults to None.
539
+
540
+ Returns:
541
+ Content: The uploaded content.
542
+ """
543
+ # TODO: Remove code duplication with _trigger_upload_content
544
+
545
+ if not chat_id and not scope_id:
546
+ raise ValueError("chat_id or scope_id must be provided")
547
+
548
+ byte_size = (
549
+ os.path.getsize(content) if isinstance(content, (Path, str)) else len(content)
550
+ )
551
+ created_content = await _upsert_content_async(
552
+ user_id=user_id,
553
+ company_id=company_id,
554
+ input_data={
555
+ "key": content_name,
556
+ "title": content_name,
557
+ "mimeType": mime_type,
558
+ },
559
+ scope_id=scope_id,
560
+ chat_id=chat_id,
561
+ )
562
+
563
+ write_url = created_content["writeUrl"]
564
+
565
+ if not write_url:
566
+ error_msg = "Write url for uploaded content is missing"
567
+ logger.error(error_msg)
568
+ raise ValueError(error_msg)
569
+
570
+ headers = {
571
+ "X-Ms-Blob-Content-Type": mime_type,
572
+ "X-Ms-Blob-Type": "BlockBlob",
573
+ }
574
+ # upload to azure blob storage SAS url uploadUrl the pdf file translatedFile make sure it is treated as a application/pdf
575
+ async with httpx.AsyncClient() as client:
576
+ if isinstance(content, bytes):
577
+ response = await client.put(
578
+ url=write_url,
579
+ content=content,
580
+ headers=headers,
581
+ )
582
+ else:
583
+ with open(content, "rb") as file:
584
+ response = await client.put(
585
+ url=write_url,
586
+ content=file,
587
+ headers=headers,
588
+ )
589
+ response.raise_for_status()
590
+
591
+ read_url = created_content["readUrl"]
592
+
593
+ if not read_url:
594
+ error_msg = "Read url for uploaded content is missing"
595
+ logger.error(error_msg)
596
+ raise ValueError(error_msg)
597
+
598
+ if ingestion_config is None:
599
+ ingestion_config = {}
600
+
601
+ if skip_excel_ingestion:
602
+ ingestion_config["uniqueIngestionMode"] = "SKIP_EXCEL_INGESTION"
603
+ elif skip_ingestion:
604
+ ingestion_config["uniqueIngestionMode"] = "SKIP_INGESTION"
605
+
606
+ input_dict = {
607
+ "key": content_name,
608
+ "title": content_name,
609
+ "mimeType": mime_type,
610
+ "byteSize": byte_size,
611
+ "ingestionConfig": ingestion_config,
612
+ "metadata": metadata,
613
+ }
614
+
615
+ if chat_id:
616
+ await _upsert_content_async(
617
+ user_id=user_id,
618
+ company_id=company_id,
619
+ input_data=input_dict,
620
+ file_url=read_url,
621
+ chat_id=chat_id,
622
+ )
623
+ else:
624
+ await _upsert_content_async(
625
+ user_id=user_id,
626
+ company_id=company_id,
627
+ input_data=input_dict,
628
+ file_url=read_url,
629
+ scope_id=scope_id,
630
+ )
631
+
632
+ return Content.model_validate(created_content, by_alias=True, by_name=True)
399
633
 
400
634
 
401
635
  def request_content_by_id(
@@ -543,3 +777,114 @@ def download_content(
543
777
  raise Exception(error_msg)
544
778
 
545
779
  return content_path
780
+
781
+
782
+ def get_content_info(
783
+ user_id: str,
784
+ company_id: str,
785
+ *,
786
+ metadata_filter: dict[str, Any] | None = None,
787
+ skip: int | None = None,
788
+ take: int | None = None,
789
+ file_path: str | None = None,
790
+ ):
791
+ """Gets the info of a content."""
792
+
793
+ get_info_params = unique_sdk.Content.ContentInfoParams(
794
+ metadataFilter=metadata_filter or None, # Dict cannot be empty
795
+ )
796
+ if skip:
797
+ get_info_params["skip"] = skip
798
+ if take:
799
+ get_info_params["take"] = take
800
+ if file_path:
801
+ get_info_params["filePath"] = file_path
802
+
803
+ content_info = unique_sdk.Content.get_infos(
804
+ user_id=user_id, company_id=company_id, **get_info_params
805
+ )
806
+ return PaginatedContentInfos.model_validate(
807
+ content_info, by_alias=True, by_name=True
808
+ )
809
+
810
+
811
+ def get_folder_info(user_id: str, company_id: str, *, scope_id: str) -> FolderInfo:
812
+ info = unique_sdk.Folder.get_info(
813
+ user_id=user_id, company_id=company_id, scopeId=scope_id
814
+ )
815
+
816
+ return FolderInfo.model_validate(info, by_alias=True, by_name=True)
817
+
818
+
819
+ def update_content(
820
+ user_id: str,
821
+ company_id: str,
822
+ *,
823
+ content_id: str,
824
+ metadata: dict[str, Any],
825
+ file_path: str | None = None,
826
+ owner_id: str | None = None,
827
+ parent_folder_path: str | None = None,
828
+ title: str | None = None,
829
+ ) -> ContentInfo:
830
+ """Updates the metadata of a content."""
831
+
832
+ update_params = unique_sdk.Content.UpdateParams(
833
+ contentId=content_id, metadata=metadata
834
+ )
835
+
836
+ if file_path:
837
+ update_params["filePath"] = file_path
838
+ if owner_id:
839
+ update_params["ownerId"] = owner_id
840
+ if parent_folder_path:
841
+ update_params["parentFolderPath"] = parent_folder_path
842
+ if title:
843
+ update_params["title"] = title
844
+
845
+ content_info = unique_sdk.Content.update(
846
+ user_id=user_id, company_id=company_id, **update_params
847
+ )
848
+ return ContentInfo.model_validate(content_info, by_alias=True, by_name=True)
849
+
850
+
851
+ def delete_content(
852
+ user_id: str,
853
+ company_id: str,
854
+ *,
855
+ content_id: str | None = None,
856
+ file_path: str | None = None,
857
+ ) -> DeleteContentResponse:
858
+ if content_id:
859
+ resp = unique_sdk.Content.delete(
860
+ user_id=user_id, company_id=company_id, contentId=content_id
861
+ )
862
+ elif file_path:
863
+ resp = unique_sdk.Content.delete(
864
+ user_id=user_id, company_id=company_id, filePath=file_path
865
+ )
866
+ else:
867
+ raise ValueError("content_id or file_path must be provided")
868
+
869
+ return DeleteContentResponse.model_validate(resp, by_alias=True, by_name=True)
870
+
871
+
872
+ async def delete_content_async(
873
+ user_id: str,
874
+ company_id: str,
875
+ *,
876
+ content_id: str | None = None,
877
+ file_path: str | None = None,
878
+ ) -> DeleteContentResponse:
879
+ if content_id:
880
+ resp = await unique_sdk.Content.delete_async(
881
+ user_id=user_id, company_id=company_id, contentId=content_id
882
+ )
883
+ elif file_path:
884
+ resp = await unique_sdk.Content.delete_async(
885
+ user_id=user_id, company_id=company_id, filePath=file_path
886
+ )
887
+ else:
888
+ raise ValueError("content_id or file_path must be provided")
889
+
890
+ return DeleteContentResponse.model_validate(resp, by_alias=True, by_name=True)
@@ -1,7 +1,8 @@
1
1
  from datetime import datetime
2
2
  from enum import StrEnum
3
- from typing import Optional
3
+ from typing import Any, Optional
4
4
 
5
+ import unique_sdk
5
6
  from humps import camelize
6
7
  from pydantic import BaseModel, ConfigDict, Field
7
8
 
@@ -26,15 +27,41 @@ class ContentMetadata(BaseModel):
26
27
 
27
28
  class ContentChunk(BaseModel):
28
29
  model_config = model_config
29
- id: str
30
- text: str
31
- order: int
32
- key: str | None = None
33
- chunk_id: str | None = None
34
- url: str | None = None
35
- title: str | None = None
36
- start_page: int | None = None
37
- end_page: int | None = None
30
+ id: str = Field(
31
+ default="",
32
+ description="The id of the content this chunk belongs to. The id starts with 'cont_' followed by an alphanumeric string of length 24.",
33
+ examples=["cont_abcdefgehijklmnopqrstuvwx"],
34
+ )
35
+ text: str = Field(default="", description="The text content of the chunk.")
36
+ order: int = Field(
37
+ default=0,
38
+ description="The order of the chunk in the original content. Concatenating the chunks in order will give the original content.",
39
+ )
40
+ key: str | None = Field(
41
+ default=None,
42
+ description="The key of the chunk. For document chunks this is the the filename",
43
+ )
44
+ chunk_id: str | None = Field(
45
+ default=None,
46
+ description="The id of the chunk. The id starts with 'chunk_' followed by an alphanumeric string of length 24.",
47
+ examples=["chunk_abcdefgehijklmnopqrstuv"],
48
+ )
49
+ url: str | None = Field(
50
+ default=None,
51
+ description="For chunk retrieved from the web this is the url of the chunk.",
52
+ )
53
+ title: str | None = Field(
54
+ default=None,
55
+ description="The title of the chunk. For document chunks this is the title of the document.",
56
+ )
57
+ start_page: int | None = Field(
58
+ default=None,
59
+ description="The start page of the chunk. For document chunks this is the start page of the document.",
60
+ )
61
+ end_page: int | None = Field(
62
+ default=None,
63
+ description="The end page of the chunk. For document chunks this is the end page of the document.",
64
+ )
38
65
 
39
66
  object: str | None = None
40
67
  metadata: ContentMetadata | None = None
@@ -45,26 +72,66 @@ class ContentChunk(BaseModel):
45
72
 
46
73
  class Content(BaseModel):
47
74
  model_config = model_config
48
- id: str
49
- key: str
50
- title: str | None = None
75
+ id: str = Field(
76
+ default="",
77
+ description="The id of the content. The id starts with 'cont_' followed by an alphanumeric string of length 24.",
78
+ examples=["cont_abcdefgehijklmnopqrstuvwx"],
79
+ )
80
+ key: str = Field(
81
+ default="",
82
+ description="The key of the content. For documents this is the the filename",
83
+ )
84
+ title: str | None = Field(
85
+ default=None,
86
+ description="The title of the content. For documents this is the title of the document.",
87
+ )
51
88
  url: str | None = None
52
89
  chunks: list[ContentChunk] = []
53
90
  write_url: str | None = None
54
91
  read_url: str | None = None
55
92
  created_at: datetime | None = None
56
93
  updated_at: datetime | None = None
94
+ expired_at: datetime | None = None
95
+ metadata: dict[str, Any] | None = None
96
+ ingestion_config: dict | None = None
97
+ ingestion_state: str | None = None
57
98
 
58
99
 
59
100
  class ContentReference(BaseModel):
60
101
  model_config = model_config
61
- id: str
62
- message_id: str
102
+ id: str = Field(
103
+ default="",
104
+ description="The id of the content reference. Can be empty on the ChatMessage Object",
105
+ )
106
+ message_id: str = Field(
107
+ default="",
108
+ description="The id of the message that this reference belongs to. Can be empty on the ChatMessage Object",
109
+ )
63
110
  name: str
64
111
  sequence_number: int
65
112
  source: str
66
113
  source_id: str
67
114
  url: str
115
+ original_index: list[int] = Field(
116
+ default=[],
117
+ description="List of indices in the ChatMessage original_content this reference refers to. This is usually the id in the functionCallResponse. List type due to implementation in node-chat",
118
+ )
119
+
120
+ @classmethod
121
+ def from_sdk_reference(
122
+ cls, reference: unique_sdk.Message.Reference | unique_sdk.Space.Reference
123
+ ) -> "ContentReference":
124
+ kwargs = {
125
+ "name": reference["name"],
126
+ "url": reference["url"],
127
+ "sequence_number": reference["sequenceNumber"],
128
+ "source": reference["source"],
129
+ "source_id": reference["sourceId"],
130
+ }
131
+ if "originalIndex" in reference:
132
+ kwargs["original_index"] = reference["originalIndex"]
133
+
134
+ return cls.model_validate(kwargs)
68
135
 
69
136
 
70
137
  class ContentSearchType(StrEnum):
@@ -101,3 +168,49 @@ class ContentRerankerConfig(BaseModel):
101
168
  model_config = model_config
102
169
  deployment_name: str = Field(serialization_alias="deploymentName")
103
170
  options: dict | None = None
171
+
172
+
173
+ class ContentInfo(BaseModel):
174
+ model_config = model_config
175
+ id: str
176
+ object: str
177
+ key: str
178
+ url: str | None = None
179
+ title: str | None = None
180
+ metadata: dict[str, Any] | None = None
181
+ byte_size: int
182
+ mime_type: str
183
+ owner_id: str
184
+ created_at: datetime
185
+ updated_at: datetime
186
+ expires_at: datetime | None = None
187
+ deleted_at: datetime | None = None
188
+ expired_at: datetime | None = None
189
+
190
+
191
+ class PaginatedContentInfos(BaseModel):
192
+ model_config = model_config
193
+ object: str
194
+ content_infos: list[ContentInfo]
195
+ total_count: int
196
+
197
+
198
+ class BaseFolderInfo(BaseModel):
199
+ model_config = model_config
200
+ id: str
201
+ name: str
202
+ parent_id: str | None
203
+
204
+
205
+ class FolderInfo(BaseFolderInfo):
206
+ model_config = model_config
207
+ ingestion_config: dict[str, Any]
208
+ created_at: str | None
209
+ updated_at: str | None
210
+ external_id: str | None
211
+
212
+
213
+ class DeleteContentResponse(BaseModel):
214
+ model_config = model_config
215
+ content_id: str
216
+ object: str