unique_toolkit 1.8.1__py3-none-any.whl → 1.23.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.
Potentially problematic release.
This version of unique_toolkit might be problematic. Click here for more details.
- unique_toolkit/__init__.py +20 -0
- unique_toolkit/_common/api_calling/human_verification_manager.py +121 -28
- unique_toolkit/_common/chunk_relevancy_sorter/config.py +3 -3
- unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +2 -5
- unique_toolkit/_common/default_language_model.py +9 -3
- unique_toolkit/_common/docx_generator/__init__.py +7 -0
- unique_toolkit/_common/docx_generator/config.py +12 -0
- unique_toolkit/_common/docx_generator/schemas.py +80 -0
- unique_toolkit/_common/docx_generator/service.py +252 -0
- unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
- unique_toolkit/_common/endpoint_builder.py +138 -117
- unique_toolkit/_common/endpoint_requestor.py +240 -14
- unique_toolkit/_common/exception.py +20 -0
- unique_toolkit/_common/feature_flags/schema.py +1 -5
- unique_toolkit/_common/referencing.py +53 -0
- unique_toolkit/_common/string_utilities.py +52 -1
- unique_toolkit/_common/tests/test_referencing.py +521 -0
- unique_toolkit/_common/tests/test_string_utilities.py +506 -0
- unique_toolkit/_common/utils/files.py +43 -0
- unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +16 -6
- unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +278 -0
- unique_toolkit/agentic/evaluation/config.py +3 -2
- unique_toolkit/agentic/evaluation/context_relevancy/service.py +2 -2
- unique_toolkit/agentic/evaluation/evaluation_manager.py +9 -5
- unique_toolkit/agentic/evaluation/hallucination/constants.py +1 -1
- unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +26 -3
- unique_toolkit/agentic/history_manager/history_manager.py +14 -11
- unique_toolkit/agentic/history_manager/loop_token_reducer.py +3 -4
- unique_toolkit/agentic/history_manager/utils.py +10 -87
- unique_toolkit/agentic/postprocessor/postprocessor_manager.py +107 -16
- unique_toolkit/agentic/reference_manager/reference_manager.py +1 -1
- unique_toolkit/agentic/responses_api/__init__.py +19 -0
- unique_toolkit/agentic/responses_api/postprocessors/code_display.py +63 -0
- unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +145 -0
- unique_toolkit/agentic/responses_api/stream_handler.py +15 -0
- unique_toolkit/agentic/tools/a2a/__init__.py +18 -2
- unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +2 -0
- unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +3 -3
- unique_toolkit/agentic/tools/a2a/evaluation/config.py +1 -1
- unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +143 -91
- unique_toolkit/agentic/tools/a2a/manager.py +7 -1
- unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +11 -3
- unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py +185 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +73 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/config.py +21 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/display.py +180 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/references.py +101 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +1335 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py +603 -0
- unique_toolkit/agentic/tools/a2a/prompts.py +46 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py +6 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/service.py +91 -0
- unique_toolkit/agentic/tools/a2a/tool/config.py +15 -5
- unique_toolkit/agentic/tools/a2a/tool/service.py +69 -36
- unique_toolkit/agentic/tools/config.py +16 -2
- unique_toolkit/agentic/tools/factory.py +4 -0
- unique_toolkit/agentic/tools/mcp/tool_wrapper.py +7 -35
- unique_toolkit/agentic/tools/openai_builtin/__init__.py +11 -0
- unique_toolkit/agentic/tools/openai_builtin/base.py +30 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +8 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +57 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +230 -0
- unique_toolkit/agentic/tools/openai_builtin/manager.py +62 -0
- unique_toolkit/agentic/tools/test/test_mcp_manager.py +95 -7
- unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +240 -0
- unique_toolkit/agentic/tools/tool.py +0 -11
- unique_toolkit/agentic/tools/tool_manager.py +337 -122
- unique_toolkit/agentic/tools/tool_progress_reporter.py +81 -15
- unique_toolkit/agentic/tools/utils/__init__.py +18 -0
- unique_toolkit/agentic/tools/utils/execution/execution.py +8 -4
- unique_toolkit/agentic/tools/utils/source_handling/schema.py +1 -1
- unique_toolkit/chat/__init__.py +8 -1
- unique_toolkit/chat/deprecated/service.py +232 -0
- unique_toolkit/chat/functions.py +54 -40
- unique_toolkit/chat/rendering.py +34 -0
- unique_toolkit/chat/responses_api.py +461 -0
- unique_toolkit/chat/schemas.py +1 -1
- unique_toolkit/chat/service.py +96 -1569
- unique_toolkit/content/functions.py +116 -1
- unique_toolkit/content/schemas.py +59 -0
- unique_toolkit/content/service.py +5 -37
- unique_toolkit/content/smart_rules.py +301 -0
- unique_toolkit/framework_utilities/langchain/client.py +27 -3
- unique_toolkit/framework_utilities/openai/client.py +12 -1
- unique_toolkit/framework_utilities/openai/message_builder.py +85 -1
- unique_toolkit/language_model/default_language_model.py +3 -0
- unique_toolkit/language_model/functions.py +25 -9
- unique_toolkit/language_model/infos.py +72 -4
- unique_toolkit/language_model/schemas.py +246 -40
- unique_toolkit/protocols/support.py +91 -9
- unique_toolkit/services/__init__.py +7 -0
- unique_toolkit/services/chat_service.py +1630 -0
- unique_toolkit/services/knowledge_base.py +861 -0
- unique_toolkit/smart_rules/compile.py +56 -301
- unique_toolkit/test_utilities/events.py +197 -0
- {unique_toolkit-1.8.1.dist-info → unique_toolkit-1.23.0.dist-info}/METADATA +173 -3
- {unique_toolkit-1.8.1.dist-info → unique_toolkit-1.23.0.dist-info}/RECORD +99 -67
- unique_toolkit/agentic/tools/a2a/postprocessing/_display.py +0 -122
- unique_toolkit/agentic/tools/a2a/postprocessing/_utils.py +0 -19
- unique_toolkit/agentic/tools/a2a/postprocessing/postprocessor.py +0 -230
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_consolidate_references.py +0 -665
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display.py +0 -391
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_postprocessor_reference_functions.py +0 -256
- {unique_toolkit-1.8.1.dist-info → unique_toolkit-1.23.0.dist-info}/LICENSE +0 -0
- {unique_toolkit-1.8.1.dist-info → unique_toolkit-1.23.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for reference utility functions in _ref_utils.py.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from unique_toolkit.agentic.tools.a2a.postprocessing._ref_utils import (
|
|
8
|
+
_add_source_ids,
|
|
9
|
+
add_content_refs,
|
|
10
|
+
add_content_refs_and_replace_in_text,
|
|
11
|
+
)
|
|
12
|
+
from unique_toolkit.content import ContentReference
|
|
13
|
+
|
|
14
|
+
# Fixtures
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def base_content_ref() -> ContentReference:
|
|
19
|
+
"""Base ContentReference fixture for testing."""
|
|
20
|
+
return ContentReference(
|
|
21
|
+
name="Test Doc",
|
|
22
|
+
url="https://example.com/doc1",
|
|
23
|
+
sequence_number=1,
|
|
24
|
+
source_id="doc-123",
|
|
25
|
+
source="test-source",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def content_refs_list() -> list[ContentReference]:
|
|
31
|
+
"""List of ContentReference fixtures."""
|
|
32
|
+
return [
|
|
33
|
+
ContentReference(
|
|
34
|
+
name="Doc 1",
|
|
35
|
+
url="https://example.com/doc1",
|
|
36
|
+
sequence_number=1,
|
|
37
|
+
source_id="doc-1",
|
|
38
|
+
source="test-source",
|
|
39
|
+
),
|
|
40
|
+
ContentReference(
|
|
41
|
+
name="Doc 2",
|
|
42
|
+
url="https://example.com/doc2",
|
|
43
|
+
sequence_number=2,
|
|
44
|
+
source_id="doc-2",
|
|
45
|
+
source="test-source",
|
|
46
|
+
),
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Tests for _add_source_ids
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.ai
|
|
54
|
+
def test_add_source_ids__returns_empty_dict__when_no_new_refs() -> None:
|
|
55
|
+
"""
|
|
56
|
+
Purpose: Verify _add_source_ids returns empty dict when no new references provided.
|
|
57
|
+
Why this matters: Ensures function handles empty input gracefully.
|
|
58
|
+
Setup summary: Empty new_refs iterable, assert empty result dict.
|
|
59
|
+
"""
|
|
60
|
+
# Arrange
|
|
61
|
+
existing_refs = {"doc-1": 1, "doc-2": 2}
|
|
62
|
+
new_refs: list[str] = []
|
|
63
|
+
|
|
64
|
+
# Act
|
|
65
|
+
result = _add_source_ids(existing_refs, new_refs)
|
|
66
|
+
|
|
67
|
+
# Assert
|
|
68
|
+
assert result == {}
|
|
69
|
+
assert isinstance(result, dict)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@pytest.mark.ai
|
|
73
|
+
def test_add_source_ids__assigns_sequential_numbers__for_new_source_ids() -> None:
|
|
74
|
+
"""
|
|
75
|
+
Purpose: Verify _add_source_ids assigns sequential numbers starting after max existing.
|
|
76
|
+
Why this matters: Core functionality for maintaining reference number uniqueness.
|
|
77
|
+
Setup summary: Existing refs with max=2, add two new IDs, verify they get 3 and 4.
|
|
78
|
+
"""
|
|
79
|
+
# Arrange
|
|
80
|
+
existing_refs = {"doc-1": 1, "doc-2": 2}
|
|
81
|
+
new_refs = ["doc-3", "doc-4"]
|
|
82
|
+
|
|
83
|
+
# Act
|
|
84
|
+
result = _add_source_ids(existing_refs, new_refs)
|
|
85
|
+
|
|
86
|
+
# Assert
|
|
87
|
+
assert result == {"doc-3": 3, "doc-4": 4}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.mark.ai
|
|
91
|
+
def test_add_source_ids__skips_existing_source_ids__does_not_duplicate() -> None:
|
|
92
|
+
"""
|
|
93
|
+
Purpose: Verify _add_source_ids skips source IDs that already exist.
|
|
94
|
+
Why this matters: Prevents duplicate references and maintains reference integrity.
|
|
95
|
+
Setup summary: New refs include existing ID, verify it's not in result.
|
|
96
|
+
"""
|
|
97
|
+
# Arrange
|
|
98
|
+
existing_refs = {"doc-1": 1, "doc-2": 2}
|
|
99
|
+
new_refs = ["doc-1", "doc-3"]
|
|
100
|
+
|
|
101
|
+
# Act
|
|
102
|
+
result = _add_source_ids(existing_refs, new_refs)
|
|
103
|
+
|
|
104
|
+
# Assert
|
|
105
|
+
assert result == {"doc-3": 3}
|
|
106
|
+
assert "doc-1" not in result
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.mark.ai
|
|
110
|
+
def test_add_source_ids__handles_empty_existing_refs__starts_at_one() -> None:
|
|
111
|
+
"""
|
|
112
|
+
Purpose: Verify _add_source_ids starts numbering at 1 when no existing refs.
|
|
113
|
+
Why this matters: Ensures correct initialization for new reference collections.
|
|
114
|
+
Setup summary: Empty existing_refs, add new refs, verify numbering starts at 1.
|
|
115
|
+
"""
|
|
116
|
+
# Arrange
|
|
117
|
+
existing_refs: dict[str, int] = {}
|
|
118
|
+
new_refs = ["doc-1", "doc-2"]
|
|
119
|
+
|
|
120
|
+
# Act
|
|
121
|
+
result = _add_source_ids(existing_refs, new_refs)
|
|
122
|
+
|
|
123
|
+
# Assert
|
|
124
|
+
assert result == {"doc-1": 1, "doc-2": 2}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@pytest.mark.ai
|
|
128
|
+
def test_add_source_ids__handles_duplicate_in_new_refs__assigns_once() -> None:
|
|
129
|
+
"""
|
|
130
|
+
Purpose: Verify _add_source_ids handles duplicates within new_refs correctly.
|
|
131
|
+
Why this matters: Prevents multiple assignments for same source ID in a batch.
|
|
132
|
+
Setup summary: New refs with duplicates, verify only one sequence number assigned.
|
|
133
|
+
"""
|
|
134
|
+
# Arrange
|
|
135
|
+
existing_refs: dict[str, int] = {}
|
|
136
|
+
new_refs = ["doc-1", "doc-2", "doc-1", "doc-3"]
|
|
137
|
+
|
|
138
|
+
# Act
|
|
139
|
+
result = _add_source_ids(existing_refs, new_refs)
|
|
140
|
+
|
|
141
|
+
# Assert
|
|
142
|
+
assert result == {"doc-1": 1, "doc-2": 2, "doc-3": 3}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# Tests for add_content_refs
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@pytest.mark.ai
|
|
149
|
+
def test_add_content_refs__returns_original_list__when_no_new_refs(
|
|
150
|
+
content_refs_list: list[ContentReference],
|
|
151
|
+
) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Purpose: Verify add_content_refs returns original list unchanged when no new refs.
|
|
154
|
+
Why this matters: Handles empty additions efficiently without modification.
|
|
155
|
+
Setup summary: Existing refs, empty new refs list, assert original returned.
|
|
156
|
+
"""
|
|
157
|
+
# Arrange
|
|
158
|
+
new_refs: list[ContentReference] = []
|
|
159
|
+
|
|
160
|
+
# Act
|
|
161
|
+
result = add_content_refs(content_refs_list, new_refs)
|
|
162
|
+
|
|
163
|
+
# Assert
|
|
164
|
+
assert result == content_refs_list
|
|
165
|
+
assert len(result) == 2
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@pytest.mark.ai
|
|
169
|
+
def test_add_content_refs__appends_new_refs__with_updated_sequence_numbers(
|
|
170
|
+
content_refs_list: list[ContentReference],
|
|
171
|
+
) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Purpose: Verify add_content_refs appends new refs with correct sequence numbers.
|
|
174
|
+
Why this matters: Core functionality for extending reference lists.
|
|
175
|
+
Setup summary: Two existing refs (seq 1,2), add one new, verify seq 3 assigned.
|
|
176
|
+
"""
|
|
177
|
+
# Arrange
|
|
178
|
+
new_ref = ContentReference(
|
|
179
|
+
name="Doc 3",
|
|
180
|
+
url="https://example.com/doc3",
|
|
181
|
+
sequence_number=1, # Original number, should be updated
|
|
182
|
+
source_id="doc-3",
|
|
183
|
+
source="test-source",
|
|
184
|
+
)
|
|
185
|
+
new_refs = [new_ref]
|
|
186
|
+
|
|
187
|
+
# Act
|
|
188
|
+
result = add_content_refs(content_refs_list, new_refs)
|
|
189
|
+
|
|
190
|
+
# Assert
|
|
191
|
+
assert len(result) == 3
|
|
192
|
+
assert result[2].source_id == "doc-3"
|
|
193
|
+
assert result[2].sequence_number == 3
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@pytest.mark.ai
|
|
197
|
+
def test_add_content_refs__skips_duplicate_source_ids__no_duplication(
|
|
198
|
+
content_refs_list: list[ContentReference],
|
|
199
|
+
) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Purpose: Verify add_content_refs skips refs with existing source_ids.
|
|
202
|
+
Why this matters: Prevents duplicate references in the final list.
|
|
203
|
+
Setup summary: New ref with existing source_id, verify not added again.
|
|
204
|
+
"""
|
|
205
|
+
# Arrange
|
|
206
|
+
duplicate_ref = ContentReference(
|
|
207
|
+
name="Doc 1 Duplicate",
|
|
208
|
+
url="https://example.com/doc1-dup",
|
|
209
|
+
sequence_number=99,
|
|
210
|
+
source_id="doc-1", # Already exists
|
|
211
|
+
source="test-source",
|
|
212
|
+
)
|
|
213
|
+
new_refs = [duplicate_ref]
|
|
214
|
+
|
|
215
|
+
# Act
|
|
216
|
+
result = add_content_refs(content_refs_list, new_refs)
|
|
217
|
+
|
|
218
|
+
# Assert
|
|
219
|
+
assert len(result) == 2 # No new item added
|
|
220
|
+
assert all(ref.source_id != "doc-1" or ref.name == "Doc 1" for ref in result)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@pytest.mark.ai
|
|
224
|
+
def test_add_content_refs__handles_multiple_new_refs__sequential_numbering() -> None:
|
|
225
|
+
"""
|
|
226
|
+
Purpose: Verify add_content_refs handles multiple new refs with sequential numbering.
|
|
227
|
+
Why this matters: Ensures batch additions maintain sequence integrity.
|
|
228
|
+
Setup summary: Add three new refs, verify they get sequential numbers 1, 2, 3.
|
|
229
|
+
"""
|
|
230
|
+
# Arrange
|
|
231
|
+
message_refs: list[ContentReference] = []
|
|
232
|
+
new_refs = [
|
|
233
|
+
ContentReference(
|
|
234
|
+
name="Doc A",
|
|
235
|
+
url="https://example.com/a",
|
|
236
|
+
sequence_number=10,
|
|
237
|
+
source_id="doc-a",
|
|
238
|
+
source="test",
|
|
239
|
+
),
|
|
240
|
+
ContentReference(
|
|
241
|
+
name="Doc B",
|
|
242
|
+
url="https://example.com/b",
|
|
243
|
+
sequence_number=20,
|
|
244
|
+
source_id="doc-b",
|
|
245
|
+
source="test",
|
|
246
|
+
),
|
|
247
|
+
ContentReference(
|
|
248
|
+
name="Doc C",
|
|
249
|
+
url="https://example.com/c",
|
|
250
|
+
sequence_number=30,
|
|
251
|
+
source_id="doc-c",
|
|
252
|
+
source="test",
|
|
253
|
+
),
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
# Act
|
|
257
|
+
result = add_content_refs(message_refs, new_refs)
|
|
258
|
+
|
|
259
|
+
# Assert
|
|
260
|
+
assert len(result) == 3
|
|
261
|
+
assert result[0].sequence_number == 1
|
|
262
|
+
assert result[1].sequence_number == 2
|
|
263
|
+
assert result[2].sequence_number == 3
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@pytest.mark.ai
|
|
267
|
+
def test_add_content_refs__preserves_original_ref_properties__except_sequence_num(
|
|
268
|
+
content_refs_list: list[ContentReference],
|
|
269
|
+
) -> None:
|
|
270
|
+
"""
|
|
271
|
+
Purpose: Verify add_content_refs preserves all properties except sequence_number.
|
|
272
|
+
Why this matters: Ensures reference data integrity during addition.
|
|
273
|
+
Setup summary: Add ref with specific properties, verify all preserved with new seq num.
|
|
274
|
+
"""
|
|
275
|
+
# Arrange
|
|
276
|
+
new_ref = ContentReference(
|
|
277
|
+
name="Special Doc",
|
|
278
|
+
url="https://example.com/special",
|
|
279
|
+
sequence_number=999,
|
|
280
|
+
source_id="doc-special",
|
|
281
|
+
source="special-source",
|
|
282
|
+
id="custom-id",
|
|
283
|
+
message_id="msg-123",
|
|
284
|
+
original_index=[1, 2, 3],
|
|
285
|
+
)
|
|
286
|
+
new_refs = [new_ref]
|
|
287
|
+
|
|
288
|
+
# Act
|
|
289
|
+
result = add_content_refs(content_refs_list, new_refs)
|
|
290
|
+
|
|
291
|
+
# Assert
|
|
292
|
+
added_ref = result[2]
|
|
293
|
+
assert added_ref.name == "Special Doc"
|
|
294
|
+
assert added_ref.url == "https://example.com/special"
|
|
295
|
+
assert added_ref.source_id == "doc-special"
|
|
296
|
+
assert added_ref.source == "special-source"
|
|
297
|
+
assert added_ref.id == "custom-id"
|
|
298
|
+
assert added_ref.message_id == "msg-123"
|
|
299
|
+
assert added_ref.original_index == [1, 2, 3]
|
|
300
|
+
assert added_ref.sequence_number == 3 # Updated
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@pytest.mark.ai
|
|
304
|
+
def test_add_content_refs__sorts_new_refs_by_sequence_number__before_processing() -> (
|
|
305
|
+
None
|
|
306
|
+
):
|
|
307
|
+
"""
|
|
308
|
+
Purpose: Verify add_content_refs processes new refs in sequence_number order.
|
|
309
|
+
Why this matters: Ensures predictable ordering when multiple refs added.
|
|
310
|
+
Setup summary: Add refs in unsorted order, verify they're assigned by original seq order.
|
|
311
|
+
"""
|
|
312
|
+
# Arrange
|
|
313
|
+
message_refs: list[ContentReference] = []
|
|
314
|
+
new_refs = [
|
|
315
|
+
ContentReference(
|
|
316
|
+
name="Third",
|
|
317
|
+
url="",
|
|
318
|
+
sequence_number=30,
|
|
319
|
+
source_id="doc-c",
|
|
320
|
+
source="test",
|
|
321
|
+
),
|
|
322
|
+
ContentReference(
|
|
323
|
+
name="First",
|
|
324
|
+
url="",
|
|
325
|
+
sequence_number=10,
|
|
326
|
+
source_id="doc-a",
|
|
327
|
+
source="test",
|
|
328
|
+
),
|
|
329
|
+
ContentReference(
|
|
330
|
+
name="Second",
|
|
331
|
+
url="",
|
|
332
|
+
sequence_number=20,
|
|
333
|
+
source_id="doc-b",
|
|
334
|
+
source="test",
|
|
335
|
+
),
|
|
336
|
+
]
|
|
337
|
+
|
|
338
|
+
# Act
|
|
339
|
+
result = add_content_refs(message_refs, new_refs)
|
|
340
|
+
|
|
341
|
+
# Assert
|
|
342
|
+
assert len(result) == 3
|
|
343
|
+
# doc-a (seq 10) should be processed first and assigned 1
|
|
344
|
+
assert result[0].source_id == "doc-a"
|
|
345
|
+
assert result[0].sequence_number == 1
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# Tests for add_content_refs_and_replace_in_text
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@pytest.mark.ai
|
|
352
|
+
def test_add_content_refs_and_replace_in_text__returns_unchanged__when_no_new_refs() -> (
|
|
353
|
+
None
|
|
354
|
+
):
|
|
355
|
+
"""
|
|
356
|
+
Purpose: Verify function returns unchanged text and refs when no new refs provided.
|
|
357
|
+
Why this matters: Handles empty additions efficiently.
|
|
358
|
+
Setup summary: Text and refs with empty new_refs, verify no changes.
|
|
359
|
+
"""
|
|
360
|
+
# Arrange
|
|
361
|
+
message_text = "Some text with <sup>1</sup> reference"
|
|
362
|
+
message_refs = [
|
|
363
|
+
ContentReference(
|
|
364
|
+
name="Doc 1",
|
|
365
|
+
url="https://example.com",
|
|
366
|
+
sequence_number=1,
|
|
367
|
+
source_id="doc-1",
|
|
368
|
+
source="test",
|
|
369
|
+
)
|
|
370
|
+
]
|
|
371
|
+
new_refs: list[ContentReference] = []
|
|
372
|
+
|
|
373
|
+
# Act
|
|
374
|
+
result_text, result_refs = add_content_refs_and_replace_in_text(
|
|
375
|
+
message_text, message_refs, new_refs
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# Assert
|
|
379
|
+
assert result_text == message_text
|
|
380
|
+
assert result_refs == message_refs
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@pytest.mark.ai
|
|
384
|
+
def test_add_content_refs_and_replace_in_text__replaces_ref_numbers__in_text() -> None:
|
|
385
|
+
"""
|
|
386
|
+
Purpose: Verify function replaces reference numbers in text with updated numbers.
|
|
387
|
+
Why this matters: Maintains text-reference synchronization.
|
|
388
|
+
Setup summary: Text with <sup>1</sup>, add new ref that renumbers it, verify replacement.
|
|
389
|
+
"""
|
|
390
|
+
# Arrange
|
|
391
|
+
message_text = "Check this reference <sup>1</sup> here"
|
|
392
|
+
message_refs: list[ContentReference] = []
|
|
393
|
+
new_refs = [
|
|
394
|
+
ContentReference(
|
|
395
|
+
name="Doc 1",
|
|
396
|
+
url="https://example.com/doc1",
|
|
397
|
+
sequence_number=1,
|
|
398
|
+
source_id="doc-1",
|
|
399
|
+
source="test",
|
|
400
|
+
)
|
|
401
|
+
]
|
|
402
|
+
|
|
403
|
+
# Act
|
|
404
|
+
result_text, result_refs = add_content_refs_and_replace_in_text(
|
|
405
|
+
message_text, message_refs, new_refs
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
# Assert
|
|
409
|
+
assert "<sup>1</sup>" in result_text
|
|
410
|
+
assert len(result_refs) == 1
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
@pytest.mark.ai
|
|
414
|
+
def test_add_content_refs_and_replace_in_text__uses_custom_pattern_functions(
|
|
415
|
+
mocker,
|
|
416
|
+
) -> None:
|
|
417
|
+
"""
|
|
418
|
+
Purpose: Verify function uses custom pattern and replacement functions when provided.
|
|
419
|
+
Why this matters: Allows flexibility for different reference formats.
|
|
420
|
+
Setup summary: Mock pattern functions with regex-escaped patterns, verify they're called correctly.
|
|
421
|
+
"""
|
|
422
|
+
# Arrange
|
|
423
|
+
# Use regex-escaped pattern since replace_in_text uses re.sub
|
|
424
|
+
message_text = "[REF-1] is here"
|
|
425
|
+
message_refs: list[ContentReference] = []
|
|
426
|
+
new_refs = [
|
|
427
|
+
ContentReference(
|
|
428
|
+
name="Doc 1",
|
|
429
|
+
url="",
|
|
430
|
+
sequence_number=1,
|
|
431
|
+
source_id="doc-1",
|
|
432
|
+
source="test",
|
|
433
|
+
)
|
|
434
|
+
]
|
|
435
|
+
|
|
436
|
+
def custom_pattern(num: int) -> str:
|
|
437
|
+
# Return regex-escaped pattern
|
|
438
|
+
return f"\\[REF-{num}\\]"
|
|
439
|
+
|
|
440
|
+
def custom_replacement(num: int) -> str:
|
|
441
|
+
return f"[REF-{num}]"
|
|
442
|
+
|
|
443
|
+
mock_pattern = mocker.Mock(side_effect=custom_pattern)
|
|
444
|
+
mock_replacement = mocker.Mock(side_effect=custom_replacement)
|
|
445
|
+
|
|
446
|
+
# Act
|
|
447
|
+
result_text, result_refs = add_content_refs_and_replace_in_text(
|
|
448
|
+
message_text,
|
|
449
|
+
message_refs,
|
|
450
|
+
new_refs,
|
|
451
|
+
ref_pattern_f=mock_pattern,
|
|
452
|
+
ref_replacement_f=mock_replacement,
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
# Assert
|
|
456
|
+
mock_pattern.assert_called_once_with(1)
|
|
457
|
+
mock_replacement.assert_called_once_with(1)
|
|
458
|
+
assert len(result_refs) == 1
|
|
459
|
+
assert result_text == "[REF-1] is here"
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
@pytest.mark.ai
|
|
463
|
+
def test_add_content_refs_and_replace_in_text__handles_multiple_refs_in_text() -> None:
|
|
464
|
+
"""
|
|
465
|
+
Purpose: Verify function correctly handles multiple reference replacements in text.
|
|
466
|
+
Why this matters: Ensures batch text updates work correctly.
|
|
467
|
+
Setup summary: Text with multiple refs, add new refs, verify all updated.
|
|
468
|
+
"""
|
|
469
|
+
# Arrange
|
|
470
|
+
message_text = "First <sup>1</sup> and second <sup>2</sup>"
|
|
471
|
+
message_refs: list[ContentReference] = []
|
|
472
|
+
new_refs = [
|
|
473
|
+
ContentReference(
|
|
474
|
+
name="Doc 1",
|
|
475
|
+
url="",
|
|
476
|
+
sequence_number=1,
|
|
477
|
+
source_id="doc-1",
|
|
478
|
+
source="test",
|
|
479
|
+
),
|
|
480
|
+
ContentReference(
|
|
481
|
+
name="Doc 2",
|
|
482
|
+
url="",
|
|
483
|
+
sequence_number=2,
|
|
484
|
+
source_id="doc-2",
|
|
485
|
+
source="test",
|
|
486
|
+
),
|
|
487
|
+
]
|
|
488
|
+
|
|
489
|
+
# Act
|
|
490
|
+
result_text, result_refs = add_content_refs_and_replace_in_text(
|
|
491
|
+
message_text, message_refs, new_refs
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
# Assert
|
|
495
|
+
assert "<sup>1</sup>" in result_text
|
|
496
|
+
assert "<sup>2</sup>" in result_text
|
|
497
|
+
assert len(result_refs) == 2
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
@pytest.mark.ai
|
|
501
|
+
def test_add_content_refs_and_replace_in_text__avoids_duplicate_source_ids() -> None:
|
|
502
|
+
"""
|
|
503
|
+
Purpose: Verify function doesn't add refs with duplicate source_ids.
|
|
504
|
+
Why this matters: Maintains reference uniqueness in combined operation.
|
|
505
|
+
Setup summary: Try to add ref with existing source_id, verify not duplicated.
|
|
506
|
+
"""
|
|
507
|
+
# Arrange
|
|
508
|
+
message_text = "Text with <sup>1</sup>"
|
|
509
|
+
message_refs = [
|
|
510
|
+
ContentReference(
|
|
511
|
+
name="Doc 1",
|
|
512
|
+
url="",
|
|
513
|
+
sequence_number=1,
|
|
514
|
+
source_id="doc-1",
|
|
515
|
+
source="test",
|
|
516
|
+
)
|
|
517
|
+
]
|
|
518
|
+
new_refs = [
|
|
519
|
+
ContentReference(
|
|
520
|
+
name="Doc 1 Duplicate",
|
|
521
|
+
url="",
|
|
522
|
+
sequence_number=2,
|
|
523
|
+
source_id="doc-1", # Duplicate
|
|
524
|
+
source="test",
|
|
525
|
+
)
|
|
526
|
+
]
|
|
527
|
+
|
|
528
|
+
# Act
|
|
529
|
+
result_text, result_refs = add_content_refs_and_replace_in_text(
|
|
530
|
+
message_text, message_refs, new_refs
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
# Assert
|
|
534
|
+
assert len(result_refs) == 1 # No duplicate added
|
|
535
|
+
assert result_text == message_text
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
@pytest.mark.ai
|
|
539
|
+
def test_add_content_refs_and_replace_in_text__returns_tuple_with_correct_types() -> (
|
|
540
|
+
None
|
|
541
|
+
):
|
|
542
|
+
"""
|
|
543
|
+
Purpose: Verify function returns correctly typed tuple.
|
|
544
|
+
Why this matters: Ensures API contract and type safety.
|
|
545
|
+
Setup summary: Call function, assert return types are str and list.
|
|
546
|
+
"""
|
|
547
|
+
# Arrange
|
|
548
|
+
message_text = "Test"
|
|
549
|
+
message_refs: list[ContentReference] = []
|
|
550
|
+
new_refs: list[ContentReference] = []
|
|
551
|
+
|
|
552
|
+
# Act
|
|
553
|
+
result_text, result_refs = add_content_refs_and_replace_in_text(
|
|
554
|
+
message_text, message_refs, new_refs
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
# Assert
|
|
558
|
+
assert isinstance(result_text, str)
|
|
559
|
+
assert isinstance(result_refs, list)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
@pytest.mark.ai
|
|
563
|
+
def test_add_content_refs_and_replace_in_text__creates_ref_map_correctly() -> None:
|
|
564
|
+
"""
|
|
565
|
+
Purpose: Verify function creates correct mapping for text replacement.
|
|
566
|
+
Why this matters: Ensures reference renumbering logic is correct.
|
|
567
|
+
Setup summary: Add refs that need renumbering, verify text updates accordingly.
|
|
568
|
+
"""
|
|
569
|
+
# Arrange
|
|
570
|
+
# Start with existing ref at sequence 1
|
|
571
|
+
message_text = "See <sup>5</sup> for details"
|
|
572
|
+
message_refs = [
|
|
573
|
+
ContentReference(
|
|
574
|
+
name="Existing",
|
|
575
|
+
url="",
|
|
576
|
+
sequence_number=1,
|
|
577
|
+
source_id="doc-existing",
|
|
578
|
+
source="test",
|
|
579
|
+
)
|
|
580
|
+
]
|
|
581
|
+
# Add new ref with sequence 5 that should become sequence 2
|
|
582
|
+
new_refs = [
|
|
583
|
+
ContentReference(
|
|
584
|
+
name="New Doc",
|
|
585
|
+
url="",
|
|
586
|
+
sequence_number=5,
|
|
587
|
+
source_id="doc-new",
|
|
588
|
+
source="test",
|
|
589
|
+
)
|
|
590
|
+
]
|
|
591
|
+
|
|
592
|
+
# Act
|
|
593
|
+
result_text, result_refs = add_content_refs_and_replace_in_text(
|
|
594
|
+
message_text, message_refs, new_refs
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
# Assert
|
|
598
|
+
# The new ref's sequence number 5 should be mapped to 2
|
|
599
|
+
assert len(result_refs) == 2
|
|
600
|
+
assert result_refs[1].sequence_number == 2
|
|
601
|
+
# Text should have <sup>5</sup> replaced with <sup>2</sup>
|
|
602
|
+
assert "<sup>2</sup>" in result_text
|
|
603
|
+
assert "<sup>5</sup>" not in result_text
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
REFERENCING_INSTRUCTIONS_FOR_SYSTEM_PROMPT = """
|
|
2
|
+
Whenever you use a fact from a sub-agent response in yours, you MUST always reference it.
|
|
3
|
+
|
|
4
|
+
CRITICAL INSTRUCTION: References must always appear immediately after the fact they support.
|
|
5
|
+
Do NOT collect, group, or move references into a list at the end.
|
|
6
|
+
|
|
7
|
+
Rules:
|
|
8
|
+
|
|
9
|
+
1. Inline placement: After every fact from SubAgentName, immediately attach its reference(s) inline.
|
|
10
|
+
Example:
|
|
11
|
+
“The stock price of Apple Inc. is $150” <sup><name>SubAgentName 2</name>1</sup>.
|
|
12
|
+
|
|
13
|
+
2. No separate reference list: Do not place references in footnotes, bibliographies, or at the bottom.
|
|
14
|
+
Wrong:
|
|
15
|
+
“The stock price of Apple Inc. is $150.”
|
|
16
|
+
References: <sup><name>SubAgentName 2</name>1</sup>
|
|
17
|
+
Correct:
|
|
18
|
+
“The stock price of Apple Inc. is $150” <sup><name>SubAgentName 2</name>1</sup>.
|
|
19
|
+
|
|
20
|
+
3. Exact copy: Copy references character-for-character from SubAgentName’s message.
|
|
21
|
+
Do not alter numbering, labels, or order.
|
|
22
|
+
|
|
23
|
+
4. Multiple references: If more than one reference supports a single fact, include all of them inline, in the same sentence, in the original order.
|
|
24
|
+
Example:
|
|
25
|
+
“MSFT would be a good investment” <sup><name>SubAgentName 3</name>4</sup><sup><name>SubAgentName 3</name>8</sup>.
|
|
26
|
+
Wrong:
|
|
27
|
+
“MSFT would be a good investment” <sup><name>SubAgentName 3</name>8</sup><sup><name>SubAgentName 3</name>4</sup>. (order changed)
|
|
28
|
+
|
|
29
|
+
5. Never at the bottom: References must always stay attached inline with the fact.
|
|
30
|
+
Multi-fact Example (Correct):
|
|
31
|
+
“Tesla delivered 400,000 cars in Q2” <sup><name>SubAgentName 4</name>2</sup>.
|
|
32
|
+
“Its revenue for the quarter was $24B” <sup><name>SubAgentName 4</name>5</sup>.
|
|
33
|
+
“The company also expanded its Berlin factory capacity” <sup><name>SubAgentName 4</name>7</sup>.
|
|
34
|
+
Wrong Multi-fact Example:
|
|
35
|
+
“Tesla delivered 400,000 cars in Q2. Its revenue for the quarter was $24B. The company also expanded its Berlin factory capacity.”
|
|
36
|
+
References: <sup><name>SubAgentName 4</name>2</sup><sup><name>SubAgentName 4</name>5</sup><sup><name>SubAgentName 4</name>7</sup>
|
|
37
|
+
|
|
38
|
+
6. Fact repetition: If you reuse a fact from SubAgentName, you MUST reference it again inline with the correct format.
|
|
39
|
+
|
|
40
|
+
Reminder:
|
|
41
|
+
Inline = directly next to the fact, inside the same sentence or bullet.
|
|
42
|
+
""".strip()
|
|
43
|
+
|
|
44
|
+
REFERENCING_INSTRUCTIONS_FOR_USER_PROMPT = """
|
|
45
|
+
Rememeber to properly reference EACH fact from a sub agent's response with the correct format INLINE.
|
|
46
|
+
""".strip()
|