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.
- unique_toolkit/__init__.py +36 -3
- unique_toolkit/_common/api_calling/human_verification_manager.py +357 -0
- unique_toolkit/_common/base_model_type_attribute.py +303 -0
- unique_toolkit/_common/chunk_relevancy_sorter/config.py +49 -0
- unique_toolkit/_common/chunk_relevancy_sorter/exception.py +5 -0
- unique_toolkit/_common/chunk_relevancy_sorter/schemas.py +46 -0
- unique_toolkit/_common/chunk_relevancy_sorter/service.py +374 -0
- unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +275 -0
- unique_toolkit/_common/default_language_model.py +12 -0
- unique_toolkit/_common/docx_generator/__init__.py +7 -0
- unique_toolkit/_common/docx_generator/config.py +12 -0
- unique_toolkit/_common/docx_generator/schemas.py +80 -0
- unique_toolkit/_common/docx_generator/service.py +225 -0
- unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
- unique_toolkit/_common/endpoint_builder.py +368 -0
- unique_toolkit/_common/endpoint_requestor.py +480 -0
- unique_toolkit/_common/exception.py +24 -0
- unique_toolkit/_common/experimental/endpoint_builder.py +368 -0
- unique_toolkit/_common/experimental/endpoint_requestor.py +488 -0
- unique_toolkit/_common/feature_flags/schema.py +9 -0
- unique_toolkit/_common/pydantic/rjsf_tags.py +936 -0
- unique_toolkit/_common/pydantic_helpers.py +174 -0
- unique_toolkit/_common/referencing.py +53 -0
- unique_toolkit/_common/string_utilities.py +140 -0
- unique_toolkit/_common/tests/test_referencing.py +521 -0
- unique_toolkit/_common/tests/test_string_utilities.py +506 -0
- unique_toolkit/_common/token/image_token_counting.py +67 -0
- unique_toolkit/_common/token/token_counting.py +204 -0
- unique_toolkit/_common/utils/__init__.py +1 -0
- unique_toolkit/_common/utils/files.py +43 -0
- unique_toolkit/_common/utils/image/encode.py +25 -0
- unique_toolkit/_common/utils/jinja/helpers.py +10 -0
- unique_toolkit/_common/utils/jinja/render.py +18 -0
- unique_toolkit/_common/utils/jinja/schema.py +65 -0
- unique_toolkit/_common/utils/jinja/utils.py +80 -0
- unique_toolkit/_common/utils/structured_output/__init__.py +1 -0
- unique_toolkit/_common/utils/structured_output/schema.py +5 -0
- unique_toolkit/_common/utils/write_configuration.py +51 -0
- unique_toolkit/_common/validators.py +101 -4
- unique_toolkit/agentic/__init__.py +1 -0
- unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +28 -0
- unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +278 -0
- unique_toolkit/agentic/evaluation/config.py +36 -0
- unique_toolkit/{evaluators → agentic/evaluation}/context_relevancy/prompts.py +25 -0
- unique_toolkit/agentic/evaluation/context_relevancy/schema.py +80 -0
- unique_toolkit/agentic/evaluation/context_relevancy/service.py +273 -0
- unique_toolkit/agentic/evaluation/evaluation_manager.py +218 -0
- unique_toolkit/agentic/evaluation/hallucination/constants.py +61 -0
- unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +112 -0
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/prompts.py +1 -1
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/service.py +20 -16
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/utils.py +32 -21
- unique_toolkit/{evaluators → agentic/evaluation}/output_parser.py +20 -2
- unique_toolkit/{evaluators → agentic/evaluation}/schemas.py +27 -7
- unique_toolkit/agentic/evaluation/tests/test_context_relevancy_service.py +253 -0
- unique_toolkit/agentic/evaluation/tests/test_output_parser.py +87 -0
- unique_toolkit/agentic/history_manager/history_construction_with_contents.py +298 -0
- unique_toolkit/agentic/history_manager/history_manager.py +241 -0
- unique_toolkit/agentic/history_manager/loop_token_reducer.py +484 -0
- unique_toolkit/agentic/history_manager/utils.py +96 -0
- unique_toolkit/agentic/message_log_manager/__init__.py +5 -0
- unique_toolkit/agentic/message_log_manager/service.py +93 -0
- unique_toolkit/agentic/postprocessor/postprocessor_manager.py +212 -0
- unique_toolkit/agentic/reference_manager/reference_manager.py +103 -0
- unique_toolkit/agentic/responses_api/__init__.py +19 -0
- unique_toolkit/agentic/responses_api/postprocessors/code_display.py +71 -0
- unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +297 -0
- unique_toolkit/agentic/responses_api/stream_handler.py +15 -0
- unique_toolkit/agentic/short_term_memory_manager/persistent_short_term_memory_manager.py +141 -0
- unique_toolkit/agentic/thinking_manager/thinking_manager.py +103 -0
- unique_toolkit/agentic/tools/__init__.py +1 -0
- unique_toolkit/agentic/tools/a2a/__init__.py +36 -0
- unique_toolkit/agentic/tools/a2a/config.py +17 -0
- unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +15 -0
- unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +66 -0
- unique_toolkit/agentic/tools/a2a/evaluation/config.py +55 -0
- unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +260 -0
- unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2 +9 -0
- unique_toolkit/agentic/tools/a2a/manager.py +55 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +21 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py +240 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +84 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/config.py +78 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/display.py +264 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/references.py +101 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display.py +421 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +2103 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py +603 -0
- unique_toolkit/agentic/tools/a2a/prompts.py +46 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py +6 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/service.py +91 -0
- unique_toolkit/agentic/tools/a2a/tool/__init__.py +4 -0
- unique_toolkit/agentic/tools/a2a/tool/_memory.py +26 -0
- unique_toolkit/agentic/tools/a2a/tool/_schema.py +9 -0
- unique_toolkit/agentic/tools/a2a/tool/config.py +158 -0
- unique_toolkit/agentic/tools/a2a/tool/service.py +393 -0
- unique_toolkit/agentic/tools/agent_chunks_hanlder.py +65 -0
- unique_toolkit/agentic/tools/config.py +128 -0
- unique_toolkit/agentic/tools/factory.py +44 -0
- unique_toolkit/agentic/tools/mcp/__init__.py +4 -0
- unique_toolkit/agentic/tools/mcp/manager.py +71 -0
- unique_toolkit/agentic/tools/mcp/models.py +28 -0
- unique_toolkit/agentic/tools/mcp/tool_wrapper.py +234 -0
- unique_toolkit/agentic/tools/openai_builtin/__init__.py +11 -0
- unique_toolkit/agentic/tools/openai_builtin/base.py +46 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +8 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +88 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +250 -0
- unique_toolkit/agentic/tools/openai_builtin/manager.py +79 -0
- unique_toolkit/agentic/tools/schemas.py +145 -0
- unique_toolkit/agentic/tools/test/test_mcp_manager.py +536 -0
- unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +445 -0
- unique_toolkit/agentic/tools/tool.py +187 -0
- unique_toolkit/agentic/tools/tool_manager.py +492 -0
- unique_toolkit/agentic/tools/tool_progress_reporter.py +285 -0
- unique_toolkit/agentic/tools/utils/__init__.py +19 -0
- unique_toolkit/agentic/tools/utils/execution/__init__.py +1 -0
- unique_toolkit/agentic/tools/utils/execution/execution.py +286 -0
- unique_toolkit/agentic/tools/utils/source_handling/__init__.py +0 -0
- unique_toolkit/agentic/tools/utils/source_handling/schema.py +21 -0
- unique_toolkit/agentic/tools/utils/source_handling/source_formatting.py +207 -0
- unique_toolkit/agentic/tools/utils/source_handling/tests/test_source_formatting.py +216 -0
- unique_toolkit/app/__init__.py +9 -0
- unique_toolkit/app/dev_util.py +180 -0
- unique_toolkit/app/fast_api_factory.py +131 -0
- unique_toolkit/app/init_sdk.py +32 -1
- unique_toolkit/app/schemas.py +206 -31
- unique_toolkit/app/unique_settings.py +367 -0
- unique_toolkit/app/webhook.py +77 -0
- unique_toolkit/chat/__init__.py +8 -1
- unique_toolkit/chat/deprecated/service.py +232 -0
- unique_toolkit/chat/functions.py +648 -78
- unique_toolkit/chat/rendering.py +34 -0
- unique_toolkit/chat/responses_api.py +461 -0
- unique_toolkit/chat/schemas.py +134 -2
- unique_toolkit/chat/service.py +115 -767
- unique_toolkit/content/functions.py +353 -8
- unique_toolkit/content/schemas.py +128 -15
- unique_toolkit/content/service.py +321 -45
- unique_toolkit/content/smart_rules.py +301 -0
- unique_toolkit/content/utils.py +10 -3
- unique_toolkit/data_extraction/README.md +96 -0
- unique_toolkit/data_extraction/__init__.py +11 -0
- unique_toolkit/data_extraction/augmented/__init__.py +5 -0
- unique_toolkit/data_extraction/augmented/service.py +93 -0
- unique_toolkit/data_extraction/base.py +25 -0
- unique_toolkit/data_extraction/basic/__init__.py +11 -0
- unique_toolkit/data_extraction/basic/config.py +18 -0
- unique_toolkit/data_extraction/basic/prompt.py +13 -0
- unique_toolkit/data_extraction/basic/service.py +55 -0
- unique_toolkit/embedding/service.py +103 -12
- unique_toolkit/framework_utilities/__init__.py +1 -0
- unique_toolkit/framework_utilities/langchain/__init__.py +10 -0
- unique_toolkit/framework_utilities/langchain/client.py +71 -0
- unique_toolkit/framework_utilities/langchain/history.py +19 -0
- unique_toolkit/framework_utilities/openai/__init__.py +6 -0
- unique_toolkit/framework_utilities/openai/client.py +84 -0
- unique_toolkit/framework_utilities/openai/message_builder.py +229 -0
- unique_toolkit/framework_utilities/utils.py +23 -0
- unique_toolkit/language_model/__init__.py +3 -0
- unique_toolkit/language_model/_responses_api_utils.py +93 -0
- unique_toolkit/language_model/builder.py +27 -11
- unique_toolkit/language_model/default_language_model.py +3 -0
- unique_toolkit/language_model/functions.py +345 -43
- unique_toolkit/language_model/infos.py +1288 -46
- unique_toolkit/language_model/reference.py +242 -0
- unique_toolkit/language_model/schemas.py +481 -49
- unique_toolkit/language_model/service.py +229 -28
- unique_toolkit/protocols/support.py +145 -0
- unique_toolkit/services/__init__.py +7 -0
- unique_toolkit/services/chat_service.py +1631 -0
- unique_toolkit/services/knowledge_base.py +1094 -0
- unique_toolkit/short_term_memory/service.py +178 -41
- unique_toolkit/smart_rules/__init__.py +0 -0
- unique_toolkit/smart_rules/compile.py +56 -0
- unique_toolkit/test_utilities/events.py +197 -0
- unique_toolkit-1.33.3.dist-info/METADATA +1145 -0
- unique_toolkit-1.33.3.dist-info/RECORD +205 -0
- unique_toolkit/evaluators/__init__.py +0 -1
- unique_toolkit/evaluators/config.py +0 -35
- unique_toolkit/evaluators/constants.py +0 -1
- unique_toolkit/evaluators/context_relevancy/constants.py +0 -32
- unique_toolkit/evaluators/context_relevancy/service.py +0 -53
- unique_toolkit/evaluators/context_relevancy/utils.py +0 -142
- unique_toolkit/evaluators/hallucination/constants.py +0 -41
- unique_toolkit-0.7.9.dist-info/METADATA +0 -413
- unique_toolkit-0.7.9.dist-info/RECORD +0 -64
- /unique_toolkit/{evaluators → agentic/evaluation}/exception.py +0 -0
- {unique_toolkit-0.7.9.dist-info → unique_toolkit-1.33.3.dist-info}/LICENSE +0 -0
- {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
|
unique_toolkit/app/init_sdk.py
CHANGED
|
@@ -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
|
-
|
|
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)
|
unique_toolkit/app/schemas.py
CHANGED
|
@@ -1,23 +1,43 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from enum import StrEnum
|
|
2
|
-
from
|
|
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
|
-
|
|
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:
|
|
99
|
-
additional_parameters:
|
|
100
|
-
user_metadata:
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
232
|
+
pass
|
|
233
|
+
# user_message: EventUserMessage
|
|
234
|
+
# assistant_message: EventAssistantMessage
|
|
235
|
+
# additional_parameters: Optional[EventAdditionalParameters] = None
|
|
115
236
|
|
|
116
237
|
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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)
|