agenta 0.52.6__py3-none-any.whl → 0.63.2__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 (271) hide show
  1. agenta/__init__.py +12 -3
  2. agenta/client/__init__.py +4 -4
  3. agenta/client/backend/__init__.py +4 -4
  4. agenta/client/backend/api_keys/client.py +2 -2
  5. agenta/client/backend/billing/client.py +2 -2
  6. agenta/client/backend/billing/raw_client.py +2 -2
  7. agenta/client/backend/client.py +56 -48
  8. agenta/client/backend/core/client_wrapper.py +2 -2
  9. agenta/client/backend/core/file.py +3 -1
  10. agenta/client/backend/core/http_client.py +3 -3
  11. agenta/client/backend/core/pydantic_utilities.py +13 -3
  12. agenta/client/backend/human_evaluations/client.py +2 -2
  13. agenta/client/backend/human_evaluations/raw_client.py +2 -2
  14. agenta/client/backend/organization/client.py +46 -34
  15. agenta/client/backend/organization/raw_client.py +32 -26
  16. agenta/client/backend/raw_client.py +26 -26
  17. agenta/client/backend/testsets/client.py +18 -18
  18. agenta/client/backend/testsets/raw_client.py +30 -30
  19. agenta/client/backend/types/__init__.py +4 -4
  20. agenta/client/backend/types/account_request.py +3 -1
  21. agenta/client/backend/types/account_response.py +3 -1
  22. agenta/client/backend/types/agenta_node_dto.py +3 -1
  23. agenta/client/backend/types/agenta_nodes_response.py +3 -1
  24. agenta/client/backend/types/agenta_root_dto.py +3 -1
  25. agenta/client/backend/types/agenta_roots_response.py +3 -1
  26. agenta/client/backend/types/agenta_tree_dto.py +3 -1
  27. agenta/client/backend/types/agenta_trees_response.py +3 -1
  28. agenta/client/backend/types/aggregated_result.py +3 -1
  29. agenta/client/backend/types/analytics_response.py +3 -1
  30. agenta/client/backend/types/annotation.py +6 -4
  31. agenta/client/backend/types/annotation_create.py +3 -1
  32. agenta/client/backend/types/annotation_edit.py +3 -1
  33. agenta/client/backend/types/annotation_link.py +3 -1
  34. agenta/client/backend/types/annotation_link_response.py +3 -1
  35. agenta/client/backend/types/annotation_query.py +3 -1
  36. agenta/client/backend/types/annotation_query_request.py +3 -1
  37. agenta/client/backend/types/annotation_reference.py +3 -1
  38. agenta/client/backend/types/annotation_references.py +3 -1
  39. agenta/client/backend/types/annotation_response.py +3 -1
  40. agenta/client/backend/types/annotations_response.py +3 -1
  41. agenta/client/backend/types/app.py +3 -1
  42. agenta/client/backend/types/app_variant_response.py +3 -1
  43. agenta/client/backend/types/app_variant_revision.py +3 -1
  44. agenta/client/backend/types/artifact.py +6 -4
  45. agenta/client/backend/types/base_output.py +3 -1
  46. agenta/client/backend/types/body_fetch_workflow_revision.py +3 -1
  47. agenta/client/backend/types/body_import_testset.py +3 -1
  48. agenta/client/backend/types/bucket_dto.py +3 -1
  49. agenta/client/backend/types/collect_status_response.py +3 -1
  50. agenta/client/backend/types/config_db.py +3 -1
  51. agenta/client/backend/types/config_dto.py +3 -1
  52. agenta/client/backend/types/config_response_model.py +3 -1
  53. agenta/client/backend/types/correct_answer.py +3 -1
  54. agenta/client/backend/types/create_app_output.py +3 -1
  55. agenta/client/backend/types/custom_model_settings_dto.py +3 -1
  56. agenta/client/backend/types/custom_provider_dto.py +3 -1
  57. agenta/client/backend/types/custom_provider_kind.py +1 -1
  58. agenta/client/backend/types/custom_provider_settings_dto.py +3 -1
  59. agenta/client/backend/types/delete_evaluation.py +3 -1
  60. agenta/client/backend/types/environment_output.py +3 -1
  61. agenta/client/backend/types/environment_output_extended.py +3 -1
  62. agenta/client/backend/types/environment_revision.py +3 -1
  63. agenta/client/backend/types/error.py +3 -1
  64. agenta/client/backend/types/evaluation.py +3 -1
  65. agenta/client/backend/types/evaluation_scenario.py +3 -1
  66. agenta/client/backend/types/evaluation_scenario_input.py +3 -1
  67. agenta/client/backend/types/evaluation_scenario_output.py +3 -1
  68. agenta/client/backend/types/evaluation_scenario_result.py +3 -1
  69. agenta/client/backend/types/evaluator.py +6 -4
  70. agenta/client/backend/types/evaluator_config.py +6 -4
  71. agenta/client/backend/types/evaluator_flags.py +3 -1
  72. agenta/client/backend/types/evaluator_mapping_output_interface.py +3 -1
  73. agenta/client/backend/types/evaluator_output_interface.py +3 -1
  74. agenta/client/backend/types/evaluator_query.py +3 -1
  75. agenta/client/backend/types/evaluator_query_request.py +3 -1
  76. agenta/client/backend/types/evaluator_request.py +3 -1
  77. agenta/client/backend/types/evaluator_response.py +3 -1
  78. agenta/client/backend/types/evaluators_response.py +3 -1
  79. agenta/client/backend/types/exception_dto.py +3 -1
  80. agenta/client/backend/types/extended_o_tel_tracing_response.py +3 -1
  81. agenta/client/backend/types/get_config_response.py +3 -1
  82. agenta/client/backend/types/header.py +3 -1
  83. agenta/client/backend/types/http_validation_error.py +3 -1
  84. agenta/client/backend/types/human_evaluation.py +3 -1
  85. agenta/client/backend/types/human_evaluation_scenario.py +3 -1
  86. agenta/client/backend/types/human_evaluation_scenario_input.py +3 -1
  87. agenta/client/backend/types/human_evaluation_scenario_output.py +3 -1
  88. agenta/client/backend/types/invite_request.py +3 -1
  89. agenta/client/backend/types/legacy_analytics_response.py +3 -1
  90. agenta/client/backend/types/legacy_data_point.py +3 -1
  91. agenta/client/backend/types/legacy_evaluator.py +3 -1
  92. agenta/client/backend/types/legacy_scope_request.py +3 -1
  93. agenta/client/backend/types/legacy_scopes_response.py +3 -1
  94. agenta/client/backend/types/legacy_subscription_request.py +3 -1
  95. agenta/client/backend/types/legacy_user_request.py +3 -1
  96. agenta/client/backend/types/legacy_user_response.py +3 -1
  97. agenta/client/backend/types/lifecycle_dto.py +3 -1
  98. agenta/client/backend/types/link_dto.py +3 -1
  99. agenta/client/backend/types/list_api_keys_response.py +3 -1
  100. agenta/client/backend/types/llm_run_rate_limit.py +3 -1
  101. agenta/client/backend/types/meta_request.py +3 -1
  102. agenta/client/backend/types/metrics_dto.py +3 -1
  103. agenta/client/backend/types/new_testset.py +3 -1
  104. agenta/client/backend/types/node_dto.py +3 -1
  105. agenta/client/backend/types/o_tel_context_dto.py +3 -1
  106. agenta/client/backend/types/o_tel_event.py +6 -4
  107. agenta/client/backend/types/o_tel_event_dto.py +3 -1
  108. agenta/client/backend/types/o_tel_extra_dto.py +3 -1
  109. agenta/client/backend/types/o_tel_flat_span.py +6 -4
  110. agenta/client/backend/types/o_tel_link.py +6 -4
  111. agenta/client/backend/types/o_tel_link_dto.py +3 -1
  112. agenta/client/backend/types/o_tel_links_response.py +3 -1
  113. agenta/client/backend/types/o_tel_span.py +1 -1
  114. agenta/client/backend/types/o_tel_span_dto.py +3 -1
  115. agenta/client/backend/types/o_tel_spans_tree.py +3 -1
  116. agenta/client/backend/types/o_tel_tracing_data_response.py +3 -1
  117. agenta/client/backend/types/o_tel_tracing_request.py +3 -1
  118. agenta/client/backend/types/o_tel_tracing_response.py +3 -1
  119. agenta/client/backend/types/organization.py +3 -1
  120. agenta/client/backend/types/organization_details.py +3 -1
  121. agenta/client/backend/types/organization_membership_request.py +3 -1
  122. agenta/client/backend/types/organization_output.py +3 -1
  123. agenta/client/backend/types/organization_request.py +3 -1
  124. agenta/client/backend/types/parent_dto.py +3 -1
  125. agenta/client/backend/types/project_membership_request.py +3 -1
  126. agenta/client/backend/types/project_request.py +3 -1
  127. agenta/client/backend/types/project_scope.py +3 -1
  128. agenta/client/backend/types/projects_response.py +3 -1
  129. agenta/client/backend/types/reference.py +6 -4
  130. agenta/client/backend/types/reference_dto.py +3 -1
  131. agenta/client/backend/types/reference_request_model.py +3 -1
  132. agenta/client/backend/types/result.py +3 -1
  133. agenta/client/backend/types/root_dto.py +3 -1
  134. agenta/client/backend/types/scopes_response_model.py +3 -1
  135. agenta/client/backend/types/secret_dto.py +3 -1
  136. agenta/client/backend/types/secret_response_dto.py +3 -1
  137. agenta/client/backend/types/simple_evaluation_output.py +3 -1
  138. agenta/client/backend/types/span_dto.py +6 -4
  139. agenta/client/backend/types/standard_provider_dto.py +3 -1
  140. agenta/client/backend/types/standard_provider_settings_dto.py +3 -1
  141. agenta/client/backend/types/status_dto.py +3 -1
  142. agenta/client/backend/types/tags_request.py +3 -1
  143. agenta/client/backend/types/testcase_response.py +6 -4
  144. agenta/client/backend/types/testset.py +6 -4
  145. agenta/client/backend/types/{test_set_output_response.py → testset_output_response.py} +4 -2
  146. agenta/client/backend/types/testset_request.py +3 -1
  147. agenta/client/backend/types/testset_response.py +3 -1
  148. agenta/client/backend/types/{test_set_simple_response.py → testset_simple_response.py} +4 -2
  149. agenta/client/backend/types/testsets_response.py +3 -1
  150. agenta/client/backend/types/time_dto.py +3 -1
  151. agenta/client/backend/types/tree_dto.py +3 -1
  152. agenta/client/backend/types/update_app_output.py +3 -1
  153. agenta/client/backend/types/user_request.py +3 -1
  154. agenta/client/backend/types/validation_error.py +3 -1
  155. agenta/client/backend/types/workflow_artifact.py +6 -4
  156. agenta/client/backend/types/workflow_data.py +3 -1
  157. agenta/client/backend/types/workflow_flags.py +3 -1
  158. agenta/client/backend/types/workflow_request.py +3 -1
  159. agenta/client/backend/types/workflow_response.py +3 -1
  160. agenta/client/backend/types/workflow_revision.py +6 -4
  161. agenta/client/backend/types/workflow_revision_request.py +3 -1
  162. agenta/client/backend/types/workflow_revision_response.py +3 -1
  163. agenta/client/backend/types/workflow_revisions_response.py +3 -1
  164. agenta/client/backend/types/workflow_variant.py +6 -4
  165. agenta/client/backend/types/workflow_variant_request.py +3 -1
  166. agenta/client/backend/types/workflow_variant_response.py +3 -1
  167. agenta/client/backend/types/workflow_variants_response.py +3 -1
  168. agenta/client/backend/types/workflows_response.py +3 -1
  169. agenta/client/backend/types/workspace.py +3 -1
  170. agenta/client/backend/types/workspace_member_response.py +3 -1
  171. agenta/client/backend/types/workspace_membership_request.py +3 -1
  172. agenta/client/backend/types/workspace_permission.py +3 -1
  173. agenta/client/backend/types/workspace_request.py +3 -1
  174. agenta/client/backend/types/workspace_response.py +3 -1
  175. agenta/client/backend/vault/raw_client.py +4 -4
  176. agenta/client/backend/workspace/client.py +2 -2
  177. agenta/client/client.py +102 -88
  178. agenta/sdk/__init__.py +52 -3
  179. agenta/sdk/agenta_init.py +43 -16
  180. agenta/sdk/assets.py +23 -15
  181. agenta/sdk/context/serving.py +20 -8
  182. agenta/sdk/context/tracing.py +40 -22
  183. agenta/sdk/contexts/__init__.py +0 -0
  184. agenta/sdk/contexts/routing.py +38 -0
  185. agenta/sdk/contexts/running.py +57 -0
  186. agenta/sdk/contexts/tracing.py +86 -0
  187. agenta/sdk/decorators/__init__.py +1 -0
  188. agenta/sdk/decorators/routing.py +284 -0
  189. agenta/sdk/decorators/running.py +692 -98
  190. agenta/sdk/decorators/serving.py +20 -21
  191. agenta/sdk/decorators/tracing.py +176 -131
  192. agenta/sdk/engines/__init__.py +0 -0
  193. agenta/sdk/engines/running/__init__.py +0 -0
  194. agenta/sdk/engines/running/utils.py +17 -0
  195. agenta/sdk/engines/tracing/__init__.py +1 -0
  196. agenta/sdk/engines/tracing/attributes.py +185 -0
  197. agenta/sdk/engines/tracing/conventions.py +49 -0
  198. agenta/sdk/engines/tracing/exporters.py +130 -0
  199. agenta/sdk/engines/tracing/inline.py +1154 -0
  200. agenta/sdk/engines/tracing/processors.py +190 -0
  201. agenta/sdk/engines/tracing/propagation.py +102 -0
  202. agenta/sdk/engines/tracing/spans.py +136 -0
  203. agenta/sdk/engines/tracing/tracing.py +324 -0
  204. agenta/sdk/evaluations/__init__.py +2 -0
  205. agenta/sdk/evaluations/metrics.py +37 -0
  206. agenta/sdk/evaluations/preview/__init__.py +0 -0
  207. agenta/sdk/evaluations/preview/evaluate.py +765 -0
  208. agenta/sdk/evaluations/preview/utils.py +861 -0
  209. agenta/sdk/evaluations/results.py +66 -0
  210. agenta/sdk/evaluations/runs.py +153 -0
  211. agenta/sdk/evaluations/scenarios.py +48 -0
  212. agenta/sdk/litellm/litellm.py +12 -0
  213. agenta/sdk/litellm/mockllm.py +6 -8
  214. agenta/sdk/litellm/mocks/__init__.py +5 -5
  215. agenta/sdk/managers/applications.py +304 -0
  216. agenta/sdk/managers/config.py +2 -2
  217. agenta/sdk/managers/evaluations.py +0 -0
  218. agenta/sdk/managers/evaluators.py +303 -0
  219. agenta/sdk/managers/secrets.py +161 -24
  220. agenta/sdk/managers/shared.py +3 -1
  221. agenta/sdk/managers/testsets.py +441 -0
  222. agenta/sdk/managers/vault.py +3 -3
  223. agenta/sdk/middleware/auth.py +0 -176
  224. agenta/sdk/middleware/config.py +27 -9
  225. agenta/sdk/middleware/vault.py +204 -9
  226. agenta/sdk/middlewares/__init__.py +0 -0
  227. agenta/sdk/middlewares/routing/__init__.py +0 -0
  228. agenta/sdk/middlewares/routing/auth.py +263 -0
  229. agenta/sdk/middlewares/routing/cors.py +30 -0
  230. agenta/sdk/middlewares/routing/otel.py +29 -0
  231. agenta/sdk/middlewares/running/__init__.py +0 -0
  232. agenta/sdk/middlewares/running/normalizer.py +321 -0
  233. agenta/sdk/middlewares/running/resolver.py +161 -0
  234. agenta/sdk/middlewares/running/vault.py +140 -0
  235. agenta/sdk/models/__init__.py +0 -0
  236. agenta/sdk/models/blobs.py +33 -0
  237. agenta/sdk/models/evaluations.py +119 -0
  238. agenta/sdk/models/git.py +126 -0
  239. agenta/sdk/models/shared.py +167 -0
  240. agenta/sdk/models/testsets.py +163 -0
  241. agenta/sdk/models/tracing.py +202 -0
  242. agenta/sdk/models/workflows.py +753 -0
  243. agenta/sdk/tracing/attributes.py +4 -4
  244. agenta/sdk/tracing/exporters.py +67 -17
  245. agenta/sdk/tracing/inline.py +37 -45
  246. agenta/sdk/tracing/processors.py +97 -0
  247. agenta/sdk/tracing/propagation.py +3 -1
  248. agenta/sdk/tracing/spans.py +4 -0
  249. agenta/sdk/tracing/tracing.py +13 -15
  250. agenta/sdk/types.py +222 -22
  251. agenta/sdk/utils/cache.py +1 -1
  252. agenta/sdk/utils/client.py +38 -0
  253. agenta/sdk/utils/helpers.py +13 -12
  254. agenta/sdk/utils/logging.py +18 -78
  255. agenta/sdk/utils/references.py +23 -0
  256. agenta/sdk/workflows/builtin.py +600 -0
  257. agenta/sdk/workflows/configurations.py +22 -0
  258. agenta/sdk/workflows/errors.py +292 -0
  259. agenta/sdk/workflows/handlers.py +1791 -0
  260. agenta/sdk/workflows/interfaces.py +948 -0
  261. agenta/sdk/workflows/sandbox.py +118 -0
  262. agenta/sdk/workflows/utils.py +303 -6
  263. {agenta-0.52.6.dist-info → agenta-0.63.2.dist-info}/METADATA +37 -33
  264. agenta-0.63.2.dist-info/RECORD +421 -0
  265. {agenta-0.52.6.dist-info → agenta-0.63.2.dist-info}/WHEEL +1 -1
  266. agenta/sdk/middleware/adapt.py +0 -253
  267. agenta/sdk/middleware/base.py +0 -40
  268. agenta/sdk/middleware/flags.py +0 -40
  269. agenta/sdk/workflows/types.py +0 -472
  270. agenta-0.52.6.dist-info/RECORD +0 -371
  271. /agenta/sdk/{workflows → engines/running}/registry.py +0 -0
agenta/sdk/types.py CHANGED
@@ -3,7 +3,7 @@ from dataclasses import dataclass
3
3
  from typing import List, Union, Optional, Dict, Literal, Any
4
4
 
5
5
  from pydantic import ConfigDict, BaseModel, HttpUrl
6
- from pydantic import BaseModel, Field, model_validator
6
+ from pydantic import BaseModel, Field, model_validator, AliasChoices
7
7
 
8
8
  from starlette.responses import StreamingResponse
9
9
 
@@ -21,13 +21,19 @@ def MCField( # pylint: disable=invalid-name
21
21
  default: str,
22
22
  choices: Union[List[str], Dict[str, List[str]]],
23
23
  ) -> Field:
24
- field = Field(default=default, description="ID of the model to use")
24
+ # Pydantic 2.12+ no longer allows post-creation mutation of field properties
25
25
  if isinstance(choices, dict):
26
- field.json_schema_extra = {"choices": choices, "x-parameter": "grouped_choice"}
26
+ json_extra = {"choices": choices, "x-parameter": "grouped_choice"}
27
27
  elif isinstance(choices, list):
28
- field.json_schema_extra = {"choices": choices, "x-parameter": "choice"}
29
-
30
- return field
28
+ json_extra = {"choices": choices, "x-parameter": "choice"}
29
+ else:
30
+ json_extra = {}
31
+
32
+ return Field(
33
+ default=default,
34
+ description="ID of the model to use",
35
+ json_schema_extra=json_extra,
36
+ )
31
37
 
32
38
 
33
39
  class LLMTokenUsage(BaseModel):
@@ -65,15 +71,15 @@ class StreamResponse(StreamingResponse):
65
71
  ):
66
72
  headers = dict(extra_headers or {})
67
73
  if version is not None:
68
- headers["X-ag-version"] = version
74
+ headers["x-ag-version"] = version
69
75
  if content_type:
70
- headers["X-ag-content-type"] = content_type
76
+ headers["x-ag-content-type"] = content_type
71
77
  if tree_id:
72
- headers["X-ag-tree-id"] = tree_id
78
+ headers["x-ag-tree-id"] = tree_id
73
79
  if trace_id:
74
- headers["X-ag-trace-id"] = trace_id
80
+ headers["x-ag-trace-id"] = trace_id
75
81
  if span_id:
76
- headers["X-ag-span-id"] = span_id
82
+ headers["x-ag-span-id"] = span_id
77
83
 
78
84
  super().__init__(
79
85
  content=content,
@@ -329,7 +335,29 @@ class ContentPartImage(BaseModel):
329
335
  image_url: ImageURL
330
336
 
331
337
 
332
- ContentPart = Union[ContentPartText, ContentPartImage]
338
+ class FileInput(BaseModel):
339
+ file_id: Optional[str] = Field(
340
+ default=None,
341
+ alias="file_id",
342
+ validation_alias=AliasChoices("file_id", "fileId"),
343
+ )
344
+ file_data: Optional[str] = Field(
345
+ default=None,
346
+ alias="file_data",
347
+ validation_alias=AliasChoices("file_data", "fileData"),
348
+ )
349
+ filename: Optional[str] = None
350
+ format: Optional[str] = None
351
+
352
+ model_config = {"populate_by_name": True}
353
+
354
+
355
+ class ContentPartFile(BaseModel):
356
+ type: Literal["file"] = "file"
357
+ file: FileInput
358
+
359
+
360
+ ContentPart = Union[ContentPartText, ContentPartImage, ContentPartFile]
333
361
 
334
362
 
335
363
  class Message(BaseModel):
@@ -381,7 +409,7 @@ class ModelConfig(BaseModel):
381
409
  """Configuration for model parameters"""
382
410
 
383
411
  model: str = MCField(
384
- default="gpt-3.5-turbo",
412
+ default="gpt-4o-mini",
385
413
  choices=supported_llm_models,
386
414
  )
387
415
 
@@ -415,6 +443,14 @@ class ModelConfig(BaseModel):
415
443
  le=2.0,
416
444
  description="Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far",
417
445
  )
446
+ reasoning_effort: Optional[Literal["none", "low", "medium", "high"]] = Field(
447
+ default=None,
448
+ description="Controls the reasoning effort for thinking models. Options: 'none' (cost-optimized, 0 tokens), 'low' (1024 tokens), 'medium' (2048 tokens), 'high' (4096 tokens)",
449
+ json_schema_extra={
450
+ "x-parameter": "choice",
451
+ "enum": ["none", "low", "medium", "high"],
452
+ },
453
+ )
418
454
  response_format: Optional[ResponseFormat] = Field(
419
455
  default=None,
420
456
  description="An object specifying the format that the model must output",
@@ -456,6 +492,154 @@ class TemplateFormatError(PromptTemplateError):
456
492
  super().__init__(message)
457
493
 
458
494
 
495
+ import json
496
+ import re
497
+ from typing import Any, Dict, Iterable, Tuple, Optional
498
+
499
+ # --- Optional dependency: python-jsonpath (provides JSONPath + JSON Pointer) ---
500
+ try:
501
+ import jsonpath # ✅ use module API
502
+ from jsonpath import JSONPointer # pointer class is fine to use
503
+ except Exception:
504
+ jsonpath = None
505
+ JSONPointer = None
506
+
507
+ # ========= Scheme detection =========
508
+
509
+
510
+ def detect_scheme(expr: str) -> str:
511
+ """Return 'json-path', 'json-pointer', or 'dot-notation' based on the placeholder prefix."""
512
+ if expr.startswith("$"):
513
+ return "json-path"
514
+ if expr.startswith("/"):
515
+ return "json-pointer"
516
+ return "dot-notation"
517
+
518
+
519
+ # ========= Resolvers =========
520
+
521
+
522
+ def resolve_dot_notation(expr: str, data: dict) -> object:
523
+ if "[" in expr or "]" in expr:
524
+ raise KeyError(f"Bracket syntax is not supported in dot-notation: {expr!r}")
525
+
526
+ # First, check if the expression exists as a literal key (e.g., "topic.story" as a single key)
527
+ # This allows users to use dots in their variable names without nested access
528
+ if expr in data:
529
+ return data[expr]
530
+
531
+ # If not found as a literal key, try to parse as dot-notation path
532
+ cur = data
533
+ for token in (p for p in expr.split(".") if p):
534
+ if isinstance(cur, list) and token.isdigit():
535
+ cur = cur[int(token)]
536
+ else:
537
+ if not isinstance(cur, dict):
538
+ raise KeyError(
539
+ f"Cannot access key {token!r} on non-dict while resolving {expr!r}"
540
+ )
541
+ if token not in cur:
542
+ raise KeyError(f"Missing key {token!r} while resolving {expr!r}")
543
+ cur = cur[token]
544
+ return cur
545
+
546
+
547
+ def resolve_json_path(expr: str, data: dict) -> object:
548
+ if jsonpath is None:
549
+ raise ImportError("python-jsonpath is required for json-path ($...)")
550
+
551
+ if not (expr == "$" or expr.startswith("$.") or expr.startswith("$[")):
552
+ raise ValueError(
553
+ f"Invalid json-path expression {expr!r}. "
554
+ "Must start with '$', '$.' or '$[' (no implicit normalization)."
555
+ )
556
+
557
+ # Use package-level APIf
558
+ results = jsonpath.findall(expr, data) # always returns a list
559
+ return results[0] if len(results) == 1 else results
560
+
561
+
562
+ def resolve_json_pointer(expr: str, data: Dict[str, Any]) -> Any:
563
+ """Resolve a JSON Pointer; returns a single value."""
564
+ if JSONPointer is None:
565
+ raise ImportError("python-jsonpath is required for json-pointer (/...)")
566
+ return JSONPointer(expr).resolve(data)
567
+
568
+
569
+ def resolve_any(expr: str, data: Dict[str, Any]) -> Any:
570
+ """Dispatch to the right resolver based on detected scheme."""
571
+ scheme = detect_scheme(expr)
572
+ if scheme == "json-path":
573
+ return resolve_json_path(expr, data)
574
+ if scheme == "json-pointer":
575
+ return resolve_json_pointer(expr, data)
576
+ return resolve_dot_notation(expr, data)
577
+
578
+
579
+ # ========= Placeholder & coercion helpers =========
580
+
581
+ _PLACEHOLDER_RE = re.compile(r"\{\{\s*(.*?)\s*\}\}")
582
+
583
+
584
+ def extract_placeholders(template: str) -> Iterable[str]:
585
+ """Yield the inner text of all {{ ... }} occurrences (trimmed)."""
586
+ for m in _PLACEHOLDER_RE.finditer(template):
587
+ yield m.group(1).strip()
588
+
589
+
590
+ def coerce_to_str(value: Any) -> str:
591
+ """Pretty stringify values for embedding into templates."""
592
+ if isinstance(value, (dict, list)):
593
+ return json.dumps(value, ensure_ascii=False)
594
+ return str(value)
595
+
596
+
597
+ def build_replacements(
598
+ placeholders: Iterable[str], data: Dict[str, Any]
599
+ ) -> Tuple[Dict[str, str], set]:
600
+ """
601
+ Resolve all placeholders against data.
602
+ Returns (replacements, unresolved_placeholders).
603
+ """
604
+ replacements: Dict[str, str] = {}
605
+ unresolved: set = set()
606
+ for expr in set(placeholders):
607
+ try:
608
+ val = resolve_any(expr, data)
609
+ # Escape backslashes to avoid regex replacement surprises
610
+ replacements[expr] = coerce_to_str(val).replace("\\", "\\\\")
611
+ except Exception:
612
+ unresolved.add(expr)
613
+ return replacements, unresolved
614
+
615
+
616
+ def apply_replacements(template: str, replacements: Dict[str, str]) -> str:
617
+ """Replace {{ expr }} using a callback to avoid regex-injection issues."""
618
+
619
+ def _repl(m: re.Match) -> str:
620
+ expr = m.group(1).strip()
621
+ return replacements.get(expr, m.group(0))
622
+
623
+ return _PLACEHOLDER_RE.sub(_repl, template)
624
+
625
+
626
+ def compute_truly_unreplaced(original: set, rendered: str) -> set:
627
+ """Only count placeholders that were in the original template and remain."""
628
+ now = set(extract_placeholders(rendered))
629
+ return original & now
630
+
631
+
632
+ def missing_lib_hints(unreplaced: set) -> Optional[str]:
633
+ """Suggest installing python-jsonpath if placeholders indicate json-path or json-pointer usage."""
634
+ if any(expr.startswith("$") or expr.startswith("/") for expr in unreplaced) and (
635
+ jsonpath is None or JSONPointer is None
636
+ ):
637
+ return (
638
+ "Install python-jsonpath to enable json-path ($...) and json-pointer (/...)"
639
+ )
640
+ return None
641
+
642
+
459
643
  class PromptTemplate(BaseModel):
460
644
  """A template for generating prompts with formatting capabilities"""
461
645
 
@@ -502,6 +686,7 @@ class PromptTemplate(BaseModel):
502
686
  try:
503
687
  if self.template_format == "fstring":
504
688
  return content.format(**kwargs)
689
+
505
690
  elif self.template_format == "jinja2":
506
691
  from jinja2 import Template, TemplateError
507
692
 
@@ -512,22 +697,33 @@ class PromptTemplate(BaseModel):
512
697
  f"Jinja2 template error in content: '{content}'. Error: {str(e)}",
513
698
  original_error=e,
514
699
  )
700
+
515
701
  elif self.template_format == "curly":
516
- import re
702
+ original_placeholders = set(extract_placeholders(content))
703
+
704
+ replacements, _unresolved = build_replacements(
705
+ original_placeholders, kwargs
706
+ )
707
+
708
+ result = apply_replacements(content, replacements)
517
709
 
518
- result = content
519
- for key, value in kwargs.items():
520
- result = re.sub(r"\{\{" + key + r"\}\}", str(value), result)
521
- if re.search(r"\{\{.*?\}\}", result):
522
- unreplaced = re.findall(r"\{\{(.*?)\}\}", result)
710
+ truly_unreplaced = compute_truly_unreplaced(
711
+ original_placeholders, result
712
+ )
713
+ if truly_unreplaced:
714
+ hint = missing_lib_hints(truly_unreplaced)
715
+ suffix = f" Hint: {hint}" if hint else ""
523
716
  raise TemplateFormatError(
524
- f"Unreplaced variables in curly template: {unreplaced}"
717
+ f"Unreplaced variables in curly template: {sorted(truly_unreplaced)}.{suffix}"
525
718
  )
719
+
526
720
  return result
721
+
527
722
  else:
528
723
  raise TemplateFormatError(
529
724
  f"Unknown template format: {self.template_format}"
530
725
  )
726
+
531
727
  except KeyError as e:
532
728
  key = str(e).strip("'")
533
729
  raise TemplateFormatError(
@@ -535,7 +731,8 @@ class PromptTemplate(BaseModel):
535
731
  )
536
732
  except Exception as e:
537
733
  raise TemplateFormatError(
538
- f"Error formatting template '{content}': {str(e)}", original_error=e
734
+ f"Error formatting template '{content}': {str(e)}",
735
+ original_error=e,
539
736
  )
540
737
 
541
738
  def _substitute_variables(self, obj: Any, kwargs: Dict[str, Any]) -> Any:
@@ -610,7 +807,7 @@ class PromptTemplate(BaseModel):
610
807
  )
611
808
  )
612
809
 
613
- new_llm_config = self.llm_config.copy(deep=True)
810
+ new_llm_config = self.llm_config.model_copy(deep=True)
614
811
  if new_llm_config.response_format is not None:
615
812
  rf_dict = new_llm_config.response_format.model_dump(by_alias=True)
616
813
  substituted = self._substitute_variables(rf_dict, kwargs)
@@ -652,6 +849,9 @@ class PromptTemplate(BaseModel):
652
849
  if self.llm_config.presence_penalty is not None:
653
850
  kwargs["presence_penalty"] = self.llm_config.presence_penalty
654
851
 
852
+ if self.llm_config.reasoning_effort is not None:
853
+ kwargs["reasoning_effort"] = self.llm_config.reasoning_effort
854
+
655
855
  if self.llm_config.response_format:
656
856
  kwargs["response_format"] = self.llm_config.response_format.dict(
657
857
  by_alias=True
agenta/sdk/utils/cache.py CHANGED
@@ -5,7 +5,7 @@ from collections import OrderedDict
5
5
  from threading import Lock
6
6
 
7
7
  CACHE_CAPACITY = int(getenv("AGENTA_MIDDLEWARE_CACHE_CAPACITY", "512"))
8
- CACHE_TTL = int(getenv("AGENTA_MIDDLEWARE_CACHE_TTL", str(5 * 60))) # 5 minutes
8
+ CACHE_TTL = int(getenv("AGENTA_MIDDLEWARE_CACHE_TTL", str(1 * 60))) # 1 minutes
9
9
 
10
10
 
11
11
  class TTLLRUCache:
@@ -0,0 +1,38 @@
1
+ import requests
2
+
3
+ BASE_TIMEOUT = 10
4
+
5
+ from agenta.sdk.utils.logging import get_module_logger
6
+
7
+ import agenta as ag
8
+
9
+ log = get_module_logger(__name__)
10
+
11
+
12
+ def authed_api():
13
+ """
14
+ Preconfigured requests for authenticated endpoints (supports all methods).
15
+ """
16
+
17
+ api_url = ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.api_url
18
+ api_key = ag.DEFAULT_AGENTA_SINGLETON_INSTANCE.api_key
19
+
20
+ if not api_url or not api_key:
21
+ log.error("Please call ag.init() first.")
22
+ log.error("And don't forget to set AGENTA_API_URL and AGENTA_API_KEY.")
23
+ raise ValueError("API URL and API Key must be set.")
24
+
25
+ def _request(method: str, endpoint: str, **kwargs):
26
+ url = f"{api_url}{endpoint}"
27
+ headers = kwargs.pop("headers", {})
28
+ headers.setdefault("Authorization", f"ApiKey {api_key}")
29
+
30
+ return requests.request(
31
+ method=method,
32
+ url=url,
33
+ headers=headers,
34
+ timeout=BASE_TIMEOUT,
35
+ **kwargs,
36
+ )
37
+
38
+ return _request
@@ -20,25 +20,26 @@ def parse_url(url: str) -> str:
20
20
  str: The parsed or rewritten URL suitable for the current environment and Docker network mode.
21
21
  """
22
22
 
23
- # Normalize: remove trailing slash and /api suffix
24
23
  url = url.rstrip("/")
25
- if url.endswith("/api"):
26
- url = url[: -len("/api")]
27
24
 
28
- if "localhost" not in url:
25
+ if "localhost" not in url and "0.0.0.0" not in url:
29
26
  return url
30
27
 
31
- internal_url = os.getenv("AGENTA_API_INTERNAL_URL")
32
- if internal_url:
33
- return internal_url
34
-
35
28
  docker_network_mode = os.getenv("DOCKER_NETWORK_MODE")
36
29
 
37
30
  if docker_network_mode and docker_network_mode.lower() == "bridge":
38
- return url.replace("localhost", "host.docker.internal")
39
-
40
- if not docker_network_mode or docker_network_mode.lower() == "host":
31
+ return url.replace(
32
+ "localhost",
33
+ "host.docker.internal",
34
+ ).replace(
35
+ "0.0.0.0",
36
+ "host.docker.internal",
37
+ )
38
+
39
+ if (
40
+ not docker_network_mode
41
+ or (docker_network_mode and docker_network_mode.lower()) == "host"
42
+ ):
41
43
  return url
42
44
 
43
- # For any other network mode, return the URL unchanged
44
45
  return url
@@ -8,15 +8,6 @@ import logging
8
8
  import structlog
9
9
  from structlog.typing import EventDict, WrappedLogger, Processor
10
10
 
11
- # from datetime import datetime
12
- # from logging.handlers import RotatingFileHandler
13
-
14
- # from opentelemetry.trace import get_current_span
15
- # from opentelemetry._logs import set_logger_provider
16
- # from opentelemetry.sdk._logs import LoggingHandler, LoggerProvider
17
- # from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
18
- # from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
19
-
20
11
  TRACE_LEVEL = 1
21
12
  logging.TRACE = TRACE_LEVEL
22
13
  logging.addLevelName(TRACE_LEVEL, "TRACE")
@@ -40,15 +31,6 @@ structlog.stdlib.BoundLogger.trace = bound_logger_trace
40
31
  AGENTA_LOG_CONSOLE_ENABLED = os.getenv("AGENTA_LOG_CONSOLE_ENABLED", "true") == "true"
41
32
  AGENTA_LOG_CONSOLE_LEVEL = os.getenv("AGENTA_LOG_CONSOLE_LEVEL", "TRACE").upper()
42
33
 
43
- # AGENTA_LOG_OTLP_ENABLED = os.getenv("AGENTA_LOG_OTLP_ENABLED", "false") == "true"
44
- # AGENTA_LOG_OTLP_LEVEL = os.getenv("AGENTA_LOG_OTLP_LEVEL", "INFO").upper()
45
-
46
- # AGENTA_LOG_FILE_ENABLED = os.getenv("AGENTA_LOG_FILE_ENABLED", "true") == "true"
47
- # AGENTA_LOG_FILE_LEVEL = os.getenv("AGENTA_LOG_FILE_LEVEL", "WARNING").upper()
48
- # AGENTA_LOG_FILE_BASE = os.getenv("AGENTA_LOG_FILE_PATH", "error")
49
- # LOG_FILE_DATE = datetime.utcnow().strftime("%Y-%m-%d")
50
- # AGENTA_LOG_FILE_PATH = f"{AGENTA_LOG_FILE_BASE}-{LOG_FILE_DATE}.log"
51
-
52
34
  # COLORS
53
35
  LEVEL_COLORS = {
54
36
  "TRACE": "\033[97m",
@@ -88,15 +70,6 @@ def process_positional_args(_, __, event_dict: EventDict) -> EventDict:
88
70
  return event_dict
89
71
 
90
72
 
91
- # def add_trace_context(_, __, event_dict: EventDict) -> EventDict:
92
- # span = get_current_span()
93
- # if span and span.get_span_context().is_valid:
94
- # ctx = span.get_span_context()
95
- # event_dict["TraceId"] = format(ctx.trace_id, "032x")
96
- # event_dict["SpanId"] = format(ctx.span_id, "016x")
97
- # return event_dict
98
-
99
-
100
73
  def add_logger_info(
101
74
  logger: WrappedLogger, method_name: str, event_dict: EventDict
102
75
  ) -> EventDict:
@@ -143,36 +116,9 @@ def colored_console_renderer() -> Processor:
143
116
  return render
144
117
 
145
118
 
146
- # def plain_renderer() -> Processor:
147
- # hidden = {
148
- # "SeverityText",
149
- # "SeverityNumber",
150
- # "MethodName",
151
- # "logger_factory",
152
- # "LoggerName",
153
- # "level",
154
- # }
155
-
156
- # def render(_, __, event_dict: EventDict) -> str:
157
- # ts = event_dict.pop("Timestamp", "")[:23] + "Z"
158
- # level = event_dict.get("level", "")
159
- # msg = event_dict.pop("event", "")
160
- # padded = f"[{level:<5}]"
161
- # logger = f"[{event_dict.pop('logger', '')}]"
162
- # extras = " ".join(f"{k}={v}" for k, v in event_dict.items() if k not in hidden)
163
- # return f"{ts} {padded} {msg} {logger} {extras}"
164
-
165
- # return render
166
-
167
-
168
- # def json_renderer() -> Processor:
169
- # return structlog.processors.JSONRenderer()
170
-
171
-
172
119
  SHARED_PROCESSORS: list[Processor] = [
173
120
  structlog.processors.TimeStamper(fmt="iso", utc=True, key="Timestamp"),
174
121
  process_positional_args,
175
- # add_trace_context,
176
122
  add_logger_info,
177
123
  structlog.processors.format_exc_info,
178
124
  structlog.processors.dict_tracebacks,
@@ -193,35 +139,29 @@ def create_struct_logger(
193
139
  )
194
140
 
195
141
 
142
+ # Guard against double initialization
143
+ _LOGGING_CONFIGURED = False
144
+
196
145
  # CONFIGURE HANDLERS AND STRUCTLOG LOGGERS
197
146
  handlers = []
198
147
  loggers = []
199
148
 
200
- if AGENTA_LOG_CONSOLE_ENABLED:
201
- h = logging.StreamHandler(sys.stdout)
202
- h.setLevel(getattr(logging, AGENTA_LOG_CONSOLE_LEVEL, TRACE_LEVEL))
203
- h.setFormatter(logging.Formatter("%(message)s"))
204
- logging.getLogger("console").addHandler(h)
205
- loggers.append(create_struct_logger([colored_console_renderer()], "console"))
149
+ if AGENTA_LOG_CONSOLE_ENABLED and not _LOGGING_CONFIGURED:
150
+ _LOGGING_CONFIGURED = True
151
+
152
+ # Check if console logger already has handlers (from OSS module)
153
+ console_logger = logging.getLogger("console")
206
154
 
207
- # if AGENTA_LOG_FILE_ENABLED:
208
- # h = RotatingFileHandler(AGENTA_LOG_FILE_PATH, maxBytes=10 * 1024 * 1024, backupCount=5)
209
- # h.setLevel(getattr(logging, AGENTA_LOG_FILE_LEVEL, logging.WARNING))
210
- # h.setFormatter(logging.Formatter("%(message)s"))
211
- # logging.getLogger("file").addHandler(h)
212
- # loggers.append(create_struct_logger([plain_renderer()], "file"))
213
-
214
- # if AGENTA_LOG_OTLP_ENABLED:
215
- # provider = LoggerProvider()
216
- # exporter = OTLPLogExporter()
217
- # provider.add_log_record_processor(BatchLogRecordProcessor(exporter))
218
- # set_logger_provider(provider)
219
- # h = LoggingHandler(
220
- # level=getattr(logging, AGENTA_LOG_OTLP_LEVEL, logging.INFO), logger_provider=provider
221
- # )
222
- # h.setFormatter(logging.Formatter("%(message)s"))
223
- # logging.getLogger("otel").addHandler(h)
224
- # loggers.append(create_struct_logger([json_renderer()], "otel"))
155
+ if not console_logger.handlers:
156
+ # Only add handler if it doesn't exist yet
157
+ h = logging.StreamHandler(sys.stdout)
158
+ h.setLevel(getattr(logging, AGENTA_LOG_CONSOLE_LEVEL, TRACE_LEVEL))
159
+ h.setFormatter(logging.Formatter("%(message)s"))
160
+ console_logger.addHandler(h)
161
+ console_logger.setLevel(TRACE_LEVEL)
162
+ console_logger.propagate = False
163
+
164
+ loggers.append(create_struct_logger([colored_console_renderer()], "console"))
225
165
 
226
166
 
227
167
  class MultiLogger:
@@ -0,0 +1,23 @@
1
+ from uuid import UUID
2
+ import re
3
+ import unicodedata
4
+
5
+
6
+ def get_slug_from_name_and_id(
7
+ name: str,
8
+ id: UUID, # pylint: disable=redefined-builtin
9
+ ) -> str:
10
+ # Normalize Unicode (e.g., é → e)
11
+ name = unicodedata.normalize("NFKD", name)
12
+ # Remove non-ASCII characters
13
+ name = name.encode("ascii", "ignore").decode("ascii")
14
+ # Lowercase and remove non-word characters except hyphens and spaces
15
+ name = re.sub(r"[^\w\s-]", "", name.lower())
16
+ # Replace any sequence of hyphens or whitespace with a single hyphen
17
+ name = re.sub(r"[-\s]+", "-", name)
18
+ # Trim leading/trailing hyphens
19
+ name = name.strip("-")
20
+ # Last 12 characters of the ID
21
+ slug = f"{name}-{id.hex[-12:]}"
22
+
23
+ return slug.lower()