unique_toolkit 0.8.0__tar.gz → 0.8.2__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.0 → unique_toolkit-0.8.2}/CHANGELOG.md +11 -0
  2. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/PKG-INFO +12 -1
  3. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/pyproject.toml +1 -1
  4. unique_toolkit-0.8.2/unique_toolkit/app/dev_util.py +146 -0
  5. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/app/init_sdk.py +32 -1
  6. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/app/schemas.py +9 -0
  7. unique_toolkit-0.8.2/unique_toolkit/app/unique_settings.py +134 -0
  8. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/chat/service.py +2 -9
  9. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/content/service.py +49 -7
  10. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/embedding/service.py +25 -3
  11. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/framework_utilities/langchain/client.py +8 -8
  12. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/framework_utilities/openai/client.py +4 -6
  13. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/language_model/service.py +39 -14
  14. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/short_term_memory/service.py +38 -6
  15. unique_toolkit-0.8.0/unique_toolkit/app/event_util.py +0 -24
  16. unique_toolkit-0.8.0/unique_toolkit/app/sse_client.py +0 -20
  17. unique_toolkit-0.8.0/unique_toolkit/app/unique_settings.py +0 -61
  18. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/LICENSE +0 -0
  19. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/README.md +0 -0
  20. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/__init__.py +0 -0
  21. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/_common/_base_service.py +0 -0
  22. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/_common/_time_utils.py +0 -0
  23. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/_common/exception.py +0 -0
  24. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/_common/validate_required_values.py +0 -0
  25. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/_common/validators.py +0 -0
  26. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/app/__init__.py +0 -0
  27. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/app/init_logging.py +0 -0
  28. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/app/performance/async_tasks.py +0 -0
  29. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/app/performance/async_wrapper.py +0 -0
  30. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/app/verification.py +0 -0
  31. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/chat/__init__.py +0 -0
  32. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/chat/constants.py +0 -0
  33. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/chat/functions.py +0 -0
  34. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/chat/schemas.py +0 -0
  35. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/chat/state.py +0 -0
  36. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/chat/utils.py +0 -0
  37. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/content/__init__.py +0 -0
  38. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/content/constants.py +0 -0
  39. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/content/functions.py +0 -0
  40. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/content/schemas.py +0 -0
  41. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/content/utils.py +0 -0
  42. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/embedding/__init__.py +0 -0
  43. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/embedding/constants.py +0 -0
  44. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/embedding/functions.py +0 -0
  45. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/embedding/schemas.py +0 -0
  46. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/embedding/utils.py +0 -0
  47. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/__init__.py +0 -0
  48. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/config.py +0 -0
  49. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/constants.py +0 -0
  50. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/context_relevancy/constants.py +0 -0
  51. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/context_relevancy/prompts.py +0 -0
  52. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/context_relevancy/service.py +0 -0
  53. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/context_relevancy/utils.py +0 -0
  54. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/exception.py +0 -0
  55. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/hallucination/constants.py +0 -0
  56. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/hallucination/prompts.py +0 -0
  57. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/hallucination/service.py +0 -0
  58. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/hallucination/utils.py +0 -0
  59. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/output_parser.py +0 -0
  60. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/evaluators/schemas.py +0 -0
  61. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/framework_utilities/langchain/history.py +0 -0
  62. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/framework_utilities/openai/message_builder.py +0 -0
  63. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/framework_utilities/utils.py +0 -0
  64. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/language_model/__init__.py +0 -0
  65. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/language_model/builder.py +0 -0
  66. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/language_model/constants.py +0 -0
  67. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/language_model/functions.py +0 -0
  68. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/language_model/infos.py +0 -0
  69. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/language_model/prompt.py +0 -0
  70. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/language_model/reference.py +0 -0
  71. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/language_model/schemas.py +0 -0
  72. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/language_model/utils.py +0 -0
  73. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/protocols/support.py +0 -0
  74. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/short_term_memory/__init__.py +0 -0
  75. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/short_term_memory/constants.py +0 -0
  76. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/short_term_memory/functions.py +0 -0
  77. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/short_term_memory/schemas.py +0 -0
  78. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/smart_rules/__init__.py +0 -0
  79. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/smart_rules/compile.py +0 -0
  80. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/tools/tool_definitions.py +0 -0
  81. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/tools/tool_definitionsV2.py +0 -0
  82. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/tools/tool_factory.py +0 -0
  83. {unique_toolkit-0.8.0 → unique_toolkit-0.8.2}/unique_toolkit/tools/tool_progress_reporter.py +0 -0
@@ -5,6 +5,17 @@ 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
+
9
+ ## [0.8.2] - 2025-08-05
10
+ - Implement overloads for services for clearer dev experience
11
+ - Proper typing for SSE event handling
12
+ - Enhanced unique settings. Expose usage of default values in logs
13
+ - SDK Initialization from unique settings
14
+ - Add utilities for to run llm/agent flows for devs
15
+
16
+ ## [0.8.1] - 2025-08-05
17
+ - Bump SDK version to support the latest features.
18
+
8
19
  ## [0.8.0] - 2025-08-04
9
20
  - Add MCP support
10
21
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_toolkit
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary:
5
5
  License: Proprietary
6
6
  Author: Martin Fadler
@@ -113,6 +113,17 @@ 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
+
117
+ ## [0.8.2] - 2025-08-05
118
+ - Implement overloads for services for clearer dev experience
119
+ - Proper typing for SSE event handling
120
+ - Enhanced unique settings. Expose usage of default values in logs
121
+ - SDK Initialization from unique settings
122
+ - Add utilities for to run llm/agent flows for devs
123
+
124
+ ## [0.8.1] - 2025-08-05
125
+ - Bump SDK version to support the latest features.
126
+
116
127
  ## [0.8.0] - 2025-08-04
117
128
  - Add MCP support
118
129
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "unique_toolkit"
3
- version = "0.8.0"
3
+ version = "0.8.2"
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
 
@@ -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(
@@ -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