unique_toolkit 0.8.1__tar.gz → 0.8.3__tar.gz

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 (83) hide show
  1. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/CHANGELOG.md +10 -0
  2. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/PKG-INFO +11 -1
  3. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/pyproject.toml +1 -1
  4. unique_toolkit-0.8.3/unique_toolkit/app/dev_util.py +146 -0
  5. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/app/init_sdk.py +32 -1
  6. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/app/schemas.py +9 -0
  7. unique_toolkit-0.8.3/unique_toolkit/app/unique_settings.py +134 -0
  8. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/chat/service.py +2 -9
  9. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/content/functions.py +5 -0
  10. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/content/service.py +55 -7
  11. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/embedding/service.py +25 -3
  12. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/framework_utilities/langchain/client.py +8 -8
  13. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/framework_utilities/openai/client.py +4 -6
  14. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/language_model/service.py +39 -14
  15. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/short_term_memory/service.py +38 -6
  16. unique_toolkit-0.8.1/unique_toolkit/app/event_util.py +0 -24
  17. unique_toolkit-0.8.1/unique_toolkit/app/sse_client.py +0 -20
  18. unique_toolkit-0.8.1/unique_toolkit/app/unique_settings.py +0 -61
  19. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/LICENSE +0 -0
  20. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/README.md +0 -0
  21. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/__init__.py +0 -0
  22. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/_common/_base_service.py +0 -0
  23. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/_common/_time_utils.py +0 -0
  24. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/_common/exception.py +0 -0
  25. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/_common/validate_required_values.py +0 -0
  26. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/_common/validators.py +0 -0
  27. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/app/__init__.py +0 -0
  28. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/app/init_logging.py +0 -0
  29. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/app/performance/async_tasks.py +0 -0
  30. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/app/performance/async_wrapper.py +0 -0
  31. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/app/verification.py +0 -0
  32. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/chat/__init__.py +0 -0
  33. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/chat/constants.py +0 -0
  34. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/chat/functions.py +0 -0
  35. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/chat/schemas.py +0 -0
  36. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/chat/state.py +0 -0
  37. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/chat/utils.py +0 -0
  38. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/content/__init__.py +0 -0
  39. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/content/constants.py +0 -0
  40. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/content/schemas.py +0 -0
  41. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/content/utils.py +0 -0
  42. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/embedding/__init__.py +0 -0
  43. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/embedding/constants.py +0 -0
  44. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/embedding/functions.py +0 -0
  45. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/embedding/schemas.py +0 -0
  46. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/embedding/utils.py +0 -0
  47. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/__init__.py +0 -0
  48. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/config.py +0 -0
  49. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/constants.py +0 -0
  50. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/context_relevancy/constants.py +0 -0
  51. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/context_relevancy/prompts.py +0 -0
  52. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/context_relevancy/service.py +0 -0
  53. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/context_relevancy/utils.py +0 -0
  54. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/exception.py +0 -0
  55. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/hallucination/constants.py +0 -0
  56. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/hallucination/prompts.py +0 -0
  57. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/hallucination/service.py +0 -0
  58. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/hallucination/utils.py +0 -0
  59. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/output_parser.py +0 -0
  60. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/evaluators/schemas.py +0 -0
  61. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/framework_utilities/langchain/history.py +0 -0
  62. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/framework_utilities/openai/message_builder.py +0 -0
  63. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/framework_utilities/utils.py +0 -0
  64. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/language_model/__init__.py +0 -0
  65. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/language_model/builder.py +0 -0
  66. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/language_model/constants.py +0 -0
  67. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/language_model/functions.py +0 -0
  68. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/language_model/infos.py +0 -0
  69. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/language_model/prompt.py +0 -0
  70. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/language_model/reference.py +0 -0
  71. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/language_model/schemas.py +0 -0
  72. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/language_model/utils.py +0 -0
  73. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/protocols/support.py +0 -0
  74. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/short_term_memory/__init__.py +0 -0
  75. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/short_term_memory/constants.py +0 -0
  76. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/short_term_memory/functions.py +0 -0
  77. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/short_term_memory/schemas.py +0 -0
  78. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/smart_rules/__init__.py +0 -0
  79. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/smart_rules/compile.py +0 -0
  80. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/tools/tool_definitions.py +0 -0
  81. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/tools/tool_definitionsV2.py +0 -0
  82. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/tools/tool_factory.py +0 -0
  83. {unique_toolkit-0.8.1 → unique_toolkit-0.8.3}/unique_toolkit/tools/tool_progress_reporter.py +0 -0
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.8.3] - 2025-08-05
9
+ - Expose threshold field for search.
10
+
11
+ ## [0.8.2] - 2025-08-05
12
+ - Implement overloads for services for clearer dev experience
13
+ - Proper typing for SSE event handling
14
+ - Enhanced unique settings. Expose usage of default values in logs
15
+ - SDK Initialization from unique settings
16
+ - Add utilities for to run llm/agent flows for devs
17
+
8
18
  ## [0.8.1] - 2025-08-05
9
19
  - Bump SDK version to support the latest features.
10
20
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_toolkit
3
- Version: 0.8.1
3
+ Version: 0.8.3
4
4
  Summary:
5
5
  License: Proprietary
6
6
  Author: Martin Fadler
@@ -113,6 +113,16 @@ All notable changes to this project will be documented in this file.
113
113
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
114
114
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
115
115
 
116
+ ## [0.8.3] - 2025-08-05
117
+ - Expose threshold field for search.
118
+
119
+ ## [0.8.2] - 2025-08-05
120
+ - Implement overloads for services for clearer dev experience
121
+ - Proper typing for SSE event handling
122
+ - Enhanced unique settings. Expose usage of default values in logs
123
+ - SDK Initialization from unique settings
124
+ - Add utilities for to run llm/agent flows for devs
125
+
116
126
  ## [0.8.1] - 2025-08-05
117
127
  - Bump SDK version to support the latest features.
118
128
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "unique_toolkit"
3
- version = "0.8.1"
3
+ version = "0.8.3"
4
4
  description = ""
5
5
  authors = [
6
6
  "Martin Fadler <martin.fadler@unique.ch>",
@@ -0,0 +1,146 @@
1
+ import asyncio
2
+ import json
3
+ from logging import getLogger
4
+ from pathlib import Path
5
+ from typing import (
6
+ Awaitable,
7
+ Callable,
8
+ Generator,
9
+ TypeVar,
10
+ )
11
+
12
+ from sseclient import SSEClient
13
+
14
+ from unique_toolkit.app import BaseEvent, ChatEvent, EventName
15
+ from unique_toolkit.app.init_sdk import init_unique_sdk
16
+ from unique_toolkit.app.unique_settings import UniqueSettings
17
+
18
+ T = TypeVar("T", bound=BaseEvent)
19
+
20
+ LOGGER = getLogger(__name__)
21
+
22
+
23
+ def get_event_name_from_event_class(event_class: type[T]) -> EventName | None:
24
+ if event_class is ChatEvent:
25
+ return EventName.EXTERNAL_MODULE_CHOSEN
26
+
27
+ return None
28
+
29
+
30
+ def get_sse_client(
31
+ unique_settings: UniqueSettings,
32
+ subscriptions: list[str],
33
+ ) -> SSEClient:
34
+ headers = {
35
+ "Authorization": f"Bearer {unique_settings.app.key.get_secret_value()}",
36
+ "x-app-id": unique_settings.app.id.get_secret_value(),
37
+ "x-company-id": unique_settings.auth.company_id.get_secret_value(),
38
+ "x-user-id": unique_settings.auth.user_id.get_secret_value(),
39
+ "x-api-version": unique_settings.api.version,
40
+ }
41
+ return SSEClient(url=unique_settings.api.sse_url(subscriptions), headers=headers)
42
+
43
+
44
+ def get_event_generator(
45
+ unique_settings: UniqueSettings, event_type: type[T]
46
+ ) -> Generator[T, None, None]:
47
+ """
48
+ Generator that yields only events of the specified type from an SSE stream.
49
+
50
+ Args:
51
+ sse_client: The SSE client to read events from
52
+ event_type: The event class type to filter for
53
+
54
+ Yields:
55
+ Events matching the specified type
56
+ """
57
+ event_name = get_event_name_from_event_class(event_type)
58
+ if (
59
+ event_name is None
60
+ or not issubclass(event_type, BaseEvent)
61
+ or event_type is BaseEvent
62
+ ):
63
+ raise ValueError(f"Event model {event_type} is not a valid event model")
64
+
65
+ subscription = event_name.value
66
+
67
+ for sse_event in get_sse_client(unique_settings, [subscription]):
68
+ try:
69
+ payload = json.loads(sse_event.data)
70
+ parsed_event = event_type.model_validate(payload)
71
+ if parsed_event is None:
72
+ continue
73
+
74
+ yield parsed_event
75
+
76
+ except Exception as e:
77
+ LOGGER.error(f"Could not parse SSE event data as JSON: {e}")
78
+ continue
79
+
80
+
81
+ def run_demo_with_sse_client(
82
+ unique_settings: UniqueSettings,
83
+ handler: Callable[[BaseEvent], Awaitable[None] | None],
84
+ event_type: type[BaseEvent],
85
+ ) -> None:
86
+ """
87
+ Run a demo with an SSE client using sync handler.
88
+
89
+ Args:
90
+ unique_settings: The unique settings to use for the SSE client
91
+ handler: The sync handler to use for the SSE client
92
+ event_type: The type of event to use for the SSE client
93
+ """
94
+
95
+ event_name = get_event_name_from_event_class(event_type)
96
+ if event_name is None:
97
+ return
98
+
99
+ init_unique_sdk(unique_settings=unique_settings)
100
+ is_async_handler = asyncio.iscoroutinefunction(handler)
101
+
102
+ for event in get_event_generator(unique_settings, event_type):
103
+ if is_async_handler:
104
+ loop = asyncio.get_event_loop()
105
+ loop.run_until_complete(handler(event))
106
+ else:
107
+ handler(event)
108
+
109
+
110
+ def load_event(file_path: Path, event_type: type[BaseEvent]) -> BaseEvent:
111
+ with file_path.open("r") as file:
112
+ event = json.load(file)
113
+
114
+ return event_type.model_validate(event)
115
+
116
+
117
+ def run_demo_with_with_saved_event(
118
+ unique_settings: UniqueSettings,
119
+ handler: Callable[[BaseEvent], Awaitable[None] | None],
120
+ event_type: type[BaseEvent],
121
+ file_path: Path,
122
+ ) -> None:
123
+ """
124
+ Run a demo with an SSE client.
125
+
126
+ Note: event_type is the type of event that the handler expects.
127
+
128
+ Args:
129
+ unique_settings: The unique settings to use for the SSE client
130
+ handler: The handler to use for the SSE client
131
+ event_type: The type of event to use for the SSE client
132
+ """
133
+ init_unique_sdk(unique_settings=unique_settings)
134
+
135
+ event_name = get_event_name_from_event_class(event_type)
136
+ if event_name is None:
137
+ return
138
+
139
+ event = load_event(file_path, event_type)
140
+ if event is None:
141
+ raise ValueError(f"Event not found in {file_path}")
142
+
143
+ if asyncio.iscoroutinefunction(handler):
144
+ asyncio.run(handler(event))
145
+ else:
146
+ handler(event)
@@ -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)
@@ -19,6 +19,15 @@ model_config = ConfigDict(
19
19
 
20
20
  class EventName(StrEnum):
21
21
  EXTERNAL_MODULE_CHOSEN = "unique.chat.external-module.chosen"
22
+ USER_MESSAGE_CREATED = "unique.chat.user-message.created"
23
+ INGESTION_CONTENT_UPLOADED = "unique.ingestion.content.uploaded"
24
+ INGESTION_CONTENT_FINISHED = "unique.ingestion.content.finished"
25
+ MAGIC_TABLE_IMPORT_COLUMNS = "unique.magic-table.import-columns"
26
+ MAGIC_TABLE_ADD_META_DATA = "unique.magic-table.add-meta-data"
27
+ MAGIC_TABLE_ADD_DOCUMENT = "unique.magic-table.add-document"
28
+ MAGIC_TABLE_DELETE_ROW = "unique.magic-table.delete-row"
29
+ MAGIC_TABLE_DELETE_COLUMN = "unique.magic-table.delete-column"
30
+ MAGIC_TABLE_UPDATE_CELL = "unique.magic-table.update-cell"
22
31
 
23
32
 
24
33
  class BaseEvent(BaseModel):
@@ -0,0 +1,134 @@
1
+ from logging import getLogger
2
+ from pathlib import Path
3
+ from typing import Self, TypeVar
4
+ from urllib.parse import urlparse, urlunparse
5
+
6
+ from pydantic import Field, SecretStr, model_validator
7
+ from pydantic_settings import BaseSettings, SettingsConfigDict
8
+
9
+ logger = getLogger(__name__)
10
+
11
+ T = TypeVar("T", bound=BaseSettings)
12
+
13
+
14
+ def warn_about_defaults(instance: T) -> T:
15
+ """Log warnings for fields that are using default values."""
16
+ for field_name, model_field in instance.model_fields.items():
17
+ field_value = getattr(instance, field_name)
18
+ if field_value == model_field.default:
19
+ logger.warning(
20
+ f"Using default value for '{field_name}': {model_field.default}"
21
+ )
22
+ return instance
23
+
24
+
25
+ class UniqueApp(BaseSettings):
26
+ id: SecretStr = Field(default=SecretStr("dummy_id"))
27
+ key: SecretStr = Field(default=SecretStr("dummy_key"))
28
+ base_url: str = Field(
29
+ default="http://localhost:8092/",
30
+ deprecated="Use UniqueApi.base_url instead",
31
+ )
32
+ endpoint: str = Field(default="dummy")
33
+ endpoint_secret: SecretStr = Field(default=SecretStr("dummy_secret"))
34
+
35
+ @model_validator(mode="after")
36
+ def _warn_about_defaults(self) -> Self:
37
+ return warn_about_defaults(self)
38
+
39
+ model_config = SettingsConfigDict(
40
+ env_prefix="unique_app_",
41
+ env_file_encoding="utf-8",
42
+ case_sensitive=False,
43
+ extra="ignore",
44
+ )
45
+
46
+
47
+ class UniqueApi(BaseSettings):
48
+ base_url: str = Field(
49
+ default="http://localhost:8092/",
50
+ description="The base URL of the Unique API. Ask your admin to provide you with the correct URL.",
51
+ )
52
+ version: str = Field(default="2023-12-06")
53
+
54
+ model_config = SettingsConfigDict(
55
+ env_prefix="unique_api_",
56
+ env_file_encoding="utf-8",
57
+ case_sensitive=False,
58
+ extra="ignore",
59
+ )
60
+
61
+ @model_validator(mode="after")
62
+ def _warn_about_defaults(self) -> Self:
63
+ return warn_about_defaults(self)
64
+
65
+ def sse_url(self, subscriptions: list[str]) -> str:
66
+ parsed = urlparse(self.base_url)
67
+ return urlunparse(
68
+ parsed._replace(
69
+ path="/public/event-socket/events/stream",
70
+ query=f"subscriptions={','.join(subscriptions)}",
71
+ fragment=None,
72
+ )
73
+ )
74
+
75
+ def sdk_url(self) -> str:
76
+ parsed = urlparse(self.base_url)
77
+
78
+ path = "/public/chat"
79
+ if parsed.hostname and "qa.unique" in parsed.hostname:
80
+ path = "/public/chat-gen2"
81
+ return urlunparse(parsed._replace(path=path, query=None, fragment=None))
82
+
83
+ def openai_proxy_url(self) -> str:
84
+ parsed = urlparse(self.base_url)
85
+ return urlunparse(
86
+ parsed._replace(path="/public/openai-proxy/", query=None, fragment=None)
87
+ )
88
+
89
+
90
+ class UniqueAuth(BaseSettings):
91
+ company_id: SecretStr = Field(default=SecretStr("dummy_company_id"))
92
+ user_id: SecretStr = Field(default=SecretStr("dummy_user_id"))
93
+
94
+ model_config = SettingsConfigDict(
95
+ env_prefix="unique_auth_",
96
+ env_file_encoding="utf-8",
97
+ case_sensitive=False,
98
+ extra="ignore",
99
+ )
100
+
101
+ @model_validator(mode="after")
102
+ def _warn_about_defaults(self) -> Self:
103
+ return warn_about_defaults(self)
104
+
105
+
106
+ class UniqueSettings:
107
+ def __init__(self, auth: UniqueAuth, app: UniqueApp, api: UniqueApi):
108
+ self.app = app
109
+ self.auth = auth
110
+ self.api = api
111
+
112
+ @classmethod
113
+ def from_env(cls, env_file: Path | None = None) -> "UniqueSettings":
114
+ """Initialize settings from environment variables and/or env file.
115
+
116
+ Args:
117
+ env_file: Optional path to environment file. If provided, will load variables from this file.
118
+
119
+ Returns:
120
+ UniqueSettings instance with values loaded from environment/env file.
121
+
122
+ Raises:
123
+ FileNotFoundError: If env_file is provided but does not exist.
124
+ ValidationError: If required environment variables are missing.
125
+ """
126
+ if env_file and not env_file.exists():
127
+ raise FileNotFoundError(f"Environment file not found: {env_file}")
128
+
129
+ # Initialize settings with environment file if provided
130
+ env_file_str = str(env_file) if env_file else None
131
+ auth = UniqueAuth(_env_file=env_file_str)
132
+ app = UniqueApp(_env_file=env_file_str)
133
+ api = UniqueApi(_env_file=env_file_str)
134
+ return cls(auth=auth, app=app, api=api)
@@ -57,15 +57,6 @@ logger = logging.getLogger(f"toolkit.{DOMAIN_NAME}.{__name__}")
57
57
  class ChatService:
58
58
  """
59
59
  Provides all functionalities to manage the chat session.
60
-
61
- Attributes:
62
- company_id (str | None): The company ID.
63
- user_id (str | None): The user ID.
64
- assistant_message_id (str | None): The assistant message ID.
65
- user_message_id (str | None): The user message ID.
66
- chat_id (str | None): The chat ID.
67
- assistant_id (str | None): The assistant ID.
68
- user_message_text (str | None): The user message text.
69
60
  """
70
61
 
71
62
  def __init__(self, event: ChatEvent | Event):
@@ -88,6 +79,7 @@ class ChatService:
88
79
 
89
80
  Returns:
90
81
  Event | BaseEvent | None: The event object.
82
+
91
83
  """
92
84
  return self._event
93
85
 
@@ -101,6 +93,7 @@ class ChatService:
101
93
 
102
94
  Returns:
103
95
  str | None: The company identifier.
96
+
104
97
  """
105
98
  return self._company_id
106
99
 
@@ -34,6 +34,7 @@ def search_content_chunks(
34
34
  chat_only: bool | None = None,
35
35
  metadata_filter: dict | None = None,
36
36
  content_ids: list[str] | None = None,
37
+ score_threshold: float | None = None,
37
38
  ) -> list[ContentChunk]:
38
39
  """
39
40
  Performs a synchronous search for content chunks in the knowledge base.
@@ -48,6 +49,7 @@ def search_content_chunks(
48
49
  chat_only (bool | None): Whether to search only in the current chat. Defaults to None.
49
50
  metadata_filter (dict | None): UniqueQL metadata filter. If unspecified/None, it tries to use the metadata filter from the event. Defaults to None.
50
51
  content_ids (list[str] | None): The content IDs to search. Defaults to None.
52
+ score_threshold (float | None): The minimum score threshold for results. Defaults to 0.
51
53
  Returns:
52
54
  list[ContentChunk]: The search results.
53
55
  """
@@ -73,6 +75,7 @@ def search_content_chunks(
73
75
  chatOnly=chat_only,
74
76
  metaDataFilter=metadata_filter,
75
77
  contentIds=content_ids,
78
+ scoreThreshold=score_threshold,
76
79
  )
77
80
  return map_to_content_chunks(searches)
78
81
  except Exception as e:
@@ -93,6 +96,7 @@ async def search_content_chunks_async(
93
96
  chat_only: bool | None = None,
94
97
  metadata_filter: dict | None = None,
95
98
  content_ids: list[str] | None = None,
99
+ score_threshold: float | None = None,
96
100
  ):
97
101
  """
98
102
  Performs an asynchronous search for content chunks in the knowledge base.
@@ -121,6 +125,7 @@ async def search_content_chunks_async(
121
125
  chatOnly=chat_only,
122
126
  metaDataFilter=metadata_filter,
123
127
  contentIds=content_ids,
128
+ scoreThreshold=score_threshold,
124
129
  )
125
130
  return map_to_content_chunks(searches)
126
131
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  from pathlib import Path
3
- from typing import Any
3
+ from typing import Any, overload
4
4
 
5
5
  import unique_sdk
6
6
  from requests import Response
@@ -35,13 +35,30 @@ logger = logging.getLogger(f"toolkit.{DOMAIN_NAME}.{__name__}")
35
35
  class ContentService:
36
36
  """
37
37
  Provides methods for searching, downloading and uploading content in the knowledge base.
38
+ """
39
+
40
+ @deprecated(
41
+ "Use __init__ with company_id, user_id and chat_id instead or use the classmethod `from_event`"
42
+ )
43
+ @overload
44
+ def __init__(self, event: Event | ChatEvent | BaseEvent): ...
45
+
46
+ """
47
+ Initialize the ContentService with an event (deprecated)
48
+ """
49
+
50
+ @overload
51
+ def __init__(
52
+ self,
53
+ *,
54
+ company_id: str,
55
+ user_id: str,
56
+ chat_id: str | None,
57
+ metadata_filter: dict | None = None,
58
+ ): ...
38
59
 
39
- Attributes:
40
- event: BaseEvent | Event, this can be None ONLY if company_id and user_id are provided.
41
- company_id (str): The company ID.
42
- user_id (str): The user ID.
43
- chat_id (str): The chat ID. Defaults to None
44
- metadata_filter (dict | None): is only initialised from an Event(Deprecated) or ChatEvent.
60
+ """
61
+ Initialize the ContentService with a company_id, user_id and chat_id and metadata_filter.
45
62
  """
46
63
 
47
64
  def __init__(
@@ -50,7 +67,12 @@ class ContentService:
50
67
  company_id: str | None = None,
51
68
  user_id: str | None = None,
52
69
  chat_id: str | None = None,
70
+ metadata_filter: dict | None = None,
53
71
  ):
72
+ """
73
+ Initialize the ContentService with a company_id, user_id and chat_id.
74
+ """
75
+
54
76
  self._event = event # Changed to protected attribute
55
77
  self._metadata_filter = None
56
78
  if event:
@@ -64,6 +86,26 @@ class ContentService:
64
86
  self._company_id: str = company_id
65
87
  self._user_id: str = user_id
66
88
  self._chat_id: str | None = chat_id
89
+ self._metadata_filter = metadata_filter
90
+
91
+ @classmethod
92
+ def from_event(cls, event: Event | ChatEvent | BaseEvent):
93
+ """
94
+ Initialize the ContentService with an event.
95
+ """
96
+ chat_id = None
97
+ metadata_filter = None
98
+
99
+ if isinstance(event, (ChatEvent | Event)):
100
+ chat_id = event.payload.chat_id
101
+ metadata_filter = event.payload.metadata_filter
102
+
103
+ return cls(
104
+ company_id=event.company_id,
105
+ user_id=event.user_id,
106
+ chat_id=chat_id,
107
+ metadata_filter=metadata_filter,
108
+ )
67
109
 
68
110
  @property
69
111
  @deprecated(
@@ -194,6 +236,7 @@ class ContentService:
194
236
  chat_only: bool | None = None,
195
237
  metadata_filter: dict | None = None,
196
238
  content_ids: list[str] | None = None,
239
+ score_threshold: float | None = None,
197
240
  ) -> list[ContentChunk]:
198
241
  """
199
242
  Performs a synchronous search for content chunks in the knowledge base.
@@ -209,6 +252,7 @@ class ContentService:
209
252
  chat_only (bool | None, optional): Whether to search only in the current chat. Defaults to None.
210
253
  metadata_filter (dict | None, optional): UniqueQL metadata filter. If unspecified/None, it tries to use the metadata filter from the event. Defaults to None.
211
254
  content_ids (list[str] | None, optional): The content IDs to search within. Defaults to None.
255
+ score_threshold (float | None, optional): Sets the minimum similarity score for search results to be considered. Defaults to 0.
212
256
 
213
257
  Returns:
214
258
  list[ContentChunk]: The search results.
@@ -239,6 +283,7 @@ class ContentService:
239
283
  chat_only=chat_only,
240
284
  metadata_filter=metadata_filter,
241
285
  content_ids=content_ids,
286
+ score_threshold=score_threshold,
242
287
  )
243
288
  return searches
244
289
  except Exception as e:
@@ -257,6 +302,7 @@ class ContentService:
257
302
  chat_only: bool | None = None,
258
303
  metadata_filter: dict | None = None,
259
304
  content_ids: list[str] | None = None,
305
+ score_threshold: float | None = None,
260
306
  ):
261
307
  """
262
308
  Performs an asynchronous search for content chunks in the knowledge base.
@@ -272,6 +318,7 @@ class ContentService:
272
318
  chat_only (bool | None, optional): Whether to search only in the current chat. Defaults to None.
273
319
  metadata_filter (dict | None, optional): UniqueQL metadata filter. If unspecified/None, it tries to use the metadata filter from the event. Defaults to None.
274
320
  content_ids (list[str] | None, optional): The content IDs to search within. Defaults to None.
321
+ score_threshold (float | None, optional): Sets the minimum similarity score for search results to be considered. Defaults to 0.
275
322
 
276
323
  Returns:
277
324
  list[ContentChunk]: The search results.
@@ -301,6 +348,7 @@ class ContentService:
301
348
  chat_only=chat_only,
302
349
  metadata_filter=metadata_filter,
303
350
  content_ids=content_ids,
351
+ score_threshold=score_threshold,
304
352
  )
305
353
  return searches
306
354
  except Exception as e:
@@ -1,3 +1,5 @@
1
+ from typing import overload
2
+
1
3
  from typing_extensions import deprecated
2
4
 
3
5
  from unique_toolkit._common._base_service import BaseService
@@ -11,10 +13,23 @@ from unique_toolkit.embedding.schemas import Embeddings
11
13
  class EmbeddingService(BaseService):
12
14
  """
13
15
  Provides methods to interact with the Embedding service.
16
+ """
14
17
 
15
- Attributes:
16
- company_id (str | None): The company ID.
17
- user_id (str | None): The user ID.
18
+ @deprecated(
19
+ "Use __init__ with company_id and user_id instead or use the classmethod `from_event`"
20
+ )
21
+ @overload
22
+ def __init__(self, event: Event | BaseEvent): ...
23
+
24
+ """
25
+ Initialize the EmbeddingService with an event (deprecated)
26
+ """
27
+
28
+ @overload
29
+ def __init__(self, *, company_id: str, user_id: str): ...
30
+
31
+ """
32
+ Initialize the EmbeddingService with a company_id and user_id.
18
33
  """
19
34
 
20
35
  def __init__(
@@ -32,6 +47,13 @@ class EmbeddingService(BaseService):
32
47
  self._company_id: str = company_id
33
48
  self._user_id: str = user_id
34
49
 
50
+ @classmethod
51
+ def from_event(cls, event: Event | BaseEvent):
52
+ """
53
+ Initialize the EmbeddingService with an event.
54
+ """
55
+ return cls(company_id=event.company_id, user_id=event.user_id)
56
+
35
57
  @property
36
58
  @deprecated(
37
59
  "The event property is deprecated and will be removed in a future version."
@@ -1,6 +1,5 @@
1
1
  import importlib.util
2
2
  import logging
3
- from pathlib import Path
4
3
 
5
4
  from unique_toolkit.app.unique_settings import UniqueSettings
6
5
  from unique_toolkit.framework_utilities.utils import get_default_headers
@@ -23,11 +22,13 @@ else:
23
22
  raise LangchainNotInstalledError()
24
23
 
25
24
 
26
- def get_client(env_file: Path | None = None) -> ChatOpenAI:
25
+ def get_client(
26
+ unique_settings: UniqueSettings, model: str = "AZURE_GPT_4o_2024_0806"
27
+ ) -> ChatOpenAI:
27
28
  """Get a Langchain ChatOpenAI client instance.
28
29
 
29
30
  Args:
30
- env_file: Optional path to environment file
31
+ unique_settings: UniqueSettings instance
31
32
 
32
33
  Returns:
33
34
  ChatOpenAI client instance
@@ -35,11 +36,10 @@ def get_client(env_file: Path | None = None) -> ChatOpenAI:
35
36
  Raises:
36
37
  LangchainNotInstalledError: If langchain-openai package is not installed
37
38
  """
38
- settings = UniqueSettings.from_env(env_file=env_file)
39
39
 
40
40
  return ChatOpenAI(
41
- base_url=settings.app.base_url + "/openai-proxy/",
42
- default_headers=get_default_headers(settings.app, settings.auth),
43
- model="AZURE_GPT_4o_2024_0806",
44
- api_key=settings.app.key,
41
+ base_url=unique_settings.api.openai_proxy_url(),
42
+ default_headers=get_default_headers(unique_settings.app, unique_settings.auth),
43
+ model=model,
44
+ api_key=unique_settings.app.key,
45
45
  )
@@ -1,6 +1,5 @@
1
1
  import importlib.util
2
2
  import logging
3
- from pathlib import Path
4
3
 
5
4
  from unique_toolkit.app.unique_settings import UniqueSettings
6
5
  from unique_toolkit.framework_utilities.utils import get_default_headers
@@ -23,7 +22,7 @@ else:
23
22
  raise OpenAINotInstalledError()
24
23
 
25
24
 
26
- def get_openai_client(env_file: Path | None = None) -> OpenAI:
25
+ def get_openai_client(unique_settings: UniqueSettings) -> OpenAI:
27
26
  """Get an OpenAI client instance.
28
27
 
29
28
  Args:
@@ -35,11 +34,10 @@ def get_openai_client(env_file: Path | None = None) -> OpenAI:
35
34
  Raises:
36
35
  OpenAINotInstalledError: If OpenAI package is not installed
37
36
  """
38
- settings = UniqueSettings.from_env(env_file=env_file)
39
- default_headers = get_default_headers(settings.app, settings.auth)
37
+ default_headers = get_default_headers(unique_settings.app, unique_settings.auth)
40
38
 
41
39
  return OpenAI(
42
- api_key=settings.app.key.get_secret_value(),
43
- base_url=settings.app.base_url + "/openai-proxy/",
40
+ api_key=unique_settings.app.key.get_secret_value(),
41
+ base_url=unique_settings.api.openai_proxy_url(),
44
42
  default_headers=default_headers,
45
43
  )
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Any, Optional, Type
2
+ from typing import Any, Optional, Type, overload
3
3
 
4
4
  from pydantic import BaseModel
5
5
  from typing_extensions import deprecated
@@ -33,36 +33,61 @@ logger = logging.getLogger(f"toolkit.{DOMAIN_NAME}.{__name__}")
33
33
  class LanguageModelService:
34
34
  """
35
35
  Provides methods to interact with the Language Model by generating responses.
36
+ """
37
+
38
+ @deprecated(
39
+ "Use __init__ with company_id and user_id instead or use the classmethod `from_event`"
40
+ )
41
+ @overload
42
+ def __init__(self, event: Event | ChatEvent | BaseEvent): ...
43
+
44
+ """
45
+ Initialize the LanguageModelService with an event (deprecated)
46
+ """
47
+
48
+ @overload
49
+ def __init__(self, *, company_id: str, user_id: str): ...
36
50
 
37
- Args:
38
- company_id (str | None, optional): The company identifier. Defaults to None.
39
- user_id (str | None, optional): The user identifier. Defaults to None.
40
- chat_id (str | None, optional): The chat identifier. Defaults to None.
41
- assistant_id (str | None, optional): The assistant identifier. Defaults to None.
51
+ """
52
+ Initialize the LanguageModelService with a company_id and user_id.
42
53
  """
43
54
 
44
55
  def __init__(
45
56
  self,
46
- event: Event | BaseEvent | None = None,
57
+ event: Event | ChatEvent | BaseEvent | None = None,
47
58
  company_id: str | None = None,
48
59
  user_id: str | None = None,
49
- chat_id: str | None = None,
50
- assistant_id: str | None = None,
60
+ **kwargs: dict[str, Any], # only here for backward compatibility
51
61
  ):
52
- self._event = event
53
- self._chat_id: str | None = chat_id
54
- self._assistant_id: str | None = assistant_id
55
-
56
- if event:
62
+ if isinstance(event, (ChatEvent, Event)):
63
+ self._event = event
64
+ self._chat_id: str | None = event.payload.chat_id
65
+ self._assistant_id: str | None = event.payload.assistant_id
57
66
  self._company_id = event.company_id
58
67
  self._user_id = event.user_id
59
68
  if isinstance(event, (ChatEvent, Event)):
60
69
  self._chat_id = event.payload.chat_id
61
70
  self._assistant_id = event.payload.assistant_id
71
+ elif isinstance(event, BaseEvent):
72
+ self._event = event
73
+ self._company_id = event.company_id
74
+ self._user_id = event.user_id
75
+ self._chat_id: str | None = None
76
+ self._assistant_id: str | None = None
62
77
  else:
63
78
  [company_id, user_id] = validate_required_values([company_id, user_id])
79
+ self._event = None
64
80
  self._company_id: str = company_id
65
81
  self._user_id: str = user_id
82
+ self._chat_id: str | None = None
83
+ self._assistant_id: str | None = None
84
+
85
+ @classmethod
86
+ def from_event(cls, event: BaseEvent):
87
+ """
88
+ Initialize the LanguageModelService with an event.
89
+ """
90
+ return cls(company_id=event.company_id, user_id=event.user_id)
66
91
 
67
92
  @property
68
93
  @deprecated(
@@ -1,3 +1,5 @@
1
+ from typing import overload
2
+
1
3
  from typing_extensions import deprecated
2
4
 
3
5
  from unique_toolkit._common.validate_required_values import validate_required_values
@@ -16,17 +18,35 @@ from .schemas import ShortTermMemory
16
18
  class ShortTermMemoryService:
17
19
  """
18
20
  Provides methods to manage short term memory.
21
+ """
22
+
23
+ @deprecated(
24
+ "Use __init__ with company_id and user_id instead or use the classmethod `from_event`"
25
+ )
26
+ @overload
27
+ def __init__(self, event: Event | ChatEvent | BaseEvent): ...
28
+
29
+ """
30
+ Initialize the ShortTermMemoryService with an event (deprecated)
31
+ """
32
+
33
+ @overload
34
+ def __init__(
35
+ self,
36
+ *,
37
+ company_id: str,
38
+ user_id: str,
39
+ chat_id: str | None,
40
+ message_id: str | None,
41
+ ): ...
19
42
 
20
- Attributes:
21
- user_id (str | None): The user ID.
22
- company_id (str | None): The company ID.
23
- chat_id (str | None): The chat ID.
24
- message_id (str | None): The message ID.
43
+ """
44
+ Initialize the ShortTermMemoryService with a company_id, user_id, chat_id and message_id.
25
45
  """
26
46
 
27
47
  def __init__(
28
48
  self,
29
- event: Event | BaseEvent | None = None,
49
+ event: Event | ChatEvent | BaseEvent | None = None,
30
50
  user_id: str | None = None,
31
51
  company_id: str | None = None,
32
52
  chat_id: str | None = None,
@@ -50,6 +70,18 @@ class ShortTermMemoryService:
50
70
  self._chat_id: str | None = chat_id
51
71
  self._message_id: str | None = message_id
52
72
 
73
+ @classmethod
74
+ def from_event(cls, event: ChatEvent):
75
+ """
76
+ Initialize the ShortTermMemoryService with a chat event.
77
+ """
78
+ return cls(
79
+ company_id=event.company_id,
80
+ user_id=event.user_id,
81
+ chat_id=event.payload.chat_id,
82
+ message_id=event.payload.user_message.id,
83
+ )
84
+
53
85
  @property
54
86
  @deprecated(
55
87
  "The event property is deprecated and will be removed in a future version."
@@ -1,24 +0,0 @@
1
- import json
2
- from logging import getLogger
3
- from typing import Literal, overload
4
-
5
- from unique_toolkit.app import ChatEvent, EventName
6
-
7
- LOGGER = getLogger(__name__)
8
-
9
-
10
- @overload
11
- def load_and_filter_event(
12
- event: dict,
13
- event_type: Literal[EventName.EXTERNAL_MODULE_CHOSEN],
14
- ) -> type[ChatEvent] | None: ...
15
-
16
-
17
- def load_and_filter_event(event: dict, event_type: EventName):
18
- event = json.loads(event.data)
19
-
20
- match event_type:
21
- case EventName.EXTERNAL_MODULE_CHOSEN:
22
- return ChatEvent(**event)
23
-
24
- return None
@@ -1,20 +0,0 @@
1
- from logging import getLogger
2
-
3
- from sseclient import SSEClient
4
-
5
- from unique_toolkit.app.unique_settings import UniqueSettings
6
-
7
- LOGGER = getLogger(__name__)
8
-
9
-
10
- def get_sse_client(
11
- unique_settings: UniqueSettings,
12
- subscriptions: list[str],
13
- ) -> SSEClient:
14
- url = f"{unique_settings.app.base_url}/public/event-socket/events/stream?subscriptions={','.join(subscriptions)}"
15
- headers = {
16
- "Authorization": f"Bearer {unique_settings.app.key.get_secret_value()}",
17
- "x-app-id": unique_settings.app.id.get_secret_value(),
18
- "x-company-id": unique_settings.auth.company_id.get_secret_value(),
19
- }
20
- return SSEClient(url=url, headers=headers)
@@ -1,61 +0,0 @@
1
- from pathlib import Path
2
-
3
- from pydantic import SecretStr
4
- from pydantic_settings import BaseSettings, SettingsConfigDict
5
-
6
-
7
- class UniqueApp(BaseSettings):
8
- id: SecretStr
9
- key: SecretStr
10
- base_url: str
11
- endpoint: str
12
- endpoint_secret: SecretStr
13
-
14
- model_config = SettingsConfigDict(
15
- env_prefix="unique_app_",
16
- env_file_encoding="utf-8",
17
- case_sensitive=False,
18
- extra="ignore",
19
- )
20
-
21
-
22
- class UniqueAuth(BaseSettings):
23
- company_id: SecretStr
24
- user_id: SecretStr
25
-
26
- model_config = SettingsConfigDict(
27
- env_prefix="unique_auth_",
28
- env_file_encoding="utf-8",
29
- case_sensitive=False,
30
- extra="ignore",
31
- )
32
-
33
-
34
- class UniqueSettings:
35
- def __init__(self, auth: UniqueAuth, app: UniqueApp):
36
- self.app = app
37
- self.auth = auth
38
-
39
- @classmethod
40
- def from_env(cls, env_file: Path | None = None) -> "UniqueSettings":
41
- """Initialize settings from environment variables and/or env file.
42
-
43
- Args:
44
- env_file: Optional path to environment file. If provided, will load variables from this file.
45
-
46
- Returns:
47
- UniqueSettings instance with values loaded from environment/env file.
48
-
49
- Raises:
50
- FileNotFoundError: If env_file is provided but does not exist.
51
- ValidationError: If required environment variables are missing.
52
- """
53
- if env_file and not env_file.exists():
54
- raise FileNotFoundError(f"Environment file not found: {env_file}")
55
-
56
- # Initialize settings with environment file if provided
57
- env_file_str = str(env_file) if env_file else None
58
- auth = UniqueAuth(_env_file=env_file_str, _env_file_encoding="utf-8")
59
- app = UniqueApp(_env_file=env_file_str, _env_file_encoding="utf-8")
60
-
61
- return cls(auth=auth, app=app)
File without changes
File without changes