unique_toolkit 0.7.7__py3-none-any.whl → 1.23.0__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.
Potentially problematic release.
This version of unique_toolkit might be problematic. Click here for more details.
- unique_toolkit/__init__.py +28 -1
- unique_toolkit/_common/api_calling/human_verification_manager.py +343 -0
- unique_toolkit/_common/base_model_type_attribute.py +303 -0
- unique_toolkit/_common/chunk_relevancy_sorter/config.py +49 -0
- unique_toolkit/_common/chunk_relevancy_sorter/exception.py +5 -0
- unique_toolkit/_common/chunk_relevancy_sorter/schemas.py +46 -0
- unique_toolkit/_common/chunk_relevancy_sorter/service.py +374 -0
- unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +275 -0
- unique_toolkit/_common/default_language_model.py +12 -0
- unique_toolkit/_common/docx_generator/__init__.py +7 -0
- unique_toolkit/_common/docx_generator/config.py +12 -0
- unique_toolkit/_common/docx_generator/schemas.py +80 -0
- unique_toolkit/_common/docx_generator/service.py +252 -0
- unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
- unique_toolkit/_common/endpoint_builder.py +305 -0
- unique_toolkit/_common/endpoint_requestor.py +430 -0
- unique_toolkit/_common/exception.py +24 -0
- unique_toolkit/_common/feature_flags/schema.py +9 -0
- unique_toolkit/_common/pydantic/rjsf_tags.py +936 -0
- unique_toolkit/_common/pydantic_helpers.py +154 -0
- unique_toolkit/_common/referencing.py +53 -0
- unique_toolkit/_common/string_utilities.py +140 -0
- unique_toolkit/_common/tests/test_referencing.py +521 -0
- unique_toolkit/_common/tests/test_string_utilities.py +506 -0
- unique_toolkit/_common/token/image_token_counting.py +67 -0
- unique_toolkit/_common/token/token_counting.py +204 -0
- unique_toolkit/_common/utils/__init__.py +1 -0
- unique_toolkit/_common/utils/files.py +43 -0
- unique_toolkit/_common/utils/structured_output/__init__.py +1 -0
- unique_toolkit/_common/utils/structured_output/schema.py +5 -0
- unique_toolkit/_common/utils/write_configuration.py +51 -0
- unique_toolkit/_common/validators.py +101 -4
- unique_toolkit/agentic/__init__.py +1 -0
- unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +28 -0
- unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +278 -0
- unique_toolkit/agentic/evaluation/config.py +36 -0
- unique_toolkit/{evaluators → agentic/evaluation}/context_relevancy/prompts.py +25 -0
- unique_toolkit/agentic/evaluation/context_relevancy/schema.py +80 -0
- unique_toolkit/agentic/evaluation/context_relevancy/service.py +273 -0
- unique_toolkit/agentic/evaluation/evaluation_manager.py +218 -0
- unique_toolkit/agentic/evaluation/hallucination/constants.py +61 -0
- unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +111 -0
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/prompts.py +1 -1
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/service.py +16 -15
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/utils.py +30 -20
- unique_toolkit/{evaluators → agentic/evaluation}/output_parser.py +20 -2
- unique_toolkit/{evaluators → agentic/evaluation}/schemas.py +27 -7
- unique_toolkit/agentic/evaluation/tests/test_context_relevancy_service.py +253 -0
- unique_toolkit/agentic/evaluation/tests/test_output_parser.py +87 -0
- unique_toolkit/agentic/history_manager/history_construction_with_contents.py +297 -0
- unique_toolkit/agentic/history_manager/history_manager.py +242 -0
- unique_toolkit/agentic/history_manager/loop_token_reducer.py +484 -0
- unique_toolkit/agentic/history_manager/utils.py +96 -0
- unique_toolkit/agentic/postprocessor/postprocessor_manager.py +212 -0
- unique_toolkit/agentic/reference_manager/reference_manager.py +103 -0
- unique_toolkit/agentic/responses_api/__init__.py +19 -0
- unique_toolkit/agentic/responses_api/postprocessors/code_display.py +63 -0
- unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +145 -0
- unique_toolkit/agentic/responses_api/stream_handler.py +15 -0
- unique_toolkit/agentic/short_term_memory_manager/persistent_short_term_memory_manager.py +141 -0
- unique_toolkit/agentic/thinking_manager/thinking_manager.py +103 -0
- unique_toolkit/agentic/tools/__init__.py +1 -0
- unique_toolkit/agentic/tools/a2a/__init__.py +36 -0
- unique_toolkit/agentic/tools/a2a/config.py +17 -0
- unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +15 -0
- unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +66 -0
- unique_toolkit/agentic/tools/a2a/evaluation/config.py +55 -0
- unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +260 -0
- unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2 +9 -0
- unique_toolkit/agentic/tools/a2a/manager.py +55 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +21 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py +185 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +73 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/config.py +45 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/display.py +180 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/references.py +101 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +1335 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py +603 -0
- unique_toolkit/agentic/tools/a2a/prompts.py +46 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py +6 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/service.py +91 -0
- unique_toolkit/agentic/tools/a2a/tool/__init__.py +4 -0
- unique_toolkit/agentic/tools/a2a/tool/_memory.py +26 -0
- unique_toolkit/agentic/tools/a2a/tool/_schema.py +9 -0
- unique_toolkit/agentic/tools/a2a/tool/config.py +73 -0
- unique_toolkit/agentic/tools/a2a/tool/service.py +306 -0
- unique_toolkit/agentic/tools/agent_chunks_hanlder.py +65 -0
- unique_toolkit/agentic/tools/config.py +167 -0
- unique_toolkit/agentic/tools/factory.py +44 -0
- unique_toolkit/agentic/tools/mcp/__init__.py +4 -0
- unique_toolkit/agentic/tools/mcp/manager.py +71 -0
- unique_toolkit/agentic/tools/mcp/models.py +28 -0
- unique_toolkit/agentic/tools/mcp/tool_wrapper.py +234 -0
- unique_toolkit/agentic/tools/openai_builtin/__init__.py +11 -0
- unique_toolkit/agentic/tools/openai_builtin/base.py +30 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +8 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +57 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +230 -0
- unique_toolkit/agentic/tools/openai_builtin/manager.py +62 -0
- unique_toolkit/agentic/tools/schemas.py +141 -0
- unique_toolkit/agentic/tools/test/test_mcp_manager.py +536 -0
- unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +445 -0
- unique_toolkit/agentic/tools/tool.py +183 -0
- unique_toolkit/agentic/tools/tool_manager.py +523 -0
- unique_toolkit/agentic/tools/tool_progress_reporter.py +285 -0
- unique_toolkit/agentic/tools/utils/__init__.py +19 -0
- unique_toolkit/agentic/tools/utils/execution/__init__.py +1 -0
- unique_toolkit/agentic/tools/utils/execution/execution.py +286 -0
- unique_toolkit/agentic/tools/utils/source_handling/__init__.py +0 -0
- unique_toolkit/agentic/tools/utils/source_handling/schema.py +21 -0
- unique_toolkit/agentic/tools/utils/source_handling/source_formatting.py +207 -0
- unique_toolkit/agentic/tools/utils/source_handling/tests/test_source_formatting.py +216 -0
- unique_toolkit/app/__init__.py +6 -0
- unique_toolkit/app/dev_util.py +180 -0
- unique_toolkit/app/init_sdk.py +32 -1
- unique_toolkit/app/schemas.py +198 -31
- unique_toolkit/app/unique_settings.py +367 -0
- unique_toolkit/chat/__init__.py +8 -1
- unique_toolkit/chat/deprecated/service.py +232 -0
- unique_toolkit/chat/functions.py +642 -77
- unique_toolkit/chat/rendering.py +34 -0
- unique_toolkit/chat/responses_api.py +461 -0
- unique_toolkit/chat/schemas.py +133 -2
- unique_toolkit/chat/service.py +115 -767
- unique_toolkit/content/functions.py +153 -4
- unique_toolkit/content/schemas.py +122 -15
- unique_toolkit/content/service.py +278 -44
- unique_toolkit/content/smart_rules.py +301 -0
- unique_toolkit/content/utils.py +8 -3
- unique_toolkit/embedding/service.py +102 -11
- unique_toolkit/framework_utilities/__init__.py +1 -0
- unique_toolkit/framework_utilities/langchain/client.py +71 -0
- unique_toolkit/framework_utilities/langchain/history.py +19 -0
- unique_toolkit/framework_utilities/openai/__init__.py +6 -0
- unique_toolkit/framework_utilities/openai/client.py +83 -0
- unique_toolkit/framework_utilities/openai/message_builder.py +229 -0
- unique_toolkit/framework_utilities/utils.py +23 -0
- unique_toolkit/language_model/__init__.py +3 -0
- unique_toolkit/language_model/builder.py +27 -11
- unique_toolkit/language_model/default_language_model.py +3 -0
- unique_toolkit/language_model/functions.py +327 -43
- unique_toolkit/language_model/infos.py +992 -50
- unique_toolkit/language_model/reference.py +242 -0
- unique_toolkit/language_model/schemas.py +475 -48
- unique_toolkit/language_model/service.py +228 -27
- unique_toolkit/protocols/support.py +145 -0
- unique_toolkit/services/__init__.py +7 -0
- unique_toolkit/services/chat_service.py +1630 -0
- unique_toolkit/services/knowledge_base.py +861 -0
- unique_toolkit/short_term_memory/service.py +178 -41
- unique_toolkit/smart_rules/__init__.py +0 -0
- unique_toolkit/smart_rules/compile.py +56 -0
- unique_toolkit/test_utilities/events.py +197 -0
- {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/METADATA +606 -7
- unique_toolkit-1.23.0.dist-info/RECORD +182 -0
- unique_toolkit/evaluators/__init__.py +0 -1
- unique_toolkit/evaluators/config.py +0 -35
- unique_toolkit/evaluators/constants.py +0 -1
- unique_toolkit/evaluators/context_relevancy/constants.py +0 -32
- unique_toolkit/evaluators/context_relevancy/service.py +0 -53
- unique_toolkit/evaluators/context_relevancy/utils.py +0 -142
- unique_toolkit/evaluators/hallucination/constants.py +0 -41
- unique_toolkit-0.7.7.dist-info/RECORD +0 -64
- /unique_toolkit/{evaluators → agentic/evaluation}/exception.py +0 -0
- {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/LICENSE +0 -0
- {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
from typing import NamedTuple
|
|
4
|
+
|
|
5
|
+
import unique_sdk
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _clone_message(
|
|
9
|
+
message: unique_sdk.Space.Message,
|
|
10
|
+
) -> unique_sdk.Space.Message:
|
|
11
|
+
# copy.deepcopy does not work for instances of UniqueObject
|
|
12
|
+
return json.loads(json.dumps(message))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SubAgentResponse(NamedTuple):
|
|
16
|
+
assistant_id: str
|
|
17
|
+
name: str
|
|
18
|
+
sequence_number: int
|
|
19
|
+
message: unique_sdk.Space.Message
|
|
20
|
+
timestamp: datetime.datetime
|
|
21
|
+
|
|
22
|
+
def clone(self) -> "SubAgentResponse":
|
|
23
|
+
return SubAgentResponse(
|
|
24
|
+
assistant_id=self.assistant_id,
|
|
25
|
+
name=self.name,
|
|
26
|
+
sequence_number=self.sequence_number,
|
|
27
|
+
message=_clone_message(self.message),
|
|
28
|
+
timestamp=self.timestamp,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SubAgentResponseWatcher:
|
|
33
|
+
"""
|
|
34
|
+
Save and retrieve sub agent responses immutably.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self) -> None:
|
|
38
|
+
self._response_registry: dict[str, list[SubAgentResponse]] = {}
|
|
39
|
+
|
|
40
|
+
def notify_response(
|
|
41
|
+
self,
|
|
42
|
+
assistant_id: str,
|
|
43
|
+
name: str,
|
|
44
|
+
sequence_number: int,
|
|
45
|
+
response: unique_sdk.Space.Message,
|
|
46
|
+
timestamp: datetime.datetime,
|
|
47
|
+
) -> None:
|
|
48
|
+
if assistant_id not in self._response_registry:
|
|
49
|
+
self._response_registry[assistant_id] = []
|
|
50
|
+
|
|
51
|
+
response = _clone_message(response)
|
|
52
|
+
|
|
53
|
+
self._response_registry[assistant_id].append(
|
|
54
|
+
SubAgentResponse(
|
|
55
|
+
assistant_id=assistant_id,
|
|
56
|
+
name=name,
|
|
57
|
+
sequence_number=sequence_number,
|
|
58
|
+
message=response,
|
|
59
|
+
timestamp=timestamp,
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def get_responses(self, assistant_id: str) -> list[SubAgentResponse]:
|
|
64
|
+
return _sort_responses( # Always return a consistent order
|
|
65
|
+
[
|
|
66
|
+
response.clone()
|
|
67
|
+
for response in self._response_registry.get(assistant_id, [])
|
|
68
|
+
],
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def get_all_responses(self) -> list[SubAgentResponse]:
|
|
72
|
+
return _sort_responses(
|
|
73
|
+
[
|
|
74
|
+
response.clone()
|
|
75
|
+
for sub_agent_responses in self._response_registry.values()
|
|
76
|
+
for response in sub_agent_responses
|
|
77
|
+
],
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _sort_responses(
|
|
82
|
+
responses: list[SubAgentResponse],
|
|
83
|
+
) -> list[SubAgentResponse]:
|
|
84
|
+
return sorted(
|
|
85
|
+
responses,
|
|
86
|
+
key=lambda response: (
|
|
87
|
+
response.timestamp,
|
|
88
|
+
response.assistant_id,
|
|
89
|
+
response.sequence_number,
|
|
90
|
+
),
|
|
91
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from unique_toolkit import ShortTermMemoryService
|
|
2
|
+
from unique_toolkit.agentic.short_term_memory_manager.persistent_short_term_memory_manager import (
|
|
3
|
+
PersistentShortMemoryManager,
|
|
4
|
+
)
|
|
5
|
+
from unique_toolkit.agentic.tools.a2a.tool._schema import SubAgentShortTermMemorySchema
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _get_short_term_memory_name(assistant_id: str) -> str:
|
|
9
|
+
return f"sub_agent_chat_id_{assistant_id}"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_sub_agent_short_term_memory_manager(
|
|
13
|
+
company_id: str, user_id: str, chat_id: str, assistant_id: str
|
|
14
|
+
) -> PersistentShortMemoryManager[SubAgentShortTermMemorySchema]:
|
|
15
|
+
short_term_memory_service = ShortTermMemoryService(
|
|
16
|
+
company_id=company_id,
|
|
17
|
+
user_id=user_id,
|
|
18
|
+
chat_id=chat_id,
|
|
19
|
+
message_id=None,
|
|
20
|
+
)
|
|
21
|
+
short_term_memory_manager = PersistentShortMemoryManager(
|
|
22
|
+
short_term_memory_service=short_term_memory_service,
|
|
23
|
+
short_term_memory_schema=SubAgentShortTermMemorySchema,
|
|
24
|
+
short_term_memory_name=_get_short_term_memory_name(assistant_id),
|
|
25
|
+
)
|
|
26
|
+
return short_term_memory_manager
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from unique_toolkit._common.pydantic_helpers import get_configuration_dict
|
|
6
|
+
from unique_toolkit.agentic.tools.schemas import BaseToolConfig
|
|
7
|
+
|
|
8
|
+
DEFAULT_PARAM_DESCRIPTION_SUB_AGENT_USER_MESSAGE = """
|
|
9
|
+
This is the message that will be sent to the sub-agent.
|
|
10
|
+
""".strip()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SubAgentToolConfig(BaseToolConfig):
|
|
14
|
+
model_config = get_configuration_dict()
|
|
15
|
+
|
|
16
|
+
assistant_id: str = Field(
|
|
17
|
+
default="",
|
|
18
|
+
description="The unique identifier of the assistant to use for the sub-agent.",
|
|
19
|
+
)
|
|
20
|
+
chat_id: str | None = Field(
|
|
21
|
+
default=None,
|
|
22
|
+
description="The chat ID to use for the sub-agent conversation. If None, a new chat will be created.",
|
|
23
|
+
)
|
|
24
|
+
reuse_chat: bool = Field(
|
|
25
|
+
default=True,
|
|
26
|
+
description="Whether to reuse the existing chat or create a new one for each sub-agent call.",
|
|
27
|
+
)
|
|
28
|
+
use_sub_agent_references: bool = Field(
|
|
29
|
+
default=True,
|
|
30
|
+
description="Whether this sub agent's references should be used in the main agent's response.",
|
|
31
|
+
)
|
|
32
|
+
forced_tools: list[str] | None = Field(
|
|
33
|
+
default=None,
|
|
34
|
+
description="The list of tool names that will be forced to be called for this sub-agent.",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
tool_description_for_system_prompt: str = Field(
|
|
38
|
+
default="",
|
|
39
|
+
description="Description of the tool that will be included in the system prompt.",
|
|
40
|
+
)
|
|
41
|
+
tool_description: str = Field(
|
|
42
|
+
default="",
|
|
43
|
+
description="Description of the tool that will be included in the tools sent to the model.",
|
|
44
|
+
)
|
|
45
|
+
param_description_sub_agent_user_message: str = Field(
|
|
46
|
+
default=DEFAULT_PARAM_DESCRIPTION_SUB_AGENT_USER_MESSAGE,
|
|
47
|
+
description="Description of the user message parameter that will be sent to the model.",
|
|
48
|
+
)
|
|
49
|
+
tool_format_information_for_system_prompt: str = Field(
|
|
50
|
+
default="",
|
|
51
|
+
description="Format information that will be included in the system prompt to guide response formatting.",
|
|
52
|
+
)
|
|
53
|
+
tool_description_for_user_prompt: str = Field(
|
|
54
|
+
default="",
|
|
55
|
+
description="Description of the tool that will be included in the user prompt.",
|
|
56
|
+
)
|
|
57
|
+
tool_format_information_for_user_prompt: str = Field(
|
|
58
|
+
default="",
|
|
59
|
+
description="Format information that will be included in the user prompt to guide response formatting.",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
poll_interval: float = Field(
|
|
63
|
+
default=1.0,
|
|
64
|
+
description="Time interval in seconds between polling attempts when waiting for sub-agent response.",
|
|
65
|
+
)
|
|
66
|
+
max_wait: float = Field(
|
|
67
|
+
default=120.0,
|
|
68
|
+
description="Maximum time in seconds to wait for the sub-agent response before timing out.",
|
|
69
|
+
)
|
|
70
|
+
stop_condition: Literal["stoppedStreamingAt", "completedAt"] = Field(
|
|
71
|
+
default="completedAt",
|
|
72
|
+
description="The condition that will be used to stop the polling for the sub-agent response.",
|
|
73
|
+
)
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import override
|
|
7
|
+
|
|
8
|
+
import unique_sdk
|
|
9
|
+
from pydantic import Field, create_model
|
|
10
|
+
from unique_sdk.utils.chat_in_space import send_message_and_wait_for_completion
|
|
11
|
+
|
|
12
|
+
from unique_toolkit._common.referencing import (
|
|
13
|
+
get_all_ref_numbers,
|
|
14
|
+
remove_all_refs,
|
|
15
|
+
replace_ref_number,
|
|
16
|
+
)
|
|
17
|
+
from unique_toolkit.agentic.evaluation.schemas import EvaluationMetricName
|
|
18
|
+
from unique_toolkit.agentic.tools.a2a.response_watcher import SubAgentResponseWatcher
|
|
19
|
+
from unique_toolkit.agentic.tools.a2a.tool._memory import (
|
|
20
|
+
get_sub_agent_short_term_memory_manager,
|
|
21
|
+
)
|
|
22
|
+
from unique_toolkit.agentic.tools.a2a.tool._schema import (
|
|
23
|
+
SubAgentShortTermMemorySchema,
|
|
24
|
+
SubAgentToolInput,
|
|
25
|
+
)
|
|
26
|
+
from unique_toolkit.agentic.tools.a2a.tool.config import (
|
|
27
|
+
SubAgentToolConfig,
|
|
28
|
+
)
|
|
29
|
+
from unique_toolkit.agentic.tools.factory import ToolFactory
|
|
30
|
+
from unique_toolkit.agentic.tools.schemas import ToolCallResponse
|
|
31
|
+
from unique_toolkit.agentic.tools.tool import Tool
|
|
32
|
+
from unique_toolkit.agentic.tools.tool_progress_reporter import (
|
|
33
|
+
ProgressState,
|
|
34
|
+
ToolProgressReporter,
|
|
35
|
+
)
|
|
36
|
+
from unique_toolkit.app import ChatEvent
|
|
37
|
+
from unique_toolkit.language_model import (
|
|
38
|
+
LanguageModelFunction,
|
|
39
|
+
LanguageModelToolDescription,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SubAgentTool(Tool[SubAgentToolConfig]):
|
|
46
|
+
name: str = "SubAgentTool"
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
configuration: SubAgentToolConfig,
|
|
51
|
+
event: ChatEvent,
|
|
52
|
+
tool_progress_reporter: ToolProgressReporter | None = None,
|
|
53
|
+
name: str = "SubAgentTool",
|
|
54
|
+
display_name: str = "SubAgentTool",
|
|
55
|
+
response_watcher: SubAgentResponseWatcher | None = None,
|
|
56
|
+
):
|
|
57
|
+
super().__init__(configuration, event, tool_progress_reporter)
|
|
58
|
+
self._user_id = event.user_id
|
|
59
|
+
self._company_id = event.company_id
|
|
60
|
+
|
|
61
|
+
self.name = name
|
|
62
|
+
self._display_name = display_name
|
|
63
|
+
|
|
64
|
+
self._short_term_memory_manager = get_sub_agent_short_term_memory_manager(
|
|
65
|
+
company_id=self._company_id,
|
|
66
|
+
user_id=self._user_id,
|
|
67
|
+
chat_id=event.payload.chat_id,
|
|
68
|
+
assistant_id=self.config.assistant_id,
|
|
69
|
+
)
|
|
70
|
+
self._should_run_evaluation = False
|
|
71
|
+
|
|
72
|
+
self._response_watcher = response_watcher
|
|
73
|
+
|
|
74
|
+
# Synchronization state
|
|
75
|
+
self._sequence_number = 1
|
|
76
|
+
self._lock = asyncio.Lock()
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def get_sub_agent_reference_format(
|
|
80
|
+
name: str, sequence_number: int, reference_number: int
|
|
81
|
+
) -> str:
|
|
82
|
+
return f"<sup><name>{name} {sequence_number}</name>{reference_number}</sup>"
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def get_sub_agent_reference_re(
|
|
86
|
+
name: str, sequence_number: int, reference_number: int
|
|
87
|
+
) -> str:
|
|
88
|
+
return rf"<sup>\s*<name>\s*{re.escape(name)}\s*{sequence_number}\s*</name>\s*{reference_number}\s*</sup>"
|
|
89
|
+
|
|
90
|
+
@override
|
|
91
|
+
def display_name(self) -> str:
|
|
92
|
+
return self._display_name
|
|
93
|
+
|
|
94
|
+
@override
|
|
95
|
+
def tool_description(self) -> LanguageModelToolDescription:
|
|
96
|
+
tool_input_model_with_description = create_model(
|
|
97
|
+
"SubAgentToolInput",
|
|
98
|
+
user_message=(
|
|
99
|
+
str,
|
|
100
|
+
Field(description=self.config.param_description_sub_agent_user_message),
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return LanguageModelToolDescription(
|
|
105
|
+
name=self.name,
|
|
106
|
+
description=self.config.tool_description,
|
|
107
|
+
parameters=tool_input_model_with_description,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@override
|
|
111
|
+
def tool_description_for_system_prompt(self) -> str:
|
|
112
|
+
return self.config.tool_description_for_system_prompt
|
|
113
|
+
|
|
114
|
+
@override
|
|
115
|
+
def tool_format_information_for_system_prompt(self) -> str:
|
|
116
|
+
return self.config.tool_format_information_for_system_prompt
|
|
117
|
+
|
|
118
|
+
@override
|
|
119
|
+
def tool_description_for_user_prompt(self) -> str:
|
|
120
|
+
return self.config.tool_description_for_user_prompt
|
|
121
|
+
|
|
122
|
+
@override
|
|
123
|
+
def tool_format_information_for_user_prompt(self) -> str:
|
|
124
|
+
return self.config.tool_format_information_for_user_prompt
|
|
125
|
+
|
|
126
|
+
@override
|
|
127
|
+
def evaluation_check_list(self) -> list[EvaluationMetricName]:
|
|
128
|
+
return [EvaluationMetricName.SUB_AGENT] if self._should_run_evaluation else []
|
|
129
|
+
|
|
130
|
+
@override
|
|
131
|
+
def get_evaluation_checks_based_on_tool_response(
|
|
132
|
+
self,
|
|
133
|
+
tool_response: ToolCallResponse,
|
|
134
|
+
) -> list[EvaluationMetricName]:
|
|
135
|
+
return []
|
|
136
|
+
|
|
137
|
+
@override
|
|
138
|
+
async def run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
|
|
139
|
+
tool_input = SubAgentToolInput.model_validate(tool_call.arguments)
|
|
140
|
+
timestamp = datetime.now()
|
|
141
|
+
|
|
142
|
+
if self._lock.locked():
|
|
143
|
+
await self._notify_progress(
|
|
144
|
+
tool_call=tool_call,
|
|
145
|
+
message=f"Waiting for another run of `{self.display_name()}` to finish",
|
|
146
|
+
state=ProgressState.STARTED,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# When reusing the chat id, executing the sub agent in parrallel leads to race conditions and undefined behavior.
|
|
150
|
+
# To avoid this, we use a lock to serialize the execution of the same sub agent.
|
|
151
|
+
context = self._lock if self.config.reuse_chat else contextlib.nullcontext()
|
|
152
|
+
|
|
153
|
+
async with context:
|
|
154
|
+
sequence_number = self._sequence_number
|
|
155
|
+
self._sequence_number += 1
|
|
156
|
+
|
|
157
|
+
await self._notify_progress(
|
|
158
|
+
tool_call=tool_call,
|
|
159
|
+
message=tool_input.user_message,
|
|
160
|
+
state=ProgressState.RUNNING,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Check if there is a saved chat id in short term memory
|
|
164
|
+
chat_id = await self._get_chat_id()
|
|
165
|
+
|
|
166
|
+
response = await self._execute_and_handle_timeout(
|
|
167
|
+
tool_user_message=tool_input.user_message,
|
|
168
|
+
chat_id=chat_id,
|
|
169
|
+
tool_call=tool_call,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
self._should_run_evaluation |= (
|
|
173
|
+
response["assessment"] is not None and len(response["assessment"]) > 0
|
|
174
|
+
) # Run evaluation if any sub agent returned an assessment
|
|
175
|
+
|
|
176
|
+
self._notify_watcher(response, sequence_number, timestamp)
|
|
177
|
+
|
|
178
|
+
if chat_id is None:
|
|
179
|
+
await self._save_chat_id(response["chatId"])
|
|
180
|
+
|
|
181
|
+
if response["text"] is None:
|
|
182
|
+
raise ValueError("No response returned from sub agent")
|
|
183
|
+
|
|
184
|
+
response_text_with_references = self._prepare_response_references(
|
|
185
|
+
response=response["text"],
|
|
186
|
+
sequence_number=sequence_number,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
await self._notify_progress(
|
|
190
|
+
tool_call=tool_call,
|
|
191
|
+
message=tool_input.user_message,
|
|
192
|
+
state=ProgressState.FINISHED,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return ToolCallResponse(
|
|
196
|
+
id=tool_call.id, # type: ignore
|
|
197
|
+
name=tool_call.name,
|
|
198
|
+
content=response_text_with_references,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
async def _get_chat_id(self) -> str | None:
|
|
202
|
+
if not self.config.reuse_chat:
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
if self.config.chat_id is not None:
|
|
206
|
+
return self.config.chat_id
|
|
207
|
+
|
|
208
|
+
# Check if there is a saved chat id in short term memory
|
|
209
|
+
short_term_memory = await self._short_term_memory_manager.load_async()
|
|
210
|
+
|
|
211
|
+
if short_term_memory is not None:
|
|
212
|
+
return short_term_memory.chat_id
|
|
213
|
+
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
def _prepare_response_references(self, response: str, sequence_number: int) -> str:
|
|
217
|
+
if not self.config.use_sub_agent_references:
|
|
218
|
+
# Remove all references from the response
|
|
219
|
+
response = remove_all_refs(response)
|
|
220
|
+
return response
|
|
221
|
+
|
|
222
|
+
for ref_number in get_all_ref_numbers(response):
|
|
223
|
+
reference = self.get_sub_agent_reference_format(
|
|
224
|
+
name=self.name,
|
|
225
|
+
sequence_number=sequence_number,
|
|
226
|
+
reference_number=ref_number,
|
|
227
|
+
)
|
|
228
|
+
response = replace_ref_number(
|
|
229
|
+
text=response, ref_number=ref_number, replacement=reference
|
|
230
|
+
)
|
|
231
|
+
return response
|
|
232
|
+
|
|
233
|
+
async def _save_chat_id(self, chat_id: str) -> None:
|
|
234
|
+
if not self.config.reuse_chat:
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
await self._short_term_memory_manager.save_async(
|
|
238
|
+
SubAgentShortTermMemorySchema(chat_id=chat_id)
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
async def _notify_progress(
|
|
242
|
+
self,
|
|
243
|
+
tool_call: LanguageModelFunction,
|
|
244
|
+
message: str,
|
|
245
|
+
state: ProgressState,
|
|
246
|
+
) -> None:
|
|
247
|
+
if self.tool_progress_reporter is not None:
|
|
248
|
+
await self.tool_progress_reporter.notify_from_tool_call(
|
|
249
|
+
tool_call=tool_call,
|
|
250
|
+
name=self._display_name,
|
|
251
|
+
message=message,
|
|
252
|
+
state=state,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
def _notify_watcher(
|
|
256
|
+
self,
|
|
257
|
+
response: unique_sdk.Space.Message,
|
|
258
|
+
sequence_number: int,
|
|
259
|
+
timestamp: datetime,
|
|
260
|
+
) -> None:
|
|
261
|
+
if self._response_watcher is not None:
|
|
262
|
+
self._response_watcher.notify_response(
|
|
263
|
+
assistant_id=self.config.assistant_id,
|
|
264
|
+
name=self.name,
|
|
265
|
+
sequence_number=sequence_number,
|
|
266
|
+
response=response,
|
|
267
|
+
timestamp=timestamp,
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
logger.warning(
|
|
271
|
+
"No response watcher found for sub agent %s (assistant_id: %s)",
|
|
272
|
+
self.name,
|
|
273
|
+
self.config.assistant_id,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
async def _execute_and_handle_timeout(
|
|
277
|
+
self,
|
|
278
|
+
tool_user_message: str,
|
|
279
|
+
chat_id: str | None,
|
|
280
|
+
tool_call: LanguageModelFunction,
|
|
281
|
+
) -> unique_sdk.Space.Message:
|
|
282
|
+
try:
|
|
283
|
+
return await send_message_and_wait_for_completion(
|
|
284
|
+
user_id=self._user_id,
|
|
285
|
+
assistant_id=self.config.assistant_id,
|
|
286
|
+
company_id=self._company_id,
|
|
287
|
+
text=tool_user_message,
|
|
288
|
+
chat_id=chat_id,
|
|
289
|
+
poll_interval=self.config.poll_interval,
|
|
290
|
+
tool_choices=self.config.forced_tools,
|
|
291
|
+
max_wait=self.config.max_wait,
|
|
292
|
+
stop_condition=self.config.stop_condition,
|
|
293
|
+
)
|
|
294
|
+
except TimeoutError as e:
|
|
295
|
+
await self._notify_progress(
|
|
296
|
+
tool_call=tool_call,
|
|
297
|
+
message="Timeout while waiting for response from sub agent.",
|
|
298
|
+
state=ProgressState.FAILED,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
raise TimeoutError(
|
|
302
|
+
"Timeout while waiting for response from sub agent. The user should consider increasing the max wait time.",
|
|
303
|
+
) from e
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
ToolFactory.register_tool(SubAgentTool, SubAgentToolConfig)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing_extensions import deprecated
|
|
2
|
+
|
|
3
|
+
from unique_toolkit.content.schemas import ContentChunk, ContentReference
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@deprecated("do not use this its only used in old tools")
|
|
7
|
+
class AgentChunksHandler:
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self._tool_chunks = {}
|
|
10
|
+
self._chunks: list[ContentChunk] = []
|
|
11
|
+
self._references: list[list[ContentReference]] = []
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def chunks(self) -> list[ContentChunk]:
|
|
15
|
+
return self._chunks
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def tool_chunks(self) -> dict:
|
|
19
|
+
return self._tool_chunks
|
|
20
|
+
|
|
21
|
+
def extend(self, chunks: list[ContentChunk]):
|
|
22
|
+
self._chunks.extend(chunks)
|
|
23
|
+
|
|
24
|
+
def replace(self, chunks: list[ContentChunk]):
|
|
25
|
+
self._chunks = chunks
|
|
26
|
+
|
|
27
|
+
def add_references(
|
|
28
|
+
self,
|
|
29
|
+
references: list[ContentReference],
|
|
30
|
+
):
|
|
31
|
+
self._references.append(references)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def all_references(
|
|
35
|
+
self,
|
|
36
|
+
) -> list[list[ContentReference]]:
|
|
37
|
+
return self._references
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def latest_references(
|
|
41
|
+
self,
|
|
42
|
+
) -> list[ContentReference]:
|
|
43
|
+
if not self._references:
|
|
44
|
+
return []
|
|
45
|
+
return self._references[-1]
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def latest_referenced_chunks(self) -> list[ContentChunk]:
|
|
49
|
+
if not self._references:
|
|
50
|
+
return []
|
|
51
|
+
return self._get_referenced_chunks_from_references(self._references[-1])
|
|
52
|
+
|
|
53
|
+
def _get_referenced_chunks_from_references(
|
|
54
|
+
self,
|
|
55
|
+
references: list[ContentReference],
|
|
56
|
+
) -> list[ContentChunk]:
|
|
57
|
+
"""
|
|
58
|
+
Get _referenced_chunks by matching sourceId from _references with merged id and chunk_id from _chunks.
|
|
59
|
+
"""
|
|
60
|
+
referenced_chunks: list[ContentChunk] = []
|
|
61
|
+
for ref in references:
|
|
62
|
+
for chunk in self._chunks:
|
|
63
|
+
if ref.source_id == str(chunk.id) + "_" + str(chunk.chunk_id):
|
|
64
|
+
referenced_chunks.append(chunk)
|
|
65
|
+
return referenced_chunks
|