unique_toolkit 0.8.3__tar.gz → 0.8.5__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.
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/CHANGELOG.md +6 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/PKG-INFO +7 -1
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/pyproject.toml +1 -1
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/app/schemas.py +3 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/app/unique_settings.py +56 -17
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/framework_utilities/langchain/history.py +2 -2
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/framework_utilities/openai/client.py +1 -1
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/language_model/schemas.py +18 -0
- unique_toolkit-0.8.5/unique_toolkit/reference_manager/reference_manager.py +72 -0
- unique_toolkit-0.8.5/unique_toolkit/tools/agent_chunks_handler.py +62 -0
- unique_toolkit-0.8.5/unique_toolkit/tools/config.py +108 -0
- unique_toolkit-0.8.3/unique_toolkit/tools/tool_factory.py → unique_toolkit-0.8.5/unique_toolkit/tools/factory.py +15 -5
- unique_toolkit-0.8.5/unique_toolkit/tools/schemas.py +138 -0
- unique_toolkit-0.8.5/unique_toolkit/tools/test/test_tool_progress_reporter.py +204 -0
- unique_toolkit-0.8.5/unique_toolkit/tools/tool.py +168 -0
- unique_toolkit-0.8.5/unique_toolkit/tools/tool_manager.py +242 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/tools/tool_progress_reporter.py +4 -11
- unique_toolkit-0.8.5/unique_toolkit/tools/utils/execution/execution.py +282 -0
- unique_toolkit-0.8.5/unique_toolkit/tools/utils/source_handling/schema.py +22 -0
- unique_toolkit-0.8.5/unique_toolkit/tools/utils/source_handling/source_formatting.py +207 -0
- unique_toolkit-0.8.5/unique_toolkit/tools/utils/source_handling/tests/test_source_formatting.py +215 -0
- unique_toolkit-0.8.3/unique_toolkit/tools/tool_definitions.py +0 -145
- unique_toolkit-0.8.3/unique_toolkit/tools/tool_definitionsV2.py +0 -137
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/LICENSE +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/README.md +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/__init__.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/_common/_base_service.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/_common/_time_utils.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/_common/exception.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/_common/validate_required_values.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/_common/validators.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/app/__init__.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/app/dev_util.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/app/init_logging.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/app/init_sdk.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/app/performance/async_tasks.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/app/performance/async_wrapper.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/app/verification.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/chat/__init__.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/chat/constants.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/chat/functions.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/chat/schemas.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/chat/service.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/chat/state.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/chat/utils.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/content/__init__.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/content/constants.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/content/functions.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/content/schemas.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/content/service.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/content/utils.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/embedding/__init__.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/embedding/constants.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/embedding/functions.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/embedding/schemas.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/embedding/service.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/embedding/utils.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/__init__.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/config.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/constants.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/context_relevancy/constants.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/context_relevancy/prompts.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/context_relevancy/service.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/context_relevancy/utils.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/exception.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/hallucination/constants.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/hallucination/prompts.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/hallucination/service.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/hallucination/utils.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/output_parser.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/evaluators/schemas.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/framework_utilities/langchain/client.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/framework_utilities/openai/message_builder.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/framework_utilities/utils.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/language_model/__init__.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/language_model/builder.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/language_model/constants.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/language_model/functions.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/language_model/infos.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/language_model/prompt.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/language_model/reference.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/language_model/service.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/language_model/utils.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/protocols/support.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/short_term_memory/__init__.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/short_term_memory/constants.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/short_term_memory/functions.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/short_term_memory/schemas.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/short_term_memory/service.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/smart_rules/__init__.py +0 -0
- {unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/smart_rules/compile.py +0 -0
|
@@ -5,6 +5,12 @@ 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.5] - 2025-08-06
|
|
9
|
+
- Refactored tools to be in the tool-kit
|
|
10
|
+
|
|
11
|
+
## [0.8.4] - 2025-08-06
|
|
12
|
+
- Make unique settings compatible with legacy environment variables
|
|
13
|
+
|
|
8
14
|
## [0.8.3] - 2025-08-05
|
|
9
15
|
- Expose threshold field for search.
|
|
10
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: unique_toolkit
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.5
|
|
4
4
|
Summary:
|
|
5
5
|
License: Proprietary
|
|
6
6
|
Author: Martin Fadler
|
|
@@ -113,6 +113,12 @@ 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.5] - 2025-08-06
|
|
117
|
+
- Refactored tools to be in the tool-kit
|
|
118
|
+
|
|
119
|
+
## [0.8.4] - 2025-08-06
|
|
120
|
+
- Make unique settings compatible with legacy environment variables
|
|
121
|
+
|
|
116
122
|
## [0.8.3] - 2025-08-05
|
|
117
123
|
- Expose threshold field for search.
|
|
118
124
|
|
|
@@ -51,6 +51,7 @@ class BaseEvent(BaseModel):
|
|
|
51
51
|
# MCP schemas
|
|
52
52
|
###
|
|
53
53
|
|
|
54
|
+
|
|
54
55
|
class McpTool(BaseModel):
|
|
55
56
|
model_config = model_config
|
|
56
57
|
|
|
@@ -79,6 +80,7 @@ class McpTool(BaseModel):
|
|
|
79
80
|
description="Whether the tool is connected to the MCP server. This is a Unique specific field.",
|
|
80
81
|
)
|
|
81
82
|
|
|
83
|
+
|
|
82
84
|
class McpServer(BaseModel):
|
|
83
85
|
model_config = model_config
|
|
84
86
|
|
|
@@ -94,6 +96,7 @@ class McpServer(BaseModel):
|
|
|
94
96
|
)
|
|
95
97
|
tools: list[McpTool] = []
|
|
96
98
|
|
|
99
|
+
|
|
97
100
|
###
|
|
98
101
|
# ChatEvent schemas
|
|
99
102
|
###
|
|
@@ -3,7 +3,7 @@ from pathlib import Path
|
|
|
3
3
|
from typing import Self, TypeVar
|
|
4
4
|
from urllib.parse import urlparse, urlunparse
|
|
5
5
|
|
|
6
|
-
from pydantic import Field, SecretStr, model_validator
|
|
6
|
+
from pydantic import AliasChoices, Field, SecretStr, model_validator
|
|
7
7
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
8
8
|
|
|
9
9
|
logger = getLogger(__name__)
|
|
@@ -13,18 +13,34 @@ T = TypeVar("T", bound=BaseSettings)
|
|
|
13
13
|
|
|
14
14
|
def warn_about_defaults(instance: T) -> T:
|
|
15
15
|
"""Log warnings for fields that are using default values."""
|
|
16
|
-
for field_name, model_field in instance.model_fields.items():
|
|
16
|
+
for field_name, model_field in instance.__class__.model_fields.items():
|
|
17
17
|
field_value = getattr(instance, field_name)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
default_value = model_field.default
|
|
19
|
+
|
|
20
|
+
# Handle SecretStr comparison by comparing the secret values
|
|
21
|
+
if isinstance(field_value, SecretStr) and isinstance(default_value, SecretStr):
|
|
22
|
+
if field_value.get_secret_value() == default_value.get_secret_value():
|
|
23
|
+
logger.warning(
|
|
24
|
+
f"Using default value for '{field_name}': {default_value.get_secret_value()}"
|
|
25
|
+
)
|
|
26
|
+
elif field_value == default_value:
|
|
27
|
+
logger.warning(f"Using default value for '{field_name}': {default_value}")
|
|
22
28
|
return instance
|
|
23
29
|
|
|
24
30
|
|
|
25
31
|
class UniqueApp(BaseSettings):
|
|
26
|
-
id: SecretStr = Field(
|
|
27
|
-
|
|
32
|
+
id: SecretStr = Field(
|
|
33
|
+
default=SecretStr("dummy_id"),
|
|
34
|
+
validation_alias=AliasChoices(
|
|
35
|
+
"unique_app_id", "app_id", "UNIQUE_APP_ID", "APP_ID"
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
key: SecretStr = Field(
|
|
39
|
+
default=SecretStr("dummy_key"),
|
|
40
|
+
validation_alias=AliasChoices(
|
|
41
|
+
"unique_app_key", "key", "UNIQUE_APP_KEY", "KEY", "API_KEY", "api_key"
|
|
42
|
+
),
|
|
43
|
+
)
|
|
28
44
|
base_url: str = Field(
|
|
29
45
|
default="http://localhost:8092/",
|
|
30
46
|
deprecated="Use UniqueApi.base_url instead",
|
|
@@ -48,8 +64,16 @@ class UniqueApi(BaseSettings):
|
|
|
48
64
|
base_url: str = Field(
|
|
49
65
|
default="http://localhost:8092/",
|
|
50
66
|
description="The base URL of the Unique API. Ask your admin to provide you with the correct URL.",
|
|
67
|
+
validation_alias=AliasChoices(
|
|
68
|
+
"unique_api_base_url", "base_url", "UNIQUE_API_BASE_URL", "BASE_URL"
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
version: str = Field(
|
|
72
|
+
default="2023-12-06",
|
|
73
|
+
validation_alias=AliasChoices(
|
|
74
|
+
"unique_api_version", "version", "UNIQUE_API_VERSION", "VERSION"
|
|
75
|
+
),
|
|
51
76
|
)
|
|
52
|
-
version: str = Field(default="2023-12-06")
|
|
53
77
|
|
|
54
78
|
model_config = SettingsConfigDict(
|
|
55
79
|
env_prefix="unique_api_",
|
|
@@ -82,14 +106,29 @@ class UniqueApi(BaseSettings):
|
|
|
82
106
|
|
|
83
107
|
def openai_proxy_url(self) -> str:
|
|
84
108
|
parsed = urlparse(self.base_url)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
109
|
+
path = "/public/chat/openai-proxy/"
|
|
110
|
+
if parsed.hostname and "qa.unique" in parsed.hostname:
|
|
111
|
+
path = "/public/chat-gen2/openai-proxy/"
|
|
112
|
+
|
|
113
|
+
return urlunparse(parsed._replace(path=path, query=None, fragment=None))
|
|
88
114
|
|
|
89
115
|
|
|
90
116
|
class UniqueAuth(BaseSettings):
|
|
91
|
-
company_id: SecretStr = Field(
|
|
92
|
-
|
|
117
|
+
company_id: SecretStr = Field(
|
|
118
|
+
default=SecretStr("dummy_company_id"),
|
|
119
|
+
validation_alias=AliasChoices(
|
|
120
|
+
"unique_auth_company_id",
|
|
121
|
+
"company_id",
|
|
122
|
+
"UNIQUE_AUTH_COMPANY_ID",
|
|
123
|
+
"COMPANY_ID",
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
user_id: SecretStr = Field(
|
|
127
|
+
default=SecretStr("dummy_user_id"),
|
|
128
|
+
validation_alias=AliasChoices(
|
|
129
|
+
"unique_auth_user_id", "user_id", "UNIQUE_AUTH_USER_ID", "USER_ID"
|
|
130
|
+
),
|
|
131
|
+
)
|
|
93
132
|
|
|
94
133
|
model_config = SettingsConfigDict(
|
|
95
134
|
env_prefix="unique_auth_",
|
|
@@ -128,7 +167,7 @@ class UniqueSettings:
|
|
|
128
167
|
|
|
129
168
|
# Initialize settings with environment file if provided
|
|
130
169
|
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)
|
|
170
|
+
auth = UniqueAuth(_env_file=env_file_str) # type: ignore[call-arg]
|
|
171
|
+
app = UniqueApp(_env_file=env_file_str) # type: ignore[call-arg]
|
|
172
|
+
api = UniqueApi(_env_file=env_file_str) # type: ignore[call-arg]
|
|
134
173
|
return cls(auth=auth, app=app, api=api)
|
|
@@ -10,9 +10,9 @@ def unique_history_to_langchain_history(
|
|
|
10
10
|
history = []
|
|
11
11
|
for m in unique_history:
|
|
12
12
|
if m.role == UniqueRole.ASSISTANT:
|
|
13
|
-
history.append(AIMessage(content=m.content))
|
|
13
|
+
history.append(AIMessage(content=m.content or ""))
|
|
14
14
|
elif m.role == UniqueRole.USER:
|
|
15
|
-
history.append(HumanMessage(content=m.content))
|
|
15
|
+
history.append(HumanMessage(content=m.content or ""))
|
|
16
16
|
else:
|
|
17
17
|
raise Exception("Unknown message role.")
|
|
18
18
|
|
{unique_toolkit-0.8.3 → unique_toolkit-0.8.5}/unique_toolkit/framework_utilities/openai/client.py
RENAMED
|
@@ -37,7 +37,7 @@ def get_openai_client(unique_settings: UniqueSettings) -> OpenAI:
|
|
|
37
37
|
default_headers = get_default_headers(unique_settings.app, unique_settings.auth)
|
|
38
38
|
|
|
39
39
|
return OpenAI(
|
|
40
|
-
api_key=
|
|
40
|
+
api_key="dummy_key",
|
|
41
41
|
base_url=unique_settings.api.openai_proxy_url(),
|
|
42
42
|
default_headers=default_headers,
|
|
43
43
|
)
|
|
@@ -86,6 +86,24 @@ class LanguageModelFunction(BaseModel):
|
|
|
86
86
|
return seralization
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
def __eq__(self, other:Self) -> bool:
|
|
90
|
+
"""
|
|
91
|
+
Compare two tool calls based on name and arguments.
|
|
92
|
+
"""
|
|
93
|
+
if not isinstance(other, LanguageModelFunction):
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
if self.id != other.id:
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
if self.name != other.name:
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
if self.arguments != other.arguments:
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
return True
|
|
106
|
+
|
|
89
107
|
# This is tailored to the unique backend
|
|
90
108
|
class LanguageModelStreamResponse(BaseModel):
|
|
91
109
|
model_config = model_config
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from unique_toolkit.content.schemas import ContentChunk, ContentReference
|
|
2
|
+
from unique_toolkit.tools.schemas import ToolCallResponse
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class tool_chunks:
|
|
6
|
+
def __init__(self, name: str, chunks: list) -> None:
|
|
7
|
+
self.name = name
|
|
8
|
+
self.chunks = chunks
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ReferenceManager:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self._tool_chunks: dict[str, tool_chunks] = {}
|
|
14
|
+
self._chunks: list[ContentChunk] = []
|
|
15
|
+
self._references: list[list[ContentReference]] = []
|
|
16
|
+
|
|
17
|
+
def extract_referenceable_chunks(
|
|
18
|
+
self, tool_responses: list[ToolCallResponse]
|
|
19
|
+
) -> None:
|
|
20
|
+
for tool_response in tool_responses:
|
|
21
|
+
if not tool_response.content_chunks:
|
|
22
|
+
continue
|
|
23
|
+
self._chunks.extend(tool_response.content_chunks or [])
|
|
24
|
+
self._tool_chunks[tool_response.id] = tool_chunks(
|
|
25
|
+
tool_response.name, tool_response.content_chunks
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def get_chunks(self) -> list[ContentChunk]:
|
|
29
|
+
return self._chunks
|
|
30
|
+
|
|
31
|
+
def get_tool_chunks(self) -> dict:
|
|
32
|
+
return self._tool_chunks
|
|
33
|
+
|
|
34
|
+
def replace(self, chunks: list[ContentChunk]):
|
|
35
|
+
self._chunks = chunks
|
|
36
|
+
|
|
37
|
+
def add_references(
|
|
38
|
+
self,
|
|
39
|
+
references: list[ContentReference],
|
|
40
|
+
):
|
|
41
|
+
self._references.append(references)
|
|
42
|
+
|
|
43
|
+
def get_references(
|
|
44
|
+
self,
|
|
45
|
+
) -> list[list[ContentReference]]:
|
|
46
|
+
return self._references
|
|
47
|
+
|
|
48
|
+
def get_latest_references(
|
|
49
|
+
self,
|
|
50
|
+
) -> list[ContentReference]:
|
|
51
|
+
if not self._references:
|
|
52
|
+
return []
|
|
53
|
+
return self._references[-1]
|
|
54
|
+
|
|
55
|
+
def get_latest_referenced_chunks(self) -> list[ContentChunk]:
|
|
56
|
+
if not self._references:
|
|
57
|
+
return []
|
|
58
|
+
return self._get_referenced_chunks_from_references(self._references[-1])
|
|
59
|
+
|
|
60
|
+
def _get_referenced_chunks_from_references(
|
|
61
|
+
self,
|
|
62
|
+
references: list[ContentReference],
|
|
63
|
+
) -> list[ContentChunk]:
|
|
64
|
+
"""
|
|
65
|
+
Get _referenced_chunks by matching sourceId from _references with merged id and chunk_id from _chunks.
|
|
66
|
+
"""
|
|
67
|
+
referenced_chunks: list[ContentChunk] = []
|
|
68
|
+
for ref in references:
|
|
69
|
+
for chunk in self._chunks:
|
|
70
|
+
if ref.source_id == f"{chunk.id}-{chunk.chunk_id}":
|
|
71
|
+
referenced_chunks.append(chunk)
|
|
72
|
+
return referenced_chunks
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from unique_toolkit.content.schemas import ContentChunk, ContentReference
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AgentChunksHandler:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self._tool_chunks = {}
|
|
7
|
+
self._chunks: list[ContentChunk] = []
|
|
8
|
+
self._references: list[list[ContentReference]] = []
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def chunks(self) -> list[ContentChunk]:
|
|
12
|
+
return self._chunks
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def tool_chunks(self) -> dict:
|
|
16
|
+
return self._tool_chunks
|
|
17
|
+
|
|
18
|
+
def extend(self, chunks: list[ContentChunk]):
|
|
19
|
+
self._chunks.extend(chunks)
|
|
20
|
+
|
|
21
|
+
def replace(self, chunks: list[ContentChunk]):
|
|
22
|
+
self._chunks = chunks
|
|
23
|
+
|
|
24
|
+
def add_references(
|
|
25
|
+
self,
|
|
26
|
+
references: list[ContentReference],
|
|
27
|
+
):
|
|
28
|
+
self._references.append(references)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def all_references(
|
|
32
|
+
self,
|
|
33
|
+
) -> list[list[ContentReference]]:
|
|
34
|
+
return self._references
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def latest_references(
|
|
38
|
+
self,
|
|
39
|
+
) -> list[ContentReference]:
|
|
40
|
+
if not self._references:
|
|
41
|
+
return []
|
|
42
|
+
return self._references[-1]
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def latest_referenced_chunks(self) -> list[ContentChunk]:
|
|
46
|
+
if not self._references:
|
|
47
|
+
return []
|
|
48
|
+
return self._get_referenced_chunks_from_references(self._references[-1])
|
|
49
|
+
|
|
50
|
+
def _get_referenced_chunks_from_references(
|
|
51
|
+
self,
|
|
52
|
+
references: list[ContentReference],
|
|
53
|
+
) -> list[ContentChunk]:
|
|
54
|
+
"""
|
|
55
|
+
Get _referenced_chunks by matching sourceId from _references with merged id and chunk_id from _chunks.
|
|
56
|
+
"""
|
|
57
|
+
referenced_chunks: list[ContentChunk] = []
|
|
58
|
+
for ref in references:
|
|
59
|
+
for chunk in self._chunks:
|
|
60
|
+
if ref.source_id == str(chunk.id) + "_" + str(chunk.chunk_id):
|
|
61
|
+
referenced_chunks.append(chunk)
|
|
62
|
+
return referenced_chunks
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
import humps
|
|
3
|
+
from typing import Any
|
|
4
|
+
from pydantic.fields import ComputedFieldInfo, FieldInfo
|
|
5
|
+
from pydantic.alias_generators import to_camel
|
|
6
|
+
from pydantic import (
|
|
7
|
+
BaseModel,
|
|
8
|
+
ConfigDict,
|
|
9
|
+
Field,
|
|
10
|
+
ValidationInfo,
|
|
11
|
+
model_validator,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from unique_toolkit.tools.schemas import BaseToolConfig
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def field_title_generator(
|
|
21
|
+
title: str,
|
|
22
|
+
info: FieldInfo | ComputedFieldInfo,
|
|
23
|
+
) -> str:
|
|
24
|
+
return humps.decamelize(title).replace("_", " ").title()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def model_title_generator(model: type) -> str:
|
|
28
|
+
return humps.decamelize(model.__name__).replace("_", " ").title()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_configuration_dict(**kwargs) -> ConfigDict:
|
|
32
|
+
return ConfigDict(
|
|
33
|
+
alias_generator=to_camel,
|
|
34
|
+
field_title_generator=field_title_generator,
|
|
35
|
+
model_title_generator=model_title_generator,
|
|
36
|
+
populate_by_name=True,
|
|
37
|
+
protected_namespaces=(),
|
|
38
|
+
**kwargs,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ToolIcon(StrEnum):
|
|
43
|
+
ANALYTICS = "IconAnalytics"
|
|
44
|
+
BOOK = "IconBook"
|
|
45
|
+
FOLDERDATA = "IconFolderData"
|
|
46
|
+
INTEGRATION = "IconIntegration"
|
|
47
|
+
TEXT_COMPARE = "IconTextCompare"
|
|
48
|
+
WORLD = "IconWorld"
|
|
49
|
+
QUICK_REPLY = "IconQuickReply"
|
|
50
|
+
CHAT_PLUS = "IconChatPlus"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ToolSelectionPolicy(StrEnum):
|
|
54
|
+
"""Determine the usage policy of tools."""
|
|
55
|
+
|
|
56
|
+
FORCED_BY_DEFAULT = "ForcedByDefault"
|
|
57
|
+
ON_BY_DEFAULT = "OnByDefault"
|
|
58
|
+
BY_USER = "ByUser"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ToolBuildConfig(BaseModel):
|
|
62
|
+
model_config = get_configuration_dict()
|
|
63
|
+
"""Main tool configuration"""
|
|
64
|
+
|
|
65
|
+
name: str
|
|
66
|
+
configuration: "BaseToolConfig"
|
|
67
|
+
display_name: str = ""
|
|
68
|
+
icon: ToolIcon = ToolIcon.BOOK
|
|
69
|
+
selection_policy: ToolSelectionPolicy = Field(
|
|
70
|
+
default=ToolSelectionPolicy.BY_USER,
|
|
71
|
+
)
|
|
72
|
+
is_exclusive: bool = Field(
|
|
73
|
+
default=False,
|
|
74
|
+
description="This tool must be chosen by the user and no other tools are used for this iteration.",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
is_enabled: bool = Field(default=True)
|
|
78
|
+
|
|
79
|
+
@model_validator(mode="before")
|
|
80
|
+
def initialize_config_based_on_tool_name(
|
|
81
|
+
cls,
|
|
82
|
+
value: Any,
|
|
83
|
+
info: ValidationInfo,
|
|
84
|
+
) -> Any:
|
|
85
|
+
"""Check the given values for."""
|
|
86
|
+
if not isinstance(value, dict):
|
|
87
|
+
return value
|
|
88
|
+
|
|
89
|
+
configuration = value.get("configuration", {})
|
|
90
|
+
if isinstance(configuration, dict):
|
|
91
|
+
# Local import to avoid circular import at module import time
|
|
92
|
+
from unique_toolkit.tools.factory import ToolFactory
|
|
93
|
+
|
|
94
|
+
config = ToolFactory.build_tool_config(
|
|
95
|
+
value["name"],
|
|
96
|
+
**configuration,
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
# Check that the type of config matches the tool name
|
|
100
|
+
from unique_toolkit.tools.factory import ToolFactory
|
|
101
|
+
|
|
102
|
+
assert isinstance(
|
|
103
|
+
configuration,
|
|
104
|
+
ToolFactory.tool_config_map[value["name"]], # type: ignore
|
|
105
|
+
)
|
|
106
|
+
config = configuration
|
|
107
|
+
value["configuration"] = config
|
|
108
|
+
return value
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
from typing import Callable
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
from unique_toolkit.tools.schemas import BaseToolConfig
|
|
5
|
+
from unique_toolkit.tools.tool import Tool
|
|
4
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from unique_toolkit.tools.config import ToolBuildConfig
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
class ToolFactory:
|
|
@@ -18,14 +22,20 @@ class ToolFactory:
|
|
|
18
22
|
cls.tool_config_map[tool.name] = tool_config
|
|
19
23
|
|
|
20
24
|
@classmethod
|
|
21
|
-
def build_tool(cls, tool_name: str, *args, **kwargs) -> Tool:
|
|
25
|
+
def build_tool(cls, tool_name: str, *args, **kwargs) -> Tool[BaseToolConfig]:
|
|
22
26
|
tool = cls.tool_map[tool_name](*args, **kwargs)
|
|
23
27
|
return tool
|
|
24
28
|
|
|
25
29
|
@classmethod
|
|
26
|
-
def
|
|
27
|
-
cls, tool_name: str, **kwargs
|
|
28
|
-
) -> BaseToolConfig:
|
|
30
|
+
def build_tool_with_settings(
|
|
31
|
+
cls, tool_name: str, settings: "ToolBuildConfig", *args, **kwargs
|
|
32
|
+
) -> Tool[BaseToolConfig]:
|
|
33
|
+
tool = cls.tool_map[tool_name](*args, **kwargs)
|
|
34
|
+
tool.settings = settings
|
|
35
|
+
return tool
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def build_tool_config(cls, tool_name: str, **kwargs) -> BaseToolConfig:
|
|
29
39
|
if tool_name not in cls.tool_config_map:
|
|
30
40
|
raise ValueError(f"Tool {tool_name} not found")
|
|
31
41
|
return cls.tool_config_map[tool_name](**kwargs)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import gzip
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator
|
|
7
|
+
from unique_toolkit.content.schemas import ContentChunk
|
|
8
|
+
|
|
9
|
+
from unique_toolkit.tools.config import get_configuration_dict
|
|
10
|
+
from unique_toolkit.tools.utils.source_handling.schema import SourceFormatConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# TODO: this needs to be more general as the tools can potentially return anything maybe make a base class and then derive per "type" of tool
|
|
14
|
+
class ToolCallResponse(BaseModel):
|
|
15
|
+
id: str
|
|
16
|
+
name: str
|
|
17
|
+
debug_info: Optional[dict] = None # TODO: Make the default {}
|
|
18
|
+
content_chunks: Optional[list[ContentChunk]] = None # TODO: Make the default []
|
|
19
|
+
reasoning_result: Optional[dict] = None # TODO: Make the default {}
|
|
20
|
+
error_message: str = ""
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def successful(self) -> bool:
|
|
24
|
+
return self.error_message == ""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BaseToolConfig(BaseModel):
|
|
28
|
+
model_config = get_configuration_dict()
|
|
29
|
+
# TODO: add a check for the parameters to all be consistent within the tool config
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Source(BaseModel):
|
|
34
|
+
"""Represents the sources in the tool call response that the llm will see
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
source_number: The number of the source
|
|
38
|
+
content: The content of the source
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
model_config = ConfigDict(
|
|
42
|
+
validate_by_alias=True, serialize_by_alias=True, validate_by_name=True
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
source_number: int | None = Field(
|
|
46
|
+
default=None,
|
|
47
|
+
serialization_alias="[source_number] - Used for citations!",
|
|
48
|
+
validation_alias="[source_number] - Used for citations!",
|
|
49
|
+
)
|
|
50
|
+
content: str = Field(
|
|
51
|
+
serialization_alias="[content] - Content of source",
|
|
52
|
+
validation_alias="[content] - Content of source",
|
|
53
|
+
)
|
|
54
|
+
order: int = Field(
|
|
55
|
+
serialization_alias="[order] - Index in the document!",
|
|
56
|
+
validation_alias="[order] - Index in the document!",
|
|
57
|
+
)
|
|
58
|
+
chunk_id: str | None = Field(
|
|
59
|
+
default=None,
|
|
60
|
+
serialization_alias="[chunk_id] - IGNORE",
|
|
61
|
+
validation_alias="[chunk_id] - IGNORE",
|
|
62
|
+
)
|
|
63
|
+
id: str = Field(
|
|
64
|
+
serialization_alias="[id] - IGNORE",
|
|
65
|
+
validation_alias="[id] - IGNORE",
|
|
66
|
+
)
|
|
67
|
+
key: str | None = Field(
|
|
68
|
+
default=None,
|
|
69
|
+
serialization_alias="[key] - IGNORE",
|
|
70
|
+
validation_alias="[key] - IGNORE",
|
|
71
|
+
)
|
|
72
|
+
metadata: dict[str, str] | str | None = Field(
|
|
73
|
+
default=None,
|
|
74
|
+
serialization_alias="[metadata] - Formatted metadata",
|
|
75
|
+
validation_alias="[metadata] - Formatted metadata",
|
|
76
|
+
)
|
|
77
|
+
url: str | None = Field(
|
|
78
|
+
default=None,
|
|
79
|
+
serialization_alias="[url] - IGNORE",
|
|
80
|
+
validation_alias="[url] - IGNORE",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@field_validator("metadata", mode="before")
|
|
84
|
+
def _metadata_str_to_dict(
|
|
85
|
+
cls, v: str | dict[str, str] | None
|
|
86
|
+
) -> dict[str, str] | None:
|
|
87
|
+
"""
|
|
88
|
+
Accept • dict → keep as-is
|
|
89
|
+
• str → parse tag-string back to dict
|
|
90
|
+
"""
|
|
91
|
+
if v is None or isinstance(v, dict):
|
|
92
|
+
return v
|
|
93
|
+
|
|
94
|
+
# v is the rendered string. Build a dict by matching the
|
|
95
|
+
# patterns defined in SourceFormatConfig.sections.
|
|
96
|
+
cfg = SourceFormatConfig() # or inject your app-wide config
|
|
97
|
+
out: dict[str, str] = {}
|
|
98
|
+
for key, tmpl in cfg.sections.items():
|
|
99
|
+
pattern = cfg.template_to_pattern(tmpl)
|
|
100
|
+
m = re.search(pattern, v, flags=re.S)
|
|
101
|
+
if m:
|
|
102
|
+
out[key] = m.group(1).strip()
|
|
103
|
+
|
|
104
|
+
return out if out else v # type: ignore
|
|
105
|
+
|
|
106
|
+
# Compression + Base64 for url to hide it from the LLM
|
|
107
|
+
@field_serializer("url")
|
|
108
|
+
def serialize_url(self, value: str | None) -> str | None:
|
|
109
|
+
if value is None:
|
|
110
|
+
return None
|
|
111
|
+
# Compress then base64 encode
|
|
112
|
+
compressed = gzip.compress(value.encode())
|
|
113
|
+
return base64.b64encode(compressed).decode()
|
|
114
|
+
|
|
115
|
+
@field_validator("url", mode="before")
|
|
116
|
+
@classmethod
|
|
117
|
+
def validate_url(cls, value: Any) -> str | None:
|
|
118
|
+
if value is None or isinstance(value, str) and not value:
|
|
119
|
+
return None
|
|
120
|
+
if isinstance(value, str):
|
|
121
|
+
try:
|
|
122
|
+
# Try to decode base64 then decompress
|
|
123
|
+
decoded_bytes = base64.b64decode(value.encode())
|
|
124
|
+
decompressed = gzip.decompress(decoded_bytes).decode()
|
|
125
|
+
return decompressed
|
|
126
|
+
except Exception:
|
|
127
|
+
# If decoding/decompression fails, assume it's plain text
|
|
128
|
+
return value
|
|
129
|
+
return str(value)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ToolPrompts(BaseModel):
|
|
133
|
+
name: str
|
|
134
|
+
display_name: str
|
|
135
|
+
tool_description: str
|
|
136
|
+
tool_format_information_for_system_prompt: str
|
|
137
|
+
tool_format_information_for_system_prompt: str
|
|
138
|
+
input_model: dict[str, Any]
|