unique_toolkit 0.7.7__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.

Files changed (166) hide show
  1. unique_toolkit/__init__.py +28 -1
  2. unique_toolkit/_common/api_calling/human_verification_manager.py +343 -0
  3. unique_toolkit/_common/base_model_type_attribute.py +303 -0
  4. unique_toolkit/_common/chunk_relevancy_sorter/config.py +49 -0
  5. unique_toolkit/_common/chunk_relevancy_sorter/exception.py +5 -0
  6. unique_toolkit/_common/chunk_relevancy_sorter/schemas.py +46 -0
  7. unique_toolkit/_common/chunk_relevancy_sorter/service.py +374 -0
  8. unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +275 -0
  9. unique_toolkit/_common/default_language_model.py +12 -0
  10. unique_toolkit/_common/docx_generator/__init__.py +7 -0
  11. unique_toolkit/_common/docx_generator/config.py +12 -0
  12. unique_toolkit/_common/docx_generator/schemas.py +80 -0
  13. unique_toolkit/_common/docx_generator/service.py +252 -0
  14. unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
  15. unique_toolkit/_common/endpoint_builder.py +305 -0
  16. unique_toolkit/_common/endpoint_requestor.py +430 -0
  17. unique_toolkit/_common/exception.py +24 -0
  18. unique_toolkit/_common/feature_flags/schema.py +9 -0
  19. unique_toolkit/_common/pydantic/rjsf_tags.py +936 -0
  20. unique_toolkit/_common/pydantic_helpers.py +154 -0
  21. unique_toolkit/_common/referencing.py +53 -0
  22. unique_toolkit/_common/string_utilities.py +140 -0
  23. unique_toolkit/_common/tests/test_referencing.py +521 -0
  24. unique_toolkit/_common/tests/test_string_utilities.py +506 -0
  25. unique_toolkit/_common/token/image_token_counting.py +67 -0
  26. unique_toolkit/_common/token/token_counting.py +204 -0
  27. unique_toolkit/_common/utils/__init__.py +1 -0
  28. unique_toolkit/_common/utils/files.py +43 -0
  29. unique_toolkit/_common/utils/structured_output/__init__.py +1 -0
  30. unique_toolkit/_common/utils/structured_output/schema.py +5 -0
  31. unique_toolkit/_common/utils/write_configuration.py +51 -0
  32. unique_toolkit/_common/validators.py +101 -4
  33. unique_toolkit/agentic/__init__.py +1 -0
  34. unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +28 -0
  35. unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +278 -0
  36. unique_toolkit/agentic/evaluation/config.py +36 -0
  37. unique_toolkit/{evaluators → agentic/evaluation}/context_relevancy/prompts.py +25 -0
  38. unique_toolkit/agentic/evaluation/context_relevancy/schema.py +80 -0
  39. unique_toolkit/agentic/evaluation/context_relevancy/service.py +273 -0
  40. unique_toolkit/agentic/evaluation/evaluation_manager.py +218 -0
  41. unique_toolkit/agentic/evaluation/hallucination/constants.py +61 -0
  42. unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +111 -0
  43. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/prompts.py +1 -1
  44. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/service.py +16 -15
  45. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/utils.py +30 -20
  46. unique_toolkit/{evaluators → agentic/evaluation}/output_parser.py +20 -2
  47. unique_toolkit/{evaluators → agentic/evaluation}/schemas.py +27 -7
  48. unique_toolkit/agentic/evaluation/tests/test_context_relevancy_service.py +253 -0
  49. unique_toolkit/agentic/evaluation/tests/test_output_parser.py +87 -0
  50. unique_toolkit/agentic/history_manager/history_construction_with_contents.py +297 -0
  51. unique_toolkit/agentic/history_manager/history_manager.py +242 -0
  52. unique_toolkit/agentic/history_manager/loop_token_reducer.py +484 -0
  53. unique_toolkit/agentic/history_manager/utils.py +96 -0
  54. unique_toolkit/agentic/postprocessor/postprocessor_manager.py +212 -0
  55. unique_toolkit/agentic/reference_manager/reference_manager.py +103 -0
  56. unique_toolkit/agentic/responses_api/__init__.py +19 -0
  57. unique_toolkit/agentic/responses_api/postprocessors/code_display.py +63 -0
  58. unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +145 -0
  59. unique_toolkit/agentic/responses_api/stream_handler.py +15 -0
  60. unique_toolkit/agentic/short_term_memory_manager/persistent_short_term_memory_manager.py +141 -0
  61. unique_toolkit/agentic/thinking_manager/thinking_manager.py +103 -0
  62. unique_toolkit/agentic/tools/__init__.py +1 -0
  63. unique_toolkit/agentic/tools/a2a/__init__.py +36 -0
  64. unique_toolkit/agentic/tools/a2a/config.py +17 -0
  65. unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +15 -0
  66. unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +66 -0
  67. unique_toolkit/agentic/tools/a2a/evaluation/config.py +55 -0
  68. unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +260 -0
  69. unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2 +9 -0
  70. unique_toolkit/agentic/tools/a2a/manager.py +55 -0
  71. unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +21 -0
  72. unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py +185 -0
  73. unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +73 -0
  74. unique_toolkit/agentic/tools/a2a/postprocessing/config.py +45 -0
  75. unique_toolkit/agentic/tools/a2a/postprocessing/display.py +180 -0
  76. unique_toolkit/agentic/tools/a2a/postprocessing/references.py +101 -0
  77. unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +1335 -0
  78. unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py +603 -0
  79. unique_toolkit/agentic/tools/a2a/prompts.py +46 -0
  80. unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py +6 -0
  81. unique_toolkit/agentic/tools/a2a/response_watcher/service.py +91 -0
  82. unique_toolkit/agentic/tools/a2a/tool/__init__.py +4 -0
  83. unique_toolkit/agentic/tools/a2a/tool/_memory.py +26 -0
  84. unique_toolkit/agentic/tools/a2a/tool/_schema.py +9 -0
  85. unique_toolkit/agentic/tools/a2a/tool/config.py +73 -0
  86. unique_toolkit/agentic/tools/a2a/tool/service.py +306 -0
  87. unique_toolkit/agentic/tools/agent_chunks_hanlder.py +65 -0
  88. unique_toolkit/agentic/tools/config.py +167 -0
  89. unique_toolkit/agentic/tools/factory.py +44 -0
  90. unique_toolkit/agentic/tools/mcp/__init__.py +4 -0
  91. unique_toolkit/agentic/tools/mcp/manager.py +71 -0
  92. unique_toolkit/agentic/tools/mcp/models.py +28 -0
  93. unique_toolkit/agentic/tools/mcp/tool_wrapper.py +234 -0
  94. unique_toolkit/agentic/tools/openai_builtin/__init__.py +11 -0
  95. unique_toolkit/agentic/tools/openai_builtin/base.py +30 -0
  96. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +8 -0
  97. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +57 -0
  98. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +230 -0
  99. unique_toolkit/agentic/tools/openai_builtin/manager.py +62 -0
  100. unique_toolkit/agentic/tools/schemas.py +141 -0
  101. unique_toolkit/agentic/tools/test/test_mcp_manager.py +536 -0
  102. unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +445 -0
  103. unique_toolkit/agentic/tools/tool.py +183 -0
  104. unique_toolkit/agentic/tools/tool_manager.py +523 -0
  105. unique_toolkit/agentic/tools/tool_progress_reporter.py +285 -0
  106. unique_toolkit/agentic/tools/utils/__init__.py +19 -0
  107. unique_toolkit/agentic/tools/utils/execution/__init__.py +1 -0
  108. unique_toolkit/agentic/tools/utils/execution/execution.py +286 -0
  109. unique_toolkit/agentic/tools/utils/source_handling/__init__.py +0 -0
  110. unique_toolkit/agentic/tools/utils/source_handling/schema.py +21 -0
  111. unique_toolkit/agentic/tools/utils/source_handling/source_formatting.py +207 -0
  112. unique_toolkit/agentic/tools/utils/source_handling/tests/test_source_formatting.py +216 -0
  113. unique_toolkit/app/__init__.py +6 -0
  114. unique_toolkit/app/dev_util.py +180 -0
  115. unique_toolkit/app/init_sdk.py +32 -1
  116. unique_toolkit/app/schemas.py +198 -31
  117. unique_toolkit/app/unique_settings.py +367 -0
  118. unique_toolkit/chat/__init__.py +8 -1
  119. unique_toolkit/chat/deprecated/service.py +232 -0
  120. unique_toolkit/chat/functions.py +642 -77
  121. unique_toolkit/chat/rendering.py +34 -0
  122. unique_toolkit/chat/responses_api.py +461 -0
  123. unique_toolkit/chat/schemas.py +133 -2
  124. unique_toolkit/chat/service.py +115 -767
  125. unique_toolkit/content/functions.py +153 -4
  126. unique_toolkit/content/schemas.py +122 -15
  127. unique_toolkit/content/service.py +278 -44
  128. unique_toolkit/content/smart_rules.py +301 -0
  129. unique_toolkit/content/utils.py +8 -3
  130. unique_toolkit/embedding/service.py +102 -11
  131. unique_toolkit/framework_utilities/__init__.py +1 -0
  132. unique_toolkit/framework_utilities/langchain/client.py +71 -0
  133. unique_toolkit/framework_utilities/langchain/history.py +19 -0
  134. unique_toolkit/framework_utilities/openai/__init__.py +6 -0
  135. unique_toolkit/framework_utilities/openai/client.py +83 -0
  136. unique_toolkit/framework_utilities/openai/message_builder.py +229 -0
  137. unique_toolkit/framework_utilities/utils.py +23 -0
  138. unique_toolkit/language_model/__init__.py +3 -0
  139. unique_toolkit/language_model/builder.py +27 -11
  140. unique_toolkit/language_model/default_language_model.py +3 -0
  141. unique_toolkit/language_model/functions.py +327 -43
  142. unique_toolkit/language_model/infos.py +992 -50
  143. unique_toolkit/language_model/reference.py +242 -0
  144. unique_toolkit/language_model/schemas.py +475 -48
  145. unique_toolkit/language_model/service.py +228 -27
  146. unique_toolkit/protocols/support.py +145 -0
  147. unique_toolkit/services/__init__.py +7 -0
  148. unique_toolkit/services/chat_service.py +1630 -0
  149. unique_toolkit/services/knowledge_base.py +861 -0
  150. unique_toolkit/short_term_memory/service.py +178 -41
  151. unique_toolkit/smart_rules/__init__.py +0 -0
  152. unique_toolkit/smart_rules/compile.py +56 -0
  153. unique_toolkit/test_utilities/events.py +197 -0
  154. {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/METADATA +606 -7
  155. unique_toolkit-1.23.0.dist-info/RECORD +182 -0
  156. unique_toolkit/evaluators/__init__.py +0 -1
  157. unique_toolkit/evaluators/config.py +0 -35
  158. unique_toolkit/evaluators/constants.py +0 -1
  159. unique_toolkit/evaluators/context_relevancy/constants.py +0 -32
  160. unique_toolkit/evaluators/context_relevancy/service.py +0 -53
  161. unique_toolkit/evaluators/context_relevancy/utils.py +0 -142
  162. unique_toolkit/evaluators/hallucination/constants.py +0 -41
  163. unique_toolkit-0.7.7.dist-info/RECORD +0 -64
  164. /unique_toolkit/{evaluators → agentic/evaluation}/exception.py +0 -0
  165. {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/LICENSE +0 -0
  166. {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,521 @@
1
+ """
2
+ Unit tests for referencing utilities.
3
+ """
4
+
5
+ import pytest
6
+
7
+
8
+ @pytest.mark.ai
9
+ def test_get_reference_pattern__returns_formatted_sup_tag__with_single_digit() -> None:
10
+ """
11
+ Purpose: Verify get_reference_pattern creates correct HTML sup tag format.
12
+ Why this matters: Ensures consistent reference formatting across the system.
13
+ Setup summary: Call with single digit, assert proper HTML sup tag structure.
14
+ """
15
+ # Arrange
16
+ from unique_toolkit._common.referencing import get_reference_pattern
17
+
18
+ ref_number = 1
19
+
20
+ # Act
21
+ result = get_reference_pattern(ref_number)
22
+
23
+ # Assert
24
+ assert result == "<sup>1</sup>"
25
+ assert isinstance(result, str)
26
+
27
+
28
+ @pytest.mark.ai
29
+ def test_get_reference_pattern__returns_formatted_sup_tag__with_multi_digit() -> None:
30
+ """
31
+ Purpose: Verify get_reference_pattern handles multi-digit reference numbers.
32
+ Why this matters: Support for documents with many references (10+).
33
+ Setup summary: Call with multi-digit number, assert proper formatting.
34
+ """
35
+ # Arrange
36
+ from unique_toolkit._common.referencing import get_reference_pattern
37
+
38
+ ref_number = 42
39
+
40
+ # Act
41
+ result = get_reference_pattern(ref_number)
42
+
43
+ # Assert
44
+ assert result == "<sup>42</sup>"
45
+
46
+
47
+ @pytest.mark.ai
48
+ def test_get_all_ref_numbers__returns_sorted_unique_list__with_multiple_refs() -> None:
49
+ """
50
+ Purpose: Verify get_all_ref_numbers extracts and sorts all reference numbers.
51
+ Why this matters: Enables reference validation and manipulation across text.
52
+ Setup summary: Provide text with multiple references, assert sorted unique list.
53
+ """
54
+ # Arrange
55
+ from unique_toolkit._common.referencing import get_all_ref_numbers
56
+
57
+ text = (
58
+ "Some text<sup>3</sup> with <sup>1</sup> multiple<sup>3</sup> refs<sup>2</sup>."
59
+ )
60
+
61
+ # Act
62
+ result = get_all_ref_numbers(text)
63
+
64
+ # Assert
65
+ assert result == [1, 2, 3]
66
+ assert isinstance(result, list)
67
+
68
+
69
+ @pytest.mark.ai
70
+ def test_get_all_ref_numbers__returns_empty_list__with_no_refs() -> None:
71
+ """
72
+ Purpose: Verify get_all_ref_numbers handles text without references.
73
+ Why this matters: Prevents errors when processing unreferenced text.
74
+ Setup summary: Provide text without references, assert empty list.
75
+ """
76
+ # Arrange
77
+ from unique_toolkit._common.referencing import get_all_ref_numbers
78
+
79
+ text = "Some text without any references."
80
+
81
+ # Act
82
+ result = get_all_ref_numbers(text)
83
+
84
+ # Assert
85
+ assert result == []
86
+
87
+
88
+ @pytest.mark.ai
89
+ def test_get_all_ref_numbers__handles_whitespace__in_sup_tags() -> None:
90
+ """
91
+ Purpose: Verify get_all_ref_numbers tolerates whitespace in sup tags.
92
+ Why this matters: Handles malformed or pretty-printed HTML gracefully.
93
+ Setup summary: Provide references with internal whitespace, assert correct extraction.
94
+ """
95
+ # Arrange
96
+ from unique_toolkit._common.referencing import get_all_ref_numbers
97
+
98
+ text = "Text<sup> 1 </sup> with <sup> 2 </sup> spaced refs."
99
+
100
+ # Act
101
+ result = get_all_ref_numbers(text)
102
+
103
+ # Assert
104
+ assert result == [1, 2]
105
+
106
+
107
+ @pytest.mark.ai
108
+ def test_get_max_ref_number__returns_highest_number__with_multiple_refs() -> None:
109
+ """
110
+ Purpose: Verify get_max_ref_number identifies the maximum reference number.
111
+ Why this matters: Essential for generating new reference numbers sequentially.
112
+ Setup summary: Provide text with multiple references, assert max value returned.
113
+ """
114
+ # Arrange
115
+ from unique_toolkit._common.referencing import get_max_ref_number
116
+
117
+ text = "Text<sup>5</sup> with <sup>10</sup> multiple<sup>2</sup> refs."
118
+
119
+ # Act
120
+ result = get_max_ref_number(text)
121
+
122
+ # Assert
123
+ assert result == 10
124
+
125
+
126
+ @pytest.mark.ai
127
+ def test_get_max_ref_number__returns_none__with_no_refs() -> None:
128
+ """
129
+ Purpose: Verify get_max_ref_number returns None for text without references.
130
+ Why this matters: Enables proper handling of unreferenced text.
131
+ Setup summary: Provide text without references, assert None returned.
132
+ """
133
+ # Arrange
134
+ from unique_toolkit._common.referencing import get_max_ref_number
135
+
136
+ text = "Text without any references."
137
+
138
+ # Act
139
+ result = get_max_ref_number(text)
140
+
141
+ # Assert
142
+ assert result is None
143
+
144
+
145
+ @pytest.mark.ai
146
+ def test_replace_ref_number__replaces_with_int__single_occurrence() -> None:
147
+ """
148
+ Purpose: Verify replace_ref_number replaces reference with new integer.
149
+ Why this matters: Enables reference renumbering during text processing.
150
+ Setup summary: Replace one reference with integer, assert proper substitution.
151
+ """
152
+ # Arrange
153
+ from unique_toolkit._common.referencing import replace_ref_number
154
+
155
+ text = "Some text<sup>1</sup> here."
156
+
157
+ # Act
158
+ result = replace_ref_number(text, ref_number=1, replacement=5)
159
+
160
+ # Assert
161
+ assert result == "Some text<sup>5</sup> here."
162
+
163
+
164
+ @pytest.mark.ai
165
+ def test_replace_ref_number__replaces_with_string__single_occurrence() -> None:
166
+ """
167
+ Purpose: Verify replace_ref_number can replace with arbitrary string.
168
+ Why this matters: Allows flexible reference transformation beyond renumbering.
169
+ Setup summary: Replace reference with string, assert exact substitution.
170
+ """
171
+ # Arrange
172
+ from unique_toolkit._common.referencing import replace_ref_number
173
+
174
+ text = "Some text<sup>1</sup> here."
175
+
176
+ # Act
177
+ result = replace_ref_number(text, ref_number=1, replacement="[REF]")
178
+
179
+ # Assert
180
+ assert result == "Some text[REF] here."
181
+
182
+
183
+ @pytest.mark.ai
184
+ def test_replace_ref_number__replaces_all_occurrences__of_same_number() -> None:
185
+ """
186
+ Purpose: Verify replace_ref_number replaces all instances of target reference.
187
+ Why this matters: Ensures consistent reference updates throughout text.
188
+ Setup summary: Provide text with duplicate reference, assert all replaced.
189
+ """
190
+ # Arrange
191
+ from unique_toolkit._common.referencing import replace_ref_number
192
+
193
+ text = "Text<sup>2</sup> with <sup>2</sup> duplicate refs<sup>2</sup>."
194
+
195
+ # Act
196
+ result = replace_ref_number(text, ref_number=2, replacement=9)
197
+
198
+ # Assert
199
+ assert result == "Text<sup>9</sup> with <sup>9</sup> duplicate refs<sup>9</sup>."
200
+
201
+
202
+ @pytest.mark.ai
203
+ def test_replace_ref_number__preserves_other_refs__when_replacing() -> None:
204
+ """
205
+ Purpose: Verify replace_ref_number only modifies target reference number.
206
+ Why this matters: Prevents unintended side effects on other references.
207
+ Setup summary: Replace one reference among many, assert others unchanged.
208
+ """
209
+ # Arrange
210
+ from unique_toolkit._common.referencing import replace_ref_number
211
+
212
+ text = "Text<sup>1</sup> with <sup>2</sup> and <sup>3</sup>."
213
+
214
+ # Act
215
+ result = replace_ref_number(text, ref_number=2, replacement=7)
216
+
217
+ # Assert
218
+ assert result == "Text<sup>1</sup> with <sup>7</sup> and <sup>3</sup>."
219
+
220
+
221
+ @pytest.mark.ai
222
+ def test_replace_ref_number__handles_whitespace__in_target_ref() -> None:
223
+ """
224
+ Purpose: Verify replace_ref_number matches references with internal whitespace.
225
+ Why this matters: Ensures robust matching of malformed HTML.
226
+ Setup summary: Replace reference with whitespace, assert successful replacement.
227
+ """
228
+ # Arrange
229
+ from unique_toolkit._common.referencing import replace_ref_number
230
+
231
+ text = "Text<sup> 3 </sup> with spacing."
232
+
233
+ # Act
234
+ result = replace_ref_number(text, ref_number=3, replacement=5)
235
+
236
+ # Assert
237
+ assert result == "Text<sup>5</sup> with spacing."
238
+
239
+
240
+ @pytest.mark.ai
241
+ def test_remove_ref_number__removes_single_ref__completely() -> None:
242
+ """
243
+ Purpose: Verify remove_ref_number deletes target reference from text.
244
+ Why this matters: Enables reference cleanup during text processing.
245
+ Setup summary: Remove one reference, assert it's deleted without trace.
246
+ """
247
+ # Arrange
248
+ from unique_toolkit._common.referencing import remove_ref_number
249
+
250
+ text = "Some text<sup>1</sup> here."
251
+
252
+ # Act
253
+ result = remove_ref_number(text, ref_number=1)
254
+
255
+ # Assert
256
+ assert result == "Some text here."
257
+
258
+
259
+ @pytest.mark.ai
260
+ def test_remove_ref_number__removes_all_occurrences__of_same_number() -> None:
261
+ """
262
+ Purpose: Verify remove_ref_number removes all instances of target reference.
263
+ Why this matters: Ensures complete cleanup of duplicate references.
264
+ Setup summary: Remove reference appearing multiple times, assert all removed.
265
+ """
266
+ # Arrange
267
+ from unique_toolkit._common.referencing import remove_ref_number
268
+
269
+ text = "Text<sup>2</sup> with <sup>2</sup> duplicate<sup>2</sup>."
270
+
271
+ # Act
272
+ result = remove_ref_number(text, ref_number=2)
273
+
274
+ # Assert
275
+ assert result == "Text with duplicate."
276
+
277
+
278
+ @pytest.mark.ai
279
+ def test_remove_ref_number__preserves_other_refs__when_removing() -> None:
280
+ """
281
+ Purpose: Verify remove_ref_number only deletes target reference number.
282
+ Why this matters: Prevents accidental deletion of other references.
283
+ Setup summary: Remove one reference among many, assert others remain.
284
+ """
285
+ # Arrange
286
+ from unique_toolkit._common.referencing import remove_ref_number
287
+
288
+ text = "Text<sup>1</sup> with <sup>2</sup> and <sup>3</sup>."
289
+
290
+ # Act
291
+ result = remove_ref_number(text, ref_number=2)
292
+
293
+ # Assert
294
+ assert result == "Text<sup>1</sup> with and <sup>3</sup>."
295
+
296
+
297
+ @pytest.mark.ai
298
+ def test_remove_all_refs__removes_all_references__from_text() -> None:
299
+ """
300
+ Purpose: Verify remove_all_refs strips all sup tags from text.
301
+ Why this matters: Enables generation of clean, unreferenced text copies.
302
+ Setup summary: Provide text with multiple references, assert all removed.
303
+ """
304
+ # Arrange
305
+ from unique_toolkit._common.referencing import remove_all_refs
306
+
307
+ text = "Text<sup>1</sup> with <sup>2</sup> multiple<sup>3</sup> refs."
308
+
309
+ # Act
310
+ result = remove_all_refs(text)
311
+
312
+ # Assert
313
+ assert result == "Text with multiple refs."
314
+
315
+
316
+ @pytest.mark.ai
317
+ def test_remove_all_refs__returns_unchanged__with_no_refs() -> None:
318
+ """
319
+ Purpose: Verify remove_all_refs handles unreferenced text correctly.
320
+ Why this matters: Ensures function is safe to call on any text.
321
+ Setup summary: Provide text without references, assert unchanged.
322
+ """
323
+ # Arrange
324
+ from unique_toolkit._common.referencing import remove_all_refs
325
+
326
+ text = "Plain text without references."
327
+
328
+ # Act
329
+ result = remove_all_refs(text)
330
+
331
+ # Assert
332
+ assert result == "Plain text without references."
333
+
334
+
335
+ @pytest.mark.ai
336
+ def test_remove_all_refs__handles_whitespace__in_all_refs() -> None:
337
+ """
338
+ Purpose: Verify remove_all_refs handles references with internal whitespace.
339
+ Why this matters: Ensures robust cleanup of malformed HTML.
340
+ Setup summary: Provide references with spacing, assert all removed.
341
+ """
342
+ # Arrange
343
+ from unique_toolkit._common.referencing import remove_all_refs
344
+
345
+ text = "Text<sup> 1 </sup> with <sup> 2 </sup> spaced."
346
+
347
+ # Act
348
+ result = remove_all_refs(text)
349
+
350
+ # Assert
351
+ assert result == "Text with spaced."
352
+
353
+
354
+ @pytest.mark.ai
355
+ def test_remove_consecutive_ref_space__removes_spaces__between_refs() -> None:
356
+ """
357
+ Purpose: Verify remove_consecutive_ref_space collapses spaces between adjacent refs.
358
+ Why this matters: Improves visual formatting of multiple citations.
359
+ Setup summary: Provide consecutive references with spaces, assert spaces removed.
360
+ """
361
+ # Arrange
362
+ from unique_toolkit._common.referencing import remove_consecutive_ref_space
363
+
364
+ text = "Text<sup>1</sup> <sup>2</sup> with spaces."
365
+
366
+ # Act
367
+ result = remove_consecutive_ref_space(text)
368
+
369
+ # Assert
370
+ assert result == "Text<sup>1</sup><sup>2</sup> with spaces."
371
+
372
+
373
+ @pytest.mark.ai
374
+ def test_remove_consecutive_ref_space__handles_multiple_spaces__between_refs() -> None:
375
+ """
376
+ Purpose: Verify remove_consecutive_ref_space handles multiple spaces.
377
+ Why this matters: Handles various whitespace formatting scenarios.
378
+ Setup summary: Provide references with multiple spaces, assert collapsed.
379
+ """
380
+ # Arrange
381
+ from unique_toolkit._common.referencing import remove_consecutive_ref_space
382
+
383
+ text = "Text<sup>1</sup> <sup>2</sup> and <sup>3</sup> <sup>4</sup>."
384
+
385
+ # Act
386
+ result = remove_consecutive_ref_space(text)
387
+
388
+ # Assert
389
+ assert result == "Text<sup>1</sup><sup>2</sup> and <sup>3</sup><sup>4</sup>."
390
+
391
+
392
+ @pytest.mark.ai
393
+ def test_remove_consecutive_ref_space__preserves_other_spaces__in_text() -> None:
394
+ """
395
+ Purpose: Verify remove_consecutive_ref_space only affects inter-reference spaces.
396
+ Why this matters: Prevents unwanted text formatting changes.
397
+ Setup summary: Provide text with various spaces, assert only ref spaces removed.
398
+ """
399
+ # Arrange
400
+ from unique_toolkit._common.referencing import remove_consecutive_ref_space
401
+
402
+ text = "Some text<sup>1</sup> <sup>2</sup> has many spaces."
403
+
404
+ # Act
405
+ result = remove_consecutive_ref_space(text)
406
+
407
+ # Assert
408
+ assert result == "Some text<sup>1</sup><sup>2</sup> has many spaces."
409
+
410
+
411
+ @pytest.mark.ai
412
+ def test_remove_consecutive_ref_space__returns_unchanged__without_consecutive_refs() -> (
413
+ None
414
+ ):
415
+ """
416
+ Purpose: Verify remove_consecutive_ref_space leaves non-consecutive refs unchanged.
417
+ Why this matters: Ensures function only modifies adjacent references.
418
+ Setup summary: Provide non-adjacent references, assert unchanged.
419
+ """
420
+ # Arrange
421
+ from unique_toolkit._common.referencing import remove_consecutive_ref_space
422
+
423
+ text = "Text<sup>1</sup> words <sup>2</sup> between."
424
+
425
+ # Act
426
+ result = remove_consecutive_ref_space(text)
427
+
428
+ # Assert
429
+ assert result == "Text<sup>1</sup> words <sup>2</sup> between."
430
+
431
+
432
+ @pytest.mark.ai
433
+ @pytest.mark.parametrize(
434
+ "ref_number, expected",
435
+ [
436
+ (0, "<sup>0</sup>"),
437
+ (1, "<sup>1</sup>"),
438
+ (99, "<sup>99</sup>"),
439
+ (999, "<sup>999</sup>"),
440
+ ],
441
+ ids=["zero", "single-digit", "double-digit", "triple-digit"],
442
+ )
443
+ def test_get_reference_pattern__handles_various_numbers(
444
+ ref_number: int, expected: str
445
+ ) -> None:
446
+ """
447
+ Purpose: Verify get_reference_pattern works for various number ranges.
448
+ Why this matters: Ensures robustness across different reference counts.
449
+ Setup summary: Parametrized test with different ref numbers.
450
+ """
451
+ # Arrange
452
+ from unique_toolkit._common.referencing import get_reference_pattern
453
+
454
+ # Act
455
+ result = get_reference_pattern(ref_number)
456
+
457
+ # Assert
458
+ assert result == expected
459
+
460
+
461
+ @pytest.mark.ai
462
+ @pytest.mark.parametrize(
463
+ "text, expected",
464
+ [
465
+ ("No refs here", []),
466
+ ("<sup>1</sup>", [1]),
467
+ ("<sup>5</sup><sup>3</sup><sup>1</sup>", [1, 3, 5]),
468
+ ("<sup>1</sup>text<sup>1</sup>", [1]),
469
+ ("<sup>10</sup><sup>20</sup><sup>5</sup>", [5, 10, 20]),
470
+ ],
471
+ ids=[
472
+ "no-refs",
473
+ "single-ref",
474
+ "multiple-unordered",
475
+ "duplicate-refs",
476
+ "multi-digit",
477
+ ],
478
+ )
479
+ def test_get_all_ref_numbers__various_text_formats(
480
+ text: str, expected: list[int]
481
+ ) -> None:
482
+ """
483
+ Purpose: Table-driven test for reference extraction across formats.
484
+ Why this matters: Ensures consistent behavior across different text patterns.
485
+ Setup summary: Parametrized inputs with expected sorted unique results.
486
+ """
487
+ # Arrange
488
+ from unique_toolkit._common.referencing import get_all_ref_numbers
489
+
490
+ # Act
491
+ result = get_all_ref_numbers(text)
492
+
493
+ # Assert
494
+ assert result == expected
495
+
496
+
497
+ @pytest.mark.ai
498
+ @pytest.mark.parametrize(
499
+ "text, expected",
500
+ [
501
+ ("No refs", None),
502
+ ("<sup>1</sup>", 1),
503
+ ("<sup>100</sup><sup>50</sup><sup>25</sup>", 100),
504
+ ("<sup>5</sup> and <sup>10</sup>", 10),
505
+ ],
506
+ ids=["no-refs-returns-none", "single-ref", "unordered-multi-digit", "simple-multi"],
507
+ )
508
+ def test_get_max_ref_number__various_scenarios(text: str, expected: int | None) -> None:
509
+ """
510
+ Purpose: Table-driven test for max reference number extraction.
511
+ Why this matters: Validates max finding logic across edge cases.
512
+ Setup summary: Parametrized test with various reference patterns.
513
+ """
514
+ # Arrange
515
+ from unique_toolkit._common.referencing import get_max_ref_number
516
+
517
+ # Act
518
+ result = get_max_ref_number(text)
519
+
520
+ # Assert
521
+ assert result == expected