unique_sdk 0.10.0__py3-none-any.whl → 0.10.2__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.
@@ -64,6 +64,7 @@ class Content(APIResource["Content"]):
64
64
  class SearchParams(RequestOptions):
65
65
  where: "Content.ContentWhereInput"
66
66
  chatId: NotRequired[str]
67
+ includeFailedContent: NotRequired[bool]
67
68
 
68
69
  class ContentInfoParams(TypedDict):
69
70
  """
@@ -163,11 +164,11 @@ class Content(APIResource["Content"]):
163
164
  class MagicTableSheetTable(TypedDict):
164
165
  rowId: str
165
166
  columns: List["Content.MagicTableSheetTableColumn"]
166
-
167
+
167
168
  class MagicTableSheetIngestionConfiguration(TypedDict):
168
169
  columnIdsInMetadata: List[str]
169
170
  columnIdsInChunkText: List[str]
170
-
171
+
171
172
  class MagicTableSheetIngestParams(TypedDict):
172
173
  data: List["Content.MagicTableSheetTable"]
173
174
  ingestionConfiguration: "Content.MagicTableSheetIngestionConfiguration"
@@ -298,10 +299,10 @@ class Content(APIResource["Content"]):
298
299
 
299
300
  @classmethod
300
301
  def ingest_magic_table_sheets(
301
- cls,
302
- user_id: str,
303
- company_id: str,
304
- **params: Unpack["Content.MagicTableSheetIngestParams"]
302
+ cls,
303
+ user_id: str,
304
+ company_id: str,
305
+ **params: Unpack["Content.MagicTableSheetIngestParams"],
305
306
  ) -> "Content.MagicTableSheetResponse":
306
307
  return cast(
307
308
  Content.MagicTableSheetResponse,
@@ -316,10 +317,10 @@ class Content(APIResource["Content"]):
316
317
 
317
318
  @classmethod
318
319
  async def ingest_magic_table_sheets_async(
319
- cls,
320
- user_id: str,
321
- company_id: str,
322
- **params: Unpack["Content.MagicTableSheetIngestParams"]
320
+ cls,
321
+ user_id: str,
322
+ company_id: str,
323
+ **params: Unpack["Content.MagicTableSheetIngestParams"],
323
324
  ) -> "Content.MagicTableSheetResponse":
324
325
  return cast(
325
326
  Content.MagicTableSheetResponse,
@@ -67,6 +67,13 @@ class Space(APIResource["Space"]):
67
67
  assessment: Optional[List["Space.Reference"]]
68
68
  messageAssessment: Optional[List["Space.Assessment"]]
69
69
 
70
+ class DeleteChatResponse(TypedDict):
71
+ """
72
+ Response for deleting a chat in a space.
73
+ """
74
+
75
+ chat_id: str
76
+
70
77
  @classmethod
71
78
  def create_message(
72
79
  cls,
@@ -142,3 +149,43 @@ class Space(APIResource["Space"]):
142
149
  company_id,
143
150
  ),
144
151
  )
152
+
153
+ @classmethod
154
+ def delete_chat(
155
+ cls,
156
+ user_id: str,
157
+ company_id: str,
158
+ chat_id: str,
159
+ ) -> "Space.DeleteChatResponse":
160
+ """
161
+ Delete a chat in a space.
162
+ """
163
+ return cast(
164
+ "Space.DeleteChatResponse",
165
+ cls._static_request(
166
+ "delete",
167
+ f"/space/chat/{chat_id}",
168
+ user_id,
169
+ company_id,
170
+ ),
171
+ )
172
+
173
+ @classmethod
174
+ async def delete_chat_async(
175
+ cls,
176
+ user_id: str,
177
+ company_id: str,
178
+ chat_id: str,
179
+ ) -> "Space.DeleteChatResponse":
180
+ """
181
+ Async delete a chat in a space.
182
+ """
183
+ return cast(
184
+ "Space.DeleteChatResponse",
185
+ await cls._static_request_async(
186
+ "delete",
187
+ f"/space/chat/{chat_id}",
188
+ user_id,
189
+ company_id,
190
+ ),
191
+ )
@@ -1,7 +1,9 @@
1
1
  import asyncio
2
2
  from typing import List
3
3
 
4
+ from unique_sdk.api_resources._content import Content
4
5
  from unique_sdk.api_resources._space import Space
6
+ from unique_sdk.utils.file_io import upload_file
5
7
 
6
8
 
7
9
  async def send_message_and_wait_for_completion(
@@ -30,7 +32,6 @@ async def send_message_and_wait_for_completion(
30
32
  Returns:
31
33
  The completed Space.Message.
32
34
  """
33
- # Send the prompt asynchronously
34
35
  response = await Space.create_message_async(
35
36
  user_id=user_id,
36
37
  company_id=company_id,
@@ -44,10 +45,123 @@ async def send_message_and_wait_for_completion(
44
45
 
45
46
  max_attempts = int(max_wait // poll_interval)
46
47
  for _ in range(max_attempts):
47
- # Poll for the answer
48
48
  answer = Space.get_latest_message(user_id, company_id, chat_id)
49
49
  if answer.get("stoppedStreamingAt") is not None:
50
50
  return answer
51
51
  await asyncio.sleep(poll_interval)
52
52
 
53
53
  raise TimeoutError("Timed out waiting for prompt completion.")
54
+
55
+
56
+ async def chat_against_file(
57
+ user_id: str,
58
+ company_id: str,
59
+ assistant_id: str,
60
+ path_to_file: str,
61
+ displayed_filename: str,
62
+ mime_type: str,
63
+ text: str,
64
+ poll_interval: float = 1.0,
65
+ max_wait: float = 60.0,
66
+ ) -> "Space.Message":
67
+ """
68
+ Chat against a file by uploading it, sending a message and waiting for a reply.
69
+ Args:
70
+
71
+ user_id: The user ID.
72
+ company_id: The company ID.
73
+ assistant_id: The assistant ID.
74
+ path_to_file: Path to the PDF file to upload.
75
+ displayed_filename: Name to display for the uploaded file.
76
+ mime_type: MIME type of the file (e.g., "application/pdf").
77
+ text: Text to send after uploading the file and chat against.
78
+ poll_interval: Seconds between polls for file ingestion.
79
+ max_wait: Maximum seconds to wait for the final response.
80
+
81
+ Returns:
82
+ The final message response.
83
+ """
84
+ chat_id = None
85
+
86
+ try:
87
+ response = await send_message_and_wait_for_completion(
88
+ user_id=user_id,
89
+ company_id=company_id,
90
+ assistant_id=assistant_id,
91
+ text="I'm going to upload a file for analysis.",
92
+ )
93
+ chat_id = response.get("chatId")
94
+
95
+ upload_response = upload_file(
96
+ userId=user_id,
97
+ companyId=company_id,
98
+ path_to_file=path_to_file,
99
+ displayed_filename=displayed_filename,
100
+ mime_type=mime_type,
101
+ chat_id=chat_id,
102
+ )
103
+ content_id = upload_response.get("id")
104
+
105
+ await wait_for_ingestion_completion(
106
+ user_id=user_id,
107
+ company_id=company_id,
108
+ content_id=content_id,
109
+ chat_id=chat_id,
110
+ poll_interval=poll_interval,
111
+ max_wait=max_wait,
112
+ )
113
+
114
+ final_response = await send_message_and_wait_for_completion(
115
+ user_id=user_id,
116
+ company_id=company_id,
117
+ assistant_id=assistant_id,
118
+ text=text,
119
+ chat_id=chat_id,
120
+ poll_interval=poll_interval,
121
+ max_wait=max_wait,
122
+ )
123
+
124
+ return final_response
125
+
126
+ except Exception as err:
127
+ print(f"Error during chat against file: {err}")
128
+ raise
129
+ finally:
130
+ if chat_id:
131
+ Space.delete_chat(
132
+ user_id=user_id,
133
+ company_id=company_id,
134
+ chat_id=chat_id,
135
+ )
136
+
137
+
138
+ async def wait_for_ingestion_completion(
139
+ user_id: str,
140
+ company_id: str,
141
+ content_id: str,
142
+ chat_id: str,
143
+ poll_interval: float = 1.0,
144
+ max_wait: float = 60.0,
145
+ ):
146
+ """
147
+ Waits until the content ingestionState is FINISHED or the maximum wait time is reached and throws if case of failed status.
148
+ """
149
+ max_attempts = int(max_wait // poll_interval)
150
+ for _ in range(max_attempts):
151
+ searched_content = Content.search(
152
+ user_id=user_id,
153
+ company_id=company_id,
154
+ where={"id": {"equals": content_id}},
155
+ chatId=chat_id,
156
+ includeFailedContent=True,
157
+ )
158
+ if searched_content:
159
+ ingestion_state = searched_content[0].get("ingestionState")
160
+ if ingestion_state == "FINISHED":
161
+ return
162
+ if isinstance(ingestion_state, str) and ingestion_state.startswith(
163
+ "FAILED"
164
+ ):
165
+ raise RuntimeError(f"Ingestion failed with state: {ingestion_state}")
166
+ await asyncio.sleep(poll_interval)
167
+ raise TimeoutError("Timed out waiting for file ingestion to finish.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_sdk
3
- Version: 0.10.0
3
+ Version: 0.10.2
4
4
  Summary:
5
5
  License: MIT
6
6
  Author: Martin Fadler
@@ -37,6 +37,7 @@ The Unique Python SDK provides access to the public API of Unique FinanceGPT. It
37
37
  - [Short Term Memory](#short-term-memory)
38
38
  - [Message Assessment](#message-assessment)
39
39
  - [Folder](#folder)
40
+ - [Space](#space)
40
41
  6. [UniqueQL](#uniqueql)
41
42
  - [Query Structure](#uniqueql-query-structure)
42
43
  - [Metadata Filtering](#metadata-filtering)
@@ -251,6 +252,7 @@ unique_sdk.Message.modify(
251
252
  - [Short Term Memory](#short-term-memory)
252
253
  - [Message Assessment](#message-assessment)
253
254
  - [Folder](#folder)
255
+ - [Space](#space)
254
256
 
255
257
  Most of the API services provide an asynchronous version of the method. The async methods are suffixed with `_async`.
256
258
 
@@ -950,6 +952,20 @@ unique_sdk.Folder.remove_access(
950
952
  )
951
953
  ```
952
954
 
955
+ ### Space
956
+
957
+ #### `unique_sdk.Space.delete_chat`
958
+
959
+ Delete a space chat by id. If the chat does not exist, the function will return an error.
960
+
961
+ ```python
962
+ unique_sdk.Space.delete_chat(
963
+ user_id=user_id,
964
+ company_id=company_id,
965
+ chat_id="chat_dejfhe729br398",
966
+ )
967
+ ```
968
+
953
969
  ## UniqueQL
954
970
 
955
971
  [UniqueQL](https://unique-ch.atlassian.net/wiki/x/coAXHQ) is an advanced query language designed to enhance search capabilities within various search modes such as Vector, Full-Text Search (FTS), and Combined. This query language enables users to perform detailed searches by filtering through metadata attributes like filenames, URLs, dates, and more. UniqueQL is versatile and can be translated into different query formats for various database systems, including PostgreSQL and Qdrant.
@@ -1008,6 +1024,12 @@ A metadata filter such as the one designed above can be used in a `Search.create
1008
1024
 
1009
1025
  ## Utils
1010
1026
 
1027
+ - [Chat History](#chat-history)
1028
+ - [File Io](#file-io)
1029
+ - [Sources](#sources)
1030
+ - [token](#token)
1031
+ - [Chat In Space](#chat-in-space)
1032
+
1011
1033
  ### Chat History
1012
1034
 
1013
1035
  #### `unique_sdk.utils.chat_history.load_history`
@@ -1237,33 +1259,64 @@ The script ensures you can flexibly interact with spaces in new or ongoing chats
1237
1259
 
1238
1260
  ```python
1239
1261
  latest_message = await unique_sdk.utils.chat_in_space.send_message_and_wait_for_completion(
1240
- user_id=user_id,
1241
- company_id=company_id,
1242
- assistant_id=assistant_id,
1243
- text="Tell me a short story.",
1244
- chat_id=chat_id, # Optional - if no chat id is specified, a new chat will be created
1245
- tool_choices=["WebSearch"],
1246
- scope_rules={
1247
- "or": [
1248
- {
1249
- "operator": "in",
1250
- "path": [
1251
- "contentId"
1252
- ],
1253
- "value": [
1254
- "cont_u888z7cazxxm4lugfdjq7pks"
1255
- ]
1256
- },
1257
- {
1258
- "operator": "contains",
1259
- "path": [
1260
- "folderIdPath"
1261
- ],
1262
- "value": "uniquepathid://scope_btfo28b3eeelwh5obwgea71bl/scope_fn56ta67knd6w4medgq3028fx"
1263
- }
1264
- ]
1265
- },
1266
- )
1262
+ user_id=user_id,
1263
+ company_id=company_id,
1264
+ assistant_id=assistant_id,
1265
+ text="Tell me a short story.",
1266
+ chat_id=chat_id, # Optional - if no chat id is specified, a new chat will be created
1267
+ tool_choices=["WebSearch"],
1268
+ scope_rules={
1269
+ "or": [
1270
+ {
1271
+ "operator": "in",
1272
+ "path": [
1273
+ "contentId"
1274
+ ],
1275
+ "value": [
1276
+ "cont_u888z7cazxxm4lugfdjq7pks"
1277
+ ]
1278
+ },
1279
+ {
1280
+ "operator": "contains",
1281
+ "path": [
1282
+ "folderIdPath"
1283
+ ],
1284
+ "value": "uniquepathid://scope_btfo28b3eeelwh5obwgea71bl/scope_fn56ta67knd6w4medgq3028fx"
1285
+ }
1286
+ ]
1287
+ },
1288
+ )
1289
+ ```
1290
+
1291
+ #### `unique_sdk.utils.chat_in_space.chat_against_file`
1292
+
1293
+ The following script enables you to chat against a file.
1294
+
1295
+ You must provide the following parameters:
1296
+ - `assistantId`: The assistant to be used for the chat.
1297
+ - `path_to_file`: The local path of the file to be uploaded.
1298
+ - `displayed_filename`: The name of the file to be displayed.
1299
+ - `mime_type`: The mime type of the ifle to be uploaded.
1300
+ - `text`: The text to be sent to the chat for chatting against the file.
1301
+
1302
+ The script creates a chat and uploads the file to it. It then keeps polling the `ingestionState` field of the message, waiting for it to reach `FINISHED`, signaling the upload is complete. Once the file uploads successfully, the script sends the text, continues polling for completion, and finally retrieves the response message.
1303
+
1304
+ **Optional parameters:**
1305
+ - `poll_interval`: The number of seconds to wait between polling attempts (default: `1` second).
1306
+ - `max_wait`: The maximum number of seconds to wait for the message to complete (default: `60` seconds).
1307
+
1308
+ Example of chatting against a PDF. (The usage can be extended to any supported file type)
1309
+
1310
+ ```python
1311
+ latest_message = await unique_sdk.utils.chat_in_space.chat_against_file(
1312
+ user_id=user_id,
1313
+ company_id=company_id,
1314
+ assistant_id="assistant_hjcdga64bkcjnhu4",
1315
+ path_to_file="/files/hello.pdf",
1316
+ displayed_filename="hello.pdf"
1317
+ mime_type="application/pdf"
1318
+ text="Give me a bullet point summary of the file.",
1319
+ )
1267
1320
  ```
1268
1321
 
1269
1322
  ## Error Handling
@@ -1283,6 +1336,12 @@ All notable changes to this project will be documented in this file.
1283
1336
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1284
1337
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1285
1338
 
1339
+ ## [0.10.2] - 2025-08-05
1340
+ - Add script to chat against file.
1341
+
1342
+ ## [0.10.1] - 2025-08-05
1343
+ - Allow deletion of a space chat.
1344
+
1286
1345
  ## [0.10.0] - 2025-08-04
1287
1346
  - Add MCP support
1288
1347
 
@@ -16,7 +16,7 @@ unique_sdk/_webhook.py,sha256=GYxbUibQN_W4XlNTHaMIksT9FQJk4LJmlKcxOu3jqiU,2855
16
16
  unique_sdk/api_resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  unique_sdk/api_resources/_acronyms.py,sha256=GIU1XH1flGWQYcpsFqTYwg4ioIGxVmb15tux84nmhEg,891
18
18
  unique_sdk/api_resources/_chat_completion.py,sha256=ILCAffxkbkfh2iV9L4KKnfe80gZmT9pWfkNmf3mq68U,2172
19
- unique_sdk/api_resources/_content.py,sha256=KIbAjXcf_-YiXAByW4wRYqLAqst47Bup8YXTIQ-PnTo,9550
19
+ unique_sdk/api_resources/_content.py,sha256=Rl1Pb0vUQ9EkEQa0XTGOLZamAYlZaz7h6jNWE-zqFaw,9554
20
20
  unique_sdk/api_resources/_embedding.py,sha256=C6qak7cCUBMBINfPhgH8taCJZ9n6w1MUElqDJJ8dG10,1281
21
21
  unique_sdk/api_resources/_event.py,sha256=bpWF9vstdoAWbUzr-iiGP713ceP0zPk77GJXiImf9zg,374
22
22
  unique_sdk/api_resources/_folder.py,sha256=dmPorUoOJSe-hcxlJq3RnkQeZg3gPqGF37ZsLgnloT0,8335
@@ -27,13 +27,13 @@ unique_sdk/api_resources/_message_assessment.py,sha256=SSfx6eW7zb_GKe8cFJzCqW-t-
27
27
  unique_sdk/api_resources/_search.py,sha256=pAVMXL2AdJNP5JG-7LlaU2Uw1152iUaMZ2YO7fHnNWc,1906
28
28
  unique_sdk/api_resources/_search_string.py,sha256=4Idw6exgZdA8qksz9WkiA68k1hTU-7yFkgT_OLU_GkE,1662
29
29
  unique_sdk/api_resources/_short_term_memory.py,sha256=vPRN-Y0WPx74E6y-A3LocGc0TxJdzT-xGL66WzZwKRg,2820
30
- unique_sdk/api_resources/_space.py,sha256=LAK-ZbHemIIfX51anthml4ojxhPUPDOu_EbQs64rOFA,3726
30
+ unique_sdk/api_resources/_space.py,sha256=9e-_3oBr_25JeRLF7GfaXnIPE33tZ19g0UWxPp_yR4k,4805
31
31
  unique_sdk/utils/chat_history.py,sha256=5UqL9hF1O9pV7skbNOlEibF5rHdYsmG3m5-YEPUowOs,3037
32
- unique_sdk/utils/chat_in_space.py,sha256=kQA43P9XZ6Kdf05JZiLhVJ5ejVtrgzZSb8osyNHSZmc,1580
32
+ unique_sdk/utils/chat_in_space.py,sha256=nYmgQYhIxqQex_6zjdCfNuGnjoU14WkxUN6_zmSOiCQ,5169
33
33
  unique_sdk/utils/file_io.py,sha256=YY8B7VJcTLOPmCXByiOfNerXGlAtjCC5EVNmAbQJ3dQ,4306
34
34
  unique_sdk/utils/sources.py,sha256=wfboE-neMKa0Wuq9QzfAEFMkNLrIrmm0v-QF33sLo6k,4952
35
35
  unique_sdk/utils/token.py,sha256=AzKuAA1AwBtnvSFxGcsHLpxXr_wWE5Mj4jYBbOz2ljA,1740
36
- unique_sdk-0.10.0.dist-info/LICENSE,sha256=EJCWoHgrXVBUb47PnjeV4MFIEOR71MAdCOIgv61J-4k,1065
37
- unique_sdk-0.10.0.dist-info/METADATA,sha256=VcwHI6WA3JGiFekfjs411NWLxE3lj22a7FmReyJv_Xw,42872
38
- unique_sdk-0.10.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
39
- unique_sdk-0.10.0.dist-info/RECORD,,
36
+ unique_sdk-0.10.2.dist-info/LICENSE,sha256=EJCWoHgrXVBUb47PnjeV4MFIEOR71MAdCOIgv61J-4k,1065
37
+ unique_sdk-0.10.2.dist-info/METADATA,sha256=L2sOYupGkEPz5rw9qOtVc5nUtjUBu14pgtC-CpLH9Os,44785
38
+ unique_sdk-0.10.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
39
+ unique_sdk-0.10.2.dist-info/RECORD,,