unique_toolkit 1.35.4__py3-none-any.whl → 1.37.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.
@@ -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]