unique_toolkit 0.7.9__py3-none-any.whl → 1.33.3__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.
Files changed (190) hide show
  1. unique_toolkit/__init__.py +36 -3
  2. unique_toolkit/_common/api_calling/human_verification_manager.py +357 -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 +225 -0
  14. unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
  15. unique_toolkit/_common/endpoint_builder.py +368 -0
  16. unique_toolkit/_common/endpoint_requestor.py +480 -0
  17. unique_toolkit/_common/exception.py +24 -0
  18. unique_toolkit/_common/experimental/endpoint_builder.py +368 -0
  19. unique_toolkit/_common/experimental/endpoint_requestor.py +488 -0
  20. unique_toolkit/_common/feature_flags/schema.py +9 -0
  21. unique_toolkit/_common/pydantic/rjsf_tags.py +936 -0
  22. unique_toolkit/_common/pydantic_helpers.py +174 -0
  23. unique_toolkit/_common/referencing.py +53 -0
  24. unique_toolkit/_common/string_utilities.py +140 -0
  25. unique_toolkit/_common/tests/test_referencing.py +521 -0
  26. unique_toolkit/_common/tests/test_string_utilities.py +506 -0
  27. unique_toolkit/_common/token/image_token_counting.py +67 -0
  28. unique_toolkit/_common/token/token_counting.py +204 -0
  29. unique_toolkit/_common/utils/__init__.py +1 -0
  30. unique_toolkit/_common/utils/files.py +43 -0
  31. unique_toolkit/_common/utils/image/encode.py +25 -0
  32. unique_toolkit/_common/utils/jinja/helpers.py +10 -0
  33. unique_toolkit/_common/utils/jinja/render.py +18 -0
  34. unique_toolkit/_common/utils/jinja/schema.py +65 -0
  35. unique_toolkit/_common/utils/jinja/utils.py +80 -0
  36. unique_toolkit/_common/utils/structured_output/__init__.py +1 -0
  37. unique_toolkit/_common/utils/structured_output/schema.py +5 -0
  38. unique_toolkit/_common/utils/write_configuration.py +51 -0
  39. unique_toolkit/_common/validators.py +101 -4
  40. unique_toolkit/agentic/__init__.py +1 -0
  41. unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +28 -0
  42. unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +278 -0
  43. unique_toolkit/agentic/evaluation/config.py +36 -0
  44. unique_toolkit/{evaluators → agentic/evaluation}/context_relevancy/prompts.py +25 -0
  45. unique_toolkit/agentic/evaluation/context_relevancy/schema.py +80 -0
  46. unique_toolkit/agentic/evaluation/context_relevancy/service.py +273 -0
  47. unique_toolkit/agentic/evaluation/evaluation_manager.py +218 -0
  48. unique_toolkit/agentic/evaluation/hallucination/constants.py +61 -0
  49. unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +112 -0
  50. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/prompts.py +1 -1
  51. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/service.py +20 -16
  52. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/utils.py +32 -21
  53. unique_toolkit/{evaluators → agentic/evaluation}/output_parser.py +20 -2
  54. unique_toolkit/{evaluators → agentic/evaluation}/schemas.py +27 -7
  55. unique_toolkit/agentic/evaluation/tests/test_context_relevancy_service.py +253 -0
  56. unique_toolkit/agentic/evaluation/tests/test_output_parser.py +87 -0
  57. unique_toolkit/agentic/history_manager/history_construction_with_contents.py +298 -0
  58. unique_toolkit/agentic/history_manager/history_manager.py +241 -0
  59. unique_toolkit/agentic/history_manager/loop_token_reducer.py +484 -0
  60. unique_toolkit/agentic/history_manager/utils.py +96 -0
  61. unique_toolkit/agentic/message_log_manager/__init__.py +5 -0
  62. unique_toolkit/agentic/message_log_manager/service.py +93 -0
  63. unique_toolkit/agentic/postprocessor/postprocessor_manager.py +212 -0
  64. unique_toolkit/agentic/reference_manager/reference_manager.py +103 -0
  65. unique_toolkit/agentic/responses_api/__init__.py +19 -0
  66. unique_toolkit/agentic/responses_api/postprocessors/code_display.py +71 -0
  67. unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +297 -0
  68. unique_toolkit/agentic/responses_api/stream_handler.py +15 -0
  69. unique_toolkit/agentic/short_term_memory_manager/persistent_short_term_memory_manager.py +141 -0
  70. unique_toolkit/agentic/thinking_manager/thinking_manager.py +103 -0
  71. unique_toolkit/agentic/tools/__init__.py +1 -0
  72. unique_toolkit/agentic/tools/a2a/__init__.py +36 -0
  73. unique_toolkit/agentic/tools/a2a/config.py +17 -0
  74. unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +15 -0
  75. unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +66 -0
  76. unique_toolkit/agentic/tools/a2a/evaluation/config.py +55 -0
  77. unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +260 -0
  78. unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2 +9 -0
  79. unique_toolkit/agentic/tools/a2a/manager.py +55 -0
  80. unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +21 -0
  81. unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py +240 -0
  82. unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +84 -0
  83. unique_toolkit/agentic/tools/a2a/postprocessing/config.py +78 -0
  84. unique_toolkit/agentic/tools/a2a/postprocessing/display.py +264 -0
  85. unique_toolkit/agentic/tools/a2a/postprocessing/references.py +101 -0
  86. unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display.py +421 -0
  87. unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +2103 -0
  88. unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py +603 -0
  89. unique_toolkit/agentic/tools/a2a/prompts.py +46 -0
  90. unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py +6 -0
  91. unique_toolkit/agentic/tools/a2a/response_watcher/service.py +91 -0
  92. unique_toolkit/agentic/tools/a2a/tool/__init__.py +4 -0
  93. unique_toolkit/agentic/tools/a2a/tool/_memory.py +26 -0
  94. unique_toolkit/agentic/tools/a2a/tool/_schema.py +9 -0
  95. unique_toolkit/agentic/tools/a2a/tool/config.py +158 -0
  96. unique_toolkit/agentic/tools/a2a/tool/service.py +393 -0
  97. unique_toolkit/agentic/tools/agent_chunks_hanlder.py +65 -0
  98. unique_toolkit/agentic/tools/config.py +128 -0
  99. unique_toolkit/agentic/tools/factory.py +44 -0
  100. unique_toolkit/agentic/tools/mcp/__init__.py +4 -0
  101. unique_toolkit/agentic/tools/mcp/manager.py +71 -0
  102. unique_toolkit/agentic/tools/mcp/models.py +28 -0
  103. unique_toolkit/agentic/tools/mcp/tool_wrapper.py +234 -0
  104. unique_toolkit/agentic/tools/openai_builtin/__init__.py +11 -0
  105. unique_toolkit/agentic/tools/openai_builtin/base.py +46 -0
  106. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +8 -0
  107. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +88 -0
  108. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +250 -0
  109. unique_toolkit/agentic/tools/openai_builtin/manager.py +79 -0
  110. unique_toolkit/agentic/tools/schemas.py +145 -0
  111. unique_toolkit/agentic/tools/test/test_mcp_manager.py +536 -0
  112. unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +445 -0
  113. unique_toolkit/agentic/tools/tool.py +187 -0
  114. unique_toolkit/agentic/tools/tool_manager.py +492 -0
  115. unique_toolkit/agentic/tools/tool_progress_reporter.py +285 -0
  116. unique_toolkit/agentic/tools/utils/__init__.py +19 -0
  117. unique_toolkit/agentic/tools/utils/execution/__init__.py +1 -0
  118. unique_toolkit/agentic/tools/utils/execution/execution.py +286 -0
  119. unique_toolkit/agentic/tools/utils/source_handling/__init__.py +0 -0
  120. unique_toolkit/agentic/tools/utils/source_handling/schema.py +21 -0
  121. unique_toolkit/agentic/tools/utils/source_handling/source_formatting.py +207 -0
  122. unique_toolkit/agentic/tools/utils/source_handling/tests/test_source_formatting.py +216 -0
  123. unique_toolkit/app/__init__.py +9 -0
  124. unique_toolkit/app/dev_util.py +180 -0
  125. unique_toolkit/app/fast_api_factory.py +131 -0
  126. unique_toolkit/app/init_sdk.py +32 -1
  127. unique_toolkit/app/schemas.py +206 -31
  128. unique_toolkit/app/unique_settings.py +367 -0
  129. unique_toolkit/app/webhook.py +77 -0
  130. unique_toolkit/chat/__init__.py +8 -1
  131. unique_toolkit/chat/deprecated/service.py +232 -0
  132. unique_toolkit/chat/functions.py +648 -78
  133. unique_toolkit/chat/rendering.py +34 -0
  134. unique_toolkit/chat/responses_api.py +461 -0
  135. unique_toolkit/chat/schemas.py +134 -2
  136. unique_toolkit/chat/service.py +115 -767
  137. unique_toolkit/content/functions.py +353 -8
  138. unique_toolkit/content/schemas.py +128 -15
  139. unique_toolkit/content/service.py +321 -45
  140. unique_toolkit/content/smart_rules.py +301 -0
  141. unique_toolkit/content/utils.py +10 -3
  142. unique_toolkit/data_extraction/README.md +96 -0
  143. unique_toolkit/data_extraction/__init__.py +11 -0
  144. unique_toolkit/data_extraction/augmented/__init__.py +5 -0
  145. unique_toolkit/data_extraction/augmented/service.py +93 -0
  146. unique_toolkit/data_extraction/base.py +25 -0
  147. unique_toolkit/data_extraction/basic/__init__.py +11 -0
  148. unique_toolkit/data_extraction/basic/config.py +18 -0
  149. unique_toolkit/data_extraction/basic/prompt.py +13 -0
  150. unique_toolkit/data_extraction/basic/service.py +55 -0
  151. unique_toolkit/embedding/service.py +103 -12
  152. unique_toolkit/framework_utilities/__init__.py +1 -0
  153. unique_toolkit/framework_utilities/langchain/__init__.py +10 -0
  154. unique_toolkit/framework_utilities/langchain/client.py +71 -0
  155. unique_toolkit/framework_utilities/langchain/history.py +19 -0
  156. unique_toolkit/framework_utilities/openai/__init__.py +6 -0
  157. unique_toolkit/framework_utilities/openai/client.py +84 -0
  158. unique_toolkit/framework_utilities/openai/message_builder.py +229 -0
  159. unique_toolkit/framework_utilities/utils.py +23 -0
  160. unique_toolkit/language_model/__init__.py +3 -0
  161. unique_toolkit/language_model/_responses_api_utils.py +93 -0
  162. unique_toolkit/language_model/builder.py +27 -11
  163. unique_toolkit/language_model/default_language_model.py +3 -0
  164. unique_toolkit/language_model/functions.py +345 -43
  165. unique_toolkit/language_model/infos.py +1288 -46
  166. unique_toolkit/language_model/reference.py +242 -0
  167. unique_toolkit/language_model/schemas.py +481 -49
  168. unique_toolkit/language_model/service.py +229 -28
  169. unique_toolkit/protocols/support.py +145 -0
  170. unique_toolkit/services/__init__.py +7 -0
  171. unique_toolkit/services/chat_service.py +1631 -0
  172. unique_toolkit/services/knowledge_base.py +1094 -0
  173. unique_toolkit/short_term_memory/service.py +178 -41
  174. unique_toolkit/smart_rules/__init__.py +0 -0
  175. unique_toolkit/smart_rules/compile.py +56 -0
  176. unique_toolkit/test_utilities/events.py +197 -0
  177. unique_toolkit-1.33.3.dist-info/METADATA +1145 -0
  178. unique_toolkit-1.33.3.dist-info/RECORD +205 -0
  179. unique_toolkit/evaluators/__init__.py +0 -1
  180. unique_toolkit/evaluators/config.py +0 -35
  181. unique_toolkit/evaluators/constants.py +0 -1
  182. unique_toolkit/evaluators/context_relevancy/constants.py +0 -32
  183. unique_toolkit/evaluators/context_relevancy/service.py +0 -53
  184. unique_toolkit/evaluators/context_relevancy/utils.py +0 -142
  185. unique_toolkit/evaluators/hallucination/constants.py +0 -41
  186. unique_toolkit-0.7.9.dist-info/METADATA +0 -413
  187. unique_toolkit-0.7.9.dist-info/RECORD +0 -64
  188. /unique_toolkit/{evaluators → agentic/evaluation}/exception.py +0 -0
  189. {unique_toolkit-0.7.9.dist-info → unique_toolkit-1.33.3.dist-info}/LICENSE +0 -0
  190. {unique_toolkit-0.7.9.dist-info → unique_toolkit-1.33.3.dist-info}/WHEEL +0 -0
@@ -0,0 +1,488 @@
1
+ from enum import StrEnum
2
+ from typing import Any, Callable, Generic, Protocol, TypeVar
3
+ from urllib.parse import urljoin, urlparse
4
+
5
+ from pydantic import BaseModel, Field
6
+ from typing_extensions import ParamSpec
7
+
8
+ from unique_toolkit._common.experimental.endpoint_builder import (
9
+ ApiOperationProtocol,
10
+ HttpMethods,
11
+ PathParamsSpec,
12
+ PathParamsType,
13
+ PayloadParamSpec,
14
+ PayloadType,
15
+ QueryParamsSpec,
16
+ QueryParamsType,
17
+ ResponseType,
18
+ )
19
+
20
+ # Paramspecs
21
+ CombinedParamsSpec = ParamSpec("CombinedParamsSpec")
22
+
23
+ # Type variables
24
+ CombinedParamsType = TypeVar("CombinedParamsType", bound=BaseModel)
25
+
26
+
27
+ ResponseT_co = TypeVar("ResponseT_co", bound=BaseModel, covariant=True)
28
+
29
+
30
+ class RequestContext(BaseModel):
31
+ base_url: str
32
+ headers: dict[str, str] = Field(default_factory=dict)
33
+
34
+
35
+ def _verify_url(url: str) -> None:
36
+ parse_result = urlparse(url)
37
+ if not (parse_result.netloc and parse_result.scheme):
38
+ raise ValueError("Scheme and netloc are required for url")
39
+
40
+
41
+ class EndpointRequestorProtocol(Protocol, Generic[CombinedParamsSpec, ResponseT_co]):
42
+ @classmethod
43
+ def request(
44
+ cls,
45
+ context: RequestContext,
46
+ *args: CombinedParamsSpec.args,
47
+ **kwargs: CombinedParamsSpec.kwargs,
48
+ ) -> ResponseT_co: ...
49
+
50
+ @classmethod
51
+ async def request_async(
52
+ cls,
53
+ context: RequestContext,
54
+ *args: CombinedParamsSpec.args,
55
+ **kwargs: CombinedParamsSpec.kwargs,
56
+ ) -> ResponseT_co: ...
57
+
58
+
59
+ def build_fake_requestor(
60
+ operation_type: type[
61
+ ApiOperationProtocol[
62
+ PathParamsSpec,
63
+ PathParamsType,
64
+ PayloadParamSpec,
65
+ PayloadType,
66
+ QueryParamsSpec,
67
+ QueryParamsType,
68
+ ResponseType,
69
+ ]
70
+ ],
71
+ combined_model: Callable[CombinedParamsSpec, CombinedParamsType],
72
+ return_value: dict[str, Any],
73
+ ) -> type[EndpointRequestorProtocol[CombinedParamsSpec, ResponseType]]:
74
+ class FakeRequestor(EndpointRequestorProtocol):
75
+ _operation = operation_type
76
+
77
+ @classmethod
78
+ def request(
79
+ cls,
80
+ context: RequestContext,
81
+ *args: CombinedParamsSpec.args,
82
+ **kwargs: CombinedParamsSpec.kwargs,
83
+ ) -> ResponseType:
84
+ try:
85
+ path_params, payload_model, query_params = (
86
+ cls._operation.models_from_combined(combined=kwargs)
87
+ )
88
+ except Exception as e:
89
+ raise ValueError(
90
+ f"Invalid parameters passed to combined model {combined_model.__name__}: {e}"
91
+ )
92
+
93
+ return cls._operation.handle_response(
94
+ return_value,
95
+ model_validate_options=cls._operation.response_validate_options(),
96
+ )
97
+
98
+ @classmethod
99
+ async def request_async(
100
+ cls,
101
+ context: RequestContext,
102
+ headers: dict[str, str] | None = None,
103
+ *args: CombinedParamsSpec.args,
104
+ **kwargs: CombinedParamsSpec.kwargs,
105
+ ) -> ResponseType:
106
+ raise NotImplementedError(
107
+ "Async request not implemented for fake requestor"
108
+ )
109
+
110
+ return FakeRequestor
111
+
112
+
113
+ def build_request_requestor(
114
+ operation_type: type[
115
+ ApiOperationProtocol[
116
+ PathParamsSpec,
117
+ PathParamsType,
118
+ PayloadParamSpec,
119
+ PayloadType,
120
+ QueryParamsSpec,
121
+ QueryParamsType,
122
+ ResponseType,
123
+ ]
124
+ ],
125
+ combined_model: Callable[CombinedParamsSpec, CombinedParamsType],
126
+ ) -> type[EndpointRequestorProtocol[CombinedParamsSpec, ResponseType]]:
127
+ import requests
128
+
129
+ class RequestRequestor(EndpointRequestorProtocol):
130
+ _operation = operation_type
131
+
132
+ @classmethod
133
+ def request(
134
+ cls,
135
+ context: RequestContext,
136
+ *args: CombinedParamsSpec.args,
137
+ **kwargs: CombinedParamsSpec.kwargs,
138
+ ) -> ResponseType:
139
+ # Create separate instances for path params and payload using endpoint helper
140
+ path_params, payload_model, query_params = (
141
+ cls._operation.models_from_combined(combined=kwargs)
142
+ )
143
+
144
+ path = cls._operation.create_path_from_model(
145
+ path_params, model_dump_options=cls._operation.path_dump_options()
146
+ )
147
+ url = urljoin(context.base_url, path)
148
+ _verify_url(url)
149
+
150
+ payload = cls._operation.create_payload_from_model(
151
+ payload_model, model_dump_options=cls._operation.payload_dump_options()
152
+ )
153
+ request_method = cls._operation.request_method()
154
+
155
+ params = cls._operation.create_query_params_from_model(
156
+ query_params=query_params,
157
+ model_dump_options=cls._operation.query_params_dump_options(),
158
+ )
159
+
160
+ response = requests.request(
161
+ method=request_method,
162
+ url=url,
163
+ headers=context.headers,
164
+ json=payload,
165
+ params=params,
166
+ )
167
+
168
+ response_json = response.json()
169
+
170
+ return cls._operation.handle_response(
171
+ response_json,
172
+ model_validate_options=cls._operation.response_validate_options(),
173
+ )
174
+
175
+ @classmethod
176
+ async def request_async(
177
+ cls,
178
+ base_url: str = "",
179
+ headers: dict[str, str] | None = None,
180
+ *args: CombinedParamsSpec.args,
181
+ **kwargs: CombinedParamsSpec.kwargs,
182
+ ) -> ResponseType:
183
+ raise NotImplementedError(
184
+ "Async request not implemented for request requestor"
185
+ )
186
+
187
+ return RequestRequestor
188
+
189
+
190
+ def build_httpx_requestor(
191
+ operation_type: type[
192
+ ApiOperationProtocol[
193
+ PathParamsSpec,
194
+ PathParamsType,
195
+ PayloadParamSpec,
196
+ PayloadType,
197
+ QueryParamsSpec,
198
+ QueryParamsType,
199
+ ResponseType,
200
+ ]
201
+ ],
202
+ combined_model: Callable[CombinedParamsSpec, CombinedParamsType],
203
+ ) -> type[EndpointRequestorProtocol[CombinedParamsSpec, ResponseType]]:
204
+ import httpx
205
+
206
+ class HttpxRequestor(EndpointRequestorProtocol):
207
+ _operation = operation_type
208
+
209
+ @classmethod
210
+ def request(
211
+ cls,
212
+ context: RequestContext,
213
+ *args: CombinedParamsSpec.args,
214
+ **kwargs: CombinedParamsSpec.kwargs,
215
+ ) -> ResponseType:
216
+ headers = context.headers or {}
217
+
218
+ path_params, payload_model, query_model = (
219
+ cls._operation.models_from_combined(combined=kwargs)
220
+ )
221
+
222
+ path = cls._operation.create_path_from_model(
223
+ path_params, model_dump_options=cls._operation.path_dump_options()
224
+ )
225
+ url = urljoin(context.base_url, path)
226
+ _verify_url(url)
227
+
228
+ payload = cls._operation.create_payload_from_model(
229
+ payload_model,
230
+ model_dump_options=cls._operation.payload_dump_options(),
231
+ )
232
+
233
+ query_params = cls._operation.create_query_params_from_model(
234
+ query_params=query_model,
235
+ model_dump_options=cls._operation.query_params_dump_options(),
236
+ )
237
+
238
+ with httpx.Client() as client:
239
+ # For GET requests, send payload as params; for others, send as json
240
+ if cls._operation.request_method() == HttpMethods.GET:
241
+ response = client.request(
242
+ method=cls._operation.request_method(),
243
+ url=url,
244
+ headers=headers,
245
+ json=payload,
246
+ params=query_params,
247
+ )
248
+ else:
249
+ response = client.request(
250
+ method=cls._operation.request_method(),
251
+ url=url,
252
+ headers=headers,
253
+ json=payload,
254
+ )
255
+ response_json = response.json()
256
+ return cls._operation.handle_response(
257
+ response_json,
258
+ model_validate_options=cls._operation.response_validate_options(),
259
+ )
260
+
261
+ @classmethod
262
+ async def request_async(
263
+ cls,
264
+ context: RequestContext,
265
+ *args: CombinedParamsSpec.args,
266
+ **kwargs: CombinedParamsSpec.kwargs,
267
+ ) -> ResponseType:
268
+ headers = context.headers or {}
269
+
270
+ path_params, payload_model, query_model = (
271
+ cls._operation.models_from_combined(combined=kwargs)
272
+ )
273
+
274
+ path = cls._operation.create_path_from_model(
275
+ path_params, model_dump_options=cls._operation.path_dump_options()
276
+ )
277
+ url = urljoin(context.base_url, path)
278
+ _verify_url(url)
279
+
280
+ payload = cls._operation.create_payload_from_model(
281
+ payload_model,
282
+ model_dump_options=cls._operation.payload_dump_options(),
283
+ )
284
+
285
+ async with httpx.AsyncClient() as client:
286
+ # For GET requests, send payload as params; for others, send as json
287
+ if cls._operation.request_method() == HttpMethods.GET:
288
+ response = await client.request(
289
+ method=cls._operation.request_method(),
290
+ url=url,
291
+ headers=headers,
292
+ params=payload,
293
+ )
294
+ else:
295
+ response = await client.request(
296
+ method=cls._operation.request_method(),
297
+ url=url,
298
+ headers=headers,
299
+ json=payload,
300
+ )
301
+ response_json = response.json()
302
+ return cls._operation.handle_response(
303
+ response_json,
304
+ model_validate_options=cls._operation.response_validate_options(),
305
+ )
306
+
307
+ return HttpxRequestor
308
+
309
+
310
+ def build_aiohttp_requestor(
311
+ operation_type: type[
312
+ ApiOperationProtocol[
313
+ PathParamsSpec,
314
+ PathParamsType,
315
+ PayloadParamSpec,
316
+ PayloadType,
317
+ QueryParamsSpec,
318
+ QueryParamsType,
319
+ ResponseType,
320
+ ]
321
+ ],
322
+ combined_model: Callable[CombinedParamsSpec, CombinedParamsType],
323
+ ) -> type[EndpointRequestorProtocol[CombinedParamsSpec, ResponseType]]:
324
+ import aiohttp
325
+
326
+ class AiohttpRequestor(EndpointRequestorProtocol):
327
+ _operation = operation_type
328
+
329
+ @classmethod
330
+ def request(
331
+ cls,
332
+ context: RequestContext,
333
+ *args: CombinedParamsSpec.args,
334
+ **kwargs: CombinedParamsSpec.kwargs,
335
+ ) -> ResponseType:
336
+ raise NotImplementedError(
337
+ "Sync request not implemented for aiohttp requestor"
338
+ )
339
+
340
+ @classmethod
341
+ async def request_async(
342
+ cls,
343
+ context: RequestContext,
344
+ headers: dict[str, str] | None = None,
345
+ *args: CombinedParamsSpec.args,
346
+ **kwargs: CombinedParamsSpec.kwargs,
347
+ ) -> ResponseType:
348
+ headers = context.headers or {}
349
+
350
+ path_params, payload_model, query_model = (
351
+ cls._operation.models_from_combined(combined=kwargs)
352
+ )
353
+ path = cls._operation.create_path_from_model(
354
+ path_params, model_dump_options=cls._operation.path_dump_options()
355
+ )
356
+ url = urljoin(context.base_url, path)
357
+ _verify_url(url)
358
+
359
+ async with aiohttp.ClientSession() as session:
360
+ response = await session.request(
361
+ method=cls._operation.request_method(),
362
+ url=url,
363
+ headers=headers,
364
+ json=cls._operation.create_payload_from_model(
365
+ payload=payload_model,
366
+ model_dump_options=cls._operation.payload_dump_options(),
367
+ ),
368
+ params=cls._operation.create_query_params_from_model(
369
+ query_params=query_model,
370
+ model_dump_options=cls._operation.query_params_dump_options(),
371
+ ),
372
+ )
373
+ response_json = await response.json()
374
+ return cls._operation.handle_response(
375
+ response=response_json,
376
+ model_validate_options=cls._operation.response_validate_options(),
377
+ )
378
+
379
+ return AiohttpRequestor
380
+
381
+
382
+ class RequestorType(StrEnum):
383
+ REQUESTS = "requests"
384
+ FAKE = "fake"
385
+ HTTPIX = "httpx"
386
+ AIOHTTP = "aiohttp"
387
+
388
+
389
+ def build_requestor(
390
+ requestor_type: RequestorType,
391
+ operation_type: type[
392
+ ApiOperationProtocol[
393
+ PathParamsSpec,
394
+ PathParamsType,
395
+ PayloadParamSpec,
396
+ PayloadType,
397
+ QueryParamsSpec,
398
+ QueryParamsType,
399
+ ResponseType,
400
+ ]
401
+ ],
402
+ combined_model: Callable[CombinedParamsSpec, CombinedParamsType],
403
+ return_value: dict[str, Any] | None = None,
404
+ **kwargs: Any,
405
+ ) -> type[EndpointRequestorProtocol[CombinedParamsSpec, ResponseType]]:
406
+ match requestor_type:
407
+ case RequestorType.REQUESTS:
408
+ return build_request_requestor(
409
+ operation_type=operation_type, combined_model=combined_model, **kwargs
410
+ )
411
+ case RequestorType.FAKE:
412
+ if return_value is None:
413
+ raise ValueError("return_value is required for fake requestor")
414
+ return build_fake_requestor(
415
+ operation_type=operation_type,
416
+ combined_model=combined_model,
417
+ return_value=return_value,
418
+ )
419
+ case RequestorType.HTTPIX:
420
+ return build_httpx_requestor(
421
+ operation_type=operation_type, combined_model=combined_model
422
+ )
423
+ case RequestorType.AIOHTTP:
424
+ return build_aiohttp_requestor(
425
+ operation_type=operation_type, combined_model=combined_model
426
+ )
427
+
428
+
429
+ if __name__ == "__main__":
430
+ from string import Template
431
+
432
+ from unique_toolkit._common.experimental.endpoint_builder import build_api_operation
433
+
434
+ class GetUserPathParams(BaseModel):
435
+ user_id: int
436
+
437
+ class GetUserRequestBody(BaseModel):
438
+ include_profile: bool = False
439
+
440
+ class GetUserQueryParams(BaseModel):
441
+ page: int = 1
442
+ limit: int = 10
443
+
444
+ class UserResponse(BaseModel):
445
+ id: int
446
+ name: str
447
+
448
+ class CombinedParams(GetUserPathParams, GetUserRequestBody, GetUserQueryParams):
449
+ pass
450
+
451
+ UserEndpoint = build_api_operation(
452
+ method=HttpMethods.GET,
453
+ path_template=Template("/users/{user_id}"),
454
+ path_params_constructor=GetUserPathParams,
455
+ payload_constructor=GetUserRequestBody,
456
+ query_params_constructor=GetUserQueryParams,
457
+ response_model_type=UserResponse,
458
+ )
459
+
460
+ FakeUserRequestor = build_fake_requestor(
461
+ operation_type=UserEndpoint,
462
+ combined_model=CombinedParams,
463
+ return_value={"id": 100, "name": "John Doe"},
464
+ )
465
+
466
+ # Note that the return value is a pydantic UserResponse object
467
+ response = FakeUserRequestor().request(
468
+ context=RequestContext(base_url="https://api.example.com", headers={"a": "b"}),
469
+ user_id=123,
470
+ include_profile=True,
471
+ )
472
+
473
+ RequestRequestor = build_request_requestor(
474
+ operation_type=UserEndpoint,
475
+ combined_model=CombinedParams,
476
+ )
477
+
478
+ # Check type hints
479
+ response = RequestRequestor().request(
480
+ context=RequestContext(base_url="https://api.example.com", headers={"a": "b"}),
481
+ user_id=123,
482
+ include_profile=True,
483
+ )
484
+
485
+ print(response.model_dump())
486
+ print(response.model_json_schema())
487
+ print(response.id)
488
+ print(response.name)
@@ -0,0 +1,9 @@
1
+ from pydantic import BaseModel
2
+
3
+ from unique_toolkit.agentic.tools.config import get_configuration_dict
4
+
5
+
6
+ class FeatureExtendedSourceSerialization(BaseModel):
7
+ """Mixin for experimental feature in Source serialization"""
8
+
9
+ model_config = get_configuration_dict()