unique_toolkit 1.8.0__py3-none-any.whl → 1.9.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.
@@ -15,6 +15,7 @@ from unique_toolkit._common.endpoint_builder import (
15
15
  ResponseType,
16
16
  )
17
17
  from unique_toolkit._common.endpoint_requestor import (
18
+ RequestContext,
18
19
  RequestorType,
19
20
  build_requestor,
20
21
  )
@@ -163,7 +164,7 @@ class HumanVerificationManagerForApiCalling(
163
164
  def call_api(
164
165
  self,
165
166
  *,
166
- headers: dict[str, str],
167
+ context: RequestContext,
167
168
  path_params: PathParamsType,
168
169
  payload: PayloadType,
169
170
  ) -> ResponseType:
@@ -171,7 +172,7 @@ class HumanVerificationManagerForApiCalling(
171
172
  params.update(payload.model_dump())
172
173
 
173
174
  response = self._requestor.request(
174
- headers=headers,
175
+ context=context,
175
176
  **params,
176
177
  )
177
178
  return self._operation.handle_response(response)
@@ -1,5 +1,6 @@
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
5
  from pydantic import BaseModel
5
6
  from typing_extensions import ParamSpec
@@ -24,11 +25,40 @@ CombinedParamsType = TypeVar("CombinedParamsType", bound=BaseModel)
24
25
  ResponseT_co = TypeVar("ResponseT_co", bound=BaseModel, covariant=True)
25
26
 
26
27
 
28
+ def _construct_full_url(base_url: str, url: str) -> str:
29
+ """
30
+ Construct full URL from base_url and url.
31
+ If base_url is provided and url is absolute, strip the scheme/netloc from url.
32
+ """
33
+ if not base_url:
34
+ return url
35
+
36
+ parsed = urlparse(url)
37
+ if parsed.scheme:
38
+ # URL is absolute, extract only path + query + fragment
39
+ url = parsed._replace(scheme="", netloc="").geturl()
40
+
41
+ return urljoin(base_url, url)
42
+
43
+
44
+ class RequestContext(BaseModel):
45
+ base_url: str = ""
46
+ headers: dict[str, str] | None = None
47
+
48
+
27
49
  class EndpointRequestorProtocol(Protocol, Generic[CombinedParamsSpec, ResponseT_co]):
28
50
  @classmethod
29
51
  def request(
30
52
  cls,
31
- headers: dict[str, str],
53
+ context: RequestContext,
54
+ *args: CombinedParamsSpec.args,
55
+ **kwargs: CombinedParamsSpec.kwargs,
56
+ ) -> ResponseT_co: ...
57
+
58
+ @classmethod
59
+ async def request_async(
60
+ cls,
61
+ context: RequestContext,
32
62
  *args: CombinedParamsSpec.args,
33
63
  **kwargs: CombinedParamsSpec.kwargs,
34
64
  ) -> ResponseT_co: ...
@@ -53,7 +83,7 @@ def build_fake_requestor(
53
83
  @classmethod
54
84
  def request(
55
85
  cls,
56
- headers: dict[str, str],
86
+ context: RequestContext,
57
87
  *args: CombinedParamsSpec.args,
58
88
  **kwargs: CombinedParamsSpec.kwargs,
59
89
  ) -> ResponseType:
@@ -68,6 +98,18 @@ def build_fake_requestor(
68
98
 
69
99
  return cls._operation.handle_response(return_value)
70
100
 
101
+ @classmethod
102
+ async def request_async(
103
+ cls,
104
+ context: RequestContext,
105
+ headers: dict[str, str] | None = None,
106
+ *args: CombinedParamsSpec.args,
107
+ **kwargs: CombinedParamsSpec.kwargs,
108
+ ) -> ResponseType:
109
+ raise NotImplementedError(
110
+ "Async request not implemented for fake requestor"
111
+ )
112
+
71
113
  return FakeRequestor
72
114
 
73
115
 
@@ -91,7 +133,7 @@ def build_request_requestor(
91
133
  @classmethod
92
134
  def request(
93
135
  cls,
94
- headers: dict[str, str],
136
+ context: RequestContext,
95
137
  *args: CombinedParamsSpec.args,
96
138
  **kwargs: CombinedParamsSpec.kwargs,
97
139
  ) -> ResponseType:
@@ -105,18 +147,159 @@ def build_request_requestor(
105
147
 
106
148
  response = requests.request(
107
149
  method=cls._operation.request_method(),
108
- url=url,
109
- headers=headers,
150
+ url=_construct_full_url(context.base_url, url),
151
+ headers=context.headers,
110
152
  json=payload,
111
153
  )
112
154
  return cls._operation.handle_response(response.json())
113
155
 
156
+ @classmethod
157
+ async def request_async(
158
+ cls,
159
+ base_url: str = "",
160
+ headers: dict[str, str] | None = None,
161
+ *args: CombinedParamsSpec.args,
162
+ **kwargs: CombinedParamsSpec.kwargs,
163
+ ) -> ResponseType:
164
+ raise NotImplementedError(
165
+ "Async request not implemented for request requestor"
166
+ )
167
+
114
168
  return RequestRequestor
115
169
 
116
170
 
171
+ def build_httpx_requestor(
172
+ operation_type: type[
173
+ ApiOperationProtocol[
174
+ PathParamsSpec,
175
+ PathParamsType,
176
+ PayloadParamSpec,
177
+ PayloadType,
178
+ ResponseType,
179
+ ]
180
+ ],
181
+ combined_model: Callable[CombinedParamsSpec, CombinedParamsType],
182
+ ) -> type[EndpointRequestorProtocol[CombinedParamsSpec, ResponseType]]:
183
+ import httpx
184
+
185
+ class HttpxRequestor(EndpointRequestorProtocol):
186
+ _operation = operation_type
187
+
188
+ @classmethod
189
+ def request(
190
+ cls,
191
+ context: RequestContext,
192
+ *args: CombinedParamsSpec.args,
193
+ **kwargs: CombinedParamsSpec.kwargs,
194
+ ) -> ResponseType:
195
+ headers = context.headers or {}
196
+
197
+ path_params, payload_model = cls._operation.models_from_combined(
198
+ combined=kwargs
199
+ )
200
+
201
+ with httpx.Client() as client:
202
+ response = client.request(
203
+ method=cls._operation.request_method(),
204
+ url=_construct_full_url(
205
+ base_url=context.base_url,
206
+ url=cls._operation.create_url_from_model(path_params),
207
+ ),
208
+ headers=headers,
209
+ json=cls._operation.create_payload_from_model(payload_model),
210
+ )
211
+ return cls._operation.handle_response(response.json())
212
+
213
+ @classmethod
214
+ async def request_async(
215
+ cls,
216
+ context: RequestContext,
217
+ *args: CombinedParamsSpec.args,
218
+ **kwargs: CombinedParamsSpec.kwargs,
219
+ ) -> ResponseType:
220
+ headers = context.headers or {}
221
+
222
+ path_params, payload_model = cls._operation.models_from_combined(
223
+ combined=kwargs
224
+ )
225
+
226
+ async with httpx.AsyncClient() as client:
227
+ response = await client.request(
228
+ method=cls._operation.request_method(),
229
+ url=_construct_full_url(
230
+ base_url=context.base_url,
231
+ url=cls._operation.create_url_from_model(path_params),
232
+ ),
233
+ headers=headers,
234
+ json=cls._operation.create_payload_from_model(payload_model),
235
+ )
236
+ return cls._operation.handle_response(response.json())
237
+
238
+ return HttpxRequestor
239
+
240
+
241
+ def build_aiohttp_requestor(
242
+ operation_type: type[
243
+ ApiOperationProtocol[
244
+ PathParamsSpec,
245
+ PathParamsType,
246
+ PayloadParamSpec,
247
+ PayloadType,
248
+ ResponseType,
249
+ ]
250
+ ],
251
+ combined_model: Callable[CombinedParamsSpec, CombinedParamsType],
252
+ ) -> type[EndpointRequestorProtocol[CombinedParamsSpec, ResponseType]]:
253
+ import aiohttp
254
+
255
+ class AiohttpRequestor(EndpointRequestorProtocol):
256
+ _operation = operation_type
257
+
258
+ @classmethod
259
+ def request(
260
+ cls,
261
+ context: RequestContext,
262
+ *args: CombinedParamsSpec.args,
263
+ **kwargs: CombinedParamsSpec.kwargs,
264
+ ) -> ResponseType:
265
+ raise NotImplementedError(
266
+ "Sync request not implemented for aiohttp requestor"
267
+ )
268
+
269
+ @classmethod
270
+ async def request_async(
271
+ cls,
272
+ context: RequestContext,
273
+ headers: dict[str, str] | None = None,
274
+ *args: CombinedParamsSpec.args,
275
+ **kwargs: CombinedParamsSpec.kwargs,
276
+ ) -> ResponseType:
277
+ headers = context.headers or {}
278
+
279
+ path_params, payload_model = cls._operation.models_from_combined(
280
+ combined=kwargs
281
+ )
282
+
283
+ async with aiohttp.ClientSession() as session:
284
+ response = await session.request(
285
+ method=cls._operation.request_method(),
286
+ url=_construct_full_url(
287
+ base_url=context.base_url,
288
+ url=cls._operation.create_url_from_model(path_params),
289
+ ),
290
+ headers=headers,
291
+ json=cls._operation.create_payload_from_model(payload_model),
292
+ )
293
+ return cls._operation.handle_response(await response.json())
294
+
295
+ return AiohttpRequestor
296
+
297
+
117
298
  class RequestorType(StrEnum):
118
299
  REQUESTS = "requests"
119
300
  FAKE = "fake"
301
+ HTTPIX = "httpx"
302
+ AIOHTTP = "aiohttp"
120
303
 
121
304
 
122
305
  def build_requestor(
@@ -147,6 +330,14 @@ def build_requestor(
147
330
  combined_model=combined_model,
148
331
  return_value=return_value,
149
332
  )
333
+ case RequestorType.HTTPIX:
334
+ return build_httpx_requestor(
335
+ operation_type=operation_type, combined_model=combined_model
336
+ )
337
+ case RequestorType.AIOHTTP:
338
+ return build_aiohttp_requestor(
339
+ operation_type=operation_type, combined_model=combined_model
340
+ )
150
341
 
151
342
 
152
343
  if __name__ == "__main__":
@@ -183,19 +374,19 @@ if __name__ == "__main__":
183
374
 
184
375
  # Note that the return value is a pydantic UserResponse object
185
376
  response = FakeUserRequestor().request(
186
- headers={"a": "b"},
377
+ context=RequestContext(headers={"a": "b"}),
187
378
  user_id=123,
188
379
  include_profile=True,
189
380
  )
190
381
 
191
- RequestRequstor = build_request_requestor(
382
+ RequestRequestor = build_request_requestor(
192
383
  operation_type=UserEndpoint,
193
384
  combined_model=CombinedParams,
194
385
  )
195
386
 
196
387
  # Check type hints
197
- response = RequestRequstor().request(
198
- headers={"a": "b"}, user_id=123, include_profile=True
388
+ response = RequestRequestor().request(
389
+ context=RequestContext(headers={"a": "b"}), user_id=123, include_profile=True
199
390
  )
200
391
 
201
392
  print(response.model_dump())
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
- from pydantic import BaseModel, Field
3
+ from pydantic import AliasChoices, BaseModel, Field
4
4
 
5
5
  from unique_toolkit._common.default_language_model import DEFAULT_GPT_4o
6
6
  from unique_toolkit._common.pydantic_helpers import get_configuration_dict
@@ -47,4 +47,9 @@ class SubAgentEvaluationConfig(BaseModel):
47
47
  include_evaluation: bool = Field(
48
48
  default=True,
49
49
  description="Whether to include the evaluation in the response.",
50
+ validation_alias=AliasChoices(
51
+ "includeEvaluation",
52
+ "displayEvalution", # typo in old config name
53
+ "display_evalution",
54
+ ),
50
55
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_toolkit
3
- Version: 1.8.0
3
+ Version: 1.9.0
4
4
  Summary:
5
5
  License: Proprietary
6
6
  Author: Cedric Klinkert
@@ -118,6 +118,12 @@ All notable changes to this project will be documented in this file.
118
118
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
119
119
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
120
120
 
121
+ ## [1.9.0] - 2026-10-04
122
+ - Define the RequestContext and add aihttp/httpx requestors
123
+
124
+ ## [1.8.1] - 2026-10-03
125
+ - Fix bug where sub agent evaluation config variable `include_evaluation` did not include aliases for previous names.
126
+
121
127
  ## [1.8.0] - 2026-10-03
122
128
  - Sub Agents now block when executing the same sub-agent multiple times with `reuse_chat` set to `True`.
123
129
  - Sub Agents tool, evaluation and post-processing refactored and tests added.
@@ -1,7 +1,7 @@
1
1
  unique_toolkit/__init__.py,sha256=nbOYPIKERt-ITsgifrnJhatn1YNR38Ntumw-dCn_tsA,714
2
2
  unique_toolkit/_common/_base_service.py,sha256=S8H0rAebx7GsOldA7xInLp3aQJt9yEPDQdsGSFRJsGg,276
3
3
  unique_toolkit/_common/_time_utils.py,sha256=ztmTovTvr-3w71Ns2VwXC65OKUUh-sQlzbHdKTQWm-w,135
4
- unique_toolkit/_common/api_calling/human_verification_manager.py,sha256=UMOkY1cNhJ6JmtiBl3a4mQ3J21uOyKoCbVu7nR3A5eY,7799
4
+ unique_toolkit/_common/api_calling/human_verification_manager.py,sha256=wgK0hefTh3pdrs8kH26Pba46ROQoFihEhT4r6h189wo,7819
5
5
  unique_toolkit/_common/base_model_type_attribute.py,sha256=7rzVqjXa0deYEixeo_pJSJcQ7nKXpWK_UGpOiEH3yZY,10382
6
6
  unique_toolkit/_common/chunk_relevancy_sorter/config.py,sha256=kDSEcXeIWGvzK4IXT3pBofTXeUnq3a9qRWaPllweR-s,1817
7
7
  unique_toolkit/_common/chunk_relevancy_sorter/exception.py,sha256=1mY4zjbvnXsd5oIxwiVsma09bS2XRnHrxW8KJBGtgCM,126
@@ -10,7 +10,7 @@ unique_toolkit/_common/chunk_relevancy_sorter/service.py,sha256=ZX1pxcy53zh3Ha0_
10
10
  unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py,sha256=UhDllC40Y1OUQvkU6pe3nu6NR7v0d25yldE6FyozuZI,8926
11
11
  unique_toolkit/_common/default_language_model.py,sha256=tmHSqg6e8G7RmKqmdE_tmLxkSN0x-aGoyUdy6Pl2oAE,334
12
12
  unique_toolkit/_common/endpoint_builder.py,sha256=WzJrJ7azUQhvQRd-vsFFoyj6omJpHiVYrh1UFxNQvVg,8242
13
- unique_toolkit/_common/endpoint_requestor.py,sha256=JbbfJGLxgxLz8a3Yx1FdJvdHGbCYO8MSBd7cLg_Mtp0,5927
13
+ unique_toolkit/_common/endpoint_requestor.py,sha256=7rDpeEvmQbLtn3iNW8NftyvAqDkLFGHoYN1h5AFDxho,12319
14
14
  unique_toolkit/_common/exception.py,sha256=hwh60UUawHDyPFNs-Wom-Gc6Yb09gPelftAuW1tXE6o,779
15
15
  unique_toolkit/_common/feature_flags/schema.py,sha256=F1NdVJFNU8PKlS7bYzrIPeDu2LxRqHSM9pyw622a1Kk,547
16
16
  unique_toolkit/_common/pydantic/rjsf_tags.py,sha256=T3AZIF8wny3fFov66s258nEl1GqfKevFouTtG6k9PqU,31219
@@ -54,7 +54,7 @@ unique_toolkit/agentic/tools/a2a/__init__.py,sha256=QG1fq2mXq8VViG9cV6KbSd9sS0Xq
54
54
  unique_toolkit/agentic/tools/a2a/config.py,sha256=6diTTSiS2prY294LfYozB-db2wmJ6jv1hAr2leRY-xk,768
55
55
  unique_toolkit/agentic/tools/a2a/evaluation/__init__.py,sha256=_cR8uBwLbG7lyXoRskTpItzacgs4n23e2LeqClrytuc,354
56
56
  unique_toolkit/agentic/tools/a2a/evaluation/_utils.py,sha256=GtcPAMWkwGwJ--hBxn35ow9jN0VKYx8h2qMUXR8DCho,1877
57
- unique_toolkit/agentic/tools/a2a/evaluation/config.py,sha256=idOOFUOtxlgeJ3DjsNwvRHAKoB69Iu59z0X_DGfet0I,2159
57
+ unique_toolkit/agentic/tools/a2a/evaluation/config.py,sha256=faYYABL3Z-u7930MduTb5VO-W7VKYblQ5mS6mqjMJQA,2348
58
58
  unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py,sha256=K4GkVOQwAUofjMF1-ofIGV3XPY1vOnOA8aw6CducRc0,7248
59
59
  unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2,sha256=acP1YqD_sCy6DT0V2EIfhQTmaUKeqpeWNJ7RGgceo8I,271
60
60
  unique_toolkit/agentic/tools/a2a/manager.py,sha256=FkO9jY7o8Td0t-HBkkatmxwhJGSJXmYkFYKFhPdbpMo,1674
@@ -144,7 +144,7 @@ unique_toolkit/short_term_memory/schemas.py,sha256=OhfcXyF6ACdwIXW45sKzjtZX_gkcJ
144
144
  unique_toolkit/short_term_memory/service.py,sha256=5PeVBu1ZCAfyDb2HLVvlmqSbyzBBuE9sI2o9Aajqjxg,8884
145
145
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
146
146
  unique_toolkit/smart_rules/compile.py,sha256=cxWjb2dxEI2HGsakKdVCkSNi7VK9mr08w5sDcFCQyWI,9553
147
- unique_toolkit-1.8.0.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
148
- unique_toolkit-1.8.0.dist-info/METADATA,sha256=wNOLxR-tQP2MU3oZHQlLpH97-wq5x0hDTp8MPsPCo2o,34870
149
- unique_toolkit-1.8.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
150
- unique_toolkit-1.8.0.dist-info/RECORD,,
147
+ unique_toolkit-1.9.0.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
148
+ unique_toolkit-1.9.0.dist-info/METADATA,sha256=93QwMegTzGuio7DAZx1G75b02h0mD90HidVNGM2jx1A,35098
149
+ unique_toolkit-1.9.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
150
+ unique_toolkit-1.9.0.dist-info/RECORD,,