unique_toolkit 1.7.0__py3-none-any.whl → 1.8.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.
- 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 +13 -2
- 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.0.dist-info}/METADATA +5 -1
- {unique_toolkit-1.7.0.dist-info → unique_toolkit-1.8.0.dist-info}/RECORD +26 -20
- {unique_toolkit-1.7.0.dist-info → unique_toolkit-1.8.0.dist-info}/LICENSE +0 -0
- {unique_toolkit-1.7.0.dist-info → unique_toolkit-1.8.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,665 @@
|
|
1
|
+
from unittest.mock import patch
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from unique_toolkit.agentic.tools.a2a.postprocessing.postprocessor import (
|
6
|
+
_consolidate_references_in_place,
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
class TestConsolidateReferencesInPlace:
|
11
|
+
"""Test cases for _consolidate_references_in_place function."""
|
12
|
+
|
13
|
+
def test_empty_messages_list(self):
|
14
|
+
"""Test with empty messages list."""
|
15
|
+
messages = []
|
16
|
+
existing_refs = {}
|
17
|
+
|
18
|
+
_consolidate_references_in_place(messages, existing_refs)
|
19
|
+
|
20
|
+
assert existing_refs == {}
|
21
|
+
|
22
|
+
def test_empty_existing_refs(self):
|
23
|
+
"""Test with empty existing references."""
|
24
|
+
messages = [
|
25
|
+
{
|
26
|
+
"display_name": "Assistant1",
|
27
|
+
"display_config": {"mode": "expanded"},
|
28
|
+
"responses": {
|
29
|
+
1: {
|
30
|
+
"text": "Test text with reference <sup>1</sup>",
|
31
|
+
"references": [
|
32
|
+
{
|
33
|
+
"sourceId": "source1",
|
34
|
+
"sequenceNumber": 1,
|
35
|
+
"name": "Test Source",
|
36
|
+
"url": "http://test.com",
|
37
|
+
"source": "test",
|
38
|
+
"originalIndex": [],
|
39
|
+
}
|
40
|
+
],
|
41
|
+
}
|
42
|
+
},
|
43
|
+
}
|
44
|
+
]
|
45
|
+
existing_refs = {}
|
46
|
+
|
47
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
48
|
+
|
49
|
+
# Should start from index 1 since existing_refs is empty
|
50
|
+
assert existing_refs == {"source1": 1}
|
51
|
+
assert messages[0]["responses"][1]["references"][0]["sequenceNumber"] == 1
|
52
|
+
assert (
|
53
|
+
messages[0]["responses"][1]["text"]
|
54
|
+
== "Test text with reference <sup>1</sup>"
|
55
|
+
)
|
56
|
+
|
57
|
+
def test_with_existing_refs(self):
|
58
|
+
"""Test with existing references."""
|
59
|
+
messages = [
|
60
|
+
{
|
61
|
+
"display_name": "Assistant1",
|
62
|
+
"display_config": {"mode": "expanded"},
|
63
|
+
"responses": {
|
64
|
+
1: {
|
65
|
+
"text": "New text with reference <sup>1</sup>",
|
66
|
+
"references": [
|
67
|
+
{
|
68
|
+
"sourceId": "new_source",
|
69
|
+
"sequenceNumber": 1,
|
70
|
+
"name": "New Source",
|
71
|
+
"url": "http://new.com",
|
72
|
+
"source": "new",
|
73
|
+
"originalIndex": [],
|
74
|
+
}
|
75
|
+
],
|
76
|
+
}
|
77
|
+
},
|
78
|
+
}
|
79
|
+
]
|
80
|
+
existing_refs = {"existing_source": 5}
|
81
|
+
|
82
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
83
|
+
|
84
|
+
# Should start from 6 (max existing + 1)
|
85
|
+
assert existing_refs == {"existing_source": 5, "new_source": 6}
|
86
|
+
assert messages[0]["responses"][1]["references"][0]["sequenceNumber"] == 6
|
87
|
+
assert (
|
88
|
+
messages[0]["responses"][1]["text"]
|
89
|
+
== "New text with reference <sup>6</sup>"
|
90
|
+
)
|
91
|
+
|
92
|
+
def test_duplicate_source_ids(self):
|
93
|
+
"""Test with duplicate source IDs across different messages."""
|
94
|
+
messages = [
|
95
|
+
{
|
96
|
+
"display_name": "Assistant1",
|
97
|
+
"display_config": {"mode": "expanded"},
|
98
|
+
"responses": {
|
99
|
+
1: {
|
100
|
+
"text": "First reference <sup>1</sup>",
|
101
|
+
"references": [
|
102
|
+
{
|
103
|
+
"sourceId": "shared_source",
|
104
|
+
"sequenceNumber": 1,
|
105
|
+
"name": "Shared Source",
|
106
|
+
"url": "http://shared.com",
|
107
|
+
"source": "shared",
|
108
|
+
"originalIndex": [],
|
109
|
+
}
|
110
|
+
],
|
111
|
+
},
|
112
|
+
2: {
|
113
|
+
"text": "Second reference <sup>2</sup>",
|
114
|
+
"references": [
|
115
|
+
{
|
116
|
+
"sourceId": "shared_source",
|
117
|
+
"sequenceNumber": 2,
|
118
|
+
"name": "Shared Source",
|
119
|
+
"url": "http://shared.com",
|
120
|
+
"source": "shared",
|
121
|
+
"originalIndex": [],
|
122
|
+
}
|
123
|
+
],
|
124
|
+
},
|
125
|
+
},
|
126
|
+
}
|
127
|
+
]
|
128
|
+
existing_refs = {}
|
129
|
+
|
130
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
131
|
+
|
132
|
+
# Both should map to the same consolidated reference number
|
133
|
+
assert existing_refs == {"shared_source": 1}
|
134
|
+
assert messages[0]["responses"][1]["references"][0]["sequenceNumber"] == 1
|
135
|
+
assert (
|
136
|
+
messages[0]["responses"][2]["references"] == []
|
137
|
+
) # Should be empty for duplicate
|
138
|
+
assert messages[0]["responses"][1]["text"] == "First reference <sup>1</sup>"
|
139
|
+
assert messages[0]["responses"][2]["text"] == "Second reference <sup>1</sup>"
|
140
|
+
|
141
|
+
def test_multiple_references_in_single_message(self):
|
142
|
+
"""Test with multiple references in a single message."""
|
143
|
+
messages = [
|
144
|
+
{
|
145
|
+
"display_name": "Assistant1",
|
146
|
+
"display_config": {"mode": "expanded"},
|
147
|
+
"responses": {
|
148
|
+
1: {
|
149
|
+
"text": "Text with <sup>1</sup> and <sup>2</sup> references",
|
150
|
+
"references": [
|
151
|
+
{
|
152
|
+
"sourceId": "source1",
|
153
|
+
"sequenceNumber": 1,
|
154
|
+
"name": "Source 1",
|
155
|
+
"url": "http://source1.com",
|
156
|
+
"source": "src1",
|
157
|
+
"originalIndex": [],
|
158
|
+
},
|
159
|
+
{
|
160
|
+
"sourceId": "source2",
|
161
|
+
"sequenceNumber": 2,
|
162
|
+
"name": "Source 2",
|
163
|
+
"url": "http://source2.com",
|
164
|
+
"source": "src2",
|
165
|
+
"originalIndex": [],
|
166
|
+
},
|
167
|
+
],
|
168
|
+
}
|
169
|
+
},
|
170
|
+
}
|
171
|
+
]
|
172
|
+
existing_refs = {}
|
173
|
+
|
174
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
175
|
+
|
176
|
+
assert existing_refs == {"source1": 1, "source2": 2}
|
177
|
+
# Both references should be in the message since they're unique
|
178
|
+
assert len(messages[0]["responses"][1]["references"]) == 2
|
179
|
+
assert (
|
180
|
+
messages[0]["responses"][1]["text"]
|
181
|
+
== "Text with <sup>1</sup> and <sup>2</sup> references"
|
182
|
+
)
|
183
|
+
|
184
|
+
def test_references_sorted_by_sequence_number(self):
|
185
|
+
"""Test that references are sorted by sequence number before processing."""
|
186
|
+
messages = [
|
187
|
+
{
|
188
|
+
"display_name": "Assistant1",
|
189
|
+
"display_config": {"mode": "expanded"},
|
190
|
+
"responses": {
|
191
|
+
1: {
|
192
|
+
"text": "Text with <sup>3</sup> and <sup>1</sup> and <sup>2</sup>",
|
193
|
+
"references": [
|
194
|
+
{
|
195
|
+
"sourceId": "source3",
|
196
|
+
"sequenceNumber": 3,
|
197
|
+
"name": "Source 3",
|
198
|
+
"url": "http://source3.com",
|
199
|
+
"source": "src3",
|
200
|
+
"originalIndex": [],
|
201
|
+
},
|
202
|
+
{
|
203
|
+
"sourceId": "source1",
|
204
|
+
"sequenceNumber": 1,
|
205
|
+
"name": "Source 1",
|
206
|
+
"url": "http://source1.com",
|
207
|
+
"source": "src1",
|
208
|
+
"originalIndex": [],
|
209
|
+
},
|
210
|
+
{
|
211
|
+
"sourceId": "source2",
|
212
|
+
"sequenceNumber": 2,
|
213
|
+
"name": "Source 2",
|
214
|
+
"url": "http://source2.com",
|
215
|
+
"source": "src2",
|
216
|
+
"originalIndex": [],
|
217
|
+
},
|
218
|
+
],
|
219
|
+
}
|
220
|
+
},
|
221
|
+
}
|
222
|
+
]
|
223
|
+
existing_refs = {}
|
224
|
+
|
225
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
226
|
+
|
227
|
+
# Should be processed in order 1, 2, 3 and assigned consecutive numbers
|
228
|
+
assert existing_refs == {"source1": 1, "source2": 2, "source3": 3}
|
229
|
+
assert (
|
230
|
+
messages[0]["responses"][1]["text"]
|
231
|
+
== "Text with <sup>3</sup> and <sup>1</sup> and <sup>2</sup>"
|
232
|
+
)
|
233
|
+
|
234
|
+
def test_empty_references_list(self):
|
235
|
+
"""Test with empty references list."""
|
236
|
+
messages = [
|
237
|
+
{
|
238
|
+
"display_name": "Assistant1",
|
239
|
+
"display_config": {"mode": "expanded"},
|
240
|
+
"responses": {1: {"text": "Text with no references", "references": []}},
|
241
|
+
}
|
242
|
+
]
|
243
|
+
existing_refs = {}
|
244
|
+
|
245
|
+
with patch(
|
246
|
+
"unique_toolkit.agentic.tools.a2a.postprocessing.postprocessor.logger"
|
247
|
+
) as mock_logger:
|
248
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
249
|
+
|
250
|
+
# Should log debug message about no references
|
251
|
+
mock_logger.debug.assert_called_once()
|
252
|
+
assert (
|
253
|
+
"does not contain any references" in mock_logger.debug.call_args[0][0]
|
254
|
+
)
|
255
|
+
|
256
|
+
assert existing_refs == {}
|
257
|
+
assert messages[0]["responses"][1]["text"] == "Text with no references"
|
258
|
+
|
259
|
+
def test_multiple_assistants(self):
|
260
|
+
"""Test with multiple assistants."""
|
261
|
+
messages = [
|
262
|
+
{
|
263
|
+
"display_name": "Assistant1",
|
264
|
+
"display_config": {"mode": "expanded"},
|
265
|
+
"responses": {
|
266
|
+
1: {
|
267
|
+
"text": "Assistant 1 text <sup>1</sup>",
|
268
|
+
"references": [
|
269
|
+
{
|
270
|
+
"sourceId": "source1",
|
271
|
+
"sequenceNumber": 1,
|
272
|
+
"name": "Source 1",
|
273
|
+
"url": "http://source1.com",
|
274
|
+
"source": "src1",
|
275
|
+
"originalIndex": [],
|
276
|
+
}
|
277
|
+
],
|
278
|
+
}
|
279
|
+
},
|
280
|
+
},
|
281
|
+
{
|
282
|
+
"display_name": "Assistant2",
|
283
|
+
"display_config": {"mode": "expanded"},
|
284
|
+
"responses": {
|
285
|
+
1: {
|
286
|
+
"text": "Assistant 2 text <sup>1</sup>",
|
287
|
+
"references": [
|
288
|
+
{
|
289
|
+
"sourceId": "source2",
|
290
|
+
"sequenceNumber": 1,
|
291
|
+
"name": "Source 2",
|
292
|
+
"url": "http://source2.com",
|
293
|
+
"source": "src2",
|
294
|
+
"originalIndex": [],
|
295
|
+
}
|
296
|
+
],
|
297
|
+
}
|
298
|
+
},
|
299
|
+
},
|
300
|
+
]
|
301
|
+
existing_refs = {}
|
302
|
+
|
303
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
304
|
+
|
305
|
+
assert existing_refs == {"source1": 1, "source2": 2}
|
306
|
+
assert messages[0]["responses"][1]["text"] == "Assistant 1 text <sup>1</sup>"
|
307
|
+
assert messages[1]["responses"][1]["text"] == "Assistant 2 text <sup>2</sup>"
|
308
|
+
|
309
|
+
def test_complex_reference_mapping(self):
|
310
|
+
"""Test complex scenario with overlapping sequence numbers and mixed source IDs."""
|
311
|
+
messages = [
|
312
|
+
{
|
313
|
+
"display_name": "Assistant1",
|
314
|
+
"display_config": {"mode": "expanded"},
|
315
|
+
"responses": {
|
316
|
+
1: {
|
317
|
+
"text": "Text with <sup>2</sup> and <sup>1</sup>",
|
318
|
+
"references": [
|
319
|
+
{
|
320
|
+
"sourceId": "sourceA",
|
321
|
+
"sequenceNumber": 2,
|
322
|
+
"name": "Source A",
|
323
|
+
"url": "http://sourceA.com",
|
324
|
+
"source": "srcA",
|
325
|
+
"originalIndex": [],
|
326
|
+
},
|
327
|
+
{
|
328
|
+
"sourceId": "sourceB",
|
329
|
+
"sequenceNumber": 1,
|
330
|
+
"name": "Source B",
|
331
|
+
"url": "http://sourceB.com",
|
332
|
+
"source": "srcB",
|
333
|
+
"originalIndex": [],
|
334
|
+
},
|
335
|
+
],
|
336
|
+
},
|
337
|
+
2: {
|
338
|
+
"text": "Another text with <sup>1</sup>",
|
339
|
+
"references": [
|
340
|
+
{
|
341
|
+
"sourceId": "sourceA", # Same source as above
|
342
|
+
"sequenceNumber": 1,
|
343
|
+
"name": "Source A",
|
344
|
+
"url": "http://sourceA.com",
|
345
|
+
"source": "srcA",
|
346
|
+
"originalIndex": [],
|
347
|
+
}
|
348
|
+
],
|
349
|
+
},
|
350
|
+
},
|
351
|
+
}
|
352
|
+
]
|
353
|
+
existing_refs = {"existing": 10} # Start from 11
|
354
|
+
|
355
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
356
|
+
|
357
|
+
# sourceB gets 11 (first in sorted order), sourceA gets 12
|
358
|
+
assert existing_refs == {"existing": 10, "sourceB": 11, "sourceA": 12}
|
359
|
+
|
360
|
+
# First message should have both references updated
|
361
|
+
assert (
|
362
|
+
messages[0]["responses"][1]["text"]
|
363
|
+
== "Text with <sup>12</sup> and <sup>11</sup>"
|
364
|
+
)
|
365
|
+
assert len(messages[0]["responses"][1]["references"]) == 2
|
366
|
+
|
367
|
+
# Second message should have empty references (duplicate sourceA) but updated text
|
368
|
+
assert messages[0]["responses"][2]["text"] == "Another text with <sup>12</sup>"
|
369
|
+
assert messages[0]["responses"][2]["references"] == []
|
370
|
+
|
371
|
+
@patch(
|
372
|
+
"unique_toolkit.agentic.tools.a2a.postprocessing.postprocessor._replace_references_in_text"
|
373
|
+
)
|
374
|
+
def test_replace_references_called_correctly(self, mock_replace):
|
375
|
+
"""Test that _replace_references_in_text is called with correct parameters."""
|
376
|
+
mock_replace.return_value = "Updated text"
|
377
|
+
|
378
|
+
messages = [
|
379
|
+
{
|
380
|
+
"display_name": "Assistant1",
|
381
|
+
"display_config": {"mode": "expanded"},
|
382
|
+
"responses": {
|
383
|
+
1: {
|
384
|
+
"text": "Original text <sup>1</sup>",
|
385
|
+
"references": [
|
386
|
+
{
|
387
|
+
"sourceId": "source1",
|
388
|
+
"sequenceNumber": 1,
|
389
|
+
"name": "Source 1",
|
390
|
+
"url": "http://source1.com",
|
391
|
+
"source": "src1",
|
392
|
+
"originalIndex": [],
|
393
|
+
}
|
394
|
+
],
|
395
|
+
}
|
396
|
+
},
|
397
|
+
}
|
398
|
+
]
|
399
|
+
existing_refs = {}
|
400
|
+
|
401
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
402
|
+
|
403
|
+
# Should call _replace_references_in_text with the original text and ref mapping
|
404
|
+
mock_replace.assert_called_once_with("Original text <sup>1</sup>", {1: 1})
|
405
|
+
assert messages[0]["responses"][1]["text"] == "Updated text"
|
406
|
+
|
407
|
+
def test_reference_sequence_number_modification(self):
|
408
|
+
"""Test that reference sequence numbers are correctly modified in place."""
|
409
|
+
original_ref = {
|
410
|
+
"sourceId": "source1",
|
411
|
+
"sequenceNumber": 5,
|
412
|
+
"name": "Source 1",
|
413
|
+
"url": "http://source1.com",
|
414
|
+
"source": "src1",
|
415
|
+
"originalIndex": [],
|
416
|
+
}
|
417
|
+
|
418
|
+
messages = [
|
419
|
+
{
|
420
|
+
"display_name": "Assistant1",
|
421
|
+
"display_config": {"mode": "expanded"},
|
422
|
+
"responses": {
|
423
|
+
1: {"text": "Text <sup>5</sup>", "references": [original_ref]}
|
424
|
+
},
|
425
|
+
}
|
426
|
+
]
|
427
|
+
existing_refs = {}
|
428
|
+
|
429
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
430
|
+
|
431
|
+
# The original reference object should be modified in place
|
432
|
+
assert original_ref["sequenceNumber"] == 1
|
433
|
+
assert messages[0]["responses"][1]["references"][0]["sequenceNumber"] == 1
|
434
|
+
|
435
|
+
@pytest.mark.parametrize(
|
436
|
+
"existing_refs,expected_start",
|
437
|
+
[
|
438
|
+
({}, 1), # Empty -> start from 1
|
439
|
+
({"a": 1}, 2), # Max 1 -> start from 2
|
440
|
+
({"a": 5, "b": 3, "c": 10}, 11), # Max 10 -> start from 11
|
441
|
+
({"a": 0}, 1), # Max 0 -> start from 1
|
442
|
+
],
|
443
|
+
)
|
444
|
+
def test_start_index_calculation(self, existing_refs, expected_start):
|
445
|
+
"""Test that start_index is calculated correctly from existing_refs."""
|
446
|
+
messages = [
|
447
|
+
{
|
448
|
+
"display_name": "Assistant1",
|
449
|
+
"display_config": {"mode": "expanded"},
|
450
|
+
"responses": {
|
451
|
+
1: {
|
452
|
+
"text": "Text <sup>1</sup>",
|
453
|
+
"references": [
|
454
|
+
{
|
455
|
+
"sourceId": "new_source",
|
456
|
+
"sequenceNumber": 1,
|
457
|
+
"name": "New Source",
|
458
|
+
"url": "http://new.com",
|
459
|
+
"source": "new",
|
460
|
+
"originalIndex": [],
|
461
|
+
}
|
462
|
+
],
|
463
|
+
}
|
464
|
+
},
|
465
|
+
}
|
466
|
+
]
|
467
|
+
|
468
|
+
existing_refs_copy = existing_refs.copy()
|
469
|
+
_consolidate_references_in_place(messages, existing_refs_copy) # type: ignore
|
470
|
+
|
471
|
+
assert existing_refs_copy["new_source"] == expected_start
|
472
|
+
|
473
|
+
def test_no_responses_in_assistant(self):
|
474
|
+
"""Test with assistant that has no responses."""
|
475
|
+
messages = [
|
476
|
+
{
|
477
|
+
"display_name": "Assistant1",
|
478
|
+
"display_config": {"mode": "expanded"},
|
479
|
+
"responses": {},
|
480
|
+
}
|
481
|
+
]
|
482
|
+
existing_refs = {}
|
483
|
+
|
484
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
485
|
+
|
486
|
+
assert existing_refs == {}
|
487
|
+
|
488
|
+
def test_mixed_valid_and_invalid_messages(self):
|
489
|
+
"""Test with mix of valid messages and messages with no references."""
|
490
|
+
messages = [
|
491
|
+
{
|
492
|
+
"display_name": "Assistant1",
|
493
|
+
"display_config": {"mode": "expanded"},
|
494
|
+
"responses": {
|
495
|
+
1: {
|
496
|
+
"text": "Valid text <sup>1</sup>",
|
497
|
+
"references": [
|
498
|
+
{
|
499
|
+
"sourceId": "source1",
|
500
|
+
"sequenceNumber": 1,
|
501
|
+
"name": "Source 1",
|
502
|
+
"url": "http://source1.com",
|
503
|
+
"source": "src1",
|
504
|
+
"originalIndex": [],
|
505
|
+
}
|
506
|
+
],
|
507
|
+
},
|
508
|
+
2: {"text": "No references", "references": []},
|
509
|
+
3: {
|
510
|
+
"text": "Another valid <sup>1</sup>",
|
511
|
+
"references": [
|
512
|
+
{
|
513
|
+
"sourceId": "source3",
|
514
|
+
"sequenceNumber": 1,
|
515
|
+
"name": "Source 3",
|
516
|
+
"url": "http://source3.com",
|
517
|
+
"source": "src3",
|
518
|
+
"originalIndex": [],
|
519
|
+
}
|
520
|
+
],
|
521
|
+
},
|
522
|
+
},
|
523
|
+
}
|
524
|
+
]
|
525
|
+
existing_refs = {}
|
526
|
+
|
527
|
+
with patch(
|
528
|
+
"unique_toolkit.agentic.tools.a2a.postprocessing.postprocessor.logger"
|
529
|
+
) as mock_logger:
|
530
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
531
|
+
|
532
|
+
# Should log once for message 2
|
533
|
+
mock_logger.debug.assert_called_once()
|
534
|
+
|
535
|
+
# Only valid messages should be processed
|
536
|
+
assert existing_refs == {"source1": 1, "source3": 2}
|
537
|
+
assert messages[0]["responses"][1]["text"] == "Valid text <sup>1</sup>"
|
538
|
+
assert messages[0]["responses"][2]["text"] == "No references"
|
539
|
+
assert messages[0]["responses"][3]["text"] == "Another valid <sup>2</sup>"
|
540
|
+
|
541
|
+
def test_sequence_number_sorting_within_responses(self):
|
542
|
+
"""Test that responses are processed in sorted sequence number order."""
|
543
|
+
messages = [
|
544
|
+
{
|
545
|
+
"display_name": "Assistant1",
|
546
|
+
"display_config": {"mode": "expanded"},
|
547
|
+
"responses": {
|
548
|
+
3: {
|
549
|
+
"text": "Third message <sup>1</sup>",
|
550
|
+
"references": [
|
551
|
+
{
|
552
|
+
"sourceId": "source3",
|
553
|
+
"sequenceNumber": 1,
|
554
|
+
"name": "Source 3",
|
555
|
+
"url": "http://source3.com",
|
556
|
+
"source": "src3",
|
557
|
+
"originalIndex": [],
|
558
|
+
}
|
559
|
+
],
|
560
|
+
},
|
561
|
+
1: {
|
562
|
+
"text": "First message <sup>1</sup>",
|
563
|
+
"references": [
|
564
|
+
{
|
565
|
+
"sourceId": "source1",
|
566
|
+
"sequenceNumber": 1,
|
567
|
+
"name": "Source 1",
|
568
|
+
"url": "http://source1.com",
|
569
|
+
"source": "src1",
|
570
|
+
"originalIndex": [],
|
571
|
+
}
|
572
|
+
],
|
573
|
+
},
|
574
|
+
2: {
|
575
|
+
"text": "Second message <sup>1</sup>",
|
576
|
+
"references": [
|
577
|
+
{
|
578
|
+
"sourceId": "source2",
|
579
|
+
"sequenceNumber": 1,
|
580
|
+
"name": "Source 2",
|
581
|
+
"url": "http://source2.com",
|
582
|
+
"source": "src2",
|
583
|
+
"originalIndex": [],
|
584
|
+
}
|
585
|
+
],
|
586
|
+
},
|
587
|
+
},
|
588
|
+
}
|
589
|
+
]
|
590
|
+
existing_refs = {}
|
591
|
+
|
592
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
593
|
+
|
594
|
+
# Should be processed in order 1, 2, 3 based on response sequence numbers
|
595
|
+
assert existing_refs == {"source1": 1, "source2": 2, "source3": 3}
|
596
|
+
assert messages[0]["responses"][1]["text"] == "First message <sup>1</sup>"
|
597
|
+
assert messages[0]["responses"][2]["text"] == "Second message <sup>2</sup>"
|
598
|
+
assert messages[0]["responses"][3]["text"] == "Third message <sup>3</sup>"
|
599
|
+
|
600
|
+
def test_source_id_already_exists_in_existing_refs(self):
|
601
|
+
"""Test when source ID already exists in existing_refs."""
|
602
|
+
messages = [
|
603
|
+
{
|
604
|
+
"display_name": "Assistant1",
|
605
|
+
"display_config": {"mode": "expanded"},
|
606
|
+
"responses": {
|
607
|
+
1: {
|
608
|
+
"text": "Message <sup>1</sup> with existing source",
|
609
|
+
"references": [
|
610
|
+
{
|
611
|
+
"sourceId": "existing_source",
|
612
|
+
"sequenceNumber": 1,
|
613
|
+
"name": "Existing Source",
|
614
|
+
"url": "http://existing.com",
|
615
|
+
"source": "existing",
|
616
|
+
"originalIndex": [],
|
617
|
+
}
|
618
|
+
],
|
619
|
+
}
|
620
|
+
},
|
621
|
+
}
|
622
|
+
]
|
623
|
+
existing_refs = {"existing_source": 99}
|
624
|
+
|
625
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
626
|
+
|
627
|
+
# Should use existing reference number and not add to new refs
|
628
|
+
assert existing_refs == {"existing_source": 99}
|
629
|
+
assert (
|
630
|
+
messages[0]["responses"][1]["text"]
|
631
|
+
== "Message <sup>99</sup> with existing source"
|
632
|
+
)
|
633
|
+
assert (
|
634
|
+
messages[0]["responses"][1]["references"] == []
|
635
|
+
) # Should be empty since it's not new
|
636
|
+
|
637
|
+
def test_edge_case_zero_and_negative_existing_refs(self):
|
638
|
+
"""Test edge case with zero and negative values in existing_refs."""
|
639
|
+
messages = [
|
640
|
+
{
|
641
|
+
"display_name": "Assistant1",
|
642
|
+
"display_config": {"mode": "expanded"},
|
643
|
+
"responses": {
|
644
|
+
1: {
|
645
|
+
"text": "Text <sup>1</sup>",
|
646
|
+
"references": [
|
647
|
+
{
|
648
|
+
"sourceId": "new_source",
|
649
|
+
"sequenceNumber": 1,
|
650
|
+
"name": "New Source",
|
651
|
+
"url": "http://new.com",
|
652
|
+
"source": "new",
|
653
|
+
"originalIndex": [],
|
654
|
+
}
|
655
|
+
],
|
656
|
+
}
|
657
|
+
},
|
658
|
+
}
|
659
|
+
]
|
660
|
+
existing_refs = {"zero": 0, "negative": -5, "positive": 3}
|
661
|
+
|
662
|
+
_consolidate_references_in_place(messages, existing_refs) # type: ignore
|
663
|
+
|
664
|
+
# Should start from max(0, -5, 3) + 1 = 4
|
665
|
+
assert existing_refs["new_source"] == 4
|