unique_toolkit 1.35.4__py3-none-any.whl → 1.36.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.
@@ -15,7 +15,7 @@ def _iter_ref_numbers(text: str) -> Generator[int, None, None]:
15
15
 
16
16
 
17
17
  @functools.cache
18
- def _get_detection_pattern_for_ref(ref_number: int) -> re.Pattern[str]:
18
+ def get_detection_pattern_for_ref(ref_number: int) -> re.Pattern[str]:
19
19
  return re.compile(rf"<sup>\s*{ref_number}\s*</sup>")
20
20
 
21
21
 
@@ -35,11 +35,11 @@ def replace_ref_number(text: str, ref_number: int, replacement: int | str) -> st
35
35
  if isinstance(replacement, int):
36
36
  replacement = get_reference_pattern(replacement)
37
37
 
38
- return _get_detection_pattern_for_ref(ref_number).sub(replacement, text)
38
+ return get_detection_pattern_for_ref(ref_number).sub(replacement, text)
39
39
 
40
40
 
41
41
  def remove_ref_number(text: str, ref_number: int) -> str:
42
- return _get_detection_pattern_for_ref(ref_number).sub("", text)
42
+ return get_detection_pattern_for_ref(ref_number).sub("", text)
43
43
 
44
44
 
45
45
  def remove_all_refs(text: str) -> str:
@@ -65,7 +65,7 @@ class SubAgentDisplayConfig(BaseModel):
65
65
  )
66
66
  force_include_references: bool = Field(
67
67
  default=False,
68
- description="If set, the sub agent references will be added to the main agent response references even in not mentioned in the main agent response text.",
68
+ description="If set, the sub agent references will be added to the main agent response references even if not mentioned in the main agent response text.",
69
69
  )
70
70
 
71
71
  answer_substrings_config: list[SubAgentAnswerSubstringConfig] = Field(
@@ -37,6 +37,8 @@ References: <sup><name>SubAgentName 4</name>2</sup><sup><name>SubAgentName 4</na
37
37
 
38
38
  6. Fact repetition: If you reuse a fact from SubAgentName, you MUST reference it again inline with the correct format.
39
39
 
40
+ 7. You can ONLY use references if they are present in the subagent response! You must NOT create any references!
41
+
40
42
  Reminder:
41
43
  Inline = directly next to the fact, inside the same sentence or bullet.
42
44
  """.strip()
@@ -13,6 +13,7 @@ class SubAgentSystemReminderType(StrEnum):
13
13
  FIXED = "fixed"
14
14
  REGEXP = "regexp"
15
15
  REFERENCE = "reference"
16
+ NO_REFERENCE = "no_reference"
16
17
 
17
18
 
18
19
  T = TypeVar("T", bound=SubAgentSystemReminderType)
@@ -31,12 +32,24 @@ The reminder to add to the tool response. The reminder can be a Jinja template a
31
32
  """.strip()
32
33
 
33
34
 
35
+ class NoReferenceSystemReminderConfig(SystemReminderConfig):
36
+ """A system reminder that is only added if the sub agent response does not contain any references."""
37
+
38
+ type: Literal[SubAgentSystemReminderType.NO_REFERENCE] = (
39
+ SubAgentSystemReminderType.NO_REFERENCE
40
+ )
41
+ reminder: str = Field(
42
+ default="Do NOT create any references from this sub agent in your response! The sub agent response does not contain any references.",
43
+ description=_SYSTEM_REMINDER_FIELD_DESCRIPTION,
44
+ )
45
+
46
+
34
47
  class ReferenceSystemReminderConfig(SystemReminderConfig):
35
48
  type: Literal[SubAgentSystemReminderType.REFERENCE] = (
36
49
  SubAgentSystemReminderType.REFERENCE
37
50
  )
38
51
  reminder: str = Field(
39
- default="Rememeber to properly reference EACH fact from sub agent {{ display_name }}'s response with the correct format INLINE.",
52
+ default="Rememeber to properly reference EACH fact from sub agent {{ display_name }}'s response with the correct format INLINE. You MUST COPY THE REFERENCE AS PRESENT IN THE SUBAGENT RESPONSE.",
40
53
  description=_SYSTEM_REMINDER_FIELD_DESCRIPTION,
41
54
  )
42
55
 
@@ -69,6 +82,13 @@ class RegExpDetectedSystemReminderConfig(SystemReminderConfig):
69
82
  )
70
83
 
71
84
 
85
+ SystemReminderConfigType = (
86
+ FixedSystemReminderConfig
87
+ | RegExpDetectedSystemReminderConfig
88
+ | ReferenceSystemReminderConfig
89
+ | NoReferenceSystemReminderConfig
90
+ )
91
+
72
92
  DEFAULT_PARAM_DESCRIPTION_SUB_AGENT_USER_MESSAGE = """
73
93
  This is the message that will be sent to the sub-agent.
74
94
  """.strip()
@@ -147,9 +167,7 @@ class SubAgentToolConfig(BaseToolConfig):
147
167
 
148
168
  system_reminders_config: list[
149
169
  Annotated[
150
- FixedSystemReminderConfig
151
- | RegExpDetectedSystemReminderConfig
152
- | ReferenceSystemReminderConfig,
170
+ SystemReminderConfigType,
153
171
  Field(discriminator="type"),
154
172
  ]
155
173
  ] = Field(
@@ -12,7 +12,7 @@ from unique_sdk.utils.chat_in_space import send_message_and_wait_for_completion
12
12
 
13
13
  from unique_toolkit._common.referencing import (
14
14
  get_all_ref_numbers,
15
- remove_all_refs,
15
+ get_detection_pattern_for_ref,
16
16
  replace_ref_number,
17
17
  )
18
18
  from unique_toolkit._common.utils.jinja.render import render_template
@@ -29,6 +29,7 @@ from unique_toolkit.agentic.tools.a2a.tool.config import (
29
29
  RegExpDetectedSystemReminderConfig,
30
30
  SubAgentSystemReminderType,
31
31
  SubAgentToolConfig,
32
+ SystemReminderConfigType,
32
33
  )
33
34
  from unique_toolkit.agentic.tools.factory import ToolFactory
34
35
  from unique_toolkit.agentic.tools.schemas import ToolCallResponse
@@ -201,15 +202,39 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
201
202
  if response["text"] is None:
202
203
  raise ValueError("No response returned from sub agent")
203
204
 
205
+ has_refs = False
206
+ content = ""
207
+ content_chunks = None
204
208
  if self.config.returns_content_chunks:
205
- content = ""
206
209
  content_chunks = _ContentChunkList.validate_json(response["text"])
207
210
  else:
208
- content = self._prepare_response_references(
211
+ has_refs = self.config.use_sub_agent_references and _response_has_refs(
212
+ response
213
+ )
214
+ content = response["text"]
215
+ if has_refs:
216
+ refs = response["references"]
217
+ assert refs is not None # Checked in _response_has_refs
218
+ content = _prepare_sub_agent_response_refs(
219
+ response=content,
220
+ name=self.name,
221
+ sequence_number=sequence_number,
222
+ refs=refs,
223
+ )
224
+ content = _remove_extra_refs(content, refs=refs)
225
+ else:
226
+ content = _remove_extra_refs(content, refs=[])
227
+
228
+ system_reminders = []
229
+ if not self.config.returns_content_chunks:
230
+ system_reminders = _get_sub_agent_system_reminders(
209
231
  response=response["text"],
232
+ configs=self.config.system_reminders_config,
233
+ name=self.name,
234
+ display_name=self.display_name(),
210
235
  sequence_number=sequence_number,
236
+ has_refs=has_refs,
211
237
  )
212
- content_chunks = None
213
238
 
214
239
  await self._notify_progress(
215
240
  tool_call=tool_call,
@@ -223,58 +248,11 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
223
248
  content=_format_response(
224
249
  tool_name=self.name,
225
250
  text=content,
226
- system_reminders=self._get_system_reminders(response),
251
+ system_reminders=system_reminders,
227
252
  ),
228
253
  content_chunks=content_chunks,
229
254
  )
230
255
 
231
- def _get_system_reminders(self, message: unique_sdk.Space.Message) -> list[str]:
232
- reminders = []
233
- for reminder_config in self.config.system_reminders_config:
234
- if reminder_config.type == SubAgentSystemReminderType.FIXED:
235
- reminders.append(
236
- render_template(
237
- reminder_config.reminder,
238
- display_name=self.display_name(),
239
- tool_name=self.name,
240
- )
241
- )
242
- elif (
243
- reminder_config.type == SubAgentSystemReminderType.REFERENCE
244
- and self.config.use_sub_agent_references
245
- and message["references"] is not None
246
- and len(message["references"]) > 0
247
- ):
248
- reminders.append(
249
- render_template(
250
- reminder_config.reminder,
251
- display_name=self.display_name(),
252
- tool_name=self.name,
253
- )
254
- )
255
- elif (
256
- reminder_config.type == SubAgentSystemReminderType.REGEXP
257
- and message["text"] is not None
258
- ):
259
- reminder_config = cast(
260
- RegExpDetectedSystemReminderConfig, reminder_config
261
- )
262
- text_matches = [
263
- match.group(0)
264
- for match in reminder_config.regexp.finditer(message["text"])
265
- ]
266
- if len(text_matches) > 0:
267
- reminders.append(
268
- render_template(
269
- reminder_config.reminder,
270
- display_name=self.display_name(),
271
- tool_name=self.name,
272
- text_matches=text_matches,
273
- )
274
- )
275
-
276
- return reminders
277
-
278
256
  async def _get_chat_id(self) -> str | None:
279
257
  if not self.config.reuse_chat:
280
258
  return None
@@ -290,23 +268,6 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
290
268
 
291
269
  return None
292
270
 
293
- def _prepare_response_references(self, response: str, sequence_number: int) -> str:
294
- if not self.config.use_sub_agent_references:
295
- # Remove all references from the response
296
- response = remove_all_refs(response)
297
- return response
298
-
299
- for ref_number in get_all_ref_numbers(response):
300
- reference = self.get_sub_agent_reference_format(
301
- name=self.name,
302
- sequence_number=sequence_number,
303
- reference_number=ref_number,
304
- )
305
- response = replace_ref_number(
306
- text=response, ref_number=ref_number, replacement=reference
307
- )
308
- return response
309
-
310
271
  async def _save_chat_id(self, chat_id: str) -> None:
311
272
  if not self.config.reuse_chat:
312
273
  return
@@ -390,4 +351,93 @@ def _format_response(tool_name: str, text: str, system_reminders: list[str]) ->
390
351
  return json.dumps(response, indent=2)
391
352
 
392
353
 
354
+ def _response_has_refs(response: unique_sdk.Space.Message) -> bool:
355
+ if (
356
+ response["text"] is None
357
+ or response["references"] is None
358
+ or len(response["references"]) == 0
359
+ ):
360
+ return False
361
+
362
+ for ref in response["references"]:
363
+ if (
364
+ re.search(
365
+ get_detection_pattern_for_ref(ref["sequenceNumber"]), response["text"]
366
+ )
367
+ is not None
368
+ ):
369
+ return True
370
+
371
+ return False
372
+
373
+
374
+ def _remove_extra_refs(response: str, refs: list[unique_sdk.Space.Reference]) -> str:
375
+ text_ref_numbers = set(get_all_ref_numbers(response))
376
+ extra_ref_numbers = text_ref_numbers - set(ref["sequenceNumber"] for ref in refs)
377
+
378
+ for ref_num in extra_ref_numbers:
379
+ response = get_detection_pattern_for_ref(ref_num).sub("", response)
380
+
381
+ return response
382
+
383
+
384
+ def _prepare_sub_agent_response_refs(
385
+ response: str,
386
+ name: str,
387
+ sequence_number: int,
388
+ refs: list[unique_sdk.Space.Reference],
389
+ ) -> str:
390
+ for ref in refs:
391
+ ref_number = ref["sequenceNumber"]
392
+ reference = SubAgentTool.get_sub_agent_reference_format(
393
+ name=name, sequence_number=sequence_number, reference_number=ref_number
394
+ )
395
+ response = replace_ref_number(
396
+ text=response, ref_number=ref_number, replacement=reference
397
+ )
398
+
399
+ return response
400
+
401
+
402
+ def _get_sub_agent_system_reminders(
403
+ response: str,
404
+ configs: list[SystemReminderConfigType],
405
+ name: str,
406
+ display_name: str,
407
+ sequence_number: int,
408
+ has_refs: bool,
409
+ ) -> list[str]:
410
+ reminders = []
411
+
412
+ for reminder_config in configs:
413
+ render_kwargs = {}
414
+ render_kwargs["display_name"] = display_name
415
+ render_kwargs["tool_name"] = name
416
+ template = None
417
+
418
+ if reminder_config.type == SubAgentSystemReminderType.FIXED:
419
+ template = reminder_config.reminder
420
+ elif (
421
+ reminder_config.type == SubAgentSystemReminderType.REFERENCE and has_refs
422
+ ) or (
423
+ reminder_config.type == SubAgentSystemReminderType.NO_REFERENCE
424
+ and not has_refs
425
+ ):
426
+ render_kwargs["tool_name"] = f"{name} {sequence_number}"
427
+ template = reminder_config.reminder
428
+ elif reminder_config.type == SubAgentSystemReminderType.REGEXP:
429
+ reminder_config = cast(RegExpDetectedSystemReminderConfig, reminder_config)
430
+ text_matches = [
431
+ match.group(0) for match in reminder_config.regexp.finditer(response)
432
+ ]
433
+ if len(text_matches) > 0:
434
+ template = reminder_config.reminder
435
+ render_kwargs["text_matches"] = text_matches
436
+
437
+ if template is not None:
438
+ reminders.append(render_template(template, **render_kwargs))
439
+
440
+ return reminders
441
+
442
+
393
443
  ToolFactory.register_tool(SubAgentTool, SubAgentToolConfig)
@@ -0,0 +1,829 @@
1
+ """
2
+ Unit tests for utility functions in service.py for SubAgentTool.
3
+ """
4
+
5
+ import json
6
+ import re
7
+ from typing import Any
8
+
9
+ import pytest
10
+
11
+ from unique_toolkit.agentic.tools.a2a.tool.config import (
12
+ FixedSystemReminderConfig,
13
+ NoReferenceSystemReminderConfig,
14
+ ReferenceSystemReminderConfig,
15
+ RegExpDetectedSystemReminderConfig,
16
+ )
17
+ from unique_toolkit.agentic.tools.a2a.tool.service import (
18
+ _format_response,
19
+ _get_sub_agent_system_reminders,
20
+ _prepare_sub_agent_response_refs,
21
+ _remove_extra_refs,
22
+ _response_has_refs,
23
+ )
24
+
25
+ # Fixtures
26
+
27
+
28
+ @pytest.fixture
29
+ def base_message_response() -> dict[str, Any]:
30
+ """Base message response fixture mimicking unique_sdk.Space.Message."""
31
+ return {
32
+ "text": "Some response text",
33
+ "references": None,
34
+ "chatId": "chat-123",
35
+ "assessment": None,
36
+ }
37
+
38
+
39
+ @pytest.fixture
40
+ def message_with_refs() -> dict[str, Any]:
41
+ """Message response with references that appear in text."""
42
+ return {
43
+ "text": "This is the answer <sup>1</sup> and also <sup>2</sup>",
44
+ "references": [
45
+ {"sequenceNumber": 1, "name": "Doc 1", "url": "http://example.com/1"},
46
+ {"sequenceNumber": 2, "name": "Doc 2", "url": "http://example.com/2"},
47
+ ],
48
+ "chatId": "chat-123",
49
+ "assessment": None,
50
+ }
51
+
52
+
53
+ @pytest.fixture
54
+ def refs_list() -> list[dict[str, Any]]:
55
+ """List of reference objects mimicking unique_sdk.Space.Reference."""
56
+ return [
57
+ {"sequenceNumber": 1, "name": "Doc 1", "url": "http://example.com/1"},
58
+ {"sequenceNumber": 2, "name": "Doc 2", "url": "http://example.com/2"},
59
+ ]
60
+
61
+
62
+ # Tests for _format_response
63
+
64
+
65
+ @pytest.mark.ai
66
+ def test_format_response__returns_text__when_no_system_reminders() -> None:
67
+ """
68
+ Purpose: Verify _format_response returns plain text when system_reminders is empty.
69
+ Why this matters: Avoids unnecessary JSON wrapping for simple responses.
70
+ Setup summary: Empty system_reminders list, assert text returned unchanged.
71
+ """
72
+ # Arrange
73
+ tool_name = "TestTool"
74
+ text = "This is the response text"
75
+ system_reminders: list[str] = []
76
+
77
+ # Act
78
+ result = _format_response(tool_name, text, system_reminders)
79
+
80
+ # Assert
81
+ assert result == text
82
+
83
+
84
+ @pytest.mark.ai
85
+ def test_format_response__returns_json__when_system_reminders_present() -> None:
86
+ """
87
+ Purpose: Verify _format_response returns JSON with reminders when reminders present.
88
+ Why this matters: Ensures system reminders are properly included for LLM context.
89
+ Setup summary: Provide system reminders, assert JSON structure with reminders key.
90
+ """
91
+ # Arrange
92
+ tool_name = "TestTool"
93
+ text = "This is the response"
94
+ system_reminders = ["Remember to cite sources", "Be concise"]
95
+
96
+ # Act
97
+ result = _format_response(tool_name, text, system_reminders)
98
+
99
+ # Assert
100
+ parsed = json.loads(result)
101
+ assert "TestTool response" in parsed
102
+ assert parsed["TestTool response"] == text
103
+ assert "SYSTEM_REMINDERS" in parsed
104
+ assert parsed["SYSTEM_REMINDERS"] == system_reminders
105
+
106
+
107
+ @pytest.mark.ai
108
+ def test_format_response__uses_tool_name_in_key__correctly() -> None:
109
+ """
110
+ Purpose: Verify the response key includes the tool name.
111
+ Why this matters: Allows LLM to identify which tool the response belongs to.
112
+ Setup summary: Use specific tool name, verify it appears in JSON key.
113
+ """
114
+ # Arrange
115
+ tool_name = "MySpecialAgent"
116
+ text = "Response content"
117
+ system_reminders = ["Reminder 1"]
118
+
119
+ # Act
120
+ result = _format_response(tool_name, text, system_reminders)
121
+
122
+ # Assert
123
+ parsed = json.loads(result)
124
+ assert "MySpecialAgent response" in parsed
125
+
126
+
127
+ @pytest.mark.ai
128
+ def test_format_response__preserves_special_characters__in_text() -> None:
129
+ """
130
+ Purpose: Verify special characters in text are preserved in JSON output.
131
+ Why this matters: Ensures markdown, code, and special chars don't break formatting.
132
+ Setup summary: Text with special JSON chars, verify they're properly escaped.
133
+ """
134
+ # Arrange
135
+ tool_name = "TestTool"
136
+ text = 'Text with "quotes" and newlines\nand tabs\t'
137
+ system_reminders = ["Reminder"]
138
+
139
+ # Act
140
+ result = _format_response(tool_name, text, system_reminders)
141
+
142
+ # Assert
143
+ parsed = json.loads(result)
144
+ assert parsed["TestTool response"] == text
145
+
146
+
147
+ # Tests for _response_has_refs
148
+
149
+
150
+ @pytest.mark.ai
151
+ def test_response_has_refs__returns_false__when_text_is_none() -> None:
152
+ """
153
+ Purpose: Verify _response_has_refs returns False when text is None.
154
+ Why this matters: Handles edge case of empty/null responses gracefully.
155
+ Setup summary: Response with None text, assert False returned.
156
+ """
157
+ # Arrange
158
+ response = {"text": None, "references": [{"sequenceNumber": 1}]}
159
+
160
+ # Act
161
+ result = _response_has_refs(response)
162
+
163
+ # Assert
164
+ assert result is False
165
+
166
+
167
+ @pytest.mark.ai
168
+ def test_response_has_refs__returns_false__when_references_is_none() -> None:
169
+ """
170
+ Purpose: Verify _response_has_refs returns False when references is None.
171
+ Why this matters: Handles missing references attribute gracefully.
172
+ Setup summary: Response with None references, assert False returned.
173
+ """
174
+ # Arrange
175
+ response = {"text": "Some text <sup>1</sup>", "references": None}
176
+
177
+ # Act
178
+ result = _response_has_refs(response)
179
+
180
+ # Assert
181
+ assert result is False
182
+
183
+
184
+ @pytest.mark.ai
185
+ def test_response_has_refs__returns_false__when_references_empty() -> None:
186
+ """
187
+ Purpose: Verify _response_has_refs returns False when references list is empty.
188
+ Why this matters: Distinguishes between missing and empty reference lists.
189
+ Setup summary: Response with empty references list, assert False returned.
190
+ """
191
+ # Arrange
192
+ response = {"text": "Some text <sup>1</sup>", "references": []}
193
+
194
+ # Act
195
+ result = _response_has_refs(response)
196
+
197
+ # Assert
198
+ assert result is False
199
+
200
+
201
+ @pytest.mark.ai
202
+ def test_response_has_refs__returns_true__when_ref_found_in_text(
203
+ message_with_refs: dict[str, Any],
204
+ ) -> None:
205
+ """
206
+ Purpose: Verify _response_has_refs returns True when reference appears in text.
207
+ Why this matters: Core functionality for detecting valid references.
208
+ Setup summary: Response with refs that appear in text, assert True returned.
209
+ """
210
+ # Act
211
+ result = _response_has_refs(message_with_refs)
212
+
213
+ # Assert
214
+ assert result is True
215
+
216
+
217
+ @pytest.mark.ai
218
+ def test_response_has_refs__returns_false__when_refs_not_in_text() -> None:
219
+ """
220
+ Purpose: Verify _response_has_refs returns False when references don't appear in text.
221
+ Why this matters: Ensures we only process actually-used references.
222
+ Setup summary: Response with refs that don't match text, assert False returned.
223
+ """
224
+ # Arrange
225
+ response = {
226
+ "text": "Text without any sup tags",
227
+ "references": [{"sequenceNumber": 1}, {"sequenceNumber": 2}],
228
+ }
229
+
230
+ # Act
231
+ result = _response_has_refs(response)
232
+
233
+ # Assert
234
+ assert result is False
235
+
236
+
237
+ @pytest.mark.ai
238
+ def test_response_has_refs__handles_whitespace_in_refs__correctly() -> None:
239
+ """
240
+ Purpose: Verify _response_has_refs handles whitespace inside sup tags.
241
+ Why this matters: Reference patterns may have varying whitespace.
242
+ Setup summary: Text with whitespace in sup tags, assert still detected.
243
+ """
244
+ # Arrange
245
+ response = {
246
+ "text": "Text with <sup> 1 </sup> reference",
247
+ "references": [{"sequenceNumber": 1}],
248
+ }
249
+
250
+ # Act
251
+ result = _response_has_refs(response)
252
+
253
+ # Assert
254
+ assert result is True
255
+
256
+
257
+ @pytest.mark.ai
258
+ def test_response_has_refs__returns_true__when_at_least_one_ref_matches() -> None:
259
+ """
260
+ Purpose: Verify returns True if any reference matches, not necessarily all.
261
+ Why this matters: Partial reference usage is still valid.
262
+ Setup summary: Multiple refs where only one appears in text, assert True.
263
+ """
264
+ # Arrange
265
+ response = {
266
+ "text": "Only first ref <sup>1</sup> is used",
267
+ "references": [
268
+ {"sequenceNumber": 1},
269
+ {"sequenceNumber": 2},
270
+ {"sequenceNumber": 3},
271
+ ],
272
+ }
273
+
274
+ # Act
275
+ result = _response_has_refs(response)
276
+
277
+ # Assert
278
+ assert result is True
279
+
280
+
281
+ # Tests for _remove_extra_refs
282
+
283
+
284
+ @pytest.mark.ai
285
+ def test_remove_extra_refs__returns_unchanged__when_no_refs_in_text() -> None:
286
+ """
287
+ Purpose: Verify _remove_extra_refs returns text unchanged when no refs present.
288
+ Why this matters: Avoids unnecessary string manipulation.
289
+ Setup summary: Plain text without refs, assert unchanged.
290
+ """
291
+ # Arrange
292
+ response = "This is plain text without references"
293
+ refs: list[dict[str, Any]] = []
294
+
295
+ # Act
296
+ result = _remove_extra_refs(response, refs)
297
+
298
+ # Assert
299
+ assert result == response
300
+
301
+
302
+ @pytest.mark.ai
303
+ def test_remove_extra_refs__removes_orphan_refs__not_in_refs_list() -> None:
304
+ """
305
+ Purpose: Verify _remove_extra_refs removes refs that aren't in the refs list.
306
+ Why this matters: Cleans up invalid/orphan reference markers.
307
+ Setup summary: Text with refs not in refs list, verify they're removed.
308
+ """
309
+ # Arrange
310
+ response = "Text with <sup>1</sup> and <sup>2</sup> and <sup>3</sup>"
311
+ refs = [{"sequenceNumber": 1}] # Only ref 1 is valid
312
+
313
+ # Act
314
+ result = _remove_extra_refs(response, refs)
315
+
316
+ # Assert
317
+ assert "<sup>1</sup>" in result
318
+ assert "<sup>2</sup>" not in result
319
+ assert "<sup>3</sup>" not in result
320
+
321
+
322
+ @pytest.mark.ai
323
+ def test_remove_extra_refs__preserves_valid_refs__in_text(
324
+ refs_list: list[dict[str, Any]],
325
+ ) -> None:
326
+ """
327
+ Purpose: Verify _remove_extra_refs preserves refs that are in the refs list.
328
+ Why this matters: Ensures valid references remain intact.
329
+ Setup summary: Text with valid refs, verify all preserved.
330
+ """
331
+ # Arrange
332
+ response = "First <sup>1</sup> and second <sup>2</sup>"
333
+
334
+ # Act
335
+ result = _remove_extra_refs(response, refs_list)
336
+
337
+ # Assert
338
+ assert result == response
339
+
340
+
341
+ @pytest.mark.ai
342
+ def test_remove_extra_refs__removes_all_refs__when_refs_list_empty() -> None:
343
+ """
344
+ Purpose: Verify all refs removed when refs list is empty.
345
+ Why this matters: Cleans up all references when none are valid.
346
+ Setup summary: Text with refs, empty refs list, verify all removed.
347
+ """
348
+ # Arrange
349
+ response = "Has <sup>1</sup> and <sup>2</sup>"
350
+ refs: list[dict[str, Any]] = []
351
+
352
+ # Act
353
+ result = _remove_extra_refs(response, refs)
354
+
355
+ # Assert
356
+ assert "<sup>" not in result
357
+ assert result == "Has and "
358
+
359
+
360
+ @pytest.mark.ai
361
+ def test_remove_extra_refs__handles_multiple_occurrences__of_same_ref() -> None:
362
+ """
363
+ Purpose: Verify removes all occurrences of an orphan ref.
364
+ Why this matters: Ensures complete cleanup of invalid refs.
365
+ Setup summary: Text with repeated orphan ref, verify all instances removed.
366
+ """
367
+ # Arrange
368
+ response = "First <sup>3</sup> middle <sup>3</sup> end <sup>3</sup>"
369
+ refs = [{"sequenceNumber": 1}] # Ref 3 is orphan
370
+
371
+ # Act
372
+ result = _remove_extra_refs(response, refs)
373
+
374
+ # Assert
375
+ assert "<sup>3</sup>" not in result
376
+ assert result == "First middle end "
377
+
378
+
379
+ # Tests for _prepare_sub_agent_response_refs
380
+
381
+
382
+ @pytest.mark.ai
383
+ def test_prepare_sub_agent_response_refs__replaces_ref_format__correctly() -> None:
384
+ """
385
+ Purpose: Verify refs are replaced with sub-agent format.
386
+ Why this matters: Core functionality for sub-agent reference attribution.
387
+ Setup summary: Text with standard refs, verify replaced with named format.
388
+ """
389
+ # Arrange
390
+ response = "Check this <sup>1</sup> reference"
391
+ name = "ResearchAgent"
392
+ sequence_number = 1
393
+ refs = [{"sequenceNumber": 1}]
394
+
395
+ # Act
396
+ result = _prepare_sub_agent_response_refs(response, name, sequence_number, refs)
397
+
398
+ # Assert
399
+ assert "<sup>1</sup>" not in result
400
+ assert "<sup><name>ResearchAgent 1</name>1</sup>" in result
401
+
402
+
403
+ @pytest.mark.ai
404
+ def test_prepare_sub_agent_response_refs__handles_multiple_refs__correctly() -> None:
405
+ """
406
+ Purpose: Verify multiple refs are all properly formatted.
407
+ Why this matters: Ensures batch processing of references works.
408
+ Setup summary: Text with multiple refs, verify all replaced correctly.
409
+ """
410
+ # Arrange
411
+ response = "First <sup>1</sup> and second <sup>2</sup>"
412
+ name = "TestAgent"
413
+ sequence_number = 3
414
+ refs = [{"sequenceNumber": 1}, {"sequenceNumber": 2}]
415
+
416
+ # Act
417
+ result = _prepare_sub_agent_response_refs(response, name, sequence_number, refs)
418
+
419
+ # Assert
420
+ assert "<sup><name>TestAgent 3</name>1</sup>" in result
421
+ assert "<sup><name>TestAgent 3</name>2</sup>" in result
422
+
423
+
424
+ @pytest.mark.ai
425
+ def test_prepare_sub_agent_response_refs__preserves_text__around_refs() -> None:
426
+ """
427
+ Purpose: Verify surrounding text is preserved when refs are replaced.
428
+ Why this matters: Ensures reference replacement doesn't corrupt text.
429
+ Setup summary: Text with refs, verify non-ref content unchanged.
430
+ """
431
+ # Arrange
432
+ response = "Start text <sup>1</sup> middle text <sup>2</sup> end text"
433
+ name = "Agent"
434
+ sequence_number = 1
435
+ refs = [{"sequenceNumber": 1}, {"sequenceNumber": 2}]
436
+
437
+ # Act
438
+ result = _prepare_sub_agent_response_refs(response, name, sequence_number, refs)
439
+
440
+ # Assert
441
+ assert "Start text" in result
442
+ assert "middle text" in result
443
+ assert "end text" in result
444
+
445
+
446
+ @pytest.mark.ai
447
+ def test_prepare_sub_agent_response_refs__returns_unchanged__when_no_refs() -> None:
448
+ """
449
+ Purpose: Verify text unchanged when refs list is empty.
450
+ Why this matters: Handles edge case of no references gracefully.
451
+ Setup summary: Empty refs list, verify text unchanged.
452
+ """
453
+ # Arrange
454
+ response = "Text without any references"
455
+ name = "Agent"
456
+ sequence_number = 1
457
+ refs: list[dict[str, Any]] = []
458
+
459
+ # Act
460
+ result = _prepare_sub_agent_response_refs(response, name, sequence_number, refs)
461
+
462
+ # Assert
463
+ assert result == response
464
+
465
+
466
+ @pytest.mark.ai
467
+ def test_prepare_sub_agent_response_refs__uses_correct_sequence_number__in_format() -> (
468
+ None
469
+ ):
470
+ """
471
+ Purpose: Verify the sequence number is correctly used in the formatted reference.
472
+ Why this matters: Sequence number identifies the sub-agent call instance.
473
+ Setup summary: Specific sequence number, verify it appears in output format.
474
+ """
475
+ # Arrange
476
+ response = "Ref <sup>1</sup>"
477
+ name = "MyAgent"
478
+ sequence_number = 42
479
+ refs = [{"sequenceNumber": 1}]
480
+
481
+ # Act
482
+ result = _prepare_sub_agent_response_refs(response, name, sequence_number, refs)
483
+
484
+ # Assert
485
+ assert "<name>MyAgent 42</name>" in result
486
+
487
+
488
+ # Tests for _get_sub_agent_system_reminders
489
+
490
+
491
+ @pytest.mark.ai
492
+ def test_get_sub_agent_system_reminders__returns_empty__when_no_configs() -> None:
493
+ """
494
+ Purpose: Verify returns empty list when no reminder configs provided.
495
+ Why this matters: Handles no-config case gracefully.
496
+ Setup summary: Empty configs list, assert empty result.
497
+ """
498
+ # Arrange
499
+ response = "Some response"
500
+ configs: list[Any] = []
501
+ name = "TestAgent"
502
+ display_name = "Test Agent"
503
+ sequence_number = 1
504
+ has_refs = False
505
+
506
+ # Act
507
+ result = _get_sub_agent_system_reminders(
508
+ response, configs, name, display_name, sequence_number, has_refs
509
+ )
510
+
511
+ # Assert
512
+ assert result == []
513
+
514
+
515
+ @pytest.mark.ai
516
+ def test_get_sub_agent_system_reminders__adds_fixed_reminder__always() -> None:
517
+ """
518
+ Purpose: Verify FIXED type reminders are always added.
519
+ Why this matters: FIXED reminders should appear regardless of response content.
520
+ Setup summary: FIXED config, verify reminder added.
521
+ """
522
+ # Arrange
523
+ response = "Any response"
524
+ configs = [FixedSystemReminderConfig(reminder="Always show this reminder")]
525
+ name = "TestAgent"
526
+ display_name = "Test Agent"
527
+ sequence_number = 1
528
+ has_refs = False
529
+
530
+ # Act
531
+ result = _get_sub_agent_system_reminders(
532
+ response, configs, name, display_name, sequence_number, has_refs
533
+ )
534
+
535
+ # Assert
536
+ assert len(result) == 1
537
+ assert "Always show this reminder" in result[0]
538
+
539
+
540
+ @pytest.mark.ai
541
+ def test_get_sub_agent_system_reminders__adds_reference_reminder__when_has_refs() -> (
542
+ None
543
+ ):
544
+ """
545
+ Purpose: Verify REFERENCE type reminder added when has_refs is True.
546
+ Why this matters: Reference reminders help LLM cite sources correctly.
547
+ Setup summary: REFERENCE config with has_refs=True, verify reminder added.
548
+ """
549
+ # Arrange
550
+ response = "Response with refs"
551
+ configs = [ReferenceSystemReminderConfig()]
552
+ name = "TestAgent"
553
+ display_name = "Test Agent"
554
+ sequence_number = 1
555
+ has_refs = True
556
+
557
+ # Act
558
+ result = _get_sub_agent_system_reminders(
559
+ response, configs, name, display_name, sequence_number, has_refs
560
+ )
561
+
562
+ # Assert
563
+ assert len(result) == 1
564
+ assert "Test Agent" in result[0] # display_name should be rendered
565
+
566
+
567
+ @pytest.mark.ai
568
+ def test_get_sub_agent_system_reminders__skips_reference_reminder__when_no_refs() -> (
569
+ None
570
+ ):
571
+ """
572
+ Purpose: Verify REFERENCE reminder skipped when has_refs is False.
573
+ Why this matters: Avoids irrelevant reminders about citations.
574
+ Setup summary: REFERENCE config with has_refs=False, verify not added.
575
+ """
576
+ # Arrange
577
+ response = "Response without refs"
578
+ configs = [ReferenceSystemReminderConfig()]
579
+ name = "TestAgent"
580
+ display_name = "Test Agent"
581
+ sequence_number = 1
582
+ has_refs = False
583
+
584
+ # Act
585
+ result = _get_sub_agent_system_reminders(
586
+ response, configs, name, display_name, sequence_number, has_refs
587
+ )
588
+
589
+ # Assert
590
+ assert result == []
591
+
592
+
593
+ @pytest.mark.ai
594
+ def test_get_sub_agent_system_reminders__adds_no_reference_reminder__when_no_refs() -> (
595
+ None
596
+ ):
597
+ """
598
+ Purpose: Verify NO_REFERENCE reminder added when has_refs is False.
599
+ Why this matters: Warns LLM not to fabricate citations.
600
+ Setup summary: NO_REFERENCE config with has_refs=False, verify added.
601
+ """
602
+ # Arrange
603
+ response = "Response without refs"
604
+ configs = [NoReferenceSystemReminderConfig()]
605
+ name = "TestAgent"
606
+ display_name = "Test Agent"
607
+ sequence_number = 1
608
+ has_refs = False
609
+
610
+ # Act
611
+ result = _get_sub_agent_system_reminders(
612
+ response, configs, name, display_name, sequence_number, has_refs
613
+ )
614
+
615
+ # Assert
616
+ assert len(result) == 1
617
+ assert "NOT" in result[0] # Default reminder contains "Do NOT"
618
+
619
+
620
+ @pytest.mark.ai
621
+ def test_get_sub_agent_system_reminders__skips_no_reference_reminder__when_has_refs() -> (
622
+ None
623
+ ):
624
+ """
625
+ Purpose: Verify NO_REFERENCE reminder skipped when has_refs is True.
626
+ Why this matters: Avoids contradictory instructions when refs exist.
627
+ Setup summary: NO_REFERENCE config with has_refs=True, verify not added.
628
+ """
629
+ # Arrange
630
+ response = "Response with refs"
631
+ configs = [NoReferenceSystemReminderConfig()]
632
+ name = "TestAgent"
633
+ display_name = "Test Agent"
634
+ sequence_number = 1
635
+ has_refs = True
636
+
637
+ # Act
638
+ result = _get_sub_agent_system_reminders(
639
+ response, configs, name, display_name, sequence_number, has_refs
640
+ )
641
+
642
+ # Assert
643
+ assert result == []
644
+
645
+
646
+ @pytest.mark.ai
647
+ def test_get_sub_agent_system_reminders__adds_regexp_reminder__when_pattern_matches() -> (
648
+ None
649
+ ):
650
+ """
651
+ Purpose: Verify REGEXP reminder added when pattern matches response.
652
+ Why this matters: Enables conditional reminders based on response content.
653
+ Setup summary: REGEXP config matching response, verify reminder added.
654
+ """
655
+ # Arrange
656
+ response = "This response contains IMPORTANT_KEYWORD here"
657
+ configs = [
658
+ RegExpDetectedSystemReminderConfig(
659
+ regexp=re.compile(r"IMPORTANT_KEYWORD"),
660
+ reminder="Found keyword: {{ text_matches }}",
661
+ )
662
+ ]
663
+ name = "TestAgent"
664
+ display_name = "Test Agent"
665
+ sequence_number = 1
666
+ has_refs = False
667
+
668
+ # Act
669
+ result = _get_sub_agent_system_reminders(
670
+ response, configs, name, display_name, sequence_number, has_refs
671
+ )
672
+
673
+ # Assert
674
+ assert len(result) == 1
675
+ assert "IMPORTANT_KEYWORD" in result[0]
676
+
677
+
678
+ @pytest.mark.ai
679
+ def test_get_sub_agent_system_reminders__skips_regexp_reminder__when_no_match() -> None:
680
+ """
681
+ Purpose: Verify REGEXP reminder skipped when pattern doesn't match.
682
+ Why this matters: Avoids irrelevant conditional reminders.
683
+ Setup summary: REGEXP config not matching response, verify not added.
684
+ """
685
+ # Arrange
686
+ response = "This response has no special keywords"
687
+ configs = [
688
+ RegExpDetectedSystemReminderConfig(
689
+ regexp=re.compile(r"NONEXISTENT_PATTERN"),
690
+ reminder="Should not appear",
691
+ )
692
+ ]
693
+ name = "TestAgent"
694
+ display_name = "Test Agent"
695
+ sequence_number = 1
696
+ has_refs = False
697
+
698
+ # Act
699
+ result = _get_sub_agent_system_reminders(
700
+ response, configs, name, display_name, sequence_number, has_refs
701
+ )
702
+
703
+ # Assert
704
+ assert result == []
705
+
706
+
707
+ @pytest.mark.ai
708
+ def test_get_sub_agent_system_reminders__renders_jinja_template__correctly() -> None:
709
+ """
710
+ Purpose: Verify Jinja templates in reminders are correctly rendered.
711
+ Why this matters: Templates allow dynamic reminder content.
712
+ Setup summary: FIXED config with template placeholders, verify rendered.
713
+ """
714
+ # Arrange
715
+ response = "Some response"
716
+ configs = [
717
+ FixedSystemReminderConfig(
718
+ reminder="Tool {{ tool_name }} ({{ display_name }}) says hello"
719
+ )
720
+ ]
721
+ name = "MyTool"
722
+ display_name = "My Special Tool"
723
+ sequence_number = 1
724
+ has_refs = False
725
+
726
+ # Act
727
+ result = _get_sub_agent_system_reminders(
728
+ response, configs, name, display_name, sequence_number, has_refs
729
+ )
730
+
731
+ # Assert
732
+ assert len(result) == 1
733
+ assert "Tool MyTool (My Special Tool) says hello" in result[0]
734
+
735
+
736
+ @pytest.mark.ai
737
+ def test_get_sub_agent_system_reminders__includes_sequence_in_tool_name__for_ref_types() -> (
738
+ None
739
+ ):
740
+ """
741
+ Purpose: Verify REFERENCE/NO_REFERENCE types include sequence number in tool_name.
742
+ Why this matters: Helps identify which sub-agent call produced the response.
743
+ Setup summary: REFERENCE config, verify tool_name includes sequence number.
744
+ """
745
+ # Arrange
746
+ response = "Response"
747
+ configs = [ReferenceSystemReminderConfig(reminder="Cite {{ tool_name }} correctly")]
748
+ name = "Agent"
749
+ display_name = "Agent"
750
+ sequence_number = 5
751
+ has_refs = True
752
+
753
+ # Act
754
+ result = _get_sub_agent_system_reminders(
755
+ response, configs, name, display_name, sequence_number, has_refs
756
+ )
757
+
758
+ # Assert
759
+ assert len(result) == 1
760
+ assert "Agent 5" in result[0]
761
+
762
+
763
+ @pytest.mark.ai
764
+ def test_get_sub_agent_system_reminders__handles_multiple_configs__correctly() -> None:
765
+ """
766
+ Purpose: Verify multiple configs are processed and applicable ones added.
767
+ Why this matters: Supports complex reminder configurations.
768
+ Setup summary: Multiple config types, verify correct ones added.
769
+ """
770
+ # Arrange
771
+ response = "Response with SPECIAL text"
772
+ configs = [
773
+ FixedSystemReminderConfig(reminder="Fixed reminder"),
774
+ NoReferenceSystemReminderConfig(), # Should be added (has_refs=False)
775
+ ReferenceSystemReminderConfig(), # Should NOT be added (has_refs=False)
776
+ RegExpDetectedSystemReminderConfig(
777
+ regexp=re.compile(r"SPECIAL"),
778
+ reminder="Found special",
779
+ ),
780
+ ]
781
+ name = "TestAgent"
782
+ display_name = "Test Agent"
783
+ sequence_number = 1
784
+ has_refs = False
785
+
786
+ # Act
787
+ result = _get_sub_agent_system_reminders(
788
+ response, configs, name, display_name, sequence_number, has_refs
789
+ )
790
+
791
+ # Assert
792
+ assert len(result) == 3
793
+ assert any("Fixed reminder" in r for r in result)
794
+ assert any("NOT" in r for r in result) # NO_REFERENCE default
795
+ assert any("Found special" in r for r in result)
796
+
797
+
798
+ @pytest.mark.ai
799
+ def test_get_sub_agent_system_reminders__captures_all_regexp_matches__in_text_matches() -> (
800
+ None
801
+ ):
802
+ """
803
+ Purpose: Verify all regexp matches are captured in text_matches variable.
804
+ Why this matters: Allows templates to reference all matched content.
805
+ Setup summary: Response with multiple pattern matches, verify all captured.
806
+ """
807
+ # Arrange
808
+ response = "Found CODE123 and CODE456 and CODE789"
809
+ configs = [
810
+ RegExpDetectedSystemReminderConfig(
811
+ regexp=re.compile(r"CODE\d+"),
812
+ reminder="Codes found: {{ text_matches | join(', ') }}",
813
+ )
814
+ ]
815
+ name = "TestAgent"
816
+ display_name = "Test Agent"
817
+ sequence_number = 1
818
+ has_refs = False
819
+
820
+ # Act
821
+ result = _get_sub_agent_system_reminders(
822
+ response, configs, name, display_name, sequence_number, has_refs
823
+ )
824
+
825
+ # Assert
826
+ assert len(result) == 1
827
+ assert "CODE123" in result[0]
828
+ assert "CODE456" in result[0]
829
+ assert "CODE789" in result[0]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_toolkit
3
- Version: 1.35.4
3
+ Version: 1.36.0
4
4
  Summary:
5
5
  License: Proprietary
6
6
  Author: Cedric Klinkert
@@ -121,6 +121,9 @@ All notable changes to this project will be documented in this file.
121
121
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
122
122
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
123
123
 
124
+ ## [1.36.0] - 2025-12-11
125
+ - Add support for a sub agent tool system reminder when no references are present in the sub agent response.
126
+
124
127
  ## [1.35.4] - 2025-12-10
125
128
  - Fix a potential stacktrace leak
126
129
 
@@ -22,7 +22,7 @@ unique_toolkit/_common/experimental/endpoint_requestor.py,sha256=YnDr8wASAEjZjLA
22
22
  unique_toolkit/_common/feature_flags/schema.py,sha256=X32VqH4VMK7bhEfSd8Wbddl8FVs7Gh7ucuIEbmqc4Kw,268
23
23
  unique_toolkit/_common/pydantic/rjsf_tags.py,sha256=T3AZIF8wny3fFov66s258nEl1GqfKevFouTtG6k9PqU,31219
24
24
  unique_toolkit/_common/pydantic_helpers.py,sha256=Yg1CHD603wVrqvinHiyh3stjIK3MjuexUe9aQQUfmXs,5406
25
- unique_toolkit/_common/referencing.py,sha256=kEWkzMaz6DMCpkxsQD7AY4YWl15KendqbK_WkASfhaw,1499
25
+ unique_toolkit/_common/referencing.py,sha256=UY7-9nJLcRqlHUNytfJZPVkdpjvy5wIps_7P33_qgA0,1496
26
26
  unique_toolkit/_common/string_utilities.py,sha256=hiNyGCNISm2HuEGWYgu01dB2YJGN_NfHI345X7rRu80,4347
27
27
  unique_toolkit/_common/tests/test_referencing.py,sha256=WYKGkO3OXPYyTL8f6fVE9pqKuFowUHja5CUCTpWzRJQ,16206
28
28
  unique_toolkit/_common/tests/test_string_utilities.py,sha256=J2HYZ1fniOfJWI149wlooVrT7ot4qe5bhehGh9B7FN8,15641
@@ -93,20 +93,21 @@ unique_toolkit/agentic/tools/a2a/manager.py,sha256=pk06UUXKQdIUY-PyykYiItubBjmIy
93
93
  unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py,sha256=aVtUBPN7kDrqA6Bze34AbqQpcBBqpvfyJG-xF65w7R0,659
94
94
  unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py,sha256=eL_5bkud6aPCKGER705ZQkNttrUZEs-zco83SfbffDQ,7107
95
95
  unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py,sha256=tfH3Bhx67uQtH3fYoUr2Zp45HVw7hV_N-5F_qRwiOas,2727
96
- unique_toolkit/agentic/tools/a2a/postprocessing/config.py,sha256=nYH1otBrVGHq1Y2i1KII3pb1IVE0efwmOwhOLLsv1qU,2841
96
+ unique_toolkit/agentic/tools/a2a/postprocessing/config.py,sha256=bmdi5CbtpSWe33fUY-VDRu0PdYI6ZB9fFqCY1cUQz30,2841
97
97
  unique_toolkit/agentic/tools/a2a/postprocessing/display.py,sha256=e8R92XJHwyVI8UxVqCG_cTzensHSfGOIfDe1nfGcDoE,9058
98
98
  unique_toolkit/agentic/tools/a2a/postprocessing/references.py,sha256=DGiv8WXMjIwumI7tlpWRgV8wSxnE282ryxEf03fgck8,3465
99
99
  unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display.py,sha256=pP7fjvCm4aamtizs4viZSwsrw4Vb4kMxwDPEEx8GI98,14676
100
100
  unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py,sha256=5d67L4msesDENsNhgeoDTH6pjw_Z4FUUC1lRM9j4PlI,63608
101
101
  unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py,sha256=u-eNrHjsRFcu4TmzkD5XfFrxvaIToB42YGyXZ-RpsR0,17830
102
- unique_toolkit/agentic/tools/a2a/prompts.py,sha256=0ILHL_RAcT04gFm2d470j4Gho7PoJXdCJy-bkZgf_wk,2401
102
+ unique_toolkit/agentic/tools/a2a/prompts.py,sha256=Qm4yGs2kVm1rUtO-qntDf87q_euG7ed1cp9yf81demQ,2515
103
103
  unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py,sha256=fS8Lq49DZo5spMcP8QGTMWwSg80rYSr2pTfYDbssGYs,184
104
104
  unique_toolkit/agentic/tools/a2a/response_watcher/service.py,sha256=Tlzd7Tvk0X8a9Jyi4qHWuAx0PffAnt6BGBbQ4QWAZus,2485
105
105
  unique_toolkit/agentic/tools/a2a/tool/__init__.py,sha256=JIJKZBTLTA39OWhxoUd6uairxmqINur1Ex6iXDk9ef8,197
106
106
  unique_toolkit/agentic/tools/a2a/tool/_memory.py,sha256=w8bxjokrqHQZgApd55b5rHXF-DpgJwaKTg4CvLBLamc,1034
107
107
  unique_toolkit/agentic/tools/a2a/tool/_schema.py,sha256=wMwyunViTnxaURvenkATEvyfXn5LvLaP0HxbYqdZGls,158
108
- unique_toolkit/agentic/tools/a2a/tool/config.py,sha256=zK9onAMYlYrPc0MCz0gBocd22jm0H5ar1ihNJ7CyjJU,5780
109
- unique_toolkit/agentic/tools/a2a/tool/service.py,sha256=OJx1zc1roPgt4aa1fDw4XTF9VOCCF72dhEJaNMEMOSU,13865
108
+ unique_toolkit/agentic/tools/a2a/tool/config.py,sha256=k-YRhy7ELVLj7WP-BIDxlPYL8WK2kQll87U7wTtCrUk,6480
109
+ unique_toolkit/agentic/tools/a2a/tool/service.py,sha256=XC5bHZfKlUTvbvUrjYpCdKFrCFqZP53CznGBUT_m6Uo,15152
110
+ unique_toolkit/agentic/tools/a2a/tool/test/test_service_utils.py,sha256=x4leRxnGgqD-XyXPkW2z1ekv4Ja7EStXd-24LGadPZA,24858
110
111
  unique_toolkit/agentic/tools/agent_chunks_hanlder.py,sha256=x32Dp1Z8cVW5i-XzXbaMwX2KHPcNGmqEU-FB4AV9ZGo,1909
111
112
  unique_toolkit/agentic/tools/config.py,sha256=71PSq8LoR80irk2iJfy_AgXOPi4Nh3luW5z5cDvY8i0,3920
112
113
  unique_toolkit/agentic/tools/factory.py,sha256=A1Aliwx037UAk9ADiDsg0zjCWWnvzV_PxwJNoPTvW6c,1434
@@ -208,7 +209,7 @@ unique_toolkit/short_term_memory/service.py,sha256=5PeVBu1ZCAfyDb2HLVvlmqSbyzBBu
208
209
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
209
210
  unique_toolkit/smart_rules/compile.py,sha256=Ozhh70qCn2yOzRWr9d8WmJeTo7AQurwd3tStgBMPFLA,1246
210
211
  unique_toolkit/test_utilities/events.py,sha256=_mwV2bs5iLjxS1ynDCjaIq-gjjKhXYCK-iy3dRfvO3g,6410
211
- unique_toolkit-1.35.4.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
212
- unique_toolkit-1.35.4.dist-info/METADATA,sha256=dcIwAZ7cJvDVgC11zc7DC29MqbTIkYZFjUI1C_vbLes,46044
213
- unique_toolkit-1.35.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
214
- unique_toolkit-1.35.4.dist-info/RECORD,,
212
+ unique_toolkit-1.36.0.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
213
+ unique_toolkit-1.36.0.dist-info/METADATA,sha256=b-BMlh-JACksT29xq0D-VY8p3OZo-wXrrwJgn6Ronf0,46179
214
+ unique_toolkit-1.36.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
215
+ unique_toolkit-1.36.0.dist-info/RECORD,,