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,7 +1,8 @@
|
|
|
1
1
|
from enum import StrEnum
|
|
2
2
|
from typing import Any, Callable, Generic, Protocol, TypeVar
|
|
3
|
+
from urllib.parse import urljoin, urlparse
|
|
3
4
|
|
|
4
|
-
from pydantic import BaseModel
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
5
6
|
from typing_extensions import ParamSpec
|
|
6
7
|
|
|
7
8
|
from unique_toolkit._common.endpoint_builder import (
|
|
@@ -24,11 +25,30 @@ CombinedParamsType = TypeVar("CombinedParamsType", bound=BaseModel)
|
|
|
24
25
|
ResponseT_co = TypeVar("ResponseT_co", bound=BaseModel, covariant=True)
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
class RequestContext(BaseModel):
|
|
29
|
+
base_url: str
|
|
30
|
+
headers: dict[str, str] = Field(default_factory=dict)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _verify_url(url: str) -> None:
|
|
34
|
+
parse_result = urlparse(url)
|
|
35
|
+
if not (parse_result.netloc and parse_result.scheme):
|
|
36
|
+
raise ValueError("Scheme and netloc are required for url")
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
class EndpointRequestorProtocol(Protocol, Generic[CombinedParamsSpec, ResponseT_co]):
|
|
28
40
|
@classmethod
|
|
29
41
|
def request(
|
|
30
42
|
cls,
|
|
31
|
-
|
|
43
|
+
context: RequestContext,
|
|
44
|
+
*args: CombinedParamsSpec.args,
|
|
45
|
+
**kwargs: CombinedParamsSpec.kwargs,
|
|
46
|
+
) -> ResponseT_co: ...
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
async def request_async(
|
|
50
|
+
cls,
|
|
51
|
+
context: RequestContext,
|
|
32
52
|
*args: CombinedParamsSpec.args,
|
|
33
53
|
**kwargs: CombinedParamsSpec.kwargs,
|
|
34
54
|
) -> ResponseT_co: ...
|
|
@@ -53,7 +73,7 @@ def build_fake_requestor(
|
|
|
53
73
|
@classmethod
|
|
54
74
|
def request(
|
|
55
75
|
cls,
|
|
56
|
-
|
|
76
|
+
context: RequestContext,
|
|
57
77
|
*args: CombinedParamsSpec.args,
|
|
58
78
|
**kwargs: CombinedParamsSpec.kwargs,
|
|
59
79
|
) -> ResponseType:
|
|
@@ -66,7 +86,22 @@ def build_fake_requestor(
|
|
|
66
86
|
f"Invalid parameters passed to combined model {combined_model.__name__}: {e}"
|
|
67
87
|
)
|
|
68
88
|
|
|
69
|
-
return cls._operation.handle_response(
|
|
89
|
+
return cls._operation.handle_response(
|
|
90
|
+
return_value,
|
|
91
|
+
model_validate_options=cls._operation.response_validate_options(),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
async def request_async(
|
|
96
|
+
cls,
|
|
97
|
+
context: RequestContext,
|
|
98
|
+
headers: dict[str, str] | None = None,
|
|
99
|
+
*args: CombinedParamsSpec.args,
|
|
100
|
+
**kwargs: CombinedParamsSpec.kwargs,
|
|
101
|
+
) -> ResponseType:
|
|
102
|
+
raise NotImplementedError(
|
|
103
|
+
"Async request not implemented for fake requestor"
|
|
104
|
+
)
|
|
70
105
|
|
|
71
106
|
return FakeRequestor
|
|
72
107
|
|
|
@@ -91,7 +126,7 @@ def build_request_requestor(
|
|
|
91
126
|
@classmethod
|
|
92
127
|
def request(
|
|
93
128
|
cls,
|
|
94
|
-
|
|
129
|
+
context: RequestContext,
|
|
95
130
|
*args: CombinedParamsSpec.args,
|
|
96
131
|
**kwargs: CombinedParamsSpec.kwargs,
|
|
97
132
|
) -> ResponseType:
|
|
@@ -100,23 +135,204 @@ def build_request_requestor(
|
|
|
100
135
|
combined=kwargs
|
|
101
136
|
)
|
|
102
137
|
|
|
103
|
-
|
|
104
|
-
|
|
138
|
+
path = cls._operation.create_path_from_model(
|
|
139
|
+
path_params, model_dump_options=cls._operation.path_dump_options()
|
|
140
|
+
)
|
|
141
|
+
url = urljoin(context.base_url, path)
|
|
142
|
+
_verify_url(url)
|
|
143
|
+
|
|
144
|
+
payload = cls._operation.create_payload_from_model(
|
|
145
|
+
payload_model, model_dump_options=cls._operation.payload_dump_options()
|
|
146
|
+
)
|
|
105
147
|
|
|
106
148
|
response = requests.request(
|
|
107
149
|
method=cls._operation.request_method(),
|
|
108
150
|
url=url,
|
|
109
|
-
headers=headers,
|
|
151
|
+
headers=context.headers,
|
|
110
152
|
json=payload,
|
|
111
153
|
)
|
|
112
|
-
|
|
154
|
+
|
|
155
|
+
response_json = response.json()
|
|
156
|
+
|
|
157
|
+
return cls._operation.handle_response(
|
|
158
|
+
response_json,
|
|
159
|
+
model_validate_options=cls._operation.response_validate_options(),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
@classmethod
|
|
163
|
+
async def request_async(
|
|
164
|
+
cls,
|
|
165
|
+
base_url: str = "",
|
|
166
|
+
headers: dict[str, str] | None = None,
|
|
167
|
+
*args: CombinedParamsSpec.args,
|
|
168
|
+
**kwargs: CombinedParamsSpec.kwargs,
|
|
169
|
+
) -> ResponseType:
|
|
170
|
+
raise NotImplementedError(
|
|
171
|
+
"Async request not implemented for request requestor"
|
|
172
|
+
)
|
|
113
173
|
|
|
114
174
|
return RequestRequestor
|
|
115
175
|
|
|
116
176
|
|
|
177
|
+
def build_httpx_requestor(
|
|
178
|
+
operation_type: type[
|
|
179
|
+
ApiOperationProtocol[
|
|
180
|
+
PathParamsSpec,
|
|
181
|
+
PathParamsType,
|
|
182
|
+
PayloadParamSpec,
|
|
183
|
+
PayloadType,
|
|
184
|
+
ResponseType,
|
|
185
|
+
]
|
|
186
|
+
],
|
|
187
|
+
combined_model: Callable[CombinedParamsSpec, CombinedParamsType],
|
|
188
|
+
) -> type[EndpointRequestorProtocol[CombinedParamsSpec, ResponseType]]:
|
|
189
|
+
import httpx
|
|
190
|
+
|
|
191
|
+
class HttpxRequestor(EndpointRequestorProtocol):
|
|
192
|
+
_operation = operation_type
|
|
193
|
+
|
|
194
|
+
@classmethod
|
|
195
|
+
def request(
|
|
196
|
+
cls,
|
|
197
|
+
context: RequestContext,
|
|
198
|
+
*args: CombinedParamsSpec.args,
|
|
199
|
+
**kwargs: CombinedParamsSpec.kwargs,
|
|
200
|
+
) -> ResponseType:
|
|
201
|
+
headers = context.headers or {}
|
|
202
|
+
|
|
203
|
+
path_params, payload_model = cls._operation.models_from_combined(
|
|
204
|
+
combined=kwargs
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
path = cls._operation.create_path_from_model(
|
|
208
|
+
path_params, model_dump_options=cls._operation.path_dump_options()
|
|
209
|
+
)
|
|
210
|
+
url = urljoin(context.base_url, path)
|
|
211
|
+
_verify_url(url)
|
|
212
|
+
with httpx.Client() as client:
|
|
213
|
+
response = client.request(
|
|
214
|
+
method=cls._operation.request_method(),
|
|
215
|
+
url=url,
|
|
216
|
+
headers=headers,
|
|
217
|
+
json=cls._operation.create_payload_from_model(
|
|
218
|
+
payload_model,
|
|
219
|
+
model_dump_options=cls._operation.payload_dump_options(),
|
|
220
|
+
),
|
|
221
|
+
)
|
|
222
|
+
response_json = response.json()
|
|
223
|
+
return cls._operation.handle_response(
|
|
224
|
+
response_json,
|
|
225
|
+
model_validate_options=cls._operation.response_validate_options(),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
@classmethod
|
|
229
|
+
async def request_async(
|
|
230
|
+
cls,
|
|
231
|
+
context: RequestContext,
|
|
232
|
+
*args: CombinedParamsSpec.args,
|
|
233
|
+
**kwargs: CombinedParamsSpec.kwargs,
|
|
234
|
+
) -> ResponseType:
|
|
235
|
+
headers = context.headers or {}
|
|
236
|
+
|
|
237
|
+
path_params, payload_model = cls._operation.models_from_combined(
|
|
238
|
+
combined=kwargs
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
path = cls._operation.create_path_from_model(
|
|
242
|
+
path_params, model_dump_options=cls._operation.path_dump_options()
|
|
243
|
+
)
|
|
244
|
+
url = urljoin(context.base_url, path)
|
|
245
|
+
_verify_url(url)
|
|
246
|
+
async with httpx.AsyncClient() as client:
|
|
247
|
+
response = await client.request(
|
|
248
|
+
method=cls._operation.request_method(),
|
|
249
|
+
url=url,
|
|
250
|
+
headers=headers,
|
|
251
|
+
json=cls._operation.create_payload_from_model(
|
|
252
|
+
payload_model,
|
|
253
|
+
model_dump_options=cls._operation.payload_dump_options(),
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
response_json = response.json()
|
|
257
|
+
return cls._operation.handle_response(
|
|
258
|
+
response_json,
|
|
259
|
+
model_validate_options=cls._operation.response_validate_options(),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
return HttpxRequestor
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def build_aiohttp_requestor(
|
|
266
|
+
operation_type: type[
|
|
267
|
+
ApiOperationProtocol[
|
|
268
|
+
PathParamsSpec,
|
|
269
|
+
PathParamsType,
|
|
270
|
+
PayloadParamSpec,
|
|
271
|
+
PayloadType,
|
|
272
|
+
ResponseType,
|
|
273
|
+
]
|
|
274
|
+
],
|
|
275
|
+
combined_model: Callable[CombinedParamsSpec, CombinedParamsType],
|
|
276
|
+
) -> type[EndpointRequestorProtocol[CombinedParamsSpec, ResponseType]]:
|
|
277
|
+
import aiohttp
|
|
278
|
+
|
|
279
|
+
class AiohttpRequestor(EndpointRequestorProtocol):
|
|
280
|
+
_operation = operation_type
|
|
281
|
+
|
|
282
|
+
@classmethod
|
|
283
|
+
def request(
|
|
284
|
+
cls,
|
|
285
|
+
context: RequestContext,
|
|
286
|
+
*args: CombinedParamsSpec.args,
|
|
287
|
+
**kwargs: CombinedParamsSpec.kwargs,
|
|
288
|
+
) -> ResponseType:
|
|
289
|
+
raise NotImplementedError(
|
|
290
|
+
"Sync request not implemented for aiohttp requestor"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
@classmethod
|
|
294
|
+
async def request_async(
|
|
295
|
+
cls,
|
|
296
|
+
context: RequestContext,
|
|
297
|
+
headers: dict[str, str] | None = None,
|
|
298
|
+
*args: CombinedParamsSpec.args,
|
|
299
|
+
**kwargs: CombinedParamsSpec.kwargs,
|
|
300
|
+
) -> ResponseType:
|
|
301
|
+
headers = context.headers or {}
|
|
302
|
+
|
|
303
|
+
path_params, payload_model = cls._operation.models_from_combined(
|
|
304
|
+
combined=kwargs
|
|
305
|
+
)
|
|
306
|
+
path = cls._operation.create_path_from_model(
|
|
307
|
+
path_params, model_dump_options=cls._operation.path_dump_options()
|
|
308
|
+
)
|
|
309
|
+
url = urljoin(context.base_url, path)
|
|
310
|
+
_verify_url(url)
|
|
311
|
+
|
|
312
|
+
async with aiohttp.ClientSession() as session:
|
|
313
|
+
response = await session.request(
|
|
314
|
+
method=cls._operation.request_method(),
|
|
315
|
+
url=url,
|
|
316
|
+
headers=headers,
|
|
317
|
+
json=cls._operation.create_payload_from_model(
|
|
318
|
+
payload=payload_model,
|
|
319
|
+
model_dump_options=cls._operation.payload_dump_options(),
|
|
320
|
+
),
|
|
321
|
+
)
|
|
322
|
+
response_json = await response.json()
|
|
323
|
+
return cls._operation.handle_response(
|
|
324
|
+
response=response_json,
|
|
325
|
+
model_validate_options=cls._operation.response_validate_options(),
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
return AiohttpRequestor
|
|
329
|
+
|
|
330
|
+
|
|
117
331
|
class RequestorType(StrEnum):
|
|
118
332
|
REQUESTS = "requests"
|
|
119
333
|
FAKE = "fake"
|
|
334
|
+
HTTPIX = "httpx"
|
|
335
|
+
AIOHTTP = "aiohttp"
|
|
120
336
|
|
|
121
337
|
|
|
122
338
|
def build_requestor(
|
|
@@ -147,6 +363,14 @@ def build_requestor(
|
|
|
147
363
|
combined_model=combined_model,
|
|
148
364
|
return_value=return_value,
|
|
149
365
|
)
|
|
366
|
+
case RequestorType.HTTPIX:
|
|
367
|
+
return build_httpx_requestor(
|
|
368
|
+
operation_type=operation_type, combined_model=combined_model
|
|
369
|
+
)
|
|
370
|
+
case RequestorType.AIOHTTP:
|
|
371
|
+
return build_aiohttp_requestor(
|
|
372
|
+
operation_type=operation_type, combined_model=combined_model
|
|
373
|
+
)
|
|
150
374
|
|
|
151
375
|
|
|
152
376
|
if __name__ == "__main__":
|
|
@@ -169,7 +393,7 @@ if __name__ == "__main__":
|
|
|
169
393
|
|
|
170
394
|
UserEndpoint = build_api_operation(
|
|
171
395
|
method=HttpMethods.GET,
|
|
172
|
-
|
|
396
|
+
path_template=Template("/users/{user_id}"),
|
|
173
397
|
path_params_constructor=GetUserPathParams,
|
|
174
398
|
payload_constructor=GetUserRequestBody,
|
|
175
399
|
response_model_type=UserResponse,
|
|
@@ -183,19 +407,21 @@ if __name__ == "__main__":
|
|
|
183
407
|
|
|
184
408
|
# Note that the return value is a pydantic UserResponse object
|
|
185
409
|
response = FakeUserRequestor().request(
|
|
186
|
-
headers={"a": "b"},
|
|
410
|
+
context=RequestContext(base_url="https://example.com", headers={"a": "b"}),
|
|
187
411
|
user_id=123,
|
|
188
412
|
include_profile=True,
|
|
189
413
|
)
|
|
190
414
|
|
|
191
|
-
|
|
415
|
+
RequestRequestor = build_request_requestor(
|
|
192
416
|
operation_type=UserEndpoint,
|
|
193
417
|
combined_model=CombinedParams,
|
|
194
418
|
)
|
|
195
419
|
|
|
196
420
|
# Check type hints
|
|
197
|
-
response =
|
|
198
|
-
headers={"a": "b"},
|
|
421
|
+
response = RequestRequestor().request(
|
|
422
|
+
context=RequestContext(base_url="https://example.com", headers={"a": "b"}),
|
|
423
|
+
user_id=123,
|
|
424
|
+
include_profile=True,
|
|
199
425
|
)
|
|
200
426
|
|
|
201
427
|
print(response.model_dump())
|
|
@@ -35,3 +35,23 @@ class CommonException(Exception):
|
|
|
35
35
|
|
|
36
36
|
class ConfigurationException(Exception):
|
|
37
37
|
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class InfoExceptionForAi(Exception):
|
|
41
|
+
"""
|
|
42
|
+
This exception is raised as information to the AI.
|
|
43
|
+
Such that it can be used to inform the user about the error.
|
|
44
|
+
In a meaningful way.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
error_message: str,
|
|
50
|
+
message_for_ai: str,
|
|
51
|
+
):
|
|
52
|
+
super().__init__(error_message)
|
|
53
|
+
self._message_for_ai = message_for_ai
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def message_for_ai(self):
|
|
57
|
+
return self._message_for_ai
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from pydantic import BaseModel
|
|
1
|
+
from pydantic import BaseModel
|
|
2
2
|
|
|
3
3
|
from unique_toolkit.agentic.tools.config import get_configuration_dict
|
|
4
4
|
|
|
@@ -7,7 +7,3 @@ class FeatureExtendedSourceSerialization(BaseModel):
|
|
|
7
7
|
"""Mixin for experimental feature in Source serialization"""
|
|
8
8
|
|
|
9
9
|
model_config = get_configuration_dict()
|
|
10
|
-
full_sources_serialize_dump: bool = Field(
|
|
11
|
-
default=False,
|
|
12
|
-
description="Whether to include the full source object in the tool response. If True, includes the full Source object. If False, uses the old format with only source_number and content.",
|
|
13
|
-
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilities for handling references in the "postprocessed" format, i.e <sup>X</sup>
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
import re
|
|
7
|
+
from typing import Generator
|
|
8
|
+
|
|
9
|
+
_REF_DETECTION_PATTERN = re.compile(r"<sup>\s*(?P<reference_number>\d+)\s*</sup>")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _iter_ref_numbers(text: str) -> Generator[int, None, None]:
|
|
13
|
+
for match in _REF_DETECTION_PATTERN.finditer(text):
|
|
14
|
+
yield int(match.group("reference_number"))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@functools.cache
|
|
18
|
+
def _get_detection_pattern_for_ref(ref_number: int) -> re.Pattern[str]:
|
|
19
|
+
return re.compile(rf"<sup>\s*{ref_number}\s*</sup>")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_reference_pattern(ref_number: int) -> str:
|
|
23
|
+
return f"<sup>{ref_number}</sup>"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_all_ref_numbers(text: str) -> list[int]:
|
|
27
|
+
return sorted(set(_iter_ref_numbers(text)))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_max_ref_number(text: str) -> int | None:
|
|
31
|
+
return max(_iter_ref_numbers(text), default=None)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def replace_ref_number(text: str, ref_number: int, replacement: int | str) -> str:
|
|
35
|
+
if isinstance(replacement, int):
|
|
36
|
+
replacement = get_reference_pattern(replacement)
|
|
37
|
+
|
|
38
|
+
return _get_detection_pattern_for_ref(ref_number).sub(replacement, text)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def remove_ref_number(text: str, ref_number: int) -> str:
|
|
42
|
+
return _get_detection_pattern_for_ref(ref_number).sub("", text)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def remove_all_refs(text: str) -> str:
|
|
46
|
+
return _REF_DETECTION_PATTERN.sub("", text)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def remove_consecutive_ref_space(text: str) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Remove spaces between consecutive references.
|
|
52
|
+
"""
|
|
53
|
+
return re.sub(r"</sup>\s*<sup>", "</sup><sup>", text)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import re
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any, Iterable, Sequence
|
|
4
|
+
from uuid import uuid4
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def _is_elementary_type(value: Any) -> bool:
|
|
@@ -87,3 +88,53 @@ def extract_dicts_from_string(text: str) -> list[dict[str, Any]]:
|
|
|
87
88
|
continue
|
|
88
89
|
|
|
89
90
|
return dictionaries
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _replace_in_text_non_overlapping(
|
|
94
|
+
text: str, repls: Iterable[tuple[str | re.Pattern[str], str]]
|
|
95
|
+
) -> str:
|
|
96
|
+
for pattern, replacement in repls:
|
|
97
|
+
text = re.sub(pattern, replacement, text)
|
|
98
|
+
return text
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def replace_in_text(
|
|
102
|
+
text: str, repls: Sequence[tuple[str | re.Pattern[str], str]]
|
|
103
|
+
) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Replace multiple patterns in text without replacement interference.
|
|
106
|
+
|
|
107
|
+
This function performs all replacements independently, preventing cases where
|
|
108
|
+
a replacement value matches another pattern, which would cause unintended
|
|
109
|
+
cascading replacements.
|
|
110
|
+
|
|
111
|
+
Why this is needed:
|
|
112
|
+
- Naive sequential replacements can interfere with each other
|
|
113
|
+
- Example: replacing "foo" -> "bar" and "bar" -> "baz" would incorrectly
|
|
114
|
+
turn "foo" into "baz" if done sequentially
|
|
115
|
+
- This function uses a two-phase approach with UUID placeholders to ensure
|
|
116
|
+
each pattern is replaced exactly once with its intended value
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
text: The input text to perform replacements on
|
|
120
|
+
repls: Sequence of (pattern, replacement) tuples where pattern can be
|
|
121
|
+
a string or compiled regex pattern
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Text with all patterns replaced by their corresponding replacements
|
|
125
|
+
|
|
126
|
+
Example:
|
|
127
|
+
>>> text = "foo and bar"
|
|
128
|
+
>>> repls = [("foo", "bar"), ("bar", "baz")]
|
|
129
|
+
>>> replace_in_text(text, repls)
|
|
130
|
+
"bar and baz" # Both replacements applied independently
|
|
131
|
+
"""
|
|
132
|
+
if len(repls) == 0:
|
|
133
|
+
return text
|
|
134
|
+
|
|
135
|
+
placeholders = [uuid4().hex for _ in range(len(repls))]
|
|
136
|
+
orig, repls = zip(*repls)
|
|
137
|
+
|
|
138
|
+
# 2 phase replacement, since the map keys and values can overlap
|
|
139
|
+
text = _replace_in_text_non_overlapping(text, zip(orig, placeholders))
|
|
140
|
+
return _replace_in_text_non_overlapping(text, zip(placeholders, repls))
|