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
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
|
|
5
|
-
from unique_toolkit.agentic.tools.a2a.postprocessing._display import (
|
|
6
|
-
_build_sub_agent_answer_display,
|
|
7
|
-
_DetailsResponseDisplayHandler,
|
|
8
|
-
_remove_sub_agent_answer_from_text,
|
|
9
|
-
)
|
|
10
|
-
from unique_toolkit.agentic.tools.a2a.postprocessing.config import (
|
|
11
|
-
SubAgentResponseDisplayMode,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class TestDetailsResponseDisplayHandler:
|
|
16
|
-
"""Test suite for DetailsResponseDisplayHandler class."""
|
|
17
|
-
|
|
18
|
-
@pytest.fixture
|
|
19
|
-
def open_handler(self) -> _DetailsResponseDisplayHandler:
|
|
20
|
-
"""Create a handler with open mode."""
|
|
21
|
-
return _DetailsResponseDisplayHandler(mode="open")
|
|
22
|
-
|
|
23
|
-
@pytest.fixture
|
|
24
|
-
def closed_handler(self) -> _DetailsResponseDisplayHandler:
|
|
25
|
-
"""Create a handler with closed mode."""
|
|
26
|
-
return _DetailsResponseDisplayHandler(mode="closed")
|
|
27
|
-
|
|
28
|
-
@pytest.fixture
|
|
29
|
-
def sample_data(self):
|
|
30
|
-
"""Sample data for testing."""
|
|
31
|
-
return {
|
|
32
|
-
"display_name": "Test Assistant",
|
|
33
|
-
"assistant_id": "test_assistant_123",
|
|
34
|
-
"answer": "This is a test answer with multiple lines.\nSecond line here.",
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
def test_build_response_display_open_mode(self, open_handler, sample_data):
|
|
38
|
-
"""Test building response display in open mode."""
|
|
39
|
-
result = open_handler.build_response_display(
|
|
40
|
-
display_name=sample_data["display_name"],
|
|
41
|
-
assistant_id=sample_data["assistant_id"],
|
|
42
|
-
answer=sample_data["answer"],
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
assert "<details open>" in result
|
|
46
|
-
assert (
|
|
47
|
-
f'<div style="display: none;">{sample_data["assistant_id"]}</div>' in result
|
|
48
|
-
)
|
|
49
|
-
assert f"<summary>{sample_data['display_name']}</summary>" in result
|
|
50
|
-
assert sample_data["answer"] in result
|
|
51
|
-
assert "</details>" in result
|
|
52
|
-
|
|
53
|
-
def test_build_response_display_closed_mode(self, closed_handler, sample_data):
|
|
54
|
-
"""Test building response display in closed mode."""
|
|
55
|
-
result = closed_handler.build_response_display(
|
|
56
|
-
display_name=sample_data["display_name"],
|
|
57
|
-
assistant_id=sample_data["assistant_id"],
|
|
58
|
-
answer=sample_data["answer"],
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
assert "<details>" in result
|
|
62
|
-
assert "<details open>" not in result
|
|
63
|
-
assert (
|
|
64
|
-
f'<div style="display: none;">{sample_data["assistant_id"]}</div>' in result
|
|
65
|
-
)
|
|
66
|
-
assert f"<summary>{sample_data['display_name']}</summary>" in result
|
|
67
|
-
assert sample_data["answer"] in result
|
|
68
|
-
assert "</details>" in result
|
|
69
|
-
|
|
70
|
-
def test_build_response_display_with_special_characters(self, open_handler):
|
|
71
|
-
"""Test building response display with special characters in content."""
|
|
72
|
-
result = open_handler.build_response_display(
|
|
73
|
-
display_name="Test & Co.",
|
|
74
|
-
assistant_id="test<>123",
|
|
75
|
-
answer="Answer with <tags> & symbols",
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
assert "Test & Co." in result
|
|
79
|
-
assert "test<>123" in result
|
|
80
|
-
assert "Answer with <tags> & symbols" in result
|
|
81
|
-
|
|
82
|
-
def test_remove_response_display_open_mode(self, open_handler, sample_data):
|
|
83
|
-
"""Test removing response display from text in open mode."""
|
|
84
|
-
# First build the display
|
|
85
|
-
display_html = open_handler.build_response_display(
|
|
86
|
-
display_name=sample_data["display_name"],
|
|
87
|
-
assistant_id=sample_data["assistant_id"],
|
|
88
|
-
answer=sample_data["answer"],
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
# Create text with the display embedded
|
|
92
|
-
text_with_display = f"Some text before\n{display_html}\nSome text after"
|
|
93
|
-
|
|
94
|
-
# Remove the display
|
|
95
|
-
result = open_handler.remove_response_display(
|
|
96
|
-
assistant_id=sample_data["assistant_id"], text=text_with_display
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
assert "Some text before" in result
|
|
100
|
-
assert "Some text after" in result
|
|
101
|
-
assert sample_data["display_name"] not in result
|
|
102
|
-
assert sample_data["answer"] not in result
|
|
103
|
-
|
|
104
|
-
def test_remove_response_display_closed_mode(self, closed_handler, sample_data):
|
|
105
|
-
"""Test removing response display from text in closed mode."""
|
|
106
|
-
# First build the display
|
|
107
|
-
display_html = closed_handler.build_response_display(
|
|
108
|
-
display_name=sample_data["display_name"],
|
|
109
|
-
assistant_id=sample_data["assistant_id"],
|
|
110
|
-
answer=sample_data["answer"],
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
# Create text with the display embedded
|
|
114
|
-
text_with_display = f"Some text before\n{display_html}\nSome text after"
|
|
115
|
-
|
|
116
|
-
# Remove the display
|
|
117
|
-
result = closed_handler.remove_response_display(
|
|
118
|
-
assistant_id=sample_data["assistant_id"], text=text_with_display
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
assert "Some text before" in result
|
|
122
|
-
assert "Some text after" in result
|
|
123
|
-
assert sample_data["display_name"] not in result
|
|
124
|
-
assert sample_data["answer"] not in result
|
|
125
|
-
|
|
126
|
-
def test_remove_response_display_multiple_instances(self, open_handler):
|
|
127
|
-
"""Test removing multiple instances of response display."""
|
|
128
|
-
assistant_id = "test_123"
|
|
129
|
-
|
|
130
|
-
display1 = open_handler.build_response_display(
|
|
131
|
-
display_name="First", assistant_id=assistant_id, answer="First answer"
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
display2 = open_handler.build_response_display(
|
|
135
|
-
display_name="Second", assistant_id=assistant_id, answer="Second answer"
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
text_with_displays = f"Start\n{display1}\nMiddle\n{display2}\nEnd"
|
|
139
|
-
|
|
140
|
-
result = open_handler.remove_response_display(
|
|
141
|
-
assistant_id=assistant_id, text=text_with_displays
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
assert "Start" in result
|
|
145
|
-
assert "Middle" in result
|
|
146
|
-
assert "End" in result
|
|
147
|
-
assert "First answer" not in result
|
|
148
|
-
assert "Second answer" not in result
|
|
149
|
-
|
|
150
|
-
def test_remove_response_display_no_match(self, open_handler):
|
|
151
|
-
"""Test removing response display when no match exists."""
|
|
152
|
-
text = "This is some text without any displays"
|
|
153
|
-
result = open_handler.remove_response_display(
|
|
154
|
-
assistant_id="nonexistent", text=text
|
|
155
|
-
)
|
|
156
|
-
assert result == text
|
|
157
|
-
|
|
158
|
-
def test_remove_response_display_with_regex_special_chars(self, open_handler):
|
|
159
|
-
"""Test removing response display with regex special characters in assistant_id."""
|
|
160
|
-
assistant_id = "test.+*?[]{}()^$|"
|
|
161
|
-
|
|
162
|
-
display_html = open_handler.build_response_display(
|
|
163
|
-
display_name="Test", assistant_id=assistant_id, answer="Test answer"
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
text_with_display = f"Before\n{display_html}\nAfter"
|
|
167
|
-
|
|
168
|
-
result = open_handler.remove_response_display(
|
|
169
|
-
assistant_id=assistant_id, text=text_with_display
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
assert "Before" in result
|
|
173
|
-
assert "After" in result
|
|
174
|
-
assert "Test answer" not in result
|
|
175
|
-
|
|
176
|
-
def test_get_detect_re_pattern_validity(self, open_handler, closed_handler):
|
|
177
|
-
"""Test that the regex patterns are valid and compilable."""
|
|
178
|
-
assistant_id = "test_123"
|
|
179
|
-
|
|
180
|
-
open_pattern = open_handler._get_detect_re(assistant_id)
|
|
181
|
-
closed_pattern = closed_handler._get_detect_re(assistant_id)
|
|
182
|
-
|
|
183
|
-
# Should not raise exceptions
|
|
184
|
-
re.compile(open_pattern)
|
|
185
|
-
re.compile(closed_pattern)
|
|
186
|
-
|
|
187
|
-
assert "(?s)" in open_pattern # multiline flag
|
|
188
|
-
assert "(?s)" in closed_pattern
|
|
189
|
-
assert "details open" in open_pattern
|
|
190
|
-
assert "details>" in closed_pattern
|
|
191
|
-
assert "details open" not in closed_pattern
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
class TestDisplayFunctions:
|
|
195
|
-
"""Test suite for module-level display functions."""
|
|
196
|
-
|
|
197
|
-
@pytest.fixture
|
|
198
|
-
def sample_data(self):
|
|
199
|
-
"""Sample data for testing."""
|
|
200
|
-
return {
|
|
201
|
-
"display_name": "Test Assistant",
|
|
202
|
-
"assistant_id": "test_assistant_123",
|
|
203
|
-
"answer": "This is a test answer.",
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
@pytest.mark.parametrize(
|
|
207
|
-
"display_mode,expected_content,not_expected_content",
|
|
208
|
-
[
|
|
209
|
-
(SubAgentResponseDisplayMode.DETAILS_OPEN, "<details open>", None),
|
|
210
|
-
(SubAgentResponseDisplayMode.DETAILS_CLOSED, "<details>", "<details open>"),
|
|
211
|
-
(SubAgentResponseDisplayMode.HIDDEN, "", None),
|
|
212
|
-
],
|
|
213
|
-
)
|
|
214
|
-
def test_build_sub_agent_answer_display(
|
|
215
|
-
self, sample_data, display_mode, expected_content, not_expected_content
|
|
216
|
-
):
|
|
217
|
-
"""Test building sub-agent answer display with different modes."""
|
|
218
|
-
result = _build_sub_agent_answer_display(
|
|
219
|
-
display_name=sample_data["display_name"],
|
|
220
|
-
display_mode=display_mode,
|
|
221
|
-
answer=sample_data["answer"],
|
|
222
|
-
assistant_id=sample_data["assistant_id"],
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
if display_mode == SubAgentResponseDisplayMode.HIDDEN:
|
|
226
|
-
assert result == ""
|
|
227
|
-
else:
|
|
228
|
-
assert expected_content in result
|
|
229
|
-
assert sample_data["display_name"] in result
|
|
230
|
-
assert sample_data["answer"] in result
|
|
231
|
-
assert sample_data["assistant_id"] in result
|
|
232
|
-
|
|
233
|
-
if not_expected_content:
|
|
234
|
-
assert not_expected_content not in result
|
|
235
|
-
|
|
236
|
-
@pytest.mark.parametrize(
|
|
237
|
-
"display_mode",
|
|
238
|
-
[
|
|
239
|
-
SubAgentResponseDisplayMode.DETAILS_OPEN,
|
|
240
|
-
SubAgentResponseDisplayMode.DETAILS_CLOSED,
|
|
241
|
-
],
|
|
242
|
-
)
|
|
243
|
-
def test_remove_sub_agent_answer_from_text_details_modes(
|
|
244
|
-
self, sample_data, display_mode
|
|
245
|
-
):
|
|
246
|
-
"""Test removing sub-agent answer from text with DETAILS_OPEN and DETAILS_CLOSED modes."""
|
|
247
|
-
# First build the display
|
|
248
|
-
display_html = _build_sub_agent_answer_display(
|
|
249
|
-
display_name=sample_data["display_name"],
|
|
250
|
-
display_mode=display_mode,
|
|
251
|
-
answer=sample_data["answer"],
|
|
252
|
-
assistant_id=sample_data["assistant_id"],
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
text_with_display = f"Before\n{display_html}\nAfter"
|
|
256
|
-
|
|
257
|
-
result = _remove_sub_agent_answer_from_text(
|
|
258
|
-
display_mode=display_mode,
|
|
259
|
-
text=text_with_display,
|
|
260
|
-
assistant_id=sample_data["assistant_id"],
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
assert "Before" in result
|
|
264
|
-
assert "After" in result
|
|
265
|
-
assert sample_data["answer"] not in result
|
|
266
|
-
|
|
267
|
-
def test_remove_sub_agent_answer_from_text_hidden_mode(self, sample_data):
|
|
268
|
-
"""Test removing sub-agent answer from text with HIDDEN mode."""
|
|
269
|
-
text = "Some text here"
|
|
270
|
-
result = _remove_sub_agent_answer_from_text(
|
|
271
|
-
display_mode=SubAgentResponseDisplayMode.HIDDEN,
|
|
272
|
-
text=text,
|
|
273
|
-
assistant_id=sample_data["assistant_id"],
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
assert result == text
|
|
277
|
-
|
|
278
|
-
def test_roundtrip_build_and_remove(self, sample_data):
|
|
279
|
-
"""Test that building and then removing display results in clean text."""
|
|
280
|
-
original_text = "This is the original text."
|
|
281
|
-
|
|
282
|
-
# Build display
|
|
283
|
-
display_html = _build_sub_agent_answer_display(
|
|
284
|
-
display_name=sample_data["display_name"],
|
|
285
|
-
display_mode=SubAgentResponseDisplayMode.DETAILS_OPEN,
|
|
286
|
-
answer=sample_data["answer"],
|
|
287
|
-
assistant_id=sample_data["assistant_id"],
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
# Insert into text
|
|
291
|
-
text_with_display = f"{original_text}\n{display_html}"
|
|
292
|
-
|
|
293
|
-
# Remove display
|
|
294
|
-
result = _remove_sub_agent_answer_from_text(
|
|
295
|
-
display_mode=SubAgentResponseDisplayMode.DETAILS_OPEN,
|
|
296
|
-
text=text_with_display,
|
|
297
|
-
assistant_id=sample_data["assistant_id"],
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
# Should be back to original (with some whitespace differences)
|
|
301
|
-
assert original_text in result.strip()
|
|
302
|
-
assert sample_data["answer"] not in result
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
class TestEdgeCases:
|
|
306
|
-
"""Test suite for edge cases and error conditions."""
|
|
307
|
-
|
|
308
|
-
def test_empty_strings(self):
|
|
309
|
-
"""Test handling of empty strings."""
|
|
310
|
-
handler = _DetailsResponseDisplayHandler(mode="open")
|
|
311
|
-
|
|
312
|
-
result = handler.build_response_display(
|
|
313
|
-
display_name="", assistant_id="test", answer=""
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
assert "<details open>" in result
|
|
317
|
-
assert "<summary></summary>" in result
|
|
318
|
-
|
|
319
|
-
def test_multiline_content(self):
|
|
320
|
-
"""Test handling of multiline content."""
|
|
321
|
-
handler = _DetailsResponseDisplayHandler(mode="open")
|
|
322
|
-
|
|
323
|
-
multiline_answer = """Line 1
|
|
324
|
-
Line 2
|
|
325
|
-
Line 3 with spaces
|
|
326
|
-
|
|
327
|
-
Line 5 after blank line"""
|
|
328
|
-
|
|
329
|
-
result = handler.build_response_display(
|
|
330
|
-
display_name="Multi-line Test",
|
|
331
|
-
assistant_id="test_ml",
|
|
332
|
-
answer=multiline_answer,
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
assert multiline_answer in result
|
|
336
|
-
|
|
337
|
-
# Test removal
|
|
338
|
-
text_with_display = f"Before\n{result}\nAfter"
|
|
339
|
-
clean_result = handler.remove_response_display(
|
|
340
|
-
assistant_id="test_ml", text=text_with_display
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
assert "Before" in clean_result
|
|
344
|
-
assert "After" in clean_result
|
|
345
|
-
assert multiline_answer not in clean_result
|
|
346
|
-
|
|
347
|
-
def test_html_content_in_answer(self):
|
|
348
|
-
"""Test handling of HTML content within the answer."""
|
|
349
|
-
handler = _DetailsResponseDisplayHandler(mode="open")
|
|
350
|
-
|
|
351
|
-
html_answer = "<p>This is <strong>bold</strong> text with <em>emphasis</em></p>"
|
|
352
|
-
|
|
353
|
-
result = handler.build_response_display(
|
|
354
|
-
display_name="HTML Test", assistant_id="test_html", answer=html_answer
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
assert html_answer in result
|
|
358
|
-
|
|
359
|
-
# Test removal
|
|
360
|
-
text_with_display = f"Before\n{result}\nAfter"
|
|
361
|
-
clean_result = handler.remove_response_display(
|
|
362
|
-
assistant_id="test_html", text=text_with_display
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
assert "Before" in clean_result
|
|
366
|
-
assert "After" in clean_result
|
|
367
|
-
assert html_answer not in clean_result
|
|
368
|
-
|
|
369
|
-
def test_unicode_content(self):
|
|
370
|
-
"""Test handling of Unicode content."""
|
|
371
|
-
handler = _DetailsResponseDisplayHandler(mode="open")
|
|
372
|
-
|
|
373
|
-
unicode_content = "Testing Unicode: 你好 🌟 café naïve résumé"
|
|
374
|
-
|
|
375
|
-
result = handler.build_response_display(
|
|
376
|
-
display_name="Unicode Test",
|
|
377
|
-
assistant_id="test_unicode",
|
|
378
|
-
answer=unicode_content,
|
|
379
|
-
)
|
|
380
|
-
|
|
381
|
-
assert unicode_content in result
|
|
382
|
-
|
|
383
|
-
# Test removal
|
|
384
|
-
text_with_display = f"Before\n{result}\nAfter"
|
|
385
|
-
clean_result = handler.remove_response_display(
|
|
386
|
-
assistant_id="test_unicode", text=text_with_display
|
|
387
|
-
)
|
|
388
|
-
|
|
389
|
-
assert "Before" in clean_result
|
|
390
|
-
assert "After" in clean_result
|
|
391
|
-
assert unicode_content not in clean_result
|
unique_toolkit/agentic/tools/a2a/postprocessing/test/test_postprocessor_reference_functions.py
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from unique_toolkit.agentic.tools.a2a.postprocessing._utils import (
|
|
4
|
-
_replace_references_in_text,
|
|
5
|
-
_replace_references_in_text_non_overlapping,
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class TestReplaceReferencesInTextNonOverlapping:
|
|
10
|
-
"""Test cases for _replace_references_in_text_non_overlapping function."""
|
|
11
|
-
|
|
12
|
-
def test_single_reference_replacement(self):
|
|
13
|
-
"""Test replacing a single reference."""
|
|
14
|
-
text = "This is a test<sup>1</sup> with one reference."
|
|
15
|
-
ref_map = {1: 5}
|
|
16
|
-
result = _replace_references_in_text_non_overlapping(text, ref_map)
|
|
17
|
-
expected = "This is a test<sup>5</sup> with one reference."
|
|
18
|
-
assert result == expected
|
|
19
|
-
|
|
20
|
-
def test_multiple_reference_replacements(self):
|
|
21
|
-
"""Test replacing multiple references."""
|
|
22
|
-
text = "First<sup>1</sup> and second<sup>2</sup> and third<sup>3</sup>."
|
|
23
|
-
ref_map = {1: 10, 2: 20, 3: 30}
|
|
24
|
-
result = _replace_references_in_text_non_overlapping(text, ref_map)
|
|
25
|
-
expected = "First<sup>10</sup> and second<sup>20</sup> and third<sup>30</sup>."
|
|
26
|
-
assert result == expected
|
|
27
|
-
|
|
28
|
-
def test_no_references_in_text(self):
|
|
29
|
-
"""Test with text that has no references."""
|
|
30
|
-
text = "This text has no references at all."
|
|
31
|
-
ref_map = {1: 5, 2: 10}
|
|
32
|
-
result = _replace_references_in_text_non_overlapping(text, ref_map)
|
|
33
|
-
assert result == text
|
|
34
|
-
|
|
35
|
-
@pytest.mark.parametrize(
|
|
36
|
-
"text,ref_map,expected",
|
|
37
|
-
[
|
|
38
|
-
(
|
|
39
|
-
"This text has<sup>1</sup> references but empty map.",
|
|
40
|
-
{},
|
|
41
|
-
"This text has<sup>1</sup> references but empty map.",
|
|
42
|
-
),
|
|
43
|
-
("", {1: 5}, ""),
|
|
44
|
-
],
|
|
45
|
-
)
|
|
46
|
-
def test_empty_inputs(self, text, ref_map, expected):
|
|
47
|
-
"""Test with empty reference map or empty text."""
|
|
48
|
-
result = _replace_references_in_text_non_overlapping(text, ref_map)
|
|
49
|
-
assert result == expected
|
|
50
|
-
|
|
51
|
-
def test_reference_not_in_map(self):
|
|
52
|
-
"""Test with references in text that are not in the map."""
|
|
53
|
-
text = "Reference<sup>1</sup> and<sup>2</sup> and<sup>3</sup>."
|
|
54
|
-
ref_map = {1: 10} # Only maps reference 1
|
|
55
|
-
result = _replace_references_in_text_non_overlapping(text, ref_map)
|
|
56
|
-
expected = "Reference<sup>10</sup> and<sup>2</sup> and<sup>3</sup>."
|
|
57
|
-
assert result == expected
|
|
58
|
-
|
|
59
|
-
def test_duplicate_references_in_text(self):
|
|
60
|
-
"""Test with duplicate references in text."""
|
|
61
|
-
text = "First<sup>1</sup> and second<sup>1</sup> occurrence."
|
|
62
|
-
ref_map = {1: 99}
|
|
63
|
-
result = _replace_references_in_text_non_overlapping(text, ref_map)
|
|
64
|
-
expected = "First<sup>99</sup> and second<sup>99</sup> occurrence."
|
|
65
|
-
assert result == expected
|
|
66
|
-
|
|
67
|
-
def test_adjacent_references(self):
|
|
68
|
-
"""Test with adjacent references."""
|
|
69
|
-
text = "Adjacent<sup>1</sup><sup>2</sup> references."
|
|
70
|
-
ref_map = {1: 10, 2: 20}
|
|
71
|
-
result = _replace_references_in_text_non_overlapping(text, ref_map)
|
|
72
|
-
expected = "Adjacent<sup>10</sup><sup>20</sup> references."
|
|
73
|
-
assert result == expected
|
|
74
|
-
|
|
75
|
-
def test_references_with_multi_digit_numbers(self):
|
|
76
|
-
"""Test with multi-digit reference numbers."""
|
|
77
|
-
text = "Reference<sup>123</sup> and<sup>456</sup>."
|
|
78
|
-
ref_map = {123: 789, 456: 101112}
|
|
79
|
-
result = _replace_references_in_text_non_overlapping(text, ref_map)
|
|
80
|
-
expected = "Reference<sup>789</sup> and<sup>101112</sup>."
|
|
81
|
-
assert result == expected
|
|
82
|
-
|
|
83
|
-
def test_references_at_text_boundaries(self):
|
|
84
|
-
"""Test with references at the beginning and end of text."""
|
|
85
|
-
text = "<sup>1</sup>Start and end<sup>2</sup>"
|
|
86
|
-
ref_map = {1: 100, 2: 200}
|
|
87
|
-
result = _replace_references_in_text_non_overlapping(text, ref_map)
|
|
88
|
-
expected = "<sup>100</sup>Start and end<sup>200</sup>"
|
|
89
|
-
assert result == expected
|
|
90
|
-
|
|
91
|
-
def test_malformed_references_ignored(self):
|
|
92
|
-
"""Test that malformed references are ignored."""
|
|
93
|
-
text = "Good<sup>1</sup> and bad<sup>abc</sup> and<sup></sup>."
|
|
94
|
-
ref_map = {1: 10}
|
|
95
|
-
result = _replace_references_in_text_non_overlapping(text, ref_map)
|
|
96
|
-
expected = "Good<sup>10</sup> and bad<sup>abc</sup> and<sup></sup>."
|
|
97
|
-
assert result == expected
|
|
98
|
-
|
|
99
|
-
@pytest.mark.parametrize(
|
|
100
|
-
"text,ref_map,expected",
|
|
101
|
-
[
|
|
102
|
-
(
|
|
103
|
-
"Zero reference<sup>0</sup> here.",
|
|
104
|
-
{0: 100},
|
|
105
|
-
"Zero reference<sup>100</sup> here.",
|
|
106
|
-
),
|
|
107
|
-
(
|
|
108
|
-
"Negative<sup>-1</sup> reference.",
|
|
109
|
-
{-1: 5},
|
|
110
|
-
"Negative<sup>5</sup> reference.",
|
|
111
|
-
),
|
|
112
|
-
],
|
|
113
|
-
)
|
|
114
|
-
def test_special_reference_numbers(self, text, ref_map, expected):
|
|
115
|
-
"""Test with zero and negative reference numbers."""
|
|
116
|
-
result = _replace_references_in_text_non_overlapping(text, ref_map)
|
|
117
|
-
assert result == expected
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
class TestReplaceReferencesInText:
|
|
121
|
-
"""Test cases for _replace_references_in_text function."""
|
|
122
|
-
|
|
123
|
-
def test_non_overlapping_simple_case(self):
|
|
124
|
-
"""Test simple non-overlapping case."""
|
|
125
|
-
text = "Reference<sup>1</sup> and<sup>2</sup>."
|
|
126
|
-
ref_map = {1: 10, 2: 20}
|
|
127
|
-
result = _replace_references_in_text(text, ref_map)
|
|
128
|
-
expected = "Reference<sup>10</sup> and<sup>20</sup>."
|
|
129
|
-
assert result == expected
|
|
130
|
-
|
|
131
|
-
def test_overlapping_references_case1(self):
|
|
132
|
-
"""Test overlapping case where new reference numbers conflict with existing ones."""
|
|
133
|
-
text = "First<sup>1</sup> and second<sup>2</sup>."
|
|
134
|
-
ref_map = {1: 2, 2: 1} # Swap references
|
|
135
|
-
result = _replace_references_in_text(text, ref_map)
|
|
136
|
-
expected = "First<sup>2</sup> and second<sup>1</sup>."
|
|
137
|
-
assert result == expected
|
|
138
|
-
|
|
139
|
-
def test_overlapping_references_case2(self):
|
|
140
|
-
"""Test overlapping case with chain of replacements."""
|
|
141
|
-
text = "Refs<sup>1</sup><sup>2</sup><sup>3</sup>."
|
|
142
|
-
ref_map = {1: 2, 2: 3, 3: 1} # Circular replacement
|
|
143
|
-
result = _replace_references_in_text(text, ref_map)
|
|
144
|
-
expected = "Refs<sup>2</sup><sup>3</sup><sup>1</sup>."
|
|
145
|
-
assert result == expected
|
|
146
|
-
|
|
147
|
-
def test_overlapping_with_higher_numbers(self):
|
|
148
|
-
"""Test overlapping where replacement numbers are higher than originals."""
|
|
149
|
-
text = "Test<sup>1</sup> and<sup>2</sup>."
|
|
150
|
-
ref_map = {1: 3, 2: 1} # 2 -> 1, but 1 -> 3
|
|
151
|
-
result = _replace_references_in_text(text, ref_map)
|
|
152
|
-
expected = "Test<sup>3</sup> and<sup>1</sup>."
|
|
153
|
-
assert result == expected
|
|
154
|
-
|
|
155
|
-
def test_complex_overlapping_scenario(self):
|
|
156
|
-
"""Test complex overlapping scenario with multiple conflicts."""
|
|
157
|
-
text = "A<sup>1</sup>B<sup>2</sup>C<sup>3</sup>D<sup>4</sup>."
|
|
158
|
-
ref_map = {1: 4, 2: 1, 3: 2, 4: 3} # Full rotation
|
|
159
|
-
result = _replace_references_in_text(text, ref_map)
|
|
160
|
-
expected = "A<sup>4</sup>B<sup>1</sup>C<sup>2</sup>D<sup>3</sup>."
|
|
161
|
-
assert result == expected
|
|
162
|
-
|
|
163
|
-
@pytest.mark.parametrize(
|
|
164
|
-
"text,ref_map,expected",
|
|
165
|
-
[
|
|
166
|
-
(
|
|
167
|
-
"Text with<sup>1</sup> references.",
|
|
168
|
-
{},
|
|
169
|
-
"Text with<sup>1</sup> references.",
|
|
170
|
-
),
|
|
171
|
-
("", {1: 2}, ""),
|
|
172
|
-
(
|
|
173
|
-
"This text has no references.",
|
|
174
|
-
{1: 10, 2: 20},
|
|
175
|
-
"This text has no references.",
|
|
176
|
-
),
|
|
177
|
-
],
|
|
178
|
-
)
|
|
179
|
-
def test_edge_cases(self, text, ref_map, expected):
|
|
180
|
-
"""Test edge cases: empty reference map, empty text, and text with no references."""
|
|
181
|
-
result = _replace_references_in_text(text, ref_map)
|
|
182
|
-
assert result == expected
|
|
183
|
-
|
|
184
|
-
def test_single_reference_no_overlap(self):
|
|
185
|
-
"""Test single reference with no overlap potential."""
|
|
186
|
-
text = "Single<sup>5</sup> reference."
|
|
187
|
-
ref_map = {5: 100}
|
|
188
|
-
result = _replace_references_in_text(text, ref_map)
|
|
189
|
-
expected = "Single<sup>100</sup> reference."
|
|
190
|
-
assert result == expected
|
|
191
|
-
|
|
192
|
-
def test_partial_overlap(self):
|
|
193
|
-
"""Test case where only some references have overlapping numbers."""
|
|
194
|
-
text = "Mix<sup>1</sup><sup>2</sup><sup>10</sup>."
|
|
195
|
-
ref_map = {1: 2, 2: 20, 10: 100} # Only 1->2 creates potential overlap
|
|
196
|
-
result = _replace_references_in_text(text, ref_map)
|
|
197
|
-
expected = "Mix<sup>2</sup><sup>20</sup><sup>100</sup>."
|
|
198
|
-
assert result == expected
|
|
199
|
-
|
|
200
|
-
def test_self_mapping(self):
|
|
201
|
-
"""Test case where a reference maps to itself."""
|
|
202
|
-
text = "Self<sup>1</sup> and other<sup>2</sup>."
|
|
203
|
-
ref_map = {1: 1, 2: 10} # 1 maps to itself
|
|
204
|
-
result = _replace_references_in_text(text, ref_map)
|
|
205
|
-
expected = "Self<sup>1</sup> and other<sup>10</sup>."
|
|
206
|
-
assert result == expected
|
|
207
|
-
|
|
208
|
-
def test_duplicate_references_with_overlap(self):
|
|
209
|
-
"""Test duplicate references in text with overlapping mappings."""
|
|
210
|
-
text = "Dup<sup>1</sup> and dup<sup>1</sup> and<sup>2</sup>."
|
|
211
|
-
ref_map = {1: 2, 2: 1} # Swap
|
|
212
|
-
result = _replace_references_in_text(text, ref_map)
|
|
213
|
-
expected = "Dup<sup>2</sup> and dup<sup>2</sup> and<sup>1</sup>."
|
|
214
|
-
assert result == expected
|
|
215
|
-
|
|
216
|
-
def test_large_reference_numbers(self):
|
|
217
|
-
"""Test with large reference numbers."""
|
|
218
|
-
text = "Large<sup>999</sup> and<sup>1000</sup>."
|
|
219
|
-
ref_map = {999: 1000, 1000: 999} # Swap large numbers
|
|
220
|
-
result = _replace_references_in_text(text, ref_map)
|
|
221
|
-
expected = "Large<sup>1000</sup> and<sup>999</sup>."
|
|
222
|
-
assert result == expected
|
|
223
|
-
|
|
224
|
-
def test_zero_and_negative_with_overlap(self):
|
|
225
|
-
"""Test zero and negative numbers with potential overlap."""
|
|
226
|
-
text = "Zero<sup>0</sup> and neg<sup>-1</sup> and pos<sup>1</sup>."
|
|
227
|
-
ref_map = {0: 1, -1: 0, 1: -1} # Circular with zero and negative
|
|
228
|
-
result = _replace_references_in_text(text, ref_map)
|
|
229
|
-
expected = "Zero<sup>1</sup> and neg<sup>0</sup> and pos<sup>-1</sup>."
|
|
230
|
-
assert result == expected
|
|
231
|
-
|
|
232
|
-
def test_max_ref_calculation_edge_case(self):
|
|
233
|
-
"""Test edge case for max_ref calculation with empty map."""
|
|
234
|
-
text = "Some<sup>1</sup> text."
|
|
235
|
-
ref_map = {}
|
|
236
|
-
result = _replace_references_in_text(text, ref_map)
|
|
237
|
-
assert result == text
|
|
238
|
-
|
|
239
|
-
def test_phase_separation_correctness(self):
|
|
240
|
-
"""Test that the two-phase approach correctly handles complex overlaps."""
|
|
241
|
-
# This test ensures the intermediate unique references don't interfere
|
|
242
|
-
text = "Test<sup>1</sup><sup>2</sup><sup>3</sup><sup>4</sup><sup>5</sup>."
|
|
243
|
-
ref_map = {1: 5, 2: 4, 3: 3, 4: 2, 5: 1} # Reverse order
|
|
244
|
-
result = _replace_references_in_text(text, ref_map)
|
|
245
|
-
expected = "Test<sup>5</sup><sup>4</sup><sup>3</sup><sup>2</sup><sup>1</sup>."
|
|
246
|
-
assert result == expected
|
|
247
|
-
|
|
248
|
-
def test_intermediate_collision_avoidance(self):
|
|
249
|
-
"""Test that intermediate unique references don't collide with existing text."""
|
|
250
|
-
# Create a scenario where intermediate refs might collide
|
|
251
|
-
text = "Refs<sup>1</sup><sup>2</sup> and existing<sup>6</sup><sup>7</sup>."
|
|
252
|
-
ref_map = {1: 2, 2: 1} # Simple swap
|
|
253
|
-
result = _replace_references_in_text(text, ref_map)
|
|
254
|
-
expected = "Refs<sup>2</sup><sup>1</sup> and existing<sup>6</sup><sup>7</sup>."
|
|
255
|
-
assert result == expected
|
|
256
|
-
# The existing <sup>6</sup> and <sup>7</sup> should remain unchanged
|
|
File without changes
|
|
File without changes
|