unique_toolkit 1.40.0__py3-none-any.whl → 1.41.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.
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from enum import StrEnum
2
3
  from string import Template
3
4
 
4
5
  from unique_toolkit.agentic.evaluation.config import EvaluationMetricConfig
@@ -9,6 +10,7 @@ from unique_toolkit.agentic.evaluation.schemas import (
9
10
  EvaluationMetricName,
10
11
  EvaluationMetricResult,
11
12
  )
13
+ from unique_toolkit.content import ContentReference
12
14
  from unique_toolkit.content.schemas import ContentChunk
13
15
  from unique_toolkit.language_model.schemas import (
14
16
  LanguageModelMessages,
@@ -201,13 +203,58 @@ def _get_user_prompt_default(config: EvaluationMetricConfig):
201
203
  )
202
204
 
203
205
 
206
+ class SourceSelectionMode(StrEnum):
207
+ FROM_IDS = "FROM_IDS"
208
+ FROM_ORDER = "FROM_ORDER"
209
+
210
+
204
211
  def context_text_from_stream_response(
205
- response: LanguageModelStreamResponse, selected_chunks: list[ContentChunk]
212
+ response: LanguageModelStreamResponse,
213
+ selected_chunks: list[ContentChunk],
214
+ source_selection_mode: SourceSelectionMode = SourceSelectionMode.FROM_IDS,
206
215
  ):
207
216
  response_references = response.message.references
208
- reference_ids = [reference.source_id for reference in response_references]
209
- filtered_contexts: list[str] = []
210
- for chunk in selected_chunks:
211
- if f"{chunk.id}_{chunk.chunk_id}" in reference_ids:
212
- filtered_contexts.append(chunk.text)
213
- return filtered_contexts
217
+ match source_selection_mode:
218
+ case SourceSelectionMode.FROM_IDS:
219
+ referenced_chunks = _default_source_selection_mode(
220
+ response_references, selected_chunks
221
+ )
222
+ case SourceSelectionMode.FROM_ORDER:
223
+ referenced_chunks = _from_order_source_selection_mode(
224
+ response_references, selected_chunks
225
+ )
226
+ case _:
227
+ raise ValueError(f"Invalid source selection mode: {source_selection_mode}")
228
+
229
+ return [chunk.text for chunk in referenced_chunks]
230
+
231
+
232
+ def _default_source_selection_mode(
233
+ references: list[ContentReference], selected_chunks: list[ContentChunk]
234
+ ):
235
+ reference_ids = {reference.source_id for reference in references}
236
+
237
+ def build_chunk_id(chunk: ContentChunk) -> str:
238
+ return f"{chunk.id}_{chunk.chunk_id}"
239
+
240
+ referenced_chunks = [
241
+ chunk for chunk in selected_chunks if build_chunk_id(chunk) in reference_ids
242
+ ]
243
+
244
+ return referenced_chunks
245
+
246
+
247
+ def _from_order_source_selection_mode(
248
+ references: list[ContentReference], selected_chunks: list[ContentChunk]
249
+ ):
250
+ original_chunks_order: list[int] = []
251
+ for reference in references:
252
+ for original_index in reference.original_index:
253
+ if original_index not in original_chunks_order:
254
+ original_chunks_order.append(original_index)
255
+
256
+ referenced_chunks: list[ContentChunk] = []
257
+ for index in original_chunks_order:
258
+ referenced_chunks.append(selected_chunks[index])
259
+
260
+ return referenced_chunks
@@ -0,0 +1,56 @@
1
+ from unique_sdk.api_resources._agentic_table import (
2
+ ActivityStatus,
3
+ CellRendererTypes,
4
+ FilterTypes,
5
+ RowVerificationStatus,
6
+ )
7
+
8
+ from .schemas import (
9
+ AgenticTableSheetState,
10
+ AgreementStatus,
11
+ ArtifactType,
12
+ LogEntry,
13
+ MagicTableAction,
14
+ MagicTableAddMetadataPayload,
15
+ MagicTableBasePayload,
16
+ MagicTableCell,
17
+ MagicTableCellMetaData,
18
+ MagicTableEvent,
19
+ MagicTableEventTypes,
20
+ MagicTableGenerateArtifactPayload,
21
+ MagicTableLibrarySheetRowVerifiedPayload,
22
+ MagicTableSheet,
23
+ MagicTableSheetCompletedPayload,
24
+ MagicTableSheetCreatedPayload,
25
+ MagicTableUpdateCellPayload,
26
+ RowMetadataEntry,
27
+ SelectionMethod,
28
+ )
29
+ from .service import AgenticTableService
30
+
31
+ __all__ = [
32
+ "AgenticTableService",
33
+ "ActivityStatus",
34
+ "AgenticTableSheetState",
35
+ "AgreementStatus",
36
+ "ArtifactType",
37
+ "CellRendererTypes",
38
+ "FilterTypes",
39
+ "LogEntry",
40
+ "MagicTableAction",
41
+ "MagicTableAddMetadataPayload",
42
+ "MagicTableBasePayload",
43
+ "MagicTableCell",
44
+ "MagicTableCellMetaData",
45
+ "MagicTableEvent",
46
+ "MagicTableEventTypes",
47
+ "MagicTableGenerateArtifactPayload",
48
+ "MagicTableLibrarySheetRowVerifiedPayload",
49
+ "MagicTableSheet",
50
+ "MagicTableSheetCompletedPayload",
51
+ "MagicTableSheetCreatedPayload",
52
+ "MagicTableUpdateCellPayload",
53
+ "RowMetadataEntry",
54
+ "RowVerificationStatus",
55
+ "SelectionMethod",
56
+ ]
@@ -0,0 +1,305 @@
1
+ from enum import StrEnum
2
+ from typing import Annotated, Any, Generic, Literal, TypeVar
3
+
4
+ from pydantic import (
5
+ BaseModel,
6
+ Field,
7
+ field_validator,
8
+ )
9
+ from unique_sdk import (
10
+ AgenticTableSheetState,
11
+ AgreementStatus,
12
+ SelectionMethod,
13
+ )
14
+ from unique_sdk import LogDetail as SDKLogDetail
15
+ from unique_sdk import LogEntry as SDKLogEntry
16
+ from unique_sdk.api_resources._agentic_table import (
17
+ MagicTableAction,
18
+ SheetType,
19
+ )
20
+
21
+ from unique_toolkit._common.pydantic_helpers import get_configuration_dict
22
+ from unique_toolkit.app.schemas import (
23
+ ChatEvent,
24
+ ChatEventAssistantMessage,
25
+ ChatEventUserMessage,
26
+ )
27
+ from unique_toolkit.language_model.schemas import (
28
+ LanguageModelMessageRole,
29
+ LanguageModelMessages,
30
+ )
31
+
32
+
33
+ class MagicTableEventTypes(StrEnum):
34
+ IMPORT_COLUMNS = "unique.magic-table.import-columns"
35
+ UPDATE_CELL = "unique.magic-table.update-cell"
36
+ ADD_DATA = "unique.magic-table.add-document"
37
+ ADD_META_DATA = "unique.magic-table.add-meta-data"
38
+ GENERATE_ARTIFACT = "unique.magic-table.generate-artifact"
39
+ SHEET_COMPLETED = "unique.magic-table.sheet-completed"
40
+ LIBRARY_SHEET_ROW_VERIFIED = "unique.magic-table.library-sheet-row.verified"
41
+ SHEET_CREATED = "unique.magic-table.sheet-created"
42
+
43
+
44
+ class BaseMetadata(BaseModel):
45
+ model_config = get_configuration_dict()
46
+
47
+ sheet_type: SheetType = Field(
48
+ description="The type of the sheet.",
49
+ default=SheetType.DEFAULT,
50
+ )
51
+
52
+
53
+ class RowMetadataEntry(BaseModel):
54
+ model_config = get_configuration_dict()
55
+ id: str = Field(description="The ID of the metadata")
56
+ key: str = Field(description="The key of the metadata")
57
+ value: str = Field(description="The value of the metadata")
58
+ exact_filter: bool = Field(
59
+ default=False,
60
+ description="Whether the metadata is to be used for strict filtering",
61
+ )
62
+
63
+
64
+ class DDMetadata(BaseMetadata):
65
+ model_config = get_configuration_dict()
66
+
67
+ question_file_ids: list[str] = Field(
68
+ default_factory=list, description="The IDs of the question files"
69
+ )
70
+ source_file_ids: list[str] = Field(
71
+ default_factory=list, description="The IDs of the source files"
72
+ )
73
+ question_texts: list[str] = Field(
74
+ default_factory=list, description="The texts of the questions"
75
+ )
76
+ context: str = Field(default="", description="The context text for the table.")
77
+
78
+ @field_validator("context", mode="before")
79
+ @classmethod
80
+ def normalize_context(cls, v):
81
+ if v is None:
82
+ return ""
83
+ return v
84
+
85
+
86
+ # Define template types
87
+ A = TypeVar("A", bound=MagicTableAction)
88
+ T = TypeVar("T", bound=BaseMetadata)
89
+
90
+
91
+ class MagicTableBasePayload(BaseModel, Generic[A, T]):
92
+ model_config = get_configuration_dict()
93
+ name: str = Field(description="The name of the module")
94
+ sheet_name: str
95
+
96
+ action: A
97
+ chat_id: str
98
+ assistant_id: str
99
+ table_id: str
100
+ user_message: ChatEventUserMessage = Field(
101
+ default=ChatEventUserMessage(
102
+ id="", text="", original_text="", created_at="", language=""
103
+ )
104
+ )
105
+ assistant_message: ChatEventAssistantMessage = Field(
106
+ default=ChatEventAssistantMessage(id="", created_at="")
107
+ )
108
+ configuration: dict[str, Any] = {}
109
+ metadata: T
110
+ metadata_filter: dict[str, Any] | None = None
111
+
112
+
113
+ ########### Specialized Payload definitions ###########
114
+
115
+
116
+ class MagicTableAddMetadataPayload(
117
+ MagicTableBasePayload[Literal[MagicTableAction.ADD_META_DATA], DDMetadata]
118
+ ): ...
119
+
120
+
121
+ class MagicTableUpdateCellPayload(
122
+ MagicTableBasePayload[Literal[MagicTableAction.UPDATE_CELL], DDMetadata]
123
+ ):
124
+ column_order: int
125
+ row_order: int
126
+ data: str
127
+
128
+
129
+ class ArtifactType(StrEnum):
130
+ QUESTIONS = "QUESTIONS"
131
+ FULL_REPORT = "FULL_REPORT"
132
+
133
+
134
+ class ArtifactData(BaseModel):
135
+ model_config = get_configuration_dict()
136
+ artifact_type: ArtifactType
137
+
138
+
139
+ class MagicTableGenerateArtifactPayload(
140
+ MagicTableBasePayload[Literal[MagicTableAction.GENERATE_ARTIFACT], BaseMetadata]
141
+ ):
142
+ data: ArtifactData
143
+
144
+
145
+ ########## Sheet Completed Payload ##########
146
+
147
+
148
+ class SheetCompletedMetadata(BaseMetadata):
149
+ model_config = get_configuration_dict()
150
+ sheet_id: str = Field(description="The ID of the sheet that was completed.")
151
+ library_sheet_id: str = Field(
152
+ description="The ID of the library corresponding to the sheet."
153
+ )
154
+ context: str = Field(default="", description="The context text for the table.")
155
+
156
+ @field_validator("context", mode="before")
157
+ @classmethod
158
+ def normalize_context(cls, v):
159
+ if v is None:
160
+ return ""
161
+ return v
162
+
163
+
164
+ class MagicTableSheetCompletedPayload(
165
+ MagicTableBasePayload[
166
+ Literal[MagicTableAction.SHEET_COMPLETED], SheetCompletedMetadata
167
+ ]
168
+ ): ...
169
+
170
+
171
+ class SheetCreatedMetadata(BaseMetadata): ...
172
+
173
+
174
+ class MagicTableSheetCreatedPayload(
175
+ MagicTableBasePayload[Literal[MagicTableAction.SHEET_CREATED], SheetCreatedMetadata]
176
+ ): ...
177
+
178
+
179
+ ########## Library Sheet Row Verified Payload ##########
180
+
181
+
182
+ class LibrarySheetRowVerifiedMetadata(BaseMetadata):
183
+ model_config = get_configuration_dict()
184
+ row_order: int = Field(description="The row index of the row that was verified.")
185
+
186
+
187
+ class MagicTableLibrarySheetRowVerifiedPayload(
188
+ MagicTableBasePayload[
189
+ Literal[MagicTableAction.LIBRARY_SHEET_ROW_VERIFIED],
190
+ LibrarySheetRowVerifiedMetadata,
191
+ ]
192
+ ): ...
193
+
194
+
195
+ ########### Magic Table Event definition ###########
196
+
197
+
198
+ PayloadTypes = (
199
+ MagicTableUpdateCellPayload
200
+ | MagicTableAddMetadataPayload
201
+ | MagicTableGenerateArtifactPayload
202
+ | MagicTableSheetCompletedPayload
203
+ | MagicTableLibrarySheetRowVerifiedPayload
204
+ | MagicTableSheetCreatedPayload
205
+ )
206
+
207
+ MagicTablePayloadTypes = Annotated[PayloadTypes, Field(discriminator="action")]
208
+
209
+
210
+ class MagicTableEvent(ChatEvent):
211
+ event: MagicTableEventTypes # type: ignore[assignment]
212
+ payload: MagicTablePayloadTypes # type: ignore[assignment]
213
+
214
+
215
+ class LogDetail(BaseModel):
216
+ model_config = get_configuration_dict()
217
+ llm_request: LanguageModelMessages | None = Field(
218
+ default=None, description="The LLM request for the log detail"
219
+ )
220
+
221
+ def to_sdk_log_detail(self) -> SDKLogDetail:
222
+ llm_request = None
223
+ if self.llm_request:
224
+ llm_request = self.llm_request.model_dump()
225
+ return SDKLogDetail(llmRequest=llm_request)
226
+
227
+
228
+ class LogEntry(BaseModel):
229
+ model_config = get_configuration_dict()
230
+
231
+ text: str
232
+ created_at: str
233
+ actor_type: LanguageModelMessageRole
234
+ message_id: str | None = None
235
+ details: LogDetail | None = Field(
236
+ default=None, description="The details of the log entry"
237
+ )
238
+
239
+ @field_validator("actor_type", mode="before")
240
+ @classmethod
241
+ def normalize_actor_type(cls, v):
242
+ if isinstance(v, str):
243
+ return v.lower()
244
+ return v
245
+
246
+ def to_sdk_log_entry(self) -> SDKLogEntry:
247
+ params: dict[str, Any] = {
248
+ "text": self.text,
249
+ "createdAt": self.created_at,
250
+ "actorType": self.actor_type.value.upper(),
251
+ }
252
+ if self.details:
253
+ params["details"] = self.details.to_sdk_log_detail()
254
+
255
+ return SDKLogEntry(**params)
256
+
257
+
258
+ class MagicTableCellMetaData(BaseModel):
259
+ model_config = get_configuration_dict()
260
+ row_order: int = Field(description="The row index of the cell.")
261
+ column_order: int = Field(description="The column index of the cell.")
262
+ selected: bool | None = None
263
+ selection_method: SelectionMethod | None = None
264
+ agreement_status: AgreementStatus | None = None
265
+
266
+
267
+ class MagicTableCell(BaseModel):
268
+ model_config = get_configuration_dict()
269
+ sheet_id: str
270
+ row_order: int = Field(description="The row index of the cell.")
271
+ column_order: int = Field(description="The column index of the cell.")
272
+ row_locked: bool = Field(default=False, description="Lock status of the row.")
273
+ text: str
274
+ log_entries: list[LogEntry] = Field(
275
+ default_factory=list, description="The log entries for the cell"
276
+ )
277
+ meta_data: MagicTableCellMetaData | None = Field(
278
+ default=None, description="The metadata for the cell"
279
+ )
280
+ row_metadata: list[RowMetadataEntry] = Field(
281
+ default_factory=list,
282
+ description="The metadata (key value pairs)for the rows.",
283
+ )
284
+
285
+
286
+ class MagicTableSheet(BaseModel):
287
+ model_config = get_configuration_dict()
288
+ sheet_id: str
289
+ name: str
290
+ state: AgenticTableSheetState
291
+ total_number_of_rows: int = Field(
292
+ default=0,
293
+ description="The total number of rows in the sheet",
294
+ alias="magicTableRowCount",
295
+ )
296
+ chat_id: str
297
+ created_by: str
298
+ company_id: str
299
+ created_at: str
300
+ magic_table_cells: list[MagicTableCell] = Field(
301
+ default_factory=list, description="The cells in the sheet"
302
+ )
303
+ magic_table_sheet_metadata: list[RowMetadataEntry] = Field(
304
+ default_factory=list, description="The metadata for the sheet"
305
+ )
@@ -0,0 +1,452 @@
1
+ import logging
2
+
3
+ from typing_extensions import deprecated
4
+ from unique_sdk import (
5
+ AgenticTable,
6
+ AgenticTableSheetState,
7
+ AgreementStatus,
8
+ CellRendererTypes,
9
+ FilterTypes,
10
+ RowVerificationStatus,
11
+ SelectionMethod,
12
+ )
13
+ from unique_sdk import AgenticTableCell as SDKAgenticTableCell
14
+ from unique_sdk.api_resources._agentic_table import ActivityStatus
15
+
16
+ from .schemas import (
17
+ ArtifactType,
18
+ LogEntry,
19
+ MagicTableAction,
20
+ MagicTableCell,
21
+ MagicTableSheet,
22
+ RowMetadataEntry,
23
+ )
24
+
25
+
26
+ class LockedAgenticTableError(Exception):
27
+ pass
28
+
29
+
30
+ class AgenticTableService:
31
+ """
32
+ Provides methods to interact with the Agentic Table.
33
+
34
+ Attributes:
35
+ #event (ChatEvent): The ChatEvent object.
36
+ logger (Optional[logging.Logger]): The logger object. Defaults to None.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ user_id: str,
42
+ company_id: str,
43
+ table_id: str,
44
+ event_id: str | None = None,
45
+ logger: logging.Logger = logging.getLogger(__name__),
46
+ ):
47
+ self._event_id = event_id
48
+ self._user_id = user_id
49
+ self._company_id = company_id
50
+ self.table_id = table_id
51
+ self.logger = logger
52
+
53
+ async def set_cell(
54
+ self,
55
+ row: int,
56
+ column: int,
57
+ text: str,
58
+ log_entries: list[LogEntry] | None = None,
59
+ ):
60
+ """
61
+ Sets the value of a cell in the Agentic Table.
62
+
63
+ Args:
64
+ row (int): The row index.
65
+ column (int): The column index.
66
+ text (str): The text to set.
67
+ log_entries (Optional[list[LogEntry]]): The log entries to set.
68
+ """
69
+ if log_entries is None:
70
+ log_entries_new = []
71
+ else:
72
+ log_entries_new = [
73
+ log_entry.to_sdk_log_entry() for log_entry in log_entries
74
+ ]
75
+ try:
76
+ await AgenticTable.set_cell(
77
+ user_id=self._user_id,
78
+ company_id=self._company_id,
79
+ tableId=self.table_id,
80
+ rowOrder=row,
81
+ columnOrder=column,
82
+ text=text,
83
+ logEntries=log_entries_new,
84
+ )
85
+ except Exception as e:
86
+ self.logger.error(f"Error setting cell {row}, {column}: {e}.")
87
+
88
+ async def get_cell(
89
+ self, row: int, column: int, include_row_metadata: bool = True
90
+ ) -> MagicTableCell:
91
+ """
92
+ Gets the value of a cell in the Agentic Table.
93
+
94
+ Args:
95
+ row (int): The row index.
96
+ column (int): The column index.
97
+ include_row_metadata (bool): Whether to include the row metadata. Defaults to True.
98
+
99
+ Returns:
100
+ MagicTableCell: The MagicTableCell object.
101
+
102
+ """
103
+ cell_data = await AgenticTable.get_cell(
104
+ user_id=self._user_id,
105
+ company_id=self._company_id,
106
+ tableId=self.table_id,
107
+ rowOrder=row,
108
+ columnOrder=column,
109
+ includeRowMetadata=include_row_metadata, # type: ignore[arg-type]
110
+ )
111
+ return MagicTableCell.model_validate(cell_data)
112
+
113
+ async def set_multiple_cells(
114
+ self, cells: list[MagicTableCell], batch_size: int = 4000
115
+ ):
116
+ """
117
+ Sets the values of multiple cells in the Agentic Table.
118
+
119
+ Args:
120
+ cells (list[MagicTableCell]): The cells to set sorted by row and column.
121
+ batch_size (int): Number of cells to set in a single request.
122
+ """
123
+ for i in range(0, len(cells), batch_size):
124
+ batch = cells[i : i + batch_size]
125
+ await AgenticTable.set_multiple_cells(
126
+ user_id=self._user_id,
127
+ company_id=self._company_id,
128
+ tableId=self.table_id,
129
+ cells=[
130
+ SDKAgenticTableCell(
131
+ rowOrder=cell.row_order,
132
+ columnOrder=cell.column_order,
133
+ text=cell.text,
134
+ )
135
+ for cell in batch
136
+ ],
137
+ )
138
+
139
+ async def set_activity(
140
+ self,
141
+ text: str,
142
+ activity: MagicTableAction,
143
+ status: ActivityStatus = ActivityStatus.IN_PROGRESS,
144
+ ):
145
+ """
146
+ Sets the activity of the Agentic Table.
147
+
148
+ Args:
149
+ activity (str): The activity to set.
150
+ """
151
+ await AgenticTable.set_activity(
152
+ user_id=self._user_id,
153
+ company_id=self._company_id,
154
+ tableId=self.table_id,
155
+ activity=activity.value, # type: ignore[arg-type]
156
+ status=status.value, # type: ignore[arg-type]
157
+ text=text,
158
+ )
159
+
160
+ async def register_agent(self) -> None:
161
+ """
162
+ Registers the agent for the Agentic Table by updating the sheet state to PROCESSING.
163
+
164
+ Raises:
165
+ LockedAgenticTableError: If the Agentic Table is busy.
166
+ """
167
+ state = await AgenticTable.get_sheet_state(
168
+ user_id=self._user_id,
169
+ company_id=self._company_id,
170
+ tableId=self.table_id,
171
+ )
172
+ if state == AgenticTableSheetState.IDLE:
173
+ await AgenticTable.update_sheet_state(
174
+ user_id=self._user_id,
175
+ company_id=self._company_id,
176
+ tableId=self.table_id,
177
+ state=AgenticTableSheetState.PROCESSING,
178
+ )
179
+ return
180
+ # If the sheet is not idle, we cannot register the agent
181
+ raise LockedAgenticTableError(
182
+ f"Agentic Table is busy. Cannot register agent {self._event_id or self.table_id}."
183
+ )
184
+
185
+ async def deregister_agent(self):
186
+ """
187
+ Deregisters the agent for the Agentic Table by updating the sheet state to IDLE.
188
+
189
+ Raises:
190
+ LockedAgenticTableError: If the Agentic Table is busy.
191
+ """
192
+ await AgenticTable.update_sheet_state(
193
+ user_id=self._user_id,
194
+ company_id=self._company_id,
195
+ tableId=self.table_id,
196
+ state=AgenticTableSheetState.IDLE,
197
+ )
198
+
199
+ async def set_artifact(
200
+ self,
201
+ artifact_type: ArtifactType,
202
+ content_id: str,
203
+ mime_type: str,
204
+ name: str,
205
+ ):
206
+ """Upload/set report files to the Agentic Table.
207
+
208
+ Args:
209
+ artifact_type (ArtifactType): The type of artifact to set.
210
+ content_id (str): The content ID of the artifact.
211
+ mime_type (str): The MIME type of the artifact.
212
+ name (str): The name of the artifact.
213
+ """
214
+ await AgenticTable.set_artifact(
215
+ user_id=self._user_id,
216
+ company_id=self._company_id,
217
+ tableId=self.table_id,
218
+ artifactType=artifact_type.value,
219
+ contentId=content_id,
220
+ mimeType=mime_type,
221
+ name=name,
222
+ )
223
+
224
+ @deprecated("Use set_column_style instead.")
225
+ async def set_column_width(self, column: int, width: int):
226
+ await self.set_column_style(column=column, width=width)
227
+
228
+ async def set_column_style(
229
+ self,
230
+ column: int,
231
+ width: int | None = None,
232
+ cell_renderer: CellRendererTypes | None = None,
233
+ filter: FilterTypes | None = None,
234
+ editable: bool | None = None,
235
+ ):
236
+ """
237
+ Sets the style of a column in the Agentic Table.
238
+
239
+ Args:
240
+ column (int): The column index.
241
+ width (int | None, optional): The width of the column. Defaults to None.
242
+ cell_renderer (CellRenderer | None, optional): The cell renderer of the column. Defaults to None.
243
+ filter (FilterComponents | None, optional): The filter of the column. Defaults to None.
244
+ editable (bool | None, optional): Whether the column is editable. Defaults to None.
245
+
246
+ Raises:
247
+ Exception: If the column style is not set.
248
+ """
249
+ # Convert the input to the correct format
250
+ params = {}
251
+ if width is not None:
252
+ params["columnWidth"] = width
253
+ if cell_renderer is not None:
254
+ params["cellRenderer"] = cell_renderer.value
255
+ if filter is not None:
256
+ params["filter"] = filter.value
257
+ if editable is not None:
258
+ params["editable"] = editable
259
+ status, message = await AgenticTable.set_column_metadata(
260
+ user_id=self._user_id,
261
+ company_id=self._company_id,
262
+ tableId=self.table_id,
263
+ columnOrder=column,
264
+ **params,
265
+ )
266
+ if status:
267
+ return
268
+ raise Exception(message)
269
+
270
+ async def get_num_rows(self) -> int:
271
+ """
272
+ Gets the number of rows in the Agentic Table.
273
+
274
+ Returns:
275
+ int: The number of rows in the Agentic Table.
276
+ """
277
+ sheet_info = await AgenticTable.get_sheet_data(
278
+ user_id=self._user_id,
279
+ company_id=self._company_id,
280
+ tableId=self.table_id,
281
+ includeRowCount=True,
282
+ includeCells=False,
283
+ includeLogHistory=False,
284
+ )
285
+ return sheet_info["magicTableRowCount"]
286
+
287
+ async def get_sheet(
288
+ self,
289
+ start_row: int = 0,
290
+ end_row: int | None = None,
291
+ batch_size: int = 100,
292
+ include_log_history: bool = False,
293
+ include_cell_meta_data: bool = False,
294
+ include_row_metadata: bool = False,
295
+ ) -> MagicTableSheet:
296
+ """
297
+ Gets the sheet data from the Agentic Table paginated by batch_size.
298
+
299
+ Args:
300
+ start_row (int): The start row (inclusive).
301
+ end_row (int | None): The end row (not inclusive).
302
+ batch_size (int): The batch size.
303
+ include_log_history (bool): Whether to include the log history.
304
+ include_cell_meta_data (bool): Whether to include the cell metadata (renderer, selection, agreement status).
305
+ include_row_metadata (bool): Whether to include the row metadata (key value pairs).
306
+ Returns:
307
+ MagicTableSheet: The sheet data.
308
+ """
309
+ # Find the total number of rows
310
+ sheet_info = await AgenticTable.get_sheet_data(
311
+ user_id=self._user_id,
312
+ company_id=self._company_id,
313
+ tableId=self.table_id,
314
+ includeRowCount=True,
315
+ includeCells=False,
316
+ includeLogHistory=False,
317
+ includeCellMetaData=False,
318
+ )
319
+ total_rows = sheet_info["magicTableRowCount"]
320
+ if end_row is None or end_row > total_rows:
321
+ end_row = total_rows
322
+ if start_row > end_row:
323
+ raise Exception("Start row is greater than end row")
324
+ if start_row < 0 or end_row < 0:
325
+ raise Exception("Start row or end row is negative")
326
+
327
+ # Get the cells
328
+ cells = []
329
+ for row in range(start_row, end_row, batch_size):
330
+ end_row_batch = min(row + batch_size, end_row)
331
+ sheet_partial = await AgenticTable.get_sheet_data(
332
+ user_id=self._user_id,
333
+ company_id=self._company_id,
334
+ tableId=self.table_id,
335
+ includeCells=True,
336
+ includeLogHistory=include_log_history,
337
+ includeRowCount=False,
338
+ includeCellMetaData=include_cell_meta_data, # renderer, selection, agreement status
339
+ startRow=row,
340
+ endRow=end_row_batch - 1,
341
+ )
342
+ if "magicTableCells" in sheet_partial:
343
+ if include_row_metadata:
344
+ # If include_row_metadata is true, we need to get the row metadata for each cell.
345
+ row_metadata_map = {}
346
+ # TODO: @thea-unique This routine is not efficient and would be nice if we had this data passed on in get_sheet_data.
347
+ for cell in sheet_partial["magicTableCells"]:
348
+ row_order = cell.get("rowOrder") # type: ignore[assignment]
349
+ if row_order is not None and row_order not in row_metadata_map:
350
+ column_order = cell.get("columnOrder") # type: ignore[assignment]
351
+ self.logger.info(
352
+ f"Getting row metadata for cell {row_order}, {column_order}"
353
+ )
354
+ cell_with_row_metadata = await self.get_cell(
355
+ row_order,
356
+ column_order, # type: ignore[arg-type]
357
+ )
358
+ if cell_with_row_metadata.row_metadata:
359
+ print(cell_with_row_metadata.row_metadata)
360
+ row_metadata_map[cell_with_row_metadata.row_order] = (
361
+ cell_with_row_metadata.row_metadata
362
+ )
363
+ cell["rowMetadata"] = ( # type: ignore[assignment]
364
+ cell_with_row_metadata.row_metadata
365
+ )
366
+ # Assign row_metadata to all cells
367
+ for cell in sheet_partial["magicTableCells"]:
368
+ row_order = cell.get("rowOrder") # type: ignore[assignment]
369
+ if row_order is not None and row_order in row_metadata_map:
370
+ cell["rowMetadata"] = row_metadata_map[ # type: ignore[assignment]
371
+ row_order
372
+ ]
373
+
374
+ cells.extend(sheet_partial["magicTableCells"])
375
+
376
+ sheet_info["magicTableCells"] = cells
377
+ return MagicTableSheet.model_validate(sheet_info)
378
+
379
+ async def get_sheet_metadata(self) -> list[RowMetadataEntry]:
380
+ """
381
+ Gets the sheet metadata from the Agentic Table.
382
+
383
+ Returns:
384
+ list[RowMetadataEntry]: The sheet metadata.
385
+ """
386
+ sheet_info = await AgenticTable.get_sheet_data(
387
+ user_id=self._user_id,
388
+ company_id=self._company_id,
389
+ tableId=self.table_id,
390
+ includeSheetMetadata=True, # type: ignore[arg-type]
391
+ )
392
+ return [
393
+ RowMetadataEntry.model_validate(metadata)
394
+ for metadata in sheet_info["magicTableSheetMetadata"]
395
+ ]
396
+
397
+ async def set_cell_metadata(
398
+ self,
399
+ row: int,
400
+ column: int,
401
+ selected: bool | None = None,
402
+ selection_method: SelectionMethod | None = None,
403
+ agreement_status: AgreementStatus | None = None,
404
+ ) -> None:
405
+ """
406
+ Sets the cell metadata for the Agentic Table.
407
+ NOTE: This is not to be confused with the sheet metadata and is associated rather with selection and agreement status, row locking etc.
408
+
409
+ Args:
410
+ row (int): The row index.
411
+ column (int): The column index.
412
+ selected (bool | None): Whether the cell is selected.
413
+ selection_method (SelectionMethod | None): The method of selection.
414
+ agreement_status (AgreementStatus | None): The agreement status.
415
+ """
416
+ params = {}
417
+ if selected is not None:
418
+ params["selected"] = selected
419
+ if selection_method is not None:
420
+ params["selectionMethod"] = selection_method
421
+ if agreement_status is not None:
422
+ params["agreementStatus"] = agreement_status
423
+ result = await AgenticTable.set_cell_metadata(
424
+ user_id=self._user_id,
425
+ company_id=self._company_id,
426
+ tableId=self.table_id,
427
+ rowOrder=row,
428
+ columnOrder=column,
429
+ **params,
430
+ )
431
+ if result["status"]: # type: ignore
432
+ return
433
+ raise Exception(result["message"]) # type: ignore
434
+
435
+ async def update_row_verification_status(
436
+ self,
437
+ row_orders: list[int],
438
+ status: RowVerificationStatus,
439
+ ):
440
+ """Update the verification status of multiple rows at once.
441
+
442
+ Args:
443
+ row_orders (list[int]): The row indexes to update.
444
+ status (RowVerificationStatus): The verification status to set.
445
+ """
446
+ await AgenticTable.bulk_update_status(
447
+ user_id=self._user_id,
448
+ company_id=self._company_id,
449
+ tableId=self.table_id,
450
+ rowOrders=row_orders,
451
+ status=status,
452
+ )
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  from logging import getLogger
3
- from typing import TYPE_CHECKING, Any, Callable, TypeVar
3
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable, TypeVar
4
4
 
5
5
  from pydantic import ValidationError
6
6
 
@@ -35,13 +35,15 @@ def default_event_handler(event: Any) -> int:
35
35
 
36
36
  T = TypeVar("T", bound=BaseEvent)
37
37
 
38
+ EventHandlerType = Callable[[T], Awaitable[int]] | Callable[[T], int]
39
+
38
40
 
39
41
  def build_unique_custom_app(
40
42
  *,
41
43
  title: str = "Unique Chat App",
42
44
  webhook_path: str = "/webhook",
43
45
  settings: UniqueSettings,
44
- event_handler: Callable[[T], int] = default_event_handler,
46
+ event_handler: EventHandlerType = default_event_handler,
45
47
  event_constructor: Callable[..., T] = ChatEvent,
46
48
  subscribed_event_names: list[str] | None = None,
47
49
  ) -> "FastAPI":
@@ -106,11 +108,17 @@ def build_unique_custom_app(
106
108
 
107
109
  try:
108
110
  event = event_constructor(**event_data)
109
- if event.filter_event(filter_options=settings.chat_event_filter_options):
110
- return JSONResponse(
111
- status_code=status.HTTP_200_OK,
112
- content={"error": "Event filtered out"},
113
- )
111
+ if (
112
+ settings.chat_event_filter_options
113
+ and settings.chat_event_filter_options.assistant_ids
114
+ ):
115
+ if event.filter_event(
116
+ filter_options=settings.chat_event_filter_options
117
+ ):
118
+ return JSONResponse(
119
+ status_code=status.HTTP_200_OK,
120
+ content={"error": "Event filtered out"},
121
+ )
114
122
  except ValidationError as e:
115
123
  # pydantic errors https://docs.pydantic.dev/2.10/errors/errors/
116
124
  logger.error(f"Validation error with model: {e.json()}", exc_info=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_toolkit
3
- Version: 1.40.0
3
+ Version: 1.41.0
4
4
  Summary:
5
5
  License: Proprietary
6
6
  Author: Cedric Klinkert
@@ -123,6 +123,13 @@ All notable changes to this project will be documented in this file.
123
123
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
124
124
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
125
125
 
126
+
127
+ ## [1.41.0] - 2025-12-29
128
+ - Add `AgenticTable` service to unique_toolkit
129
+
130
+ ## [1.40.0] - 2025-12-22
131
+ - Add option to use retrieve referenced chunks from their order
132
+
126
133
  ## [1.40.0] - 2025-12-22
127
134
  - Add `hide_in_chat` parameter to `upload_to_chat_from_bytes` and `upload_to_chat_from_bytes_async`
128
135
  - Hide code interpreter files in chat
@@ -54,7 +54,7 @@ unique_toolkit/agentic/evaluation/hallucination/constants.py,sha256=SoGmoYti2J33
54
54
  unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py,sha256=x5ta2Fum4fE5ySgIXPKlnbTtmV140z0IazSATd0-REg,4092
55
55
  unique_toolkit/agentic/evaluation/hallucination/prompts.py,sha256=O3Hi_rOzZlujvnO2wn2jhoPmrYLjzVtRWwxn5Q81m9Y,3405
56
56
  unique_toolkit/agentic/evaluation/hallucination/service.py,sha256=WJF1f45uHnYLx1S4TW31bSFobFpV-YlOS3G_zMhuBVU,2512
57
- unique_toolkit/agentic/evaluation/hallucination/utils.py,sha256=kM5kqUZb5riINr1Oqf3wyrj25o25H2WaJ64haKTJLMo,8213
57
+ unique_toolkit/agentic/evaluation/hallucination/utils.py,sha256=uHKTJw4kJyq0_Gi-EOhbocBAij4_Vzn3dW1wTxAuFg4,9706
58
58
  unique_toolkit/agentic/evaluation/output_parser.py,sha256=0FDo8YY_Dc4qlTNeYyQkznzIFj9aX9wMrLOTbhhTl6g,1418
59
59
  unique_toolkit/agentic/evaluation/schemas.py,sha256=m9JMCUmeqP8KhsJOVEzsz6dRXUe1uKw-bxRDtn5qwvM,3156
60
60
  unique_toolkit/agentic/evaluation/tests/test_context_relevancy_service.py,sha256=4tDxHTApbaTMxN1sNS8WCqj2BweRk6YqZ5_zHP45jto,7977
@@ -137,9 +137,12 @@ unique_toolkit/agentic/tools/utils/source_handling/__init__.py,sha256=47DEQpj8HB
137
137
  unique_toolkit/agentic/tools/utils/source_handling/schema.py,sha256=iHBKuks6tUy8tvian4Pd0B6_-8__SehVVNcxIUAUjEA,882
138
138
  unique_toolkit/agentic/tools/utils/source_handling/source_formatting.py,sha256=uZ0QXqrPWgId3ZA67dvjHQ6xrW491LK1xxx_sVJmFHg,9160
139
139
  unique_toolkit/agentic/tools/utils/source_handling/tests/test_source_formatting.py,sha256=EA8iVvb3L91OFk2XMbGcFuhe2etqm3Sx9QCYDGiOSOM,6995
140
+ unique_toolkit/agentic_table/__init__.py,sha256=smJFstF5qH35RmZfzJUigdsVgUVNOza9KxMbraWrm9E,1411
141
+ unique_toolkit/agentic_table/schemas.py,sha256=VOEp3yjpgE3j1plPAlWNoQ16tDB3pBvu6HsS9WSqdu0,8907
142
+ unique_toolkit/agentic_table/service.py,sha256=Bgi0B_AT7Mswbud3VI-pNcnc_85xFu4S1dbLttYF7yY,16290
140
143
  unique_toolkit/app/__init__.py,sha256=OaylhLwxeRlsHlcFGSlR5R7oREFsjv9wRdxuVZBYM_8,1371
141
144
  unique_toolkit/app/dev_util.py,sha256=J20peCvrSQKfMGdYPYwCirs3Yq2v_e33GzNBzNKbWN4,5531
142
- unique_toolkit/app/fast_api_factory.py,sha256=i65-zjTg2D4GOKIbaSkHBTxgjAP-rmFDeJpVYKxziXk,4633
145
+ unique_toolkit/app/fast_api_factory.py,sha256=4xoN23abH4LnRsM9atu97WEHK3-nH29LiWsI50whH_U,4923
143
146
  unique_toolkit/app/init_logging.py,sha256=Sh26SRxOj8i8dzobKhYha2lLrkrMTHfB1V4jR3h23gQ,678
144
147
  unique_toolkit/app/init_sdk.py,sha256=5_oDoETr6akwYyBCb0ivTdMNu3SVgPSkrXcDS6ELyY8,2269
145
148
  unique_toolkit/app/performance/async_tasks.py,sha256=H0l3OAcosLwNHZ8d2pd-Di4wHIXfclEvagi5kfqLFPA,1941
@@ -212,7 +215,7 @@ unique_toolkit/short_term_memory/service.py,sha256=5PeVBu1ZCAfyDb2HLVvlmqSbyzBBu
212
215
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
213
216
  unique_toolkit/smart_rules/compile.py,sha256=Ozhh70qCn2yOzRWr9d8WmJeTo7AQurwd3tStgBMPFLA,1246
214
217
  unique_toolkit/test_utilities/events.py,sha256=_mwV2bs5iLjxS1ynDCjaIq-gjjKhXYCK-iy3dRfvO3g,6410
215
- unique_toolkit-1.40.0.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
216
- unique_toolkit-1.40.0.dist-info/METADATA,sha256=iEwBy-tYmByLek8I9T4Eck-LCO5eDQKtk8fkAgnhS-g,46331
217
- unique_toolkit-1.40.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
218
- unique_toolkit-1.40.0.dist-info/RECORD,,
218
+ unique_toolkit-1.41.0.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
219
+ unique_toolkit-1.41.0.dist-info/METADATA,sha256=nfWPkb3z7-IQBPeeLrcFN8AJm2MmuwPGlA9t-5XgStY,46495
220
+ unique_toolkit-1.41.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
221
+ unique_toolkit-1.41.0.dist-info/RECORD,,