unique_toolkit 1.7.0__py3-none-any.whl → 1.8.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.
- unique_toolkit/agentic/tools/a2a/__init__.py +19 -3
- unique_toolkit/agentic/tools/a2a/config.py +12 -52
- unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +10 -3
- unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +66 -0
- unique_toolkit/agentic/tools/a2a/evaluation/config.py +19 -3
- unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +82 -89
- unique_toolkit/agentic/tools/a2a/manager.py +2 -2
- unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +9 -1
- unique_toolkit/agentic/tools/a2a/postprocessing/{display.py → _display.py} +16 -7
- unique_toolkit/agentic/tools/a2a/postprocessing/_utils.py +19 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/config.py +24 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/postprocessor.py +109 -110
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_consolidate_references.py +665 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display.py +54 -75
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_postprocessor_reference_functions.py +53 -45
- unique_toolkit/agentic/tools/a2a/tool/__init__.py +4 -0
- unique_toolkit/agentic/tools/a2a/{memory.py → tool/_memory.py} +1 -1
- unique_toolkit/agentic/tools/a2a/{schema.py → tool/_schema.py} +0 -6
- unique_toolkit/agentic/tools/a2a/tool/config.py +63 -0
- unique_toolkit/agentic/tools/a2a/{service.py → tool/service.py} +108 -65
- unique_toolkit/agentic/tools/config.py +2 -2
- unique_toolkit/agentic/tools/tool_manager.py +1 -2
- {unique_toolkit-1.7.0.dist-info → unique_toolkit-1.8.1.dist-info}/METADATA +8 -1
- {unique_toolkit-1.7.0.dist-info → unique_toolkit-1.8.1.dist-info}/RECORD +26 -20
- {unique_toolkit-1.7.0.dist-info → unique_toolkit-1.8.1.dist-info}/LICENSE +0 -0
- {unique_toolkit-1.7.0.dist-info → unique_toolkit-1.8.1.dist-info}/WHEEL +0 -0
@@ -1,19 +1,21 @@
|
|
1
1
|
import logging
|
2
|
-
import re
|
3
2
|
from typing import TypedDict, override
|
4
3
|
|
5
4
|
import unique_sdk
|
6
5
|
|
7
6
|
from unique_toolkit.agentic.postprocessor.postprocessor_manager import Postprocessor
|
8
|
-
from unique_toolkit.agentic.tools.a2a.
|
9
|
-
|
10
|
-
|
7
|
+
from unique_toolkit.agentic.tools.a2a.postprocessing._display import (
|
8
|
+
_build_sub_agent_answer_display,
|
9
|
+
_remove_sub_agent_answer_from_text,
|
11
10
|
)
|
12
|
-
from unique_toolkit.agentic.tools.a2a.postprocessing.
|
13
|
-
|
14
|
-
remove_sub_agent_answer_from_text,
|
11
|
+
from unique_toolkit.agentic.tools.a2a.postprocessing._utils import (
|
12
|
+
_replace_references_in_text,
|
15
13
|
)
|
16
|
-
from unique_toolkit.agentic.tools.a2a.
|
14
|
+
from unique_toolkit.agentic.tools.a2a.postprocessing.config import (
|
15
|
+
SubAgentDisplayConfig,
|
16
|
+
SubAgentResponseDisplayMode,
|
17
|
+
)
|
18
|
+
from unique_toolkit.agentic.tools.a2a.tool import SubAgentTool
|
17
19
|
from unique_toolkit.content.schemas import ContentReference
|
18
20
|
from unique_toolkit.language_model.schemas import LanguageModelStreamResponse
|
19
21
|
|
@@ -23,14 +25,14 @@ SpaceMessage = unique_sdk.Space.Message
|
|
23
25
|
|
24
26
|
|
25
27
|
class _SubAgentMessageInfo(TypedDict):
|
26
|
-
text: str
|
28
|
+
text: str
|
27
29
|
references: list[unique_sdk.Space.Reference]
|
28
30
|
|
29
31
|
|
30
32
|
class _SubAgentToolInfo(TypedDict):
|
31
33
|
display_name: str
|
32
|
-
display_config:
|
33
|
-
responses:
|
34
|
+
display_config: SubAgentDisplayConfig
|
35
|
+
responses: dict[int, _SubAgentMessageInfo]
|
34
36
|
|
35
37
|
|
36
38
|
class SubAgentResponsesPostprocessor(Postprocessor):
|
@@ -38,93 +40,79 @@ class SubAgentResponsesPostprocessor(Postprocessor):
|
|
38
40
|
self,
|
39
41
|
user_id: str,
|
40
42
|
company_id: str,
|
41
|
-
|
42
|
-
|
43
|
-
):
|
43
|
+
main_agent_chat_id: str,
|
44
|
+
) -> None:
|
44
45
|
super().__init__(name=self.__class__.__name__)
|
45
46
|
|
46
47
|
self._user_id = user_id
|
47
48
|
self._company_id = company_id
|
48
|
-
|
49
|
-
self._agent_chat_id = agent_chat_id
|
49
|
+
self._main_agent_chat_id = main_agent_chat_id
|
50
50
|
|
51
51
|
self._assistant_id_to_tool_info: dict[str, _SubAgentToolInfo] = {}
|
52
|
-
|
53
|
-
for sub_agent_tool in sub_agent_tools:
|
54
|
-
sub_agent_tool.subscribe(self)
|
55
|
-
|
56
|
-
self._assistant_id_to_tool_info[sub_agent_tool.config.assistant_id] = (
|
57
|
-
_SubAgentToolInfo(
|
58
|
-
display_config=sub_agent_tool.config.response_display_config,
|
59
|
-
display_name=sub_agent_tool.display_name(),
|
60
|
-
responses=[],
|
61
|
-
)
|
62
|
-
)
|
63
|
-
|
64
|
-
self._sub_agent_message = None
|
52
|
+
self._main_agent_message: SpaceMessage | None = None
|
65
53
|
|
66
54
|
@override
|
67
55
|
async def run(self, loop_response: LanguageModelStreamResponse) -> None:
|
68
|
-
self.
|
56
|
+
self._main_agent_message = await unique_sdk.Space.get_latest_message_async(
|
69
57
|
user_id=self._user_id,
|
70
58
|
company_id=self._company_id,
|
71
|
-
chat_id=self.
|
59
|
+
chat_id=self._main_agent_chat_id,
|
72
60
|
)
|
73
61
|
|
74
62
|
@override
|
75
63
|
def apply_postprocessing_to_response(
|
76
64
|
self, loop_response: LanguageModelStreamResponse
|
77
65
|
) -> bool:
|
78
|
-
logger.info("
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
if display_mode != ResponseDisplayMode.HIDDEN:
|
93
|
-
displayed[assistant_id] = tool_info["responses"]
|
66
|
+
logger.info("Prepending sub agent responses to the main agent response")
|
67
|
+
|
68
|
+
if len(self._assistant_id_to_tool_info) == 0 or all(
|
69
|
+
len(tool_info["responses"]) == 0
|
70
|
+
for tool_info in self._assistant_id_to_tool_info.values()
|
71
|
+
):
|
72
|
+
logger.info("No sub agent responses to prepend")
|
73
|
+
return False
|
74
|
+
|
75
|
+
if self._main_agent_message is None:
|
76
|
+
raise ValueError(
|
77
|
+
"Main agent message is not set, the `run` method must be called first"
|
78
|
+
)
|
94
79
|
|
95
80
|
existing_refs = {
|
96
81
|
ref.source_id: ref.sequence_number
|
97
82
|
for ref in loop_response.message.references
|
98
83
|
}
|
99
|
-
_consolidate_references_in_place(displayed, existing_refs)
|
100
84
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
85
|
+
_consolidate_references_in_place(
|
86
|
+
list(self._assistant_id_to_tool_info.values()), existing_refs
|
87
|
+
)
|
88
|
+
|
89
|
+
answers = []
|
90
|
+
for assistant_id in self._assistant_id_to_tool_info.keys():
|
91
|
+
messages = self._assistant_id_to_tool_info[assistant_id]["responses"]
|
92
|
+
|
93
|
+
for sequence_number in sorted(messages):
|
94
|
+
message = messages[sequence_number]
|
105
95
|
tool_info = self._assistant_id_to_tool_info[assistant_id]
|
96
|
+
|
106
97
|
display_mode = tool_info["display_config"].mode
|
107
98
|
display_name = tool_info["display_name"]
|
108
99
|
if len(messages) > 1:
|
109
|
-
display_name += f" {
|
100
|
+
display_name += f" {sequence_number}"
|
110
101
|
|
111
|
-
|
112
|
-
|
102
|
+
answers.append(
|
103
|
+
_build_sub_agent_answer_display(
|
113
104
|
display_name=display_name,
|
114
105
|
assistant_id=assistant_id,
|
115
106
|
display_mode=display_mode,
|
116
107
|
answer=message["text"],
|
117
108
|
)
|
118
|
-
+ loop_response.message.text
|
119
109
|
)
|
120
110
|
|
121
|
-
assert self._sub_agent_message is not None
|
122
|
-
|
123
111
|
loop_response.message.references.extend(
|
124
112
|
ContentReference(
|
125
|
-
message_id=self.
|
113
|
+
message_id=self._main_agent_message["id"],
|
126
114
|
source_id=ref["sourceId"],
|
127
|
-
url=ref["url"],
|
115
|
+
url=ref["url"] or "",
|
128
116
|
source=ref["source"],
|
129
117
|
name=ref["name"],
|
130
118
|
sequence_number=ref["sequenceNumber"],
|
@@ -132,22 +120,44 @@ class SubAgentResponsesPostprocessor(Postprocessor):
|
|
132
120
|
for ref in message["references"]
|
133
121
|
)
|
134
122
|
|
135
|
-
|
123
|
+
loop_response.message.text = "\n".join(answers) + loop_response.message.text
|
124
|
+
|
125
|
+
return True
|
136
126
|
|
137
127
|
@override
|
138
128
|
async def remove_from_text(self, text) -> str:
|
139
129
|
for assistant_id, tool_info in self._assistant_id_to_tool_info.items():
|
140
130
|
display_config = tool_info["display_config"]
|
141
131
|
if display_config.remove_from_history:
|
142
|
-
text =
|
132
|
+
text = _remove_sub_agent_answer_from_text(
|
143
133
|
display_mode=display_config.mode,
|
144
134
|
text=text,
|
145
135
|
assistant_id=assistant_id,
|
146
136
|
)
|
147
137
|
return text
|
148
138
|
|
139
|
+
def register_sub_agent_tool(
|
140
|
+
self, tool: SubAgentTool, display_config: SubAgentDisplayConfig
|
141
|
+
) -> None:
|
142
|
+
if display_config.mode == SubAgentResponseDisplayMode.HIDDEN:
|
143
|
+
logger.info(
|
144
|
+
"Sub agent tool %s has display mode `hidden`, responses will be ignored.",
|
145
|
+
tool.config.assistant_id,
|
146
|
+
)
|
147
|
+
return
|
148
|
+
|
149
|
+
if tool.config.assistant_id not in self._assistant_id_to_tool_info:
|
150
|
+
tool.subscribe(self)
|
151
|
+
self._assistant_id_to_tool_info[tool.config.assistant_id] = (
|
152
|
+
_SubAgentToolInfo(
|
153
|
+
display_config=display_config,
|
154
|
+
display_name=tool.display_name(),
|
155
|
+
responses={},
|
156
|
+
)
|
157
|
+
)
|
158
|
+
|
149
159
|
def notify_sub_agent_response(
|
150
|
-
self, sub_agent_assistant_id: str,
|
160
|
+
self, response: SpaceMessage, sub_agent_assistant_id: str, sequence_number: int
|
151
161
|
) -> None:
|
152
162
|
if sub_agent_assistant_id not in self._assistant_id_to_tool_info:
|
153
163
|
logger.warning(
|
@@ -156,47 +166,57 @@ class SubAgentResponsesPostprocessor(Postprocessor):
|
|
156
166
|
)
|
157
167
|
return
|
158
168
|
|
159
|
-
|
160
|
-
|
161
|
-
"
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
169
|
+
if response["text"] is None:
|
170
|
+
logger.warning(
|
171
|
+
"Sub agent response %s has no text, message will be ignored.",
|
172
|
+
sequence_number,
|
173
|
+
)
|
174
|
+
return
|
175
|
+
|
176
|
+
self._assistant_id_to_tool_info[sub_agent_assistant_id]["responses"][
|
177
|
+
sequence_number
|
178
|
+
] = {
|
179
|
+
"text": response["text"],
|
180
|
+
"references": [
|
181
|
+
{
|
182
|
+
"name": ref["name"],
|
183
|
+
"url": ref["url"],
|
184
|
+
"sequenceNumber": ref["sequenceNumber"],
|
185
|
+
"originalIndex": [],
|
186
|
+
"sourceId": ref["sourceId"],
|
187
|
+
"source": ref["source"],
|
188
|
+
}
|
189
|
+
for ref in response["references"] or []
|
190
|
+
],
|
191
|
+
}
|
175
192
|
|
176
193
|
|
177
194
|
def _consolidate_references_in_place(
|
178
|
-
messages:
|
195
|
+
messages: list[_SubAgentToolInfo], existing_refs: dict[str, int]
|
179
196
|
) -> None:
|
180
197
|
start_index = max(existing_refs.values(), default=0) + 1
|
181
198
|
|
182
|
-
for
|
183
|
-
|
199
|
+
for assistant_tool_info in messages:
|
200
|
+
assistant_messages = assistant_tool_info["responses"]
|
201
|
+
|
202
|
+
for sequence_number in sorted(assistant_messages):
|
203
|
+
message = assistant_messages[sequence_number]
|
204
|
+
|
184
205
|
references = message["references"]
|
185
|
-
if len(references) == 0
|
186
|
-
logger.
|
187
|
-
"Message from assistant %s does not contain any references",
|
188
|
-
|
206
|
+
if len(references) == 0:
|
207
|
+
logger.debug(
|
208
|
+
"Message from assistant %s with sequence number %s does not contain any references",
|
209
|
+
assistant_tool_info["display_name"],
|
210
|
+
sequence_number,
|
189
211
|
)
|
190
212
|
continue
|
191
213
|
|
192
214
|
references = list(sorted(references, key=lambda ref: ref["sequenceNumber"]))
|
193
|
-
|
194
215
|
ref_map = {}
|
195
|
-
|
196
216
|
message_new_refs = []
|
217
|
+
|
197
218
|
for reference in references:
|
198
219
|
source_id = reference["sourceId"]
|
199
|
-
|
200
220
|
if source_id not in existing_refs:
|
201
221
|
message_new_refs.append(reference)
|
202
222
|
existing_refs[source_id] = start_index
|
@@ -208,24 +228,3 @@ def _consolidate_references_in_place(
|
|
208
228
|
|
209
229
|
message["text"] = _replace_references_in_text(message["text"], ref_map)
|
210
230
|
message["references"] = message_new_refs
|
211
|
-
|
212
|
-
|
213
|
-
def _replace_references_in_text_non_overlapping(
|
214
|
-
text: str, ref_map: dict[int, int]
|
215
|
-
) -> str:
|
216
|
-
for orig, repl in ref_map.items():
|
217
|
-
text = re.sub(rf"<sup>{orig}</sup>", f"<sup>{repl}</sup>", text)
|
218
|
-
return text
|
219
|
-
|
220
|
-
|
221
|
-
def _replace_references_in_text(text: str, ref_map: dict[int, int]) -> str:
|
222
|
-
# 2 phase replacement, since the map keys and values can overlap
|
223
|
-
max_ref = max(max(ref_map.keys(), default=0), max(ref_map.values(), default=0)) + 1
|
224
|
-
unique_refs = range(max_ref, max_ref + len(ref_map))
|
225
|
-
|
226
|
-
text = _replace_references_in_text_non_overlapping(
|
227
|
-
text, dict(zip(ref_map.keys(), unique_refs))
|
228
|
-
)
|
229
|
-
return _replace_references_in_text_non_overlapping(
|
230
|
-
text, dict(zip(unique_refs, ref_map.values()))
|
231
|
-
)
|