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
@@ -0,0 +1,131 @@
1
+ import json
2
+ from logging import getLogger
3
+ from typing import TYPE_CHECKING, Any, Callable, TypeVar
4
+
5
+ from pydantic import ValidationError
6
+
7
+ if TYPE_CHECKING:
8
+ from fastapi import BackgroundTasks, FastAPI, Request, status
9
+ from fastapi.responses import JSONResponse
10
+ else:
11
+ try:
12
+ from fastapi import BackgroundTasks, FastAPI, Request, status
13
+ from fastapi.responses import JSONResponse
14
+ except ImportError:
15
+ FastAPI = None # type: ignore[assignment, misc]
16
+ Request = None # type: ignore[assignment, misc]
17
+ status = None # type: ignore[assignment, misc]
18
+ JSONResponse = None # type: ignore[assignment, misc]
19
+ BackgroundTasks = None # type: ignore[assignment, misc]
20
+
21
+ from unique_toolkit.app.schemas import BaseEvent, ChatEvent, EventName
22
+ from unique_toolkit.app.unique_settings import UniqueSettings
23
+
24
+ logger = getLogger(__name__)
25
+
26
+
27
+ def default_event_handler(event: Any) -> int:
28
+ logger.info("Event received at event handler")
29
+ if status is not None:
30
+ return status.HTTP_200_OK
31
+ else:
32
+ # No fastapi installed
33
+ return 200
34
+
35
+
36
+ T = TypeVar("T", bound=BaseEvent)
37
+
38
+
39
+ def build_unique_custom_app(
40
+ *,
41
+ title: str = "Unique Chat App",
42
+ webhook_path: str = "/webhook",
43
+ settings: UniqueSettings,
44
+ event_handler: Callable[[T], int] = default_event_handler,
45
+ event_constructor: Callable[..., T] = ChatEvent,
46
+ subscribed_event_names: list[str] | None = None,
47
+ ) -> "FastAPI":
48
+ """Factory class for creating FastAPI apps with Unique webhook handling."""
49
+ if FastAPI is None:
50
+ raise ImportError(
51
+ "FastAPI is not installed. Install it with: poetry install --with fastapi"
52
+ )
53
+
54
+ app = FastAPI(title=title)
55
+
56
+ if subscribed_event_names is None:
57
+ subscribed_event_names = [EventName.EXTERNAL_MODULE_CHOSEN]
58
+
59
+ @app.get(path="/")
60
+ async def health_check() -> JSONResponse:
61
+ """Health check endpoint."""
62
+ return JSONResponse(content={"status": "healthy", "service": title})
63
+
64
+ @app.post(path=webhook_path)
65
+ async def webhook_handler(
66
+ request: Request, background_tasks: BackgroundTasks
67
+ ) -> JSONResponse:
68
+ """
69
+ Webhook endpoint for receiving events from Unique platform.
70
+
71
+ This endpoint:
72
+ 1. Verifies the webhook signature
73
+ 2. Constructs an event from the payload
74
+ 3. Calls the configured event handler
75
+ """
76
+ # Get raw body and headers
77
+ body = await request.body()
78
+ headers = dict(request.headers)
79
+
80
+ from unique_toolkit.app.webhook import is_webhook_signature_valid
81
+
82
+ if not is_webhook_signature_valid(
83
+ headers=headers,
84
+ payload=body,
85
+ endpoint_secret=settings.app.endpoint_secret.get_secret_value(),
86
+ ):
87
+ return JSONResponse(
88
+ status_code=status.HTTP_401_UNAUTHORIZED,
89
+ content={"error": "Invalid webhook signature"},
90
+ )
91
+
92
+ try:
93
+ event_data = json.loads(body.decode(encoding="utf-8"))
94
+ except json.JSONDecodeError as e:
95
+ logger.error(f"Error parsing event: {e}", exc_info=True)
96
+ return JSONResponse(
97
+ status_code=status.HTTP_400_BAD_REQUEST,
98
+ content={"error": f"Invalid event format: {str(e)}"},
99
+ )
100
+
101
+ if event_data["event"] not in subscribed_event_names:
102
+ return JSONResponse(
103
+ status_code=status.HTTP_400_BAD_REQUEST,
104
+ content={"error": "Not subscribed event"},
105
+ )
106
+
107
+ try:
108
+ 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
+ )
114
+ except ValidationError as e:
115
+ # pydantic errors https://docs.pydantic.dev/2.10/errors/errors/
116
+ logger.error(f"Validation error with model: {e.json()}", exc_info=True)
117
+ raise e
118
+ except ValueError as e:
119
+ logger.error(f"Error deserializing event: {e}", exc_info=True)
120
+ return JSONResponse(
121
+ status_code=status.HTTP_400_BAD_REQUEST,
122
+ content={"error": "Invalid event"},
123
+ )
124
+
125
+ # Run the task in background so that we don't block for long running tasks
126
+ background_tasks.add_task(event_handler, event)
127
+ return JSONResponse(
128
+ status_code=status.HTTP_200_OK, content={"message": "Event received"}
129
+ )
130
+
131
+ return app
@@ -1,6 +1,11 @@
1
1
  import os
2
+ from pathlib import Path
3
+ from typing import overload
2
4
 
3
5
  import unique_sdk
6
+ from typing_extensions import deprecated
7
+
8
+ from unique_toolkit.app.unique_settings import UniqueSettings
4
9
 
5
10
 
6
11
  def get_env(var_name, default=None, strict=False):
@@ -24,12 +29,38 @@ def get_env(var_name, default=None, strict=False):
24
29
  return val or default
25
30
 
26
31
 
27
- def init_sdk(strict_all_vars=False):
32
+ @overload
33
+ def init_unique_sdk(*, env_file: Path | None = None): ...
34
+
35
+
36
+ @overload
37
+ def init_unique_sdk(*, unique_settings: UniqueSettings): ...
38
+
39
+
40
+ def init_unique_sdk(
41
+ *, unique_settings: UniqueSettings | None = None, env_file: Path | None = None
42
+ ):
43
+ if unique_settings:
44
+ unique_sdk.api_key = unique_settings.app.key.get_secret_value()
45
+ unique_sdk.app_id = unique_settings.app.id.get_secret_value()
46
+ unique_sdk.api_base = unique_settings.api.sdk_url()
47
+ elif env_file:
48
+ unique_settings = UniqueSettings.from_env(env_file=env_file)
49
+ unique_sdk.api_key = unique_settings.app.key.get_secret_value()
50
+ unique_sdk.app_id = unique_settings.app.id.get_secret_value()
51
+ unique_sdk.api_base = unique_settings.api.sdk_url()
52
+
53
+
54
+ @deprecated("Use init_unique_sdk instead")
55
+ def init_sdk(
56
+ strict_all_vars: bool = False,
57
+ ):
28
58
  """Initialize the SDK.
29
59
 
30
60
  Args:
31
61
  strict_all_vars (bool, optional): This method raises a ValueError if strict and no value is found in the environment. Defaults to False.
32
62
  """
63
+
33
64
  unique_sdk.api_key = get_env("API_KEY", default="dummy", strict=strict_all_vars)
34
65
  unique_sdk.app_id = get_env("APP_ID", default="dummy", strict=strict_all_vars)
35
66
  unique_sdk.api_base = get_env("API_BASE", default=None, strict=strict_all_vars)
@@ -1,23 +1,43 @@
1
+ import json
1
2
  from enum import StrEnum
2
- from typing import Any, Optional
3
+ from logging import getLogger
4
+ from pathlib import Path
5
+ from typing import Any, Generic, Optional, TypeVar, override
3
6
 
4
7
  from humps import camelize
5
- from pydantic import BaseModel, ConfigDict, Field
8
+ from pydantic import BaseModel, ConfigDict, Field, JsonValue, field_validator
9
+ from pydantic_settings import BaseSettings
6
10
  from typing_extensions import deprecated
7
11
 
12
+ from unique_toolkit._common.exception import ConfigurationException
13
+ from unique_toolkit.app.unique_settings import UniqueChatEventFilterOptions
14
+ from unique_toolkit.smart_rules.compile import UniqueQL, parse_uniqueql
15
+
16
+ FilterOptionsT = TypeVar("FilterOptionsT", bound=BaseSettings)
17
+
8
18
  # set config to convert camelCase to snake_case
9
19
  model_config = ConfigDict(
10
20
  alias_generator=camelize,
11
21
  populate_by_name=True,
12
22
  arbitrary_types_allowed=True,
13
23
  )
24
+ _logger = getLogger(__name__)
14
25
 
15
26
 
16
27
  class EventName(StrEnum):
17
28
  EXTERNAL_MODULE_CHOSEN = "unique.chat.external-module.chosen"
18
-
19
-
20
- class BaseEvent(BaseModel):
29
+ USER_MESSAGE_CREATED = "unique.chat.user-message.created"
30
+ INGESTION_CONTENT_UPLOADED = "unique.ingestion.content.uploaded"
31
+ INGESTION_CONTENT_FINISHED = "unique.ingestion.content.finished"
32
+ MAGIC_TABLE_IMPORT_COLUMNS = "unique.magic-table.import-columns"
33
+ MAGIC_TABLE_ADD_META_DATA = "unique.magic-table.add-meta-data"
34
+ MAGIC_TABLE_ADD_DOCUMENT = "unique.magic-table.add-document"
35
+ MAGIC_TABLE_DELETE_ROW = "unique.magic-table.delete-row"
36
+ MAGIC_TABLE_DELETE_COLUMN = "unique.magic-table.delete-column"
37
+ MAGIC_TABLE_UPDATE_CELL = "unique.magic-table.update-cell"
38
+
39
+
40
+ class BaseEvent(BaseModel, Generic[FilterOptionsT]):
21
41
  model_config = model_config
22
42
 
23
43
  id: str
@@ -25,6 +45,72 @@ class BaseEvent(BaseModel):
25
45
  user_id: str
26
46
  company_id: str
27
47
 
48
+ @classmethod
49
+ def from_json_file(cls, file_path: Path) -> "BaseEvent":
50
+ if not file_path.exists():
51
+ raise FileNotFoundError(f"File not found: {file_path}")
52
+ with file_path.open("r", encoding="utf-8") as f:
53
+ data = json.load(f)
54
+ return cls.model_validate(data)
55
+
56
+ def filter_event(self, *, filter_options: FilterOptionsT | None = None) -> bool:
57
+ """Determine if event should be filtered out and be neglected."""
58
+ return False
59
+
60
+
61
+ ###
62
+ # MCP schemas
63
+ ###
64
+
65
+
66
+ class McpTool(BaseModel):
67
+ model_config = model_config
68
+
69
+ name: str
70
+ description: Optional[str] = None
71
+ input_schema: dict[str, Any]
72
+ output_schema: Optional[dict[str, Any]] = None
73
+ annotations: Optional[dict[str, Any]] = None
74
+ title: Optional[str] = Field(
75
+ default=None,
76
+ description="The display title for a tool. This is a Unique specific field.",
77
+ )
78
+ icon: Optional[str] = Field(
79
+ default=None,
80
+ description="An icon name from the Lucide icon set for the tool. This is a Unique specific field.",
81
+ )
82
+ system_prompt: Optional[str] = Field(
83
+ default=None,
84
+ description="An optional system prompt for the tool. This is a Unique specific field.",
85
+ )
86
+ user_prompt: Optional[str] = Field(
87
+ default=None,
88
+ description="An optional user prompt for the tool. This is a Unique specific field.",
89
+ )
90
+ tool_format_information: Optional[str] = Field(
91
+ default=None,
92
+ description="An optional tool format information. This is a Unique specific field.",
93
+ )
94
+ is_connected: bool = Field(
95
+ description="Whether the tool is connected to the MCP server. This is a Unique specific field.",
96
+ )
97
+
98
+
99
+ class McpServer(BaseModel):
100
+ model_config = model_config
101
+
102
+ id: str
103
+ name: str
104
+ system_prompt: Optional[str] = Field(
105
+ default=None,
106
+ description="An optional system prompt for the MCP server.",
107
+ )
108
+ user_prompt: Optional[str] = Field(
109
+ default=None,
110
+ description="An optional user prompt for the MCP server.",
111
+ )
112
+ tools: list[McpTool] = []
113
+
28
114
 
29
115
  ###
30
116
  # ChatEvent schemas
@@ -95,45 +181,134 @@ class ChatEventPayload(BaseModel):
95
181
  assistant_id: str
96
182
  user_message: ChatEventUserMessage
97
183
  assistant_message: ChatEventAssistantMessage
98
- text: Optional[str] = None
99
- additional_parameters: Optional[ChatEventAdditionalParameters] = None
100
- user_metadata: Optional[dict[str, Any]] = None
101
- tool_choices: Optional[list[str]] = Field(
102
- default=[],
184
+ text: str | None = None
185
+ additional_parameters: ChatEventAdditionalParameters | None = None
186
+ user_metadata: dict[str, Any] | None = Field(
187
+ default_factory=dict,
188
+ )
189
+ tool_choices: list[str] = Field(
190
+ default_factory=list,
103
191
  description="A list containing the tool names the user has chosen to be activated.",
104
192
  )
105
- tool_parameters: Optional[dict[str, Any]] = None
106
- metadata_filter: Optional[dict[str, Any]] = None
193
+ disabled_tools: list[str] = Field(
194
+ default_factory=list,
195
+ description="A list containing the tool names of tools that are disabled at the company level",
196
+ )
197
+ tool_parameters: dict[str, Any] = Field(
198
+ default_factory=dict,
199
+ description="Parameters extracted from module selection function calling the tool.",
200
+ )
201
+ # Default is None as empty dict triggers error in `backend-ingestion`
202
+ metadata_filter: dict[str, Any] | None = Field(
203
+ default=None,
204
+ description="Metadata filter compiled after module selection function calling and scope rules.",
205
+ )
206
+ raw_scope_rules: UniqueQL | None = Field(
207
+ default=None,
208
+ description="Raw UniqueQL rule that can be compiled to a metadata filter.",
209
+ )
210
+ mcp_servers: list[McpServer] = Field(
211
+ default_factory=list,
212
+ description="A list of MCP servers with tools available for the chat session.",
213
+ )
214
+ message_execution_id: str | None = Field(
215
+ default=None,
216
+ description="The message execution id for triggering the chat event. Originates from the message execution service.",
217
+ )
218
+ session_config: JsonValue | None = Field(
219
+ default=None,
220
+ description="The session configuration for the chat session.",
221
+ )
222
+
223
+ @field_validator("raw_scope_rules", mode="before")
224
+ def validate_scope_rules(cls, value: dict[str, Any] | None) -> UniqueQL | None:
225
+ if value:
226
+ return parse_uniqueql(value)
107
227
 
108
228
 
109
229
  @deprecated("""Use `ChatEventPayload` instead.
110
230
  This class will be removed in the next major version.""")
111
231
  class EventPayload(ChatEventPayload):
112
- user_message: EventUserMessage
113
- assistant_message: EventAssistantMessage
114
- additional_parameters: Optional[EventAdditionalParameters] = None
232
+ pass
233
+ # user_message: EventUserMessage
234
+ # assistant_message: EventAssistantMessage
235
+ # additional_parameters: Optional[EventAdditionalParameters] = None
115
236
 
116
237
 
117
- @deprecated(
118
- """Use the more specific `ChatEvent` instead that has the same properties. \
119
- This class will be removed in the next major version."""
120
- )
121
- class Event(BaseModel):
238
+ class ChatEvent(BaseEvent):
122
239
  model_config = model_config
123
240
 
124
- id: str
125
- event: EventName
126
- user_id: str
127
- company_id: str
128
- payload: EventPayload
241
+ payload: ChatEventPayload
129
242
  created_at: Optional[int] = None
130
243
  version: Optional[str] = None
131
244
 
245
+ @classmethod
246
+ def from_json_file(cls, file_path: Path) -> "ChatEvent":
247
+ if not file_path.exists():
248
+ raise FileNotFoundError(f"File not found: {file_path}")
249
+ with file_path.open("r", encoding="utf-8") as f:
250
+ data = json.load(f)
251
+ return cls.model_validate(data)
252
+
253
+ def get_initial_debug_info(self) -> dict[str, Any]:
254
+ """Get the debug information for the chat event"""
255
+
256
+ # TODO: Make sure this coincides with what is shown in the first user message
257
+ return {
258
+ "user_metadata": self.payload.user_metadata,
259
+ "tool_parameters": self.payload.tool_parameters,
260
+ "chosen_module": self.payload.name,
261
+ "assistant": {"id": self.payload.assistant_id},
262
+ }
263
+
264
+ @override
265
+ def filter_event(
266
+ self, *, filter_options: UniqueChatEventFilterOptions | None = None
267
+ ) -> bool:
268
+ # Empty string evals to False
269
+
270
+ if filter_options is None:
271
+ return False # Don't filter when no options provided
272
+
273
+ if not filter_options.assistant_ids and not filter_options.references_in_code:
274
+ raise ConfigurationException(
275
+ "No filter options provided, all events will be filtered! \n"
276
+ "Please define: \n"
277
+ " - 'UNIQUE_CHAT_EVENT_FILTER_OPTIONS_ASSISTANT_IDS' \n"
278
+ " - 'UNIQUE_CHAT_EVENT_FILTER_OPTIONS_REFERENCES_IN_CODE' \n"
279
+ "in your environment variables."
280
+ )
281
+
282
+ # Per reference in code there can be multiple assistants
283
+ if (
284
+ filter_options.assistant_ids
285
+ and self.payload.assistant_id not in filter_options.assistant_ids
286
+ ):
287
+ return True
288
+
289
+ if (
290
+ filter_options.references_in_code
291
+ and self.payload.name not in filter_options.references_in_code
292
+ ):
293
+ return True
294
+
295
+ return super().filter_event(filter_options=filter_options)
132
296
 
133
- class ChatEvent(BaseEvent):
134
- model_config = model_config
135
297
 
136
- event: EventName
137
- payload: ChatEventPayload
138
- created_at: Optional[int] = None
139
- version: Optional[str] = None
298
+ @deprecated(
299
+ """Use the more specific `ChatEvent` instead that has the same properties. \
300
+ This class will be removed in the next major version."""
301
+ )
302
+ class Event(ChatEvent):
303
+ pass
304
+ # The below should only affect type hints
305
+ # event: EventName T
306
+ # payload: EventPayload
307
+
308
+ @classmethod
309
+ def from_json_file(cls, file_path: Path) -> "Event":
310
+ if not file_path.exists():
311
+ raise FileNotFoundError(f"File not found: {file_path}")
312
+ with file_path.open("r", encoding="utf-8") as f:
313
+ data = json.load(f)
314
+ return cls.model_validate(data)