unique_toolkit 1.17.3__py3-none-any.whl → 1.18.1__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/agentic/tools/a2a/evaluation/evaluator.py +56 -9
- unique_toolkit/agentic/tools/a2a/postprocessing/_display.py +5 -1
- unique_toolkit/agentic/tools/a2a/postprocessing/postprocessor.py +100 -37
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_consolidate_references.py +17 -34
- unique_toolkit/agentic/tools/a2a/tool/config.py +10 -0
- unique_toolkit/agentic/tools/a2a/tool/service.py +2 -1
- unique_toolkit/agentic/tools/utils/__init__.py +18 -0
- {unique_toolkit-1.17.3.dist-info → unique_toolkit-1.18.1.dist-info}/METADATA +9 -1
- {unique_toolkit-1.17.3.dist-info → unique_toolkit-1.18.1.dist-info}/RECORD +11 -11
- {unique_toolkit-1.17.3.dist-info → unique_toolkit-1.18.1.dist-info}/LICENSE +0 -0
- {unique_toolkit-1.17.3.dist-info → unique_toolkit-1.18.1.dist-info}/WHEEL +0 -0
|
@@ -3,6 +3,7 @@ from typing import override
|
|
|
3
3
|
|
|
4
4
|
import unique_sdk
|
|
5
5
|
from jinja2 import Template
|
|
6
|
+
from pydantic import BaseModel
|
|
6
7
|
from typing_extensions import TypedDict
|
|
7
8
|
|
|
8
9
|
from unique_toolkit.agentic.evaluation.evaluation_manager import Evaluation
|
|
@@ -21,6 +22,7 @@ from unique_toolkit.agentic.tools.a2a.evaluation.config import (
|
|
|
21
22
|
SubAgentEvaluationServiceConfig,
|
|
22
23
|
)
|
|
23
24
|
from unique_toolkit.agentic.tools.a2a.tool import SubAgentTool
|
|
25
|
+
from unique_toolkit.agentic.tools.utils import failsafe
|
|
24
26
|
from unique_toolkit.chat.schemas import (
|
|
25
27
|
ChatMessageAssessmentLabel,
|
|
26
28
|
ChatMessageAssessmentStatus,
|
|
@@ -38,7 +40,27 @@ class _SubAgentToolInfo(TypedDict):
|
|
|
38
40
|
display_name: str
|
|
39
41
|
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
_NO_ASSESSMENTS_FOUND = "NO_ASSESSMENTS_FOUND"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _SingleAssessmentData(BaseModel):
|
|
47
|
+
name: str
|
|
48
|
+
explanation: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _format_single_assessment_found(name: str, explanation: str) -> str:
|
|
52
|
+
return _SingleAssessmentData(name=name, explanation=explanation).model_dump_json()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@failsafe(failure_return_value=False)
|
|
56
|
+
def _is_single_assessment_found(value: str) -> bool:
|
|
57
|
+
_ = _SingleAssessmentData.model_validate_json(value)
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _parse_single_assessment_found(value: str) -> tuple[str, str]:
|
|
62
|
+
data = _SingleAssessmentData.model_validate_json(value)
|
|
63
|
+
return data.name, data.explanation
|
|
42
64
|
|
|
43
65
|
|
|
44
66
|
class SubAgentEvaluationService(Evaluation):
|
|
@@ -99,15 +121,36 @@ class SubAgentEvaluationService(Evaluation):
|
|
|
99
121
|
|
|
100
122
|
sub_agents_display_data.append(data)
|
|
101
123
|
|
|
124
|
+
# No valid assessments found
|
|
102
125
|
if len(sub_agents_display_data) == 0:
|
|
103
126
|
logger.warning("No valid sub agent assessments found")
|
|
104
127
|
|
|
105
128
|
return EvaluationMetricResult(
|
|
106
129
|
name=self.get_name(),
|
|
107
|
-
|
|
130
|
+
# This is a trick to be able to indicate to `evaluation_metric_to_assessment`
|
|
131
|
+
# that no valid assessments were found
|
|
132
|
+
value=_NO_ASSESSMENTS_FOUND,
|
|
108
133
|
reason="No sub agents assessments found",
|
|
109
134
|
)
|
|
110
135
|
|
|
136
|
+
# Only one valid assessment found, no need to perform summarization
|
|
137
|
+
if (
|
|
138
|
+
len(sub_agents_display_data) == 1
|
|
139
|
+
and len(sub_agents_display_data[0]["assessments"]) == 1
|
|
140
|
+
):
|
|
141
|
+
assessment = sub_agents_display_data[0]["assessments"][0]
|
|
142
|
+
explanation = assessment["explanation"] or ""
|
|
143
|
+
name = sub_agents_display_data[0]["name"]
|
|
144
|
+
label = assessment["label"]
|
|
145
|
+
|
|
146
|
+
return EvaluationMetricResult(
|
|
147
|
+
name=self.get_name(),
|
|
148
|
+
value=label,
|
|
149
|
+
# This is a trick to be able to pass the display name to the UI in `evaluation_metric_to_assessment`
|
|
150
|
+
reason=_format_single_assessment_found(name, explanation),
|
|
151
|
+
is_positive=label == ChatMessageAssessmentLabel.GREEN,
|
|
152
|
+
)
|
|
153
|
+
|
|
111
154
|
reason = await self._get_reason(sub_agents_display_data)
|
|
112
155
|
|
|
113
156
|
return EvaluationMetricResult(
|
|
@@ -121,7 +164,7 @@ class SubAgentEvaluationService(Evaluation):
|
|
|
121
164
|
async def evaluation_metric_to_assessment(
|
|
122
165
|
self, evaluation_result: EvaluationMetricResult
|
|
123
166
|
) -> EvaluationAssessmentMessage:
|
|
124
|
-
if evaluation_result.value ==
|
|
167
|
+
if evaluation_result.value == _NO_ASSESSMENTS_FOUND:
|
|
125
168
|
return EvaluationAssessmentMessage(
|
|
126
169
|
status=ChatMessageAssessmentStatus.DONE,
|
|
127
170
|
explanation="No valid sub agents assessments found to consolidate.",
|
|
@@ -130,6 +173,16 @@ class SubAgentEvaluationService(Evaluation):
|
|
|
130
173
|
type=self.get_assessment_type(),
|
|
131
174
|
)
|
|
132
175
|
|
|
176
|
+
if _is_single_assessment_found(evaluation_result.reason):
|
|
177
|
+
name, reason = _parse_single_assessment_found(evaluation_result.reason)
|
|
178
|
+
return EvaluationAssessmentMessage(
|
|
179
|
+
status=ChatMessageAssessmentStatus.DONE,
|
|
180
|
+
explanation=reason,
|
|
181
|
+
title=name,
|
|
182
|
+
label=evaluation_result.value, # type: ignore
|
|
183
|
+
type=self.get_assessment_type(),
|
|
184
|
+
)
|
|
185
|
+
|
|
133
186
|
return EvaluationAssessmentMessage(
|
|
134
187
|
status=ChatMessageAssessmentStatus.DONE,
|
|
135
188
|
explanation=evaluation_result.reason,
|
|
@@ -182,12 +235,6 @@ class SubAgentEvaluationService(Evaluation):
|
|
|
182
235
|
)
|
|
183
236
|
|
|
184
237
|
async def _get_reason(self, sub_agents_display_data: list[dict]) -> str:
|
|
185
|
-
if (
|
|
186
|
-
len(sub_agents_display_data) == 1
|
|
187
|
-
and len(sub_agents_display_data[0]["assessments"]) == 1
|
|
188
|
-
):
|
|
189
|
-
return sub_agents_display_data[0]["assessments"][0]["explanation"] or ""
|
|
190
|
-
|
|
191
238
|
messages = (
|
|
192
239
|
MessagesBuilder()
|
|
193
240
|
.system_message_append(self._config.summarization_system_message)
|
|
@@ -94,13 +94,17 @@ def _get_display_template(
|
|
|
94
94
|
assistant_id_placeholder, "{%s}" % answer_placeholder, sep="\n\n"
|
|
95
95
|
) # Double line break is needed for markdown formatting
|
|
96
96
|
|
|
97
|
+
template = _add_line_break(template, before=True, after=False)
|
|
98
|
+
|
|
97
99
|
if add_quote_border:
|
|
98
100
|
template = _wrap_with_quote_border(template)
|
|
99
101
|
|
|
100
102
|
match mode:
|
|
101
103
|
case SubAgentResponseDisplayMode.DETAILS_OPEN:
|
|
102
104
|
template = _wrap_with_details_tag(
|
|
103
|
-
template,
|
|
105
|
+
template,
|
|
106
|
+
"open",
|
|
107
|
+
display_name_placeholder,
|
|
104
108
|
)
|
|
105
109
|
case SubAgentResponseDisplayMode.DETAILS_CLOSED:
|
|
106
110
|
template = _wrap_with_details_tag(
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import logging
|
|
2
3
|
import re
|
|
3
4
|
from typing import TypedDict, override
|
|
4
5
|
|
|
5
6
|
import unique_sdk
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
6
8
|
|
|
9
|
+
from unique_toolkit._common.pydantic_helpers import get_configuration_dict
|
|
7
10
|
from unique_toolkit.agentic.postprocessor.postprocessor_manager import Postprocessor
|
|
8
11
|
from unique_toolkit.agentic.tools.a2a.postprocessing._display import (
|
|
9
12
|
_build_sub_agent_answer_display,
|
|
@@ -37,12 +40,21 @@ class _SubAgentToolInfo(TypedDict):
|
|
|
37
40
|
responses: dict[int, _SubAgentMessageInfo]
|
|
38
41
|
|
|
39
42
|
|
|
43
|
+
class SubAgentResponsesPostprocessorConfig(BaseModel):
|
|
44
|
+
model_config = get_configuration_dict()
|
|
45
|
+
|
|
46
|
+
sleep_time_before_update: float = Field(
|
|
47
|
+
default=0.5, description="Time to sleep before updating the main agent message."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
40
51
|
class SubAgentResponsesPostprocessor(Postprocessor):
|
|
41
52
|
def __init__(
|
|
42
53
|
self,
|
|
43
54
|
user_id: str,
|
|
44
55
|
company_id: str,
|
|
45
56
|
main_agent_chat_id: str,
|
|
57
|
+
config: SubAgentResponsesPostprocessorConfig | None = None,
|
|
46
58
|
) -> None:
|
|
47
59
|
super().__init__(name=self.__class__.__name__)
|
|
48
60
|
|
|
@@ -52,6 +64,7 @@ class SubAgentResponsesPostprocessor(Postprocessor):
|
|
|
52
64
|
|
|
53
65
|
self._assistant_id_to_tool_info: dict[str, _SubAgentToolInfo] = {}
|
|
54
66
|
self._main_agent_message: SpaceMessage | None = None
|
|
67
|
+
self._config = config or SubAgentResponsesPostprocessorConfig()
|
|
55
68
|
|
|
56
69
|
@override
|
|
57
70
|
async def run(self, loop_response: LanguageModelStreamResponse) -> None:
|
|
@@ -60,6 +73,9 @@ class SubAgentResponsesPostprocessor(Postprocessor):
|
|
|
60
73
|
company_id=self._company_id,
|
|
61
74
|
chat_id=self._main_agent_chat_id,
|
|
62
75
|
)
|
|
76
|
+
await asyncio.sleep(
|
|
77
|
+
self._config.sleep_time_before_update
|
|
78
|
+
) # Frontend rendering issues
|
|
63
79
|
|
|
64
80
|
@override
|
|
65
81
|
def apply_postprocessing_to_response(
|
|
@@ -79,17 +95,29 @@ class SubAgentResponsesPostprocessor(Postprocessor):
|
|
|
79
95
|
"Main agent message is not set, the `run` method must be called first"
|
|
80
96
|
)
|
|
81
97
|
|
|
98
|
+
_add_sub_agent_references_in_place(
|
|
99
|
+
loop_response=loop_response,
|
|
100
|
+
sub_agent_infos=list(self._assistant_id_to_tool_info.values()),
|
|
101
|
+
)
|
|
102
|
+
|
|
82
103
|
existing_refs = {
|
|
83
104
|
ref.source_id: ref.sequence_number
|
|
84
105
|
for ref in loop_response.message.references
|
|
85
106
|
}
|
|
86
107
|
|
|
108
|
+
displayed_sub_agent_infos = dict(
|
|
109
|
+
(assistant_id, sub_agent_info)
|
|
110
|
+
for assistant_id, sub_agent_info in self._assistant_id_to_tool_info.items()
|
|
111
|
+
if sub_agent_info["display_config"].mode
|
|
112
|
+
!= SubAgentResponseDisplayMode.HIDDEN
|
|
113
|
+
)
|
|
114
|
+
|
|
87
115
|
_consolidate_references_in_place(
|
|
88
|
-
list(
|
|
116
|
+
list(displayed_sub_agent_infos.values()), existing_refs
|
|
89
117
|
)
|
|
90
118
|
|
|
91
119
|
answers = []
|
|
92
|
-
for assistant_id in
|
|
120
|
+
for assistant_id in displayed_sub_agent_infos.keys():
|
|
93
121
|
messages = self._assistant_id_to_tool_info[assistant_id]["responses"]
|
|
94
122
|
|
|
95
123
|
for sequence_number in sorted(messages):
|
|
@@ -124,9 +152,12 @@ class SubAgentResponsesPostprocessor(Postprocessor):
|
|
|
124
152
|
for ref in message["references"]
|
|
125
153
|
)
|
|
126
154
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
155
|
+
if len(answers) > 0:
|
|
156
|
+
loop_response.message.text = (
|
|
157
|
+
"<br>\n\n".join(answers)
|
|
158
|
+
+ "<br>\n\n"
|
|
159
|
+
+ loop_response.message.text.strip()
|
|
160
|
+
)
|
|
130
161
|
|
|
131
162
|
return True
|
|
132
163
|
|
|
@@ -147,13 +178,6 @@ class SubAgentResponsesPostprocessor(Postprocessor):
|
|
|
147
178
|
def register_sub_agent_tool(
|
|
148
179
|
self, tool: SubAgentTool, display_config: SubAgentDisplayConfig
|
|
149
180
|
) -> None:
|
|
150
|
-
if display_config.mode == SubAgentResponseDisplayMode.HIDDEN:
|
|
151
|
-
logger.info(
|
|
152
|
-
"Sub agent tool %s has display mode `hidden`, responses will be ignored.",
|
|
153
|
-
tool.config.assistant_id,
|
|
154
|
-
)
|
|
155
|
-
return
|
|
156
|
-
|
|
157
181
|
if tool.config.assistant_id not in self._assistant_id_to_tool_info:
|
|
158
182
|
tool.subscribe(self)
|
|
159
183
|
self._assistant_id_to_tool_info[tool.config.assistant_id] = (
|
|
@@ -200,10 +224,73 @@ class SubAgentResponsesPostprocessor(Postprocessor):
|
|
|
200
224
|
}
|
|
201
225
|
|
|
202
226
|
|
|
227
|
+
def _add_sub_agent_references_in_place(
|
|
228
|
+
loop_response: LanguageModelStreamResponse,
|
|
229
|
+
sub_agent_infos: list[_SubAgentToolInfo],
|
|
230
|
+
) -> None:
|
|
231
|
+
message_text = loop_response.message.text
|
|
232
|
+
|
|
233
|
+
existing_refs = {
|
|
234
|
+
ref.source_id: ref.sequence_number
|
|
235
|
+
for ref in loop_response.message.references or []
|
|
236
|
+
}
|
|
237
|
+
start_index = max(existing_refs.values(), default=0) + 1
|
|
238
|
+
|
|
239
|
+
for sub_agent_info in sub_agent_infos:
|
|
240
|
+
sub_agent_name = sub_agent_info["name"]
|
|
241
|
+
for sequence_number in sorted(sub_agent_info["responses"]):
|
|
242
|
+
sub_agent_response = sub_agent_info["responses"][sequence_number]
|
|
243
|
+
|
|
244
|
+
for reference in sorted(
|
|
245
|
+
sub_agent_response["references"], key=lambda r: r["sequenceNumber"]
|
|
246
|
+
):
|
|
247
|
+
ref_num = reference["sequenceNumber"]
|
|
248
|
+
source_id = reference["sourceId"]
|
|
249
|
+
|
|
250
|
+
reference_str = SubAgentTool.get_sub_agent_reference_format(
|
|
251
|
+
name=sub_agent_name,
|
|
252
|
+
sequence_number=sequence_number,
|
|
253
|
+
reference_number=ref_num,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if reference_str not in message_text:
|
|
257
|
+
# Reference not used
|
|
258
|
+
continue
|
|
259
|
+
|
|
260
|
+
if source_id in existing_refs:
|
|
261
|
+
new_ref_num = existing_refs[source_id]
|
|
262
|
+
|
|
263
|
+
else:
|
|
264
|
+
new_ref_num = start_index
|
|
265
|
+
existing_refs[source_id] = new_ref_num
|
|
266
|
+
start_index += 1
|
|
267
|
+
|
|
268
|
+
loop_response.message.references.append(
|
|
269
|
+
ContentReference(
|
|
270
|
+
name=reference["name"],
|
|
271
|
+
url=reference["url"] or "",
|
|
272
|
+
sequence_number=new_ref_num,
|
|
273
|
+
source_id=source_id,
|
|
274
|
+
source=reference["source"],
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
message_text = re.sub(
|
|
279
|
+
rf"\s*{re.escape(reference_str)}",
|
|
280
|
+
f" <sup>{new_ref_num}</sup>",
|
|
281
|
+
message_text,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
loop_response.message.text = message_text
|
|
285
|
+
# Remove spaces between consecutive references
|
|
286
|
+
loop_response.message.text = re.sub(
|
|
287
|
+
r"</sup>\s*<sup>", "</sup><sup>", loop_response.message.text
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
203
291
|
def _consolidate_references_in_place(
|
|
204
292
|
messages: list[_SubAgentToolInfo],
|
|
205
293
|
existing_refs: dict[str, int],
|
|
206
|
-
loop_response: LanguageModelStreamResponse,
|
|
207
294
|
) -> None:
|
|
208
295
|
start_index = max(existing_refs.values(), default=0) + 1
|
|
209
296
|
|
|
@@ -237,29 +324,5 @@ def _consolidate_references_in_place(
|
|
|
237
324
|
ref_map[reference["sequenceNumber"]] = reference_num
|
|
238
325
|
reference["sequenceNumber"] = reference_num
|
|
239
326
|
|
|
240
|
-
loop_response.message.text = (
|
|
241
|
-
_replace_sub_agent_references_in_main_agent_message(
|
|
242
|
-
loop_response.message.text,
|
|
243
|
-
assistant_tool_info["name"],
|
|
244
|
-
sequence_number,
|
|
245
|
-
ref_map,
|
|
246
|
-
)
|
|
247
|
-
)
|
|
248
327
|
message["text"] = _replace_references_in_text(message["text"], ref_map)
|
|
249
328
|
message["references"] = message_new_refs
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
def _replace_sub_agent_references_in_main_agent_message(
|
|
253
|
-
message: str, sub_agent_name: str, sequence_number: int, ref_map: dict[int, int]
|
|
254
|
-
) -> str:
|
|
255
|
-
for old_seq_num, new_seq_num in ref_map.items():
|
|
256
|
-
reference = SubAgentTool.get_sub_agent_reference_format(
|
|
257
|
-
name=sub_agent_name,
|
|
258
|
-
sequence_number=sequence_number,
|
|
259
|
-
reference_number=old_seq_num,
|
|
260
|
-
)
|
|
261
|
-
message = re.sub(rf"\s*{reference}", f" <sup>{new_seq_num}</sup>", message)
|
|
262
|
-
|
|
263
|
-
# Remove spaces between consecutive references
|
|
264
|
-
message = re.sub(r"</sup>\s*<sup>", "</sup><sup>", message)
|
|
265
|
-
return message
|
|
@@ -22,9 +22,8 @@ class TestConsolidateReferencesInPlace:
|
|
|
22
22
|
"""Test with empty messages list."""
|
|
23
23
|
messages = []
|
|
24
24
|
existing_refs = {}
|
|
25
|
-
loop_response = self._create_mock_loop_response()
|
|
26
25
|
|
|
27
|
-
_consolidate_references_in_place(messages, existing_refs
|
|
26
|
+
_consolidate_references_in_place(messages, existing_refs)
|
|
28
27
|
|
|
29
28
|
assert existing_refs == {}
|
|
30
29
|
|
|
@@ -53,9 +52,8 @@ class TestConsolidateReferencesInPlace:
|
|
|
53
52
|
}
|
|
54
53
|
]
|
|
55
54
|
existing_refs = {}
|
|
56
|
-
loop_response = self._create_mock_loop_response()
|
|
57
55
|
|
|
58
|
-
_consolidate_references_in_place(messages, existing_refs
|
|
56
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
59
57
|
|
|
60
58
|
# Should start from index 1 since existing_refs is empty
|
|
61
59
|
assert existing_refs == {"source1": 1}
|
|
@@ -90,9 +88,8 @@ class TestConsolidateReferencesInPlace:
|
|
|
90
88
|
}
|
|
91
89
|
]
|
|
92
90
|
existing_refs = {"existing_source": 5}
|
|
93
|
-
loop_response = self._create_mock_loop_response()
|
|
94
91
|
|
|
95
|
-
_consolidate_references_in_place(messages, existing_refs
|
|
92
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
96
93
|
|
|
97
94
|
# Should start from 6 (max existing + 1)
|
|
98
95
|
assert existing_refs == {"existing_source": 5, "new_source": 6}
|
|
@@ -141,8 +138,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
141
138
|
]
|
|
142
139
|
existing_refs = {}
|
|
143
140
|
|
|
144
|
-
|
|
145
|
-
_consolidate_references_in_place(messages, existing_refs, loop_response) # type: ignore
|
|
141
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
146
142
|
|
|
147
143
|
# Both should map to the same consolidated reference number
|
|
148
144
|
assert existing_refs == {"shared_source": 1}
|
|
@@ -187,8 +183,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
187
183
|
]
|
|
188
184
|
existing_refs = {}
|
|
189
185
|
|
|
190
|
-
|
|
191
|
-
_consolidate_references_in_place(messages, existing_refs, loop_response) # type: ignore
|
|
186
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
192
187
|
|
|
193
188
|
assert existing_refs == {"source1": 1, "source2": 2}
|
|
194
189
|
# Both references should be in the message since they're unique
|
|
@@ -240,8 +235,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
240
235
|
]
|
|
241
236
|
existing_refs = {}
|
|
242
237
|
|
|
243
|
-
|
|
244
|
-
_consolidate_references_in_place(messages, existing_refs, loop_response) # type: ignore
|
|
238
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
245
239
|
|
|
246
240
|
# Should be processed in order 1, 2, 3 and assigned consecutive numbers
|
|
247
241
|
assert existing_refs == {"source1": 1, "source2": 2, "source3": 3}
|
|
@@ -262,8 +256,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
262
256
|
]
|
|
263
257
|
existing_refs = {}
|
|
264
258
|
|
|
265
|
-
|
|
266
|
-
_consolidate_references_in_place(messages, existing_refs, loop_response) # type: ignore
|
|
259
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
267
260
|
|
|
268
261
|
assert existing_refs == {}
|
|
269
262
|
assert messages[0]["responses"][1]["text"] == "Text with no references"
|
|
@@ -314,8 +307,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
314
307
|
]
|
|
315
308
|
existing_refs = {}
|
|
316
309
|
|
|
317
|
-
|
|
318
|
-
_consolidate_references_in_place(messages, existing_refs, loop_response) # type: ignore
|
|
310
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
319
311
|
|
|
320
312
|
assert existing_refs == {"source1": 1, "source2": 2}
|
|
321
313
|
assert messages[0]["responses"][1]["text"] == "Assistant 1 text <sup>1</sup>"
|
|
@@ -368,8 +360,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
368
360
|
]
|
|
369
361
|
existing_refs = {"existing": 10} # Start from 11
|
|
370
362
|
|
|
371
|
-
|
|
372
|
-
_consolidate_references_in_place(messages, existing_refs, loop_response) # type: ignore
|
|
363
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
373
364
|
|
|
374
365
|
# sourceB gets 11 (first in sorted order), sourceA gets 12
|
|
375
366
|
assert existing_refs == {"existing": 10, "sourceB": 11, "sourceA": 12}
|
|
@@ -416,8 +407,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
416
407
|
]
|
|
417
408
|
existing_refs = {}
|
|
418
409
|
|
|
419
|
-
|
|
420
|
-
_consolidate_references_in_place(messages, existing_refs, loop_response) # type: ignore
|
|
410
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
421
411
|
|
|
422
412
|
# Should call _replace_references_in_text with the original text and ref mapping
|
|
423
413
|
mock_replace.assert_called_once_with("Original text <sup>1</sup>", {1: 1})
|
|
@@ -446,8 +436,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
446
436
|
]
|
|
447
437
|
existing_refs = {}
|
|
448
438
|
|
|
449
|
-
|
|
450
|
-
_consolidate_references_in_place(messages, existing_refs, loop_response) # type: ignore
|
|
439
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
451
440
|
|
|
452
441
|
# The original reference object should be modified in place
|
|
453
442
|
assert original_ref["sequenceNumber"] == 1
|
|
@@ -488,8 +477,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
488
477
|
]
|
|
489
478
|
|
|
490
479
|
existing_refs_copy = existing_refs.copy()
|
|
491
|
-
|
|
492
|
-
_consolidate_references_in_place(messages, existing_refs_copy, loop_response) # type: ignore
|
|
480
|
+
_consolidate_references_in_place(messages, existing_refs_copy) # type: ignore
|
|
493
481
|
|
|
494
482
|
assert existing_refs_copy["new_source"] == expected_start
|
|
495
483
|
|
|
@@ -505,8 +493,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
505
493
|
]
|
|
506
494
|
existing_refs = {}
|
|
507
495
|
|
|
508
|
-
|
|
509
|
-
_consolidate_references_in_place(messages, existing_refs, loop_response) # type: ignore
|
|
496
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
510
497
|
|
|
511
498
|
assert existing_refs == {}
|
|
512
499
|
|
|
@@ -550,8 +537,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
550
537
|
]
|
|
551
538
|
existing_refs = {}
|
|
552
539
|
|
|
553
|
-
|
|
554
|
-
_consolidate_references_in_place(messages, existing_refs, loop_response) # type: ignore
|
|
540
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
555
541
|
|
|
556
542
|
# Only valid messages should be processed
|
|
557
543
|
assert existing_refs == {"source1": 1, "source3": 2}
|
|
@@ -611,8 +597,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
611
597
|
]
|
|
612
598
|
existing_refs = {}
|
|
613
599
|
|
|
614
|
-
|
|
615
|
-
_consolidate_references_in_place(messages, existing_refs, loop_response) # type: ignore
|
|
600
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
616
601
|
|
|
617
602
|
# Should be processed in order 1, 2, 3 based on response sequence numbers
|
|
618
603
|
assert existing_refs == {"source1": 1, "source2": 2, "source3": 3}
|
|
@@ -646,8 +631,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
646
631
|
]
|
|
647
632
|
existing_refs = {"existing_source": 99}
|
|
648
633
|
|
|
649
|
-
|
|
650
|
-
_consolidate_references_in_place(messages, existing_refs, loop_response) # type: ignore
|
|
634
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
651
635
|
|
|
652
636
|
# Should use existing reference number and not add to new refs
|
|
653
637
|
assert existing_refs == {"existing_source": 99}
|
|
@@ -685,8 +669,7 @@ class TestConsolidateReferencesInPlace:
|
|
|
685
669
|
]
|
|
686
670
|
existing_refs = {"zero": 0, "negative": -5, "positive": 3}
|
|
687
671
|
|
|
688
|
-
|
|
689
|
-
_consolidate_references_in_place(messages, existing_refs, loop_response) # type: ignore
|
|
672
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
|
690
673
|
|
|
691
674
|
# Should start from max(0, -5, 3) + 1 = 4
|
|
692
675
|
assert existing_refs["new_source"] == 4
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
1
3
|
from pydantic import Field
|
|
2
4
|
|
|
3
5
|
from unique_toolkit._common.pydantic_helpers import get_configuration_dict
|
|
@@ -27,6 +29,10 @@ class SubAgentToolConfig(BaseToolConfig):
|
|
|
27
29
|
default=True,
|
|
28
30
|
description="Whether this sub agent's references should be used in the main agent's response.",
|
|
29
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
|
+
)
|
|
30
36
|
|
|
31
37
|
tool_description_for_system_prompt: str = Field(
|
|
32
38
|
default="",
|
|
@@ -61,3 +67,7 @@ class SubAgentToolConfig(BaseToolConfig):
|
|
|
61
67
|
default=120.0,
|
|
62
68
|
description="Maximum time in seconds to wait for the sub-agent response before timing out.",
|
|
63
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
|
+
)
|
|
@@ -282,8 +282,9 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
|
|
|
282
282
|
text=tool_user_message,
|
|
283
283
|
chat_id=chat_id,
|
|
284
284
|
poll_interval=self.config.poll_interval,
|
|
285
|
+
tool_choices=self.config.forced_tools,
|
|
285
286
|
max_wait=self.config.max_wait,
|
|
286
|
-
stop_condition=
|
|
287
|
+
stop_condition=self.config.stop_condition,
|
|
287
288
|
)
|
|
288
289
|
except TimeoutError as e:
|
|
289
290
|
await self._notify_progress(
|
|
@@ -1 +1,19 @@
|
|
|
1
1
|
"""Utilities for tools."""
|
|
2
|
+
|
|
3
|
+
from unique_toolkit.agentic.tools.utils.execution.execution import (
|
|
4
|
+
Result,
|
|
5
|
+
SafeTaskExecutor,
|
|
6
|
+
failsafe,
|
|
7
|
+
failsafe_async,
|
|
8
|
+
safe_execute,
|
|
9
|
+
safe_execute_async,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"failsafe",
|
|
14
|
+
"failsafe_async",
|
|
15
|
+
"safe_execute",
|
|
16
|
+
"safe_execute_async",
|
|
17
|
+
"SafeTaskExecutor",
|
|
18
|
+
"Result",
|
|
19
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: unique_toolkit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.18.1
|
|
4
4
|
Summary:
|
|
5
5
|
License: Proprietary
|
|
6
6
|
Author: Cedric Klinkert
|
|
@@ -118,6 +118,14 @@ All notable changes to this project will be documented in this file.
|
|
|
118
118
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
119
119
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
120
120
|
|
|
121
|
+
## [1.18.1] - 2025-10-28
|
|
122
|
+
- Fix bug where sub agent references were not properly displayed in the main agent response when the sub agent response was hidden.
|
|
123
|
+
|
|
124
|
+
## [1.18.0] - 2025-10-27
|
|
125
|
+
- Temporary fix to rendering of sub agent responses.
|
|
126
|
+
- Add config option `stop_condition` to `SubAgentToolConfig`
|
|
127
|
+
- Add config option `tool_choices` to `SubAgentToolConfig`
|
|
128
|
+
- Make sub agent evaluation use the name of the sub agent name if only one assessment is valid
|
|
121
129
|
|
|
122
130
|
## [1.17.3] - 2025-10-27
|
|
123
131
|
- Update Hallucination check citation regex parsing pattern
|
|
@@ -61,23 +61,23 @@ unique_toolkit/agentic/tools/a2a/config.py,sha256=6diTTSiS2prY294LfYozB-db2wmJ6j
|
|
|
61
61
|
unique_toolkit/agentic/tools/a2a/evaluation/__init__.py,sha256=_cR8uBwLbG7lyXoRskTpItzacgs4n23e2LeqClrytuc,354
|
|
62
62
|
unique_toolkit/agentic/tools/a2a/evaluation/_utils.py,sha256=GtcPAMWkwGwJ--hBxn35ow9jN0VKYx8h2qMUXR8DCho,1877
|
|
63
63
|
unique_toolkit/agentic/tools/a2a/evaluation/config.py,sha256=Ra5rzJArS3r8C7RTmzKB8cLEYh4w-u3pe_XLgul3GOA,2355
|
|
64
|
-
unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py,sha256
|
|
64
|
+
unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py,sha256=-XDqkC2fLDFMkl8KZIefb1fl9dvlwug-uzcWmhil0vk,9144
|
|
65
65
|
unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2,sha256=acP1YqD_sCy6DT0V2EIfhQTmaUKeqpeWNJ7RGgceo8I,271
|
|
66
66
|
unique_toolkit/agentic/tools/a2a/manager.py,sha256=FkO9jY7o8Td0t-HBkkatmxwhJGSJXmYkFYKFhPdbpMo,1674
|
|
67
67
|
unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py,sha256=R90CSecxJrKH7TbwiMYPyTwsXUUmouL8fbEUlL4ee9Q,362
|
|
68
|
-
unique_toolkit/agentic/tools/a2a/postprocessing/_display.py,sha256=
|
|
68
|
+
unique_toolkit/agentic/tools/a2a/postprocessing/_display.py,sha256=rdWk-6M6V27v7G3dqaaDj1vXcgsFvDrHswFuSKQfd2I,5186
|
|
69
69
|
unique_toolkit/agentic/tools/a2a/postprocessing/_utils.py,sha256=JsWwylR2Ao_L0wk1UlhqeN2fTxPnrbhoi1klYHVBnLk,750
|
|
70
70
|
unique_toolkit/agentic/tools/a2a/postprocessing/config.py,sha256=hqo3vQZX63yXoLT7wN13vf0rC7qKiozKKfdkicYCQno,1061
|
|
71
|
-
unique_toolkit/agentic/tools/a2a/postprocessing/postprocessor.py,sha256=
|
|
72
|
-
unique_toolkit/agentic/tools/a2a/postprocessing/test/test_consolidate_references.py,sha256=
|
|
71
|
+
unique_toolkit/agentic/tools/a2a/postprocessing/postprocessor.py,sha256=MMy3NeN4koHWmamaXvsEnrRbsh7XBaWhD0PRcr1i7C4,11931
|
|
72
|
+
unique_toolkit/agentic/tools/a2a/postprocessing/test/test_consolidate_references.py,sha256=HvisnbhPUwuR9uELGunxLjNHvCXheoD5eW52L0tvUXk,26789
|
|
73
73
|
unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display.py,sha256=Hn2GOsVhpCkpFV9Asvy_IyiYvCd88rxDzi0E_zt2kWc,37553
|
|
74
74
|
unique_toolkit/agentic/tools/a2a/postprocessing/test/test_postprocessor_reference_functions.py,sha256=GxSkkY-Xgd61Bk8sIRfEtkT9hqL1VgPLWrq-6XoB0rA,11360
|
|
75
75
|
unique_toolkit/agentic/tools/a2a/prompts.py,sha256=0ILHL_RAcT04gFm2d470j4Gho7PoJXdCJy-bkZgf_wk,2401
|
|
76
76
|
unique_toolkit/agentic/tools/a2a/tool/__init__.py,sha256=JIJKZBTLTA39OWhxoUd6uairxmqINur1Ex6iXDk9ef8,197
|
|
77
77
|
unique_toolkit/agentic/tools/a2a/tool/_memory.py,sha256=w8bxjokrqHQZgApd55b5rHXF-DpgJwaKTg4CvLBLamc,1034
|
|
78
78
|
unique_toolkit/agentic/tools/a2a/tool/_schema.py,sha256=wMwyunViTnxaURvenkATEvyfXn5LvLaP0HxbYqdZGls,158
|
|
79
|
-
unique_toolkit/agentic/tools/a2a/tool/config.py,sha256=
|
|
80
|
-
unique_toolkit/agentic/tools/a2a/tool/service.py,sha256=
|
|
79
|
+
unique_toolkit/agentic/tools/a2a/tool/config.py,sha256=NcrS0hzIeYvjv1oMobAAPlZrbTPPWhcPhi0jUHLFBTI,2903
|
|
80
|
+
unique_toolkit/agentic/tools/a2a/tool/service.py,sha256=Rutos0nmY4PhvO_ETlHr5mRdQFa2UuzSJWC2rmiAJng,10593
|
|
81
81
|
unique_toolkit/agentic/tools/agent_chunks_hanlder.py,sha256=x32Dp1Z8cVW5i-XzXbaMwX2KHPcNGmqEU-FB4AV9ZGo,1909
|
|
82
82
|
unique_toolkit/agentic/tools/config.py,sha256=QD83iy2xAJFyoPggYyLWq-MaSGSq00yS9iI31My1NBc,5387
|
|
83
83
|
unique_toolkit/agentic/tools/factory.py,sha256=A1Aliwx037UAk9ADiDsg0zjCWWnvzV_PxwJNoPTvW6c,1434
|
|
@@ -97,7 +97,7 @@ unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py,sha256=dod5QPqg
|
|
|
97
97
|
unique_toolkit/agentic/tools/tool.py,sha256=m56VLxiHuKU2_J5foZp00xhm5lTxWEW7zRLGbIE9ssU,6744
|
|
98
98
|
unique_toolkit/agentic/tools/tool_manager.py,sha256=DtxJobe_7QKFe6CjnMhCP-mnKO6MjnZeDXsO3jBoC9w,16283
|
|
99
99
|
unique_toolkit/agentic/tools/tool_progress_reporter.py,sha256=ixud9VoHey1vlU1t86cW0-WTvyTwMxNSWBon8I11SUk,7955
|
|
100
|
-
unique_toolkit/agentic/tools/utils/__init__.py,sha256=
|
|
100
|
+
unique_toolkit/agentic/tools/utils/__init__.py,sha256=s75sjY5nrJchjLGs3MwSIqhDW08fFXIaX7eRQjFIA4s,346
|
|
101
101
|
unique_toolkit/agentic/tools/utils/execution/__init__.py,sha256=OHiKpqBnfhBiEQagKVWJsZlHv8smPp5OI4dFIexzibw,37
|
|
102
102
|
unique_toolkit/agentic/tools/utils/execution/execution.py,sha256=vjG2Y6awsGNtlvyQAGCTthQ5thWHYnn-vzZXaYLb3QE,7922
|
|
103
103
|
unique_toolkit/agentic/tools/utils/source_handling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -165,7 +165,7 @@ unique_toolkit/short_term_memory/service.py,sha256=5PeVBu1ZCAfyDb2HLVvlmqSbyzBBu
|
|
|
165
165
|
unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
166
166
|
unique_toolkit/smart_rules/compile.py,sha256=Ozhh70qCn2yOzRWr9d8WmJeTo7AQurwd3tStgBMPFLA,1246
|
|
167
167
|
unique_toolkit/test_utilities/events.py,sha256=_mwV2bs5iLjxS1ynDCjaIq-gjjKhXYCK-iy3dRfvO3g,6410
|
|
168
|
-
unique_toolkit-1.
|
|
169
|
-
unique_toolkit-1.
|
|
170
|
-
unique_toolkit-1.
|
|
171
|
-
unique_toolkit-1.
|
|
168
|
+
unique_toolkit-1.18.1.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
|
|
169
|
+
unique_toolkit-1.18.1.dist-info/METADATA,sha256=JM7lTRErEGHbrIrH_VsVvp4NC2snqXYAzq89D6qOrF8,38686
|
|
170
|
+
unique_toolkit-1.18.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
171
|
+
unique_toolkit-1.18.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|