unique_toolkit 0.8.1__py3-none-any.whl → 0.8.2__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.
@@ -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):
@@ -1,15 +1,40 @@
1
+ from logging import getLogger
1
2
  from pathlib import Path
3
+ from typing import Self, TypeVar
4
+ from urllib.parse import urlparse, urlunparse
2
5
 
3
- from pydantic import SecretStr
6
+ from pydantic import Field, SecretStr, model_validator
4
7
  from pydantic_settings import BaseSettings, SettingsConfigDict
5
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
+
6
24
 
7
25
  class UniqueApp(BaseSettings):
8
- id: SecretStr
9
- key: SecretStr
10
- base_url: str
11
- endpoint: str
12
- endpoint_secret: SecretStr
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)
13
38
 
14
39
  model_config = SettingsConfigDict(
15
40
  env_prefix="unique_app_",
@@ -19,9 +44,52 @@ class UniqueApp(BaseSettings):
19
44
  )
20
45
 
21
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
+
22
90
  class UniqueAuth(BaseSettings):
23
- company_id: SecretStr
24
- user_id: SecretStr
91
+ company_id: SecretStr = Field(default=SecretStr("dummy_company_id"))
92
+ user_id: SecretStr = Field(default=SecretStr("dummy_user_id"))
25
93
 
26
94
  model_config = SettingsConfigDict(
27
95
  env_prefix="unique_auth_",
@@ -30,11 +98,16 @@ class UniqueAuth(BaseSettings):
30
98
  extra="ignore",
31
99
  )
32
100
 
101
+ @model_validator(mode="after")
102
+ def _warn_about_defaults(self) -> Self:
103
+ return warn_about_defaults(self)
104
+
33
105
 
34
106
  class UniqueSettings:
35
- def __init__(self, auth: UniqueAuth, app: UniqueApp):
107
+ def __init__(self, auth: UniqueAuth, app: UniqueApp, api: UniqueApi):
36
108
  self.app = app
37
109
  self.auth = auth
110
+ self.api = api
38
111
 
39
112
  @classmethod
40
113
  def from_env(cls, env_file: Path | None = None) -> "UniqueSettings":
@@ -55,7 +128,7 @@ class UniqueSettings:
55
128
 
56
129
  # Initialize settings with environment file if provided
57
130
  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)
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_toolkit
3
- Version: 0.8.1
3
+ Version: 0.8.2
4
4
  Summary:
5
5
  License: Proprietary
6
6
  Author: Martin Fadler
@@ -113,6 +113,14 @@ 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
+
116
124
  ## [0.8.1] - 2025-08-05
117
125
  - Bump SDK version to support the latest features.
118
126
 
@@ -5,33 +5,32 @@ unique_toolkit/_common/exception.py,sha256=caQIE1btsQnpKCHqL2cgWUSbHup06enQu_Pt7
5
5
  unique_toolkit/_common/validate_required_values.py,sha256=Y_M1ub9gIKP9qZ45F6Zq3ZHtuIqhmOjl8Z2Vd3avg8w,588
6
6
  unique_toolkit/_common/validators.py,sha256=l7-hWyRTZ3aF_e73oTQFZdz93s06VhNWVpkERbg2a64,1569
7
7
  unique_toolkit/app/__init__.py,sha256=Vq3RXzrWGZNC0EU9SoheKbUvr8g7Wnc5DLBVazaZBNM,1284
8
- unique_toolkit/app/event_util.py,sha256=lfI64OcOXJZhh2GcVTmvE8AVc888jPWhi7l1vEzDsgs,535
8
+ unique_toolkit/app/dev_util.py,sha256=rN-xSg4OGfmwjaToy8m_hQroehcLYyk9-GCmQJ-f6uY,4302
9
9
  unique_toolkit/app/init_logging.py,sha256=Sh26SRxOj8i8dzobKhYha2lLrkrMTHfB1V4jR3h23gQ,678
10
- unique_toolkit/app/init_sdk.py,sha256=Nv4Now4pMfM0AgRhbtatLpm_39rKxn0WmRLwmPhRl-8,1285
10
+ unique_toolkit/app/init_sdk.py,sha256=5_oDoETr6akwYyBCb0ivTdMNu3SVgPSkrXcDS6ELyY8,2269
11
11
  unique_toolkit/app/performance/async_tasks.py,sha256=H0l3OAcosLwNHZ8d2pd-Di4wHIXfclEvagi5kfqLFPA,1941
12
12
  unique_toolkit/app/performance/async_wrapper.py,sha256=yVVcRDkcdyfjsxro-N29SBvi-7773wnfDplef6-y8xw,1077
13
- unique_toolkit/app/schemas.py,sha256=fQJWKZVTzIFXDpwSqY0joPX2WFKBuK1HchuMp6VeGLQ,6837
14
- unique_toolkit/app/sse_client.py,sha256=jtOhB2g_oE-vJBqtVuWnWyYUJYv3oOhN2U8j8wuHJ5Y,668
15
- unique_toolkit/app/unique_settings.py,sha256=fi3V9Dru1G1YK7Pxju_KBGiNJHkHdx7JzpDglcvMZro,1820
13
+ unique_toolkit/app/schemas.py,sha256=JdC2rNVPRrr6QhGMZweE0ID760onbRY2oq9m1LVFego,7429
14
+ unique_toolkit/app/unique_settings.py,sha256=YwKXKKSrLSoNk5kow5ChC0uOqii37gd3ttFL4rSAVF4,4443
16
15
  unique_toolkit/app/verification.py,sha256=GxFFwcJMy25fCA_Xe89wKW7bgqOu8PAs5y8QpHF0GSc,3861
17
16
  unique_toolkit/chat/__init__.py,sha256=LRs2G-JTVuci4lbtHTkVUiNcZcSR6uqqfnAyo7af6nY,619
18
17
  unique_toolkit/chat/constants.py,sha256=05kq6zjqUVB2d6_P7s-90nbljpB3ryxwCI-CAz0r2O4,83
19
18
  unique_toolkit/chat/functions.py,sha256=QsJVhBXgK6jDWRYpEAt-22jy5NKGsYs4fmHrOEdHAyc,29865
20
19
  unique_toolkit/chat/schemas.py,sha256=abPPeDtUIeEyKDnLhIqgIyqTsFANxh3j44EYrITBlHw,2786
21
- unique_toolkit/chat/service.py,sha256=dRydaABJspU1KlFUQ_Q-rXl9lDkhgeh99SxAguY4s_M,37432
20
+ unique_toolkit/chat/service.py,sha256=CKJfCrW0mTDP0SePlJtEoDUQ8-WB5xI8GslQx1_lnBg,37038
22
21
  unique_toolkit/chat/state.py,sha256=Cjgwv_2vhDFbV69xxsn7SefhaoIAEqLx3ferdVFCnOg,1445
23
22
  unique_toolkit/chat/utils.py,sha256=ihm-wQykBWhB4liR3LnwPVPt_qGW6ETq21Mw4HY0THE,854
24
23
  unique_toolkit/content/__init__.py,sha256=EdJg_A_7loEtCQf4cah3QARQreJx6pdz89Rm96YbMVg,940
25
24
  unique_toolkit/content/constants.py,sha256=1iy4Y67xobl5VTnJB6SxSyuoBWbdLl9244xfVMUZi5o,60
26
25
  unique_toolkit/content/functions.py,sha256=0ELepm3_sl0SD_SYzvQVQ-jTdrcUqK5mVJZv0nQBuAw,18367
27
26
  unique_toolkit/content/schemas.py,sha256=KJ604BOx0vBh2AwlTCZkOo55aHsI6yj8vxDAARKKqEo,2995
28
- unique_toolkit/content/service.py,sha256=jGRTIt0JQKL6qRyZ9-_njWJejKKvIvDOzEQrf8CdMaU,19491
27
+ unique_toolkit/content/service.py,sha256=9Re_UK7pkv-zNROlKRmfoomfhUQcuzFQvQ05aUl8q6Y,20502
29
28
  unique_toolkit/content/utils.py,sha256=qNVmHTuETaPNGqheg7TbgPr1_1jbNHDc09N5RrmUIyo,7901
30
29
  unique_toolkit/embedding/__init__.py,sha256=uUyzjonPvuDCYsvXCIt7ErQXopLggpzX-MEQd3_e2kE,250
31
30
  unique_toolkit/embedding/constants.py,sha256=Lj8-Lcy1FvuC31PM9Exq7vaFuxQV4pEI1huUMFX-J2M,52
32
31
  unique_toolkit/embedding/functions.py,sha256=3qp-BfuMAbnp8YB04rh3xH8vsJuCBPizoy-JeaBFtoQ,1944
33
32
  unique_toolkit/embedding/schemas.py,sha256=1GvKCaSk4jixzVQ2PKq8yDqwGEVY_hWclYtoAr6CC2g,96
34
- unique_toolkit/embedding/service.py,sha256=xdR28rqxCicXN9qGPm0oaw88NAbRXJNIBwTZSARLn2g,4082
33
+ unique_toolkit/embedding/service.py,sha256=z1MwwaeEloLvgT8rwUAydlt28TnKZ9TkcPgdYIyqZvs,4657
35
34
  unique_toolkit/embedding/utils.py,sha256=v86lo__bCJbxZBQ3OcLu5SuwT6NbFfWlcq8iyk6BuzQ,279
36
35
  unique_toolkit/evaluators/__init__.py,sha256=3Rfpnowm7MUXHWmeU4UV4s_3Hk-sw3V20oBwQCYlejQ,50
37
36
  unique_toolkit/evaluators/config.py,sha256=_DIXToJ-hGNpDAdWa7Q6GMjAsxiC_DquLF-SS5s9rTE,717
@@ -47,9 +46,9 @@ unique_toolkit/evaluators/hallucination/service.py,sha256=k8qro5Lw4Ak58m4HYp3G4H
47
46
  unique_toolkit/evaluators/hallucination/utils.py,sha256=gO2AOzDQwVTev2_5vDKgJ9A6A9e0himJyAta_wglVG8,8326
48
47
  unique_toolkit/evaluators/output_parser.py,sha256=eI72qkzK1dZyUvnfP2SOAQCGBj_-PwX5wy_aLPMsJMY,883
49
48
  unique_toolkit/evaluators/schemas.py,sha256=Jaue6Uhx75X1CyHKWj8sT3RE1JZXTqoLtfLt2xQNCX8,2507
50
- unique_toolkit/framework_utilities/langchain/client.py,sha256=GWzL2GSxErj52dpYC15w8ABmDB5S2nr2QmV8rKmeEFM,1367
49
+ unique_toolkit/framework_utilities/langchain/client.py,sha256=Msfmr7uezwqagyRJ2zjWbQRFqzDExWYK0y5KLEnDNqM,1329
51
50
  unique_toolkit/framework_utilities/langchain/history.py,sha256=8ejA0utPUIlX4z8gtX9IkVFiooY-vOT1UcjI_MoJaAU,638
52
- unique_toolkit/framework_utilities/openai/client.py,sha256=itbEv098j4alz2Rftp4kbjeloFsAqaxyyBHdZTH5OYM,1297
51
+ unique_toolkit/framework_utilities/openai/client.py,sha256=Zs5zxFbRfa0XrlbZvhGf-GpHomKLBJx0D28iowk1Eek,1236
53
52
  unique_toolkit/framework_utilities/openai/message_builder.py,sha256=meJVGPaSQ6xEafNZtACujcD0KXdiSYGTNiv02FSJULE,3834
54
53
  unique_toolkit/framework_utilities/utils.py,sha256=JK7g2yMfEx3eMprug26769xqNpS5WJcizf8n2zWMBng,789
55
54
  unique_toolkit/language_model/__init__.py,sha256=lRQyLlbwHbNFf4-0foBU13UGb09lwEeodbVsfsSgaCk,1971
@@ -60,21 +59,21 @@ unique_toolkit/language_model/infos.py,sha256=Abxcw-l7UXkBohxZsigpNz0OUUCtDf404L
60
59
  unique_toolkit/language_model/prompt.py,sha256=JSawaLjQg3VR-E2fK8engFyJnNdk21zaO8pPIodzN4Q,3991
61
60
  unique_toolkit/language_model/reference.py,sha256=nkX2VFz-IrUz8yqyc3G5jUMNwrNpxITBrMEKkbqqYoI,8583
62
61
  unique_toolkit/language_model/schemas.py,sha256=AeuDRJFblGzEYcEMyrlxpOPk12Di3J45I9rT2xZrhEU,14332
63
- unique_toolkit/language_model/service.py,sha256=VRkUk2XbijqGlnTTvqU7uCue6qtT7lpLd_Y8f3bWv1I,10486
62
+ unique_toolkit/language_model/service.py,sha256=zlvC_t9T1wixwcGDPRxl6yYniaKl2725NxWrbW51jUs,11290
64
63
  unique_toolkit/language_model/utils.py,sha256=bPQ4l6_YO71w-zaIPanUUmtbXC1_hCvLK0tAFc3VCRc,1902
65
64
  unique_toolkit/protocols/support.py,sha256=V15WEIFKVMyF1QCnR8vIi4GrJy4dfTCB6d6JlqPZ58o,2341
66
65
  unique_toolkit/short_term_memory/__init__.py,sha256=2mI3AUrffgH7Yt-xS57EGqnHf7jnn6xquoKEhJqk3Wg,185
67
66
  unique_toolkit/short_term_memory/constants.py,sha256=698CL6-wjup2MvU19RxSmQk3gX7aqW_OOpZB7sbz_Xg,34
68
67
  unique_toolkit/short_term_memory/functions.py,sha256=3WiK-xatY5nh4Dr5zlDUye1k3E6kr41RiscwtTplw5k,4484
69
68
  unique_toolkit/short_term_memory/schemas.py,sha256=OhfcXyF6ACdwIXW45sKzjtZX_gkcJs8FEZXcgQTNenw,1406
70
- unique_toolkit/short_term_memory/service.py,sha256=8sW7cFJRd4vcRfotJSWb0uHZYl-e5hQWp3G1SddL5Bg,8110
69
+ unique_toolkit/short_term_memory/service.py,sha256=5PeVBu1ZCAfyDb2HLVvlmqSbyzBBuE9sI2o9Aajqjxg,8884
71
70
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
71
  unique_toolkit/smart_rules/compile.py,sha256=cxWjb2dxEI2HGsakKdVCkSNi7VK9mr08w5sDcFCQyWI,9553
73
72
  unique_toolkit/tools/tool_definitions.py,sha256=YYu53vXMJBeJtuSU1L_FJBsiN52LSA5LIDt9O-1HBgE,4500
74
73
  unique_toolkit/tools/tool_definitionsV2.py,sha256=yjLmP85pFGd1QtIVMC3oLQPSQ2NckBj9hIihjIr2FZg,5728
75
74
  unique_toolkit/tools/tool_factory.py,sha256=ux11jd7Oobb-6eBeS51T-tviH14k6HKqsKmljA7h6qA,879
76
75
  unique_toolkit/tools/tool_progress_reporter.py,sha256=AyPdgxpd48qotJyPB8qJ7h7ghiv2w2EK8nlyqQVFRt4,8048
77
- unique_toolkit-0.8.1.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
78
- unique_toolkit-0.8.1.dist-info/METADATA,sha256=mCMbEXB_uaZEQfGplkJMktiMDO45l5QZ2ZfiSlSYY9g,25737
79
- unique_toolkit-0.8.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
80
- unique_toolkit-0.8.1.dist-info/RECORD,,
76
+ unique_toolkit-0.8.2.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
77
+ unique_toolkit-0.8.2.dist-info/METADATA,sha256=nfNsDNjxM9xxW1FdMV3UVLEIXry-6kQU5Uu7RGUPrsc,26025
78
+ unique_toolkit-0.8.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
79
+ unique_toolkit-0.8.2.dist-info/RECORD,,
@@ -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)