aeri-python 4.0.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.
Files changed (391) hide show
  1. aeri/__init__.py +72 -0
  2. aeri/_client/_validation.py +204 -0
  3. aeri/_client/attributes.py +188 -0
  4. aeri/_client/client.py +3761 -0
  5. aeri/_client/constants.py +65 -0
  6. aeri/_client/datasets.py +302 -0
  7. aeri/_client/environment_variables.py +158 -0
  8. aeri/_client/get_client.py +149 -0
  9. aeri/_client/observe.py +661 -0
  10. aeri/_client/propagation.py +475 -0
  11. aeri/_client/resource_manager.py +510 -0
  12. aeri/_client/span.py +1519 -0
  13. aeri/_client/span_filter.py +76 -0
  14. aeri/_client/span_processor.py +206 -0
  15. aeri/_client/utils.py +132 -0
  16. aeri/_task_manager/media_manager.py +331 -0
  17. aeri/_task_manager/media_upload_consumer.py +44 -0
  18. aeri/_task_manager/media_upload_queue.py +12 -0
  19. aeri/_task_manager/score_ingestion_consumer.py +208 -0
  20. aeri/_task_manager/task_manager.py +475 -0
  21. aeri/_utils/__init__.py +19 -0
  22. aeri/_utils/environment.py +34 -0
  23. aeri/_utils/error_logging.py +47 -0
  24. aeri/_utils/parse_error.py +99 -0
  25. aeri/_utils/prompt_cache.py +188 -0
  26. aeri/_utils/request.py +137 -0
  27. aeri/_utils/serializer.py +205 -0
  28. aeri/api/.fern/metadata.json +14 -0
  29. aeri/api/__init__.py +836 -0
  30. aeri/api/annotation_queues/__init__.py +82 -0
  31. aeri/api/annotation_queues/client.py +1111 -0
  32. aeri/api/annotation_queues/raw_client.py +2288 -0
  33. aeri/api/annotation_queues/types/__init__.py +84 -0
  34. aeri/api/annotation_queues/types/annotation_queue.py +28 -0
  35. aeri/api/annotation_queues/types/annotation_queue_assignment_request.py +16 -0
  36. aeri/api/annotation_queues/types/annotation_queue_item.py +34 -0
  37. aeri/api/annotation_queues/types/annotation_queue_object_type.py +26 -0
  38. aeri/api/annotation_queues/types/annotation_queue_status.py +22 -0
  39. aeri/api/annotation_queues/types/create_annotation_queue_assignment_response.py +18 -0
  40. aeri/api/annotation_queues/types/create_annotation_queue_item_request.py +25 -0
  41. aeri/api/annotation_queues/types/create_annotation_queue_request.py +20 -0
  42. aeri/api/annotation_queues/types/delete_annotation_queue_assignment_response.py +14 -0
  43. aeri/api/annotation_queues/types/delete_annotation_queue_item_response.py +15 -0
  44. aeri/api/annotation_queues/types/paginated_annotation_queue_items.py +17 -0
  45. aeri/api/annotation_queues/types/paginated_annotation_queues.py +17 -0
  46. aeri/api/annotation_queues/types/update_annotation_queue_item_request.py +15 -0
  47. aeri/api/blob_storage_integrations/__init__.py +73 -0
  48. aeri/api/blob_storage_integrations/client.py +550 -0
  49. aeri/api/blob_storage_integrations/raw_client.py +976 -0
  50. aeri/api/blob_storage_integrations/types/__init__.py +77 -0
  51. aeri/api/blob_storage_integrations/types/blob_storage_export_frequency.py +26 -0
  52. aeri/api/blob_storage_integrations/types/blob_storage_export_mode.py +26 -0
  53. aeri/api/blob_storage_integrations/types/blob_storage_integration_deletion_response.py +14 -0
  54. aeri/api/blob_storage_integrations/types/blob_storage_integration_file_type.py +26 -0
  55. aeri/api/blob_storage_integrations/types/blob_storage_integration_response.py +64 -0
  56. aeri/api/blob_storage_integrations/types/blob_storage_integration_status_response.py +50 -0
  57. aeri/api/blob_storage_integrations/types/blob_storage_integration_type.py +26 -0
  58. aeri/api/blob_storage_integrations/types/blob_storage_integrations_response.py +15 -0
  59. aeri/api/blob_storage_integrations/types/blob_storage_sync_status.py +47 -0
  60. aeri/api/blob_storage_integrations/types/create_blob_storage_integration_request.py +91 -0
  61. aeri/api/client.py +679 -0
  62. aeri/api/comments/__init__.py +44 -0
  63. aeri/api/comments/client.py +407 -0
  64. aeri/api/comments/raw_client.py +750 -0
  65. aeri/api/comments/types/__init__.py +46 -0
  66. aeri/api/comments/types/create_comment_request.py +47 -0
  67. aeri/api/comments/types/create_comment_response.py +17 -0
  68. aeri/api/comments/types/get_comments_response.py +17 -0
  69. aeri/api/commons/__init__.py +210 -0
  70. aeri/api/commons/errors/__init__.py +56 -0
  71. aeri/api/commons/errors/access_denied_error.py +12 -0
  72. aeri/api/commons/errors/error.py +12 -0
  73. aeri/api/commons/errors/method_not_allowed_error.py +12 -0
  74. aeri/api/commons/errors/not_found_error.py +12 -0
  75. aeri/api/commons/errors/unauthorized_error.py +12 -0
  76. aeri/api/commons/types/__init__.py +190 -0
  77. aeri/api/commons/types/base_score.py +90 -0
  78. aeri/api/commons/types/base_score_v1.py +70 -0
  79. aeri/api/commons/types/boolean_score.py +26 -0
  80. aeri/api/commons/types/boolean_score_v1.py +26 -0
  81. aeri/api/commons/types/categorical_score.py +26 -0
  82. aeri/api/commons/types/categorical_score_v1.py +26 -0
  83. aeri/api/commons/types/comment.py +36 -0
  84. aeri/api/commons/types/comment_object_type.py +30 -0
  85. aeri/api/commons/types/config_category.py +15 -0
  86. aeri/api/commons/types/correction_score.py +26 -0
  87. aeri/api/commons/types/create_score_value.py +5 -0
  88. aeri/api/commons/types/dataset.py +49 -0
  89. aeri/api/commons/types/dataset_item.py +58 -0
  90. aeri/api/commons/types/dataset_run.py +63 -0
  91. aeri/api/commons/types/dataset_run_item.py +40 -0
  92. aeri/api/commons/types/dataset_run_with_items.py +19 -0
  93. aeri/api/commons/types/dataset_status.py +22 -0
  94. aeri/api/commons/types/map_value.py +11 -0
  95. aeri/api/commons/types/model.py +125 -0
  96. aeri/api/commons/types/model_price.py +14 -0
  97. aeri/api/commons/types/model_usage_unit.py +42 -0
  98. aeri/api/commons/types/numeric_score.py +17 -0
  99. aeri/api/commons/types/numeric_score_v1.py +17 -0
  100. aeri/api/commons/types/observation.py +142 -0
  101. aeri/api/commons/types/observation_level.py +30 -0
  102. aeri/api/commons/types/observation_v2.py +235 -0
  103. aeri/api/commons/types/observations_view.py +89 -0
  104. aeri/api/commons/types/pricing_tier.py +91 -0
  105. aeri/api/commons/types/pricing_tier_condition.py +68 -0
  106. aeri/api/commons/types/pricing_tier_input.py +76 -0
  107. aeri/api/commons/types/pricing_tier_operator.py +42 -0
  108. aeri/api/commons/types/score.py +201 -0
  109. aeri/api/commons/types/score_config.py +66 -0
  110. aeri/api/commons/types/score_config_data_type.py +26 -0
  111. aeri/api/commons/types/score_data_type.py +30 -0
  112. aeri/api/commons/types/score_source.py +26 -0
  113. aeri/api/commons/types/score_v1.py +131 -0
  114. aeri/api/commons/types/session.py +25 -0
  115. aeri/api/commons/types/session_with_traces.py +15 -0
  116. aeri/api/commons/types/trace.py +84 -0
  117. aeri/api/commons/types/trace_with_details.py +43 -0
  118. aeri/api/commons/types/trace_with_full_details.py +45 -0
  119. aeri/api/commons/types/usage.py +59 -0
  120. aeri/api/core/__init__.py +111 -0
  121. aeri/api/core/api_error.py +23 -0
  122. aeri/api/core/client_wrapper.py +141 -0
  123. aeri/api/core/datetime_utils.py +30 -0
  124. aeri/api/core/enum.py +20 -0
  125. aeri/api/core/file.py +70 -0
  126. aeri/api/core/force_multipart.py +18 -0
  127. aeri/api/core/http_client.py +711 -0
  128. aeri/api/core/http_response.py +55 -0
  129. aeri/api/core/http_sse/__init__.py +48 -0
  130. aeri/api/core/http_sse/_api.py +114 -0
  131. aeri/api/core/http_sse/_decoders.py +66 -0
  132. aeri/api/core/http_sse/_exceptions.py +7 -0
  133. aeri/api/core/http_sse/_models.py +17 -0
  134. aeri/api/core/jsonable_encoder.py +102 -0
  135. aeri/api/core/pydantic_utilities.py +310 -0
  136. aeri/api/core/query_encoder.py +60 -0
  137. aeri/api/core/remove_none_from_dict.py +11 -0
  138. aeri/api/core/request_options.py +35 -0
  139. aeri/api/core/serialization.py +282 -0
  140. aeri/api/dataset_items/__init__.py +52 -0
  141. aeri/api/dataset_items/client.py +499 -0
  142. aeri/api/dataset_items/raw_client.py +973 -0
  143. aeri/api/dataset_items/types/__init__.py +50 -0
  144. aeri/api/dataset_items/types/create_dataset_item_request.py +37 -0
  145. aeri/api/dataset_items/types/delete_dataset_item_response.py +17 -0
  146. aeri/api/dataset_items/types/paginated_dataset_items.py +17 -0
  147. aeri/api/dataset_run_items/__init__.py +43 -0
  148. aeri/api/dataset_run_items/client.py +323 -0
  149. aeri/api/dataset_run_items/raw_client.py +547 -0
  150. aeri/api/dataset_run_items/types/__init__.py +44 -0
  151. aeri/api/dataset_run_items/types/create_dataset_run_item_request.py +51 -0
  152. aeri/api/dataset_run_items/types/paginated_dataset_run_items.py +17 -0
  153. aeri/api/datasets/__init__.py +55 -0
  154. aeri/api/datasets/client.py +661 -0
  155. aeri/api/datasets/raw_client.py +1368 -0
  156. aeri/api/datasets/types/__init__.py +53 -0
  157. aeri/api/datasets/types/create_dataset_request.py +31 -0
  158. aeri/api/datasets/types/delete_dataset_run_response.py +14 -0
  159. aeri/api/datasets/types/paginated_dataset_runs.py +17 -0
  160. aeri/api/datasets/types/paginated_datasets.py +17 -0
  161. aeri/api/health/__init__.py +44 -0
  162. aeri/api/health/client.py +112 -0
  163. aeri/api/health/errors/__init__.py +42 -0
  164. aeri/api/health/errors/service_unavailable_error.py +13 -0
  165. aeri/api/health/raw_client.py +227 -0
  166. aeri/api/health/types/__init__.py +40 -0
  167. aeri/api/health/types/health_response.py +30 -0
  168. aeri/api/ingestion/__init__.py +169 -0
  169. aeri/api/ingestion/client.py +221 -0
  170. aeri/api/ingestion/raw_client.py +293 -0
  171. aeri/api/ingestion/types/__init__.py +169 -0
  172. aeri/api/ingestion/types/base_event.py +27 -0
  173. aeri/api/ingestion/types/create_event_body.py +14 -0
  174. aeri/api/ingestion/types/create_event_event.py +15 -0
  175. aeri/api/ingestion/types/create_generation_body.py +40 -0
  176. aeri/api/ingestion/types/create_generation_event.py +15 -0
  177. aeri/api/ingestion/types/create_observation_event.py +15 -0
  178. aeri/api/ingestion/types/create_span_body.py +19 -0
  179. aeri/api/ingestion/types/create_span_event.py +15 -0
  180. aeri/api/ingestion/types/ingestion_error.py +17 -0
  181. aeri/api/ingestion/types/ingestion_event.py +155 -0
  182. aeri/api/ingestion/types/ingestion_response.py +17 -0
  183. aeri/api/ingestion/types/ingestion_success.py +15 -0
  184. aeri/api/ingestion/types/ingestion_usage.py +8 -0
  185. aeri/api/ingestion/types/observation_body.py +53 -0
  186. aeri/api/ingestion/types/observation_type.py +54 -0
  187. aeri/api/ingestion/types/open_ai_completion_usage_schema.py +26 -0
  188. aeri/api/ingestion/types/open_ai_response_usage_schema.py +24 -0
  189. aeri/api/ingestion/types/open_ai_usage.py +28 -0
  190. aeri/api/ingestion/types/optional_observation_body.py +36 -0
  191. aeri/api/ingestion/types/score_body.py +75 -0
  192. aeri/api/ingestion/types/score_event.py +15 -0
  193. aeri/api/ingestion/types/sdk_log_body.py +14 -0
  194. aeri/api/ingestion/types/sdk_log_event.py +15 -0
  195. aeri/api/ingestion/types/trace_body.py +36 -0
  196. aeri/api/ingestion/types/trace_event.py +15 -0
  197. aeri/api/ingestion/types/update_event_body.py +14 -0
  198. aeri/api/ingestion/types/update_generation_body.py +40 -0
  199. aeri/api/ingestion/types/update_generation_event.py +15 -0
  200. aeri/api/ingestion/types/update_observation_event.py +15 -0
  201. aeri/api/ingestion/types/update_span_body.py +19 -0
  202. aeri/api/ingestion/types/update_span_event.py +15 -0
  203. aeri/api/ingestion/types/usage_details.py +10 -0
  204. aeri/api/legacy/__init__.py +61 -0
  205. aeri/api/legacy/client.py +105 -0
  206. aeri/api/legacy/metrics_v1/__init__.py +40 -0
  207. aeri/api/legacy/metrics_v1/client.py +214 -0
  208. aeri/api/legacy/metrics_v1/raw_client.py +322 -0
  209. aeri/api/legacy/metrics_v1/types/__init__.py +40 -0
  210. aeri/api/legacy/metrics_v1/types/metrics_response.py +19 -0
  211. aeri/api/legacy/observations_v1/__init__.py +43 -0
  212. aeri/api/legacy/observations_v1/client.py +523 -0
  213. aeri/api/legacy/observations_v1/raw_client.py +759 -0
  214. aeri/api/legacy/observations_v1/types/__init__.py +44 -0
  215. aeri/api/legacy/observations_v1/types/observations.py +17 -0
  216. aeri/api/legacy/observations_v1/types/observations_views.py +17 -0
  217. aeri/api/legacy/raw_client.py +13 -0
  218. aeri/api/legacy/score_v1/__init__.py +43 -0
  219. aeri/api/legacy/score_v1/client.py +329 -0
  220. aeri/api/legacy/score_v1/raw_client.py +545 -0
  221. aeri/api/legacy/score_v1/types/__init__.py +44 -0
  222. aeri/api/legacy/score_v1/types/create_score_request.py +75 -0
  223. aeri/api/legacy/score_v1/types/create_score_response.py +17 -0
  224. aeri/api/llm_connections/__init__.py +55 -0
  225. aeri/api/llm_connections/client.py +311 -0
  226. aeri/api/llm_connections/raw_client.py +541 -0
  227. aeri/api/llm_connections/types/__init__.py +53 -0
  228. aeri/api/llm_connections/types/llm_adapter.py +38 -0
  229. aeri/api/llm_connections/types/llm_connection.py +77 -0
  230. aeri/api/llm_connections/types/paginated_llm_connections.py +17 -0
  231. aeri/api/llm_connections/types/upsert_llm_connection_request.py +69 -0
  232. aeri/api/media/__init__.py +58 -0
  233. aeri/api/media/client.py +427 -0
  234. aeri/api/media/raw_client.py +739 -0
  235. aeri/api/media/types/__init__.py +56 -0
  236. aeri/api/media/types/get_media_response.py +55 -0
  237. aeri/api/media/types/get_media_upload_url_request.py +51 -0
  238. aeri/api/media/types/get_media_upload_url_response.py +28 -0
  239. aeri/api/media/types/media_content_type.py +232 -0
  240. aeri/api/media/types/patch_media_body.py +43 -0
  241. aeri/api/metrics/__init__.py +40 -0
  242. aeri/api/metrics/client.py +422 -0
  243. aeri/api/metrics/raw_client.py +530 -0
  244. aeri/api/metrics/types/__init__.py +40 -0
  245. aeri/api/metrics/types/metrics_v2response.py +19 -0
  246. aeri/api/models/__init__.py +43 -0
  247. aeri/api/models/client.py +523 -0
  248. aeri/api/models/raw_client.py +993 -0
  249. aeri/api/models/types/__init__.py +44 -0
  250. aeri/api/models/types/create_model_request.py +103 -0
  251. aeri/api/models/types/paginated_models.py +17 -0
  252. aeri/api/observations/__init__.py +43 -0
  253. aeri/api/observations/client.py +522 -0
  254. aeri/api/observations/raw_client.py +641 -0
  255. aeri/api/observations/types/__init__.py +44 -0
  256. aeri/api/observations/types/observations_v2meta.py +21 -0
  257. aeri/api/observations/types/observations_v2response.py +28 -0
  258. aeri/api/opentelemetry/__init__.py +67 -0
  259. aeri/api/opentelemetry/client.py +276 -0
  260. aeri/api/opentelemetry/raw_client.py +291 -0
  261. aeri/api/opentelemetry/types/__init__.py +65 -0
  262. aeri/api/opentelemetry/types/otel_attribute.py +27 -0
  263. aeri/api/opentelemetry/types/otel_attribute_value.py +46 -0
  264. aeri/api/opentelemetry/types/otel_resource.py +24 -0
  265. aeri/api/opentelemetry/types/otel_resource_span.py +32 -0
  266. aeri/api/opentelemetry/types/otel_scope.py +34 -0
  267. aeri/api/opentelemetry/types/otel_scope_span.py +28 -0
  268. aeri/api/opentelemetry/types/otel_span.py +76 -0
  269. aeri/api/opentelemetry/types/otel_trace_response.py +16 -0
  270. aeri/api/organizations/__init__.py +73 -0
  271. aeri/api/organizations/client.py +756 -0
  272. aeri/api/organizations/raw_client.py +1707 -0
  273. aeri/api/organizations/types/__init__.py +71 -0
  274. aeri/api/organizations/types/delete_membership_request.py +16 -0
  275. aeri/api/organizations/types/membership_deletion_response.py +17 -0
  276. aeri/api/organizations/types/membership_request.py +18 -0
  277. aeri/api/organizations/types/membership_response.py +20 -0
  278. aeri/api/organizations/types/membership_role.py +30 -0
  279. aeri/api/organizations/types/memberships_response.py +15 -0
  280. aeri/api/organizations/types/organization_api_key.py +31 -0
  281. aeri/api/organizations/types/organization_api_keys_response.py +19 -0
  282. aeri/api/organizations/types/organization_project.py +25 -0
  283. aeri/api/organizations/types/organization_projects_response.py +15 -0
  284. aeri/api/projects/__init__.py +67 -0
  285. aeri/api/projects/client.py +760 -0
  286. aeri/api/projects/raw_client.py +1577 -0
  287. aeri/api/projects/types/__init__.py +65 -0
  288. aeri/api/projects/types/api_key_deletion_response.py +18 -0
  289. aeri/api/projects/types/api_key_list.py +23 -0
  290. aeri/api/projects/types/api_key_response.py +30 -0
  291. aeri/api/projects/types/api_key_summary.py +35 -0
  292. aeri/api/projects/types/organization.py +22 -0
  293. aeri/api/projects/types/project.py +34 -0
  294. aeri/api/projects/types/project_deletion_response.py +15 -0
  295. aeri/api/projects/types/projects.py +15 -0
  296. aeri/api/prompt_version/__init__.py +4 -0
  297. aeri/api/prompt_version/client.py +157 -0
  298. aeri/api/prompt_version/raw_client.py +264 -0
  299. aeri/api/prompts/__init__.py +100 -0
  300. aeri/api/prompts/client.py +550 -0
  301. aeri/api/prompts/raw_client.py +987 -0
  302. aeri/api/prompts/types/__init__.py +96 -0
  303. aeri/api/prompts/types/base_prompt.py +42 -0
  304. aeri/api/prompts/types/chat_message.py +17 -0
  305. aeri/api/prompts/types/chat_message_type.py +15 -0
  306. aeri/api/prompts/types/chat_message_with_placeholders.py +8 -0
  307. aeri/api/prompts/types/chat_prompt.py +15 -0
  308. aeri/api/prompts/types/create_chat_prompt_request.py +37 -0
  309. aeri/api/prompts/types/create_chat_prompt_type.py +15 -0
  310. aeri/api/prompts/types/create_prompt_request.py +8 -0
  311. aeri/api/prompts/types/create_text_prompt_request.py +36 -0
  312. aeri/api/prompts/types/create_text_prompt_type.py +15 -0
  313. aeri/api/prompts/types/placeholder_message.py +16 -0
  314. aeri/api/prompts/types/placeholder_message_type.py +15 -0
  315. aeri/api/prompts/types/prompt.py +58 -0
  316. aeri/api/prompts/types/prompt_meta.py +35 -0
  317. aeri/api/prompts/types/prompt_meta_list_response.py +17 -0
  318. aeri/api/prompts/types/prompt_type.py +20 -0
  319. aeri/api/prompts/types/text_prompt.py +14 -0
  320. aeri/api/scim/__init__.py +94 -0
  321. aeri/api/scim/client.py +686 -0
  322. aeri/api/scim/raw_client.py +1528 -0
  323. aeri/api/scim/types/__init__.py +92 -0
  324. aeri/api/scim/types/authentication_scheme.py +20 -0
  325. aeri/api/scim/types/bulk_config.py +22 -0
  326. aeri/api/scim/types/empty_response.py +16 -0
  327. aeri/api/scim/types/filter_config.py +17 -0
  328. aeri/api/scim/types/resource_meta.py +17 -0
  329. aeri/api/scim/types/resource_type.py +27 -0
  330. aeri/api/scim/types/resource_types_response.py +21 -0
  331. aeri/api/scim/types/schema_extension.py +17 -0
  332. aeri/api/scim/types/schema_resource.py +19 -0
  333. aeri/api/scim/types/schemas_response.py +21 -0
  334. aeri/api/scim/types/scim_email.py +16 -0
  335. aeri/api/scim/types/scim_feature_support.py +14 -0
  336. aeri/api/scim/types/scim_name.py +14 -0
  337. aeri/api/scim/types/scim_user.py +24 -0
  338. aeri/api/scim/types/scim_users_list_response.py +25 -0
  339. aeri/api/scim/types/service_provider_config.py +36 -0
  340. aeri/api/scim/types/user_meta.py +20 -0
  341. aeri/api/score_configs/__init__.py +44 -0
  342. aeri/api/score_configs/client.py +526 -0
  343. aeri/api/score_configs/raw_client.py +1012 -0
  344. aeri/api/score_configs/types/__init__.py +46 -0
  345. aeri/api/score_configs/types/create_score_config_request.py +46 -0
  346. aeri/api/score_configs/types/score_configs.py +17 -0
  347. aeri/api/score_configs/types/update_score_config_request.py +53 -0
  348. aeri/api/scores/__init__.py +76 -0
  349. aeri/api/scores/client.py +420 -0
  350. aeri/api/scores/raw_client.py +656 -0
  351. aeri/api/scores/types/__init__.py +76 -0
  352. aeri/api/scores/types/get_scores_response.py +17 -0
  353. aeri/api/scores/types/get_scores_response_data.py +211 -0
  354. aeri/api/scores/types/get_scores_response_data_boolean.py +15 -0
  355. aeri/api/scores/types/get_scores_response_data_categorical.py +15 -0
  356. aeri/api/scores/types/get_scores_response_data_correction.py +15 -0
  357. aeri/api/scores/types/get_scores_response_data_numeric.py +15 -0
  358. aeri/api/scores/types/get_scores_response_trace_data.py +38 -0
  359. aeri/api/sessions/__init__.py +40 -0
  360. aeri/api/sessions/client.py +262 -0
  361. aeri/api/sessions/raw_client.py +500 -0
  362. aeri/api/sessions/types/__init__.py +40 -0
  363. aeri/api/sessions/types/paginated_sessions.py +17 -0
  364. aeri/api/trace/__init__.py +44 -0
  365. aeri/api/trace/client.py +728 -0
  366. aeri/api/trace/raw_client.py +1208 -0
  367. aeri/api/trace/types/__init__.py +46 -0
  368. aeri/api/trace/types/delete_trace_response.py +14 -0
  369. aeri/api/trace/types/sort.py +14 -0
  370. aeri/api/trace/types/traces.py +17 -0
  371. aeri/api/utils/__init__.py +44 -0
  372. aeri/api/utils/pagination/__init__.py +40 -0
  373. aeri/api/utils/pagination/types/__init__.py +40 -0
  374. aeri/api/utils/pagination/types/meta_response.py +38 -0
  375. aeri/batch_evaluation.py +1643 -0
  376. aeri/experiment.py +1044 -0
  377. aeri/langchain/CallbackHandler.py +1377 -0
  378. aeri/langchain/__init__.py +5 -0
  379. aeri/langchain/utils.py +212 -0
  380. aeri/logger.py +28 -0
  381. aeri/media.py +352 -0
  382. aeri/model.py +477 -0
  383. aeri/openai.py +1124 -0
  384. aeri/py.typed +0 -0
  385. aeri/span_filter.py +17 -0
  386. aeri/types.py +79 -0
  387. aeri/version.py +3 -0
  388. aeri_python-4.0.0.dist-info/METADATA +51 -0
  389. aeri_python-4.0.0.dist-info/RECORD +391 -0
  390. aeri_python-4.0.0.dist-info/WHEEL +4 -0
  391. aeri_python-4.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,65 @@
1
+ """Constants used by the Aeri OpenTelemetry integration.
2
+
3
+ This module defines constants used throughout the Aeri OpenTelemetry integration.
4
+ """
5
+
6
+ from typing import Any, List, Literal, Union, get_args
7
+
8
+ from typing_extensions import TypeAlias
9
+
10
+ AERI_TRACER_NAME = "aeri-sdk"
11
+
12
+ AERI_SDK_EXPERIMENT_ENVIRONMENT = "sdk-experiment"
13
+
14
+ """Note: this type is used with .__args__ / get_args in some cases and therefore must remain flat"""
15
+ ObservationTypeGenerationLike: TypeAlias = Literal[
16
+ "generation",
17
+ "embedding",
18
+ ]
19
+
20
+ ObservationTypeSpanLike: TypeAlias = Literal[
21
+ "span",
22
+ "agent",
23
+ "tool",
24
+ "chain",
25
+ "retriever",
26
+ "evaluator",
27
+ "guardrail",
28
+ ]
29
+
30
+ ObservationTypeLiteralNoEvent: TypeAlias = Union[
31
+ ObservationTypeGenerationLike,
32
+ ObservationTypeSpanLike,
33
+ ]
34
+
35
+ """Enumeration of valid observation types for Aeri tracing.
36
+
37
+ This Literal defines all available observation types that can be used with the @observe
38
+ decorator and other Aeri SDK methods.
39
+ """
40
+ ObservationTypeLiteral: TypeAlias = Union[
41
+ ObservationTypeLiteralNoEvent, Literal["event"]
42
+ ]
43
+
44
+
45
+ def get_observation_types_list(
46
+ literal_type: Any,
47
+ ) -> List[str]:
48
+ """Flattens the Literal type to provide a list of strings.
49
+
50
+ Args:
51
+ literal_type: A Literal type, TypeAlias, or union of Literals to flatten
52
+
53
+ Returns:
54
+ Flat list of all string values contained in the Literal type
55
+ """
56
+ result = []
57
+ args = get_args(literal_type)
58
+
59
+ for arg in args:
60
+ if hasattr(arg, "__args__"):
61
+ result.extend(get_observation_types_list(arg))
62
+ else:
63
+ result.append(arg)
64
+
65
+ return result
@@ -0,0 +1,302 @@
1
+ import datetime as dt
2
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+
4
+ from aeri.api import (
5
+ Dataset,
6
+ DatasetItem,
7
+ )
8
+ from aeri.batch_evaluation import CompositeEvaluatorFunction
9
+ from aeri.experiment import (
10
+ EvaluatorFunction,
11
+ ExperimentResult,
12
+ RunEvaluatorFunction,
13
+ TaskFunction,
14
+ )
15
+
16
+ if TYPE_CHECKING:
17
+ from aeri._client.client import Aeri
18
+
19
+
20
+ class DatasetClient:
21
+ """Class for managing datasets in Aeri.
22
+
23
+ Attributes:
24
+ id (str): Unique identifier of the dataset.
25
+ name (str): Name of the dataset.
26
+ description (Optional[str]): Description of the dataset.
27
+ metadata (Optional[typing.Any]): Additional metadata of the dataset.
28
+ project_id (str): Identifier of the project to which the dataset belongs.
29
+ created_at (datetime): Timestamp of dataset creation.
30
+ updated_at (datetime): Timestamp of the last update to the dataset.
31
+ items (List[DatasetItem]): List of dataset items associated with the dataset.
32
+ version (Optional[datetime]): Timestamp of the dataset version.
33
+ Example:
34
+ Print the input of each dataset item in a dataset.
35
+ ```python
36
+ from aeri import Aeri
37
+
38
+ aeri = Aeri()
39
+
40
+ dataset = aeri.get_dataset("<dataset_name>")
41
+
42
+ for item in dataset.items:
43
+ print(item.input)
44
+ ```
45
+ """
46
+
47
+ id: str
48
+ name: str
49
+ description: Optional[str]
50
+ project_id: str
51
+ metadata: Optional[Any]
52
+ created_at: dt.datetime
53
+ updated_at: dt.datetime
54
+ input_schema: Optional[Any]
55
+ expected_output_schema: Optional[Any]
56
+ items: List[DatasetItem]
57
+ version: Optional[dt.datetime]
58
+
59
+ def __init__(
60
+ self,
61
+ *,
62
+ dataset: Dataset,
63
+ items: List[DatasetItem],
64
+ aeri_client: "Aeri",
65
+ version: Optional[dt.datetime] = None,
66
+ ):
67
+ """Initialize the DatasetClient."""
68
+ self.id = dataset.id
69
+ self.name = dataset.name
70
+ self.description = dataset.description
71
+ self.project_id = dataset.project_id
72
+ self.metadata = dataset.metadata
73
+ self.created_at = dataset.created_at
74
+ self.updated_at = dataset.updated_at
75
+ self.input_schema = dataset.input_schema
76
+ self.expected_output_schema = dataset.expected_output_schema
77
+ self.items = items
78
+ self.version = version
79
+ self._aeri_client: "Aeri" = aeri_client
80
+
81
+ def run_experiment(
82
+ self,
83
+ *,
84
+ name: str,
85
+ run_name: Optional[str] = None,
86
+ description: Optional[str] = None,
87
+ task: TaskFunction,
88
+ evaluators: List[EvaluatorFunction] = [],
89
+ composite_evaluator: Optional[CompositeEvaluatorFunction] = None,
90
+ run_evaluators: List[RunEvaluatorFunction] = [],
91
+ max_concurrency: int = 50,
92
+ metadata: Optional[Dict[str, Any]] = None,
93
+ ) -> ExperimentResult:
94
+ """Run an experiment on this Aeri dataset with automatic tracking.
95
+
96
+ This is a convenience method that runs an experiment using all items in this
97
+ dataset. It automatically creates a dataset run in Aeri for tracking and
98
+ comparison purposes, linking all experiment results to the dataset.
99
+
100
+ Key benefits of using dataset.run_experiment():
101
+ - Automatic dataset run creation and linking in Aeri UI
102
+ - Built-in experiment tracking and versioning
103
+ - Easy comparison between different experiment runs
104
+ - Direct access to dataset items with their metadata and expected outputs
105
+ - Automatic URL generation for viewing results in Aeri dashboard
106
+
107
+ Args:
108
+ name: Human-readable name for the experiment run. This will be used as
109
+ the dataset run name in Aeri for tracking and identification.
110
+ run_name: Optional exact name for the dataset run. If provided, this will be
111
+ used as the exact dataset run name in Aeri. If not provided, this will
112
+ default to the experiment name appended with an ISO timestamp.
113
+ description: Optional description of the experiment's purpose, methodology,
114
+ or what you're testing. Appears in the Aeri UI for context.
115
+ task: Function that processes each dataset item and returns output.
116
+ The function will receive DatasetItem objects with .input, .expected_output,
117
+ .metadata attributes. Signature should be: task(*, item, **kwargs) -> Any
118
+ evaluators: List of functions to evaluate each item's output individually.
119
+ These will have access to the item's expected_output for comparison.
120
+ composite_evaluator: Optional function that creates composite scores from item-level evaluations.
121
+ Receives the same inputs as item-level evaluators (input, output, expected_output, metadata)
122
+ plus the list of evaluations from item-level evaluators. Useful for weighted averages,
123
+ pass/fail decisions based on multiple criteria, or custom scoring logic combining multiple metrics.
124
+ run_evaluators: List of functions to evaluate the entire experiment run.
125
+ Useful for computing aggregate statistics across all dataset items.
126
+ max_concurrency: Maximum number of concurrent task executions (default: 50).
127
+ Adjust based on API rate limits and system resources.
128
+ metadata: Optional metadata to attach to the experiment run and all traces.
129
+ Will be combined with individual item metadata.
130
+
131
+ Returns:
132
+ ExperimentResult object containing:
133
+ - name: The experiment name.
134
+ - run_name: The experiment run name (equivalent to the dataset run name).
135
+ - description: Optional experiment description.
136
+ - item_results: Results for each dataset item with outputs and evaluations.
137
+ - run_evaluations: Aggregate evaluation results for the entire run.
138
+ - dataset_run_id: ID of the created dataset run in Aeri.
139
+ - dataset_run_url: Direct URL to view the experiment results in Aeri UI.
140
+
141
+ The result object provides a format() method for human-readable output:
142
+ ```python
143
+ result = dataset.run_experiment(...)
144
+ print(result.format()) # Summary view
145
+ print(result.format(include_item_results=True)) # Detailed view
146
+ ```
147
+
148
+ Raises:
149
+ ValueError: If the dataset has no items or no Aeri client is available.
150
+
151
+ Examples:
152
+ Basic dataset experiment:
153
+ ```python
154
+ dataset = aeri.get_dataset("qa-evaluation-set")
155
+
156
+ def answer_questions(*, item, **kwargs):
157
+ # item is a DatasetItem with .input, .expected_output, .metadata
158
+ question = item.input
159
+ return my_qa_system.answer(question)
160
+
161
+ def accuracy_evaluator(*, input, output, expected_output=None, **kwargs):
162
+ if not expected_output:
163
+ return {"name": "accuracy", "value": 0, "comment": "No expected output"}
164
+
165
+ is_correct = output.strip().lower() == expected_output.strip().lower()
166
+ return {
167
+ "name": "accuracy",
168
+ "value": 1.0 if is_correct else 0.0,
169
+ "comment": "Correct" if is_correct else "Incorrect"
170
+ }
171
+
172
+ result = dataset.run_experiment(
173
+ name="QA System v2.0 Evaluation",
174
+ description="Testing improved QA system on curated question set",
175
+ task=answer_questions,
176
+ evaluators=[accuracy_evaluator]
177
+ )
178
+
179
+ print(f"Evaluated {len(result['item_results'])} questions")
180
+ print(f"View detailed results: {result['dataset_run_url']}")
181
+ ```
182
+
183
+ Advanced experiment with multiple evaluators and run-level analysis:
184
+ ```python
185
+ dataset = aeri.get_dataset("content-generation-benchmark")
186
+
187
+ async def generate_content(*, item, **kwargs):
188
+ prompt = item.input
189
+ response = await openai_client.chat.completions.create(
190
+ model="gpt-4",
191
+ messages=[{"role": "user", "content": prompt}],
192
+ temperature=0.7
193
+ )
194
+ return response.choices[0].message.content
195
+
196
+ def quality_evaluator(*, input, output, expected_output=None, metadata=None, **kwargs):
197
+ # Use metadata for context-aware evaluation
198
+ content_type = metadata.get("type", "general") if metadata else "general"
199
+
200
+ # Basic quality checks
201
+ word_count = len(output.split())
202
+ min_words = {"blog": 300, "tweet": 10, "summary": 100}.get(content_type, 50)
203
+
204
+ return [
205
+ {
206
+ "name": "word_count",
207
+ "value": word_count,
208
+ "comment": f"Generated {word_count} words"
209
+ },
210
+ {
211
+ "name": "meets_length_requirement",
212
+ "value": word_count >= min_words,
213
+ "comment": f"{'Meets' if word_count >= min_words else 'Below'} minimum {min_words} words for {content_type}"
214
+ }
215
+ ]
216
+
217
+ def content_diversity(*, item_results, **kwargs):
218
+ # Analyze diversity across all generated content
219
+ all_outputs = [result["output"] for result in item_results]
220
+ unique_words = set()
221
+ total_words = 0
222
+
223
+ for output in all_outputs:
224
+ words = output.lower().split()
225
+ unique_words.update(words)
226
+ total_words += len(words)
227
+
228
+ diversity_ratio = len(unique_words) / total_words if total_words > 0 else 0
229
+
230
+ return {
231
+ "name": "vocabulary_diversity",
232
+ "value": diversity_ratio,
233
+ "comment": f"Used {len(unique_words)} unique words out of {total_words} total ({diversity_ratio:.2%} diversity)"
234
+ }
235
+
236
+ result = dataset.run_experiment(
237
+ name="Content Generation Diversity Test",
238
+ description="Evaluating content quality and vocabulary diversity across different content types",
239
+ task=generate_content,
240
+ evaluators=[quality_evaluator],
241
+ run_evaluators=[content_diversity],
242
+ max_concurrency=3, # Limit API calls
243
+ metadata={"model": "gpt-4", "temperature": 0.7}
244
+ )
245
+
246
+ # Results are automatically linked to dataset in Aeri
247
+ print(f"Experiment completed! View in Aeri: {result['dataset_run_url']}")
248
+
249
+ # Access individual results
250
+ for i, item_result in enumerate(result["item_results"]):
251
+ print(f"Item {i+1}: {item_result['evaluations']}")
252
+ ```
253
+
254
+ Comparing different model versions:
255
+ ```python
256
+ # Run multiple experiments on the same dataset for comparison
257
+ dataset = aeri.get_dataset("model-benchmark")
258
+
259
+ # Experiment 1: GPT-4
260
+ result_gpt4 = dataset.run_experiment(
261
+ name="GPT-4 Baseline",
262
+ description="Baseline performance with GPT-4",
263
+ task=lambda *, item, **kwargs: gpt4_model.generate(item.input),
264
+ evaluators=[accuracy_evaluator, fluency_evaluator]
265
+ )
266
+
267
+ # Experiment 2: Custom model
268
+ result_custom = dataset.run_experiment(
269
+ name="Custom Model v1.2",
270
+ description="Testing our fine-tuned model",
271
+ task=lambda *, item, **kwargs: custom_model.generate(item.input),
272
+ evaluators=[accuracy_evaluator, fluency_evaluator]
273
+ )
274
+
275
+ # Both experiments are now visible in Aeri for easy comparison
276
+ print("Compare results in Aeri:")
277
+ print(f"GPT-4: {result_gpt4.dataset_run_url}")
278
+ print(f"Custom: {result_custom.dataset_run_url}")
279
+ ```
280
+
281
+ Note:
282
+ - All experiment results are automatically tracked in Aeri as dataset runs
283
+ - Dataset items provide .input, .expected_output, and .metadata attributes
284
+ - Results can be easily compared across different experiment runs in the UI
285
+ - The dataset_run_url provides direct access to detailed results and analysis
286
+ - Failed items are handled gracefully and logged without stopping the experiment
287
+ - This method works in both sync and async contexts (Jupyter notebooks, web apps, etc.)
288
+ - Async execution is handled automatically with smart event loop detection
289
+ """
290
+ return self._aeri_client.run_experiment(
291
+ name=name,
292
+ run_name=run_name,
293
+ description=description,
294
+ data=self.items,
295
+ task=task,
296
+ evaluators=evaluators,
297
+ composite_evaluator=composite_evaluator,
298
+ run_evaluators=run_evaluators,
299
+ max_concurrency=max_concurrency,
300
+ metadata=metadata,
301
+ _dataset_version=self.version,
302
+ )
@@ -0,0 +1,158 @@
1
+ """Environment variable definitions for Aeri OpenTelemetry integration.
2
+
3
+ This module defines environment variables used to configure the Aeri OpenTelemetry integration.
4
+ Each environment variable includes documentation on its purpose, expected values, and defaults.
5
+ """
6
+
7
+ AERI_TRACING_ENVIRONMENT = "AERI_TRACING_ENVIRONMENT"
8
+ """
9
+ .. envvar:: AERI_TRACING_ENVIRONMENT
10
+
11
+ The tracing environment. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'aeri'.
12
+
13
+ **Default value:** ``"default"``
14
+ """
15
+
16
+ AERI_RELEASE = "AERI_RELEASE"
17
+ """
18
+ .. envvar:: AERI_RELEASE
19
+
20
+ Release number/hash of the application to provide analytics grouped by release.
21
+ """
22
+
23
+
24
+ AERI_PUBLIC_KEY = "AERI_PUBLIC_KEY"
25
+ """
26
+ .. envvar:: AERI_PUBLIC_KEY
27
+
28
+ Public API key of Aeri project
29
+ """
30
+
31
+ AERI_SECRET_KEY = "AERI_SECRET_KEY"
32
+ """
33
+ .. envvar:: AERI_SECRET_KEY
34
+
35
+ Secret API key of Aeri project
36
+ """
37
+
38
+ AERI_BASE_URL = "AERI_BASE_URL"
39
+ """
40
+ .. envvar:: AERI_BASE_URL
41
+
42
+ Base URL of Aeri API. Can be set via `AERI_BASE_URL` environment variable.
43
+
44
+ **Default value:** ``"https://cloud.aeri.com"``
45
+ """
46
+
47
+ AERI_HOST = "AERI_HOST"
48
+ """
49
+ .. envvar:: AERI_HOST
50
+
51
+ Deprecated. Use AERI_BASE_URL instead. Host of Aeri API. Can be set via `AERI_HOST` environment variable.
52
+
53
+ **Default value:** ``"https://cloud.aeri.com"``
54
+ """
55
+
56
+ AERI_OTEL_TRACES_EXPORT_PATH = "AERI_OTEL_TRACES_EXPORT_PATH"
57
+ """
58
+ .. envvar:: AERI_OTEL_TRACES_EXPORT_PATH
59
+
60
+ URL path on the configured host to export traces to.
61
+
62
+ **Default value:** ``/api/public/otel/v1/traces``
63
+ """
64
+
65
+ AERI_DEBUG = "AERI_DEBUG"
66
+ """
67
+ .. envvar:: AERI_DEBUG
68
+
69
+ Enables debug mode for more verbose logging.
70
+
71
+ **Default value:** ``"False"``
72
+ """
73
+
74
+ AERI_TRACING_ENABLED = "AERI_TRACING_ENABLED"
75
+ """
76
+ .. envvar:: AERI_TRACING_ENABLED
77
+
78
+ Enables or disables the Aeri client. If disabled, all observability calls to the backend will be no-ops. Default is True. Set to `False` to disable tracing.
79
+
80
+ **Default value:** ``"True"``
81
+ """
82
+
83
+ AERI_MEDIA_UPLOAD_THREAD_COUNT = "AERI_MEDIA_UPLOAD_THREAD_COUNT"
84
+ """
85
+ .. envvar:: AERI_MEDIA_UPLOAD_THREAD_COUNT
86
+
87
+ Number of background threads to handle media uploads from trace ingestion.
88
+
89
+ **Default value:** ``1``
90
+ """
91
+
92
+ AERI_FLUSH_AT = "AERI_FLUSH_AT"
93
+ """
94
+ .. envvar:: AERI_FLUSH_AT
95
+
96
+ Max batch size until a new ingestion batch is sent to the API.
97
+ **Default value:** same as OTEL ``OTEL_BSP_MAX_EXPORT_BATCH_SIZE``
98
+ """
99
+
100
+ AERI_FLUSH_INTERVAL = "AERI_FLUSH_INTERVAL"
101
+ """
102
+ .. envvar:: AERI_FLUSH_INTERVAL
103
+
104
+ Max delay in seconds until a new ingestion batch is sent to the API.
105
+ **Default value:** same as OTEL ``OTEL_BSP_SCHEDULE_DELAY``
106
+ """
107
+
108
+ AERI_SAMPLE_RATE = "AERI_SAMPLE_RATE"
109
+ """
110
+ .. envvar: AERI_SAMPLE_RATE
111
+
112
+ Float between 0 and 1 indicating the sample rate of traces to bet sent to Aeri servers.
113
+
114
+ **Default value**: ``1.0``
115
+
116
+ """
117
+ AERI_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED = (
118
+ "AERI_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED"
119
+ )
120
+ """
121
+ .. envvar: AERI_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED
122
+
123
+ Default capture of function args, kwargs and return value when using the @observe decorator.
124
+
125
+ Having default IO capture enabled for observe decorated function may have a performance impact on your application
126
+ if large or deeply nested objects are attempted to be serialized. Set this value to `False` and use manual
127
+ input/output setting on your observation to avoid this.
128
+
129
+ **Default value**: ``True``
130
+ """
131
+
132
+ AERI_MEDIA_UPLOAD_ENABLED = "AERI_MEDIA_UPLOAD_ENABLED"
133
+ """
134
+ .. envvar: AERI_MEDIA_UPLOAD_ENABLED
135
+
136
+ Controls whether media detection and upload is attempted by the SDK.
137
+
138
+ **Default value**: ``True``
139
+ """
140
+
141
+ AERI_TIMEOUT = "AERI_TIMEOUT"
142
+ """
143
+ .. envvar: AERI_TIMEOUT
144
+
145
+ Controls the timeout for all API requests in seconds
146
+
147
+ **Default value**: ``5``
148
+ """
149
+
150
+ AERI_PROMPT_CACHE_DEFAULT_TTL_SECONDS = "AERI_PROMPT_CACHE_DEFAULT_TTL_SECONDS"
151
+ """
152
+ .. envvar: AERI_PROMPT_CACHE_DEFAULT_TTL_SECONDS
153
+
154
+ Controls the default time-to-live (TTL) in seconds for cached prompts.
155
+ This setting determines how long prompt responses are cached before they expire.
156
+
157
+ **Default value**: ``60``
158
+ """
@@ -0,0 +1,149 @@
1
+ from contextlib import contextmanager
2
+ from contextvars import ContextVar
3
+ from typing import Iterator, Optional
4
+
5
+ from aeri._client.client import Aeri
6
+ from aeri._client.resource_manager import AeriResourceManager
7
+ from aeri.logger import aeri_logger
8
+
9
+ # Context variable to track the current aeri_public_key in execution context
10
+ _current_public_key: ContextVar[Optional[str]] = ContextVar(
11
+ "aeri_public_key", default=None
12
+ )
13
+
14
+
15
+ @contextmanager
16
+ def _set_current_public_key(public_key: Optional[str]) -> Iterator[None]:
17
+ """Context manager to set and restore the current public key in execution context.
18
+
19
+ Args:
20
+ public_key: The public key to set in context. If None, context is not modified.
21
+
22
+ Yields:
23
+ None
24
+ """
25
+ if public_key is None:
26
+ yield # Don't modify context if no key provided
27
+ return
28
+
29
+ token = _current_public_key.set(public_key)
30
+ try:
31
+ yield
32
+ finally:
33
+ _current_public_key.reset(token)
34
+
35
+
36
+ def _create_client_from_instance(
37
+ instance: "AeriResourceManager", public_key: Optional[str] = None
38
+ ) -> Aeri:
39
+ """Create a Aeri client from a resource manager instance with all settings preserved."""
40
+ return Aeri(
41
+ public_key=public_key or instance.public_key,
42
+ secret_key=instance.secret_key,
43
+ base_url=instance.base_url,
44
+ tracing_enabled=instance.tracing_enabled,
45
+ environment=instance.environment,
46
+ timeout=instance.timeout,
47
+ flush_at=instance.flush_at,
48
+ flush_interval=instance.flush_interval,
49
+ release=instance.release,
50
+ media_upload_thread_count=instance.media_upload_thread_count,
51
+ sample_rate=instance.sample_rate,
52
+ mask=instance.mask,
53
+ blocked_instrumentation_scopes=instance.blocked_instrumentation_scopes,
54
+ should_export_span=instance.should_export_span,
55
+ additional_headers=instance.additional_headers,
56
+ tracer_provider=instance.tracer_provider,
57
+ httpx_client=instance.httpx_client,
58
+ )
59
+
60
+
61
+ def get_client(*, public_key: Optional[str] = None) -> Aeri:
62
+ """Get or create a Aeri client instance.
63
+
64
+ Returns an existing Aeri client or creates a new one if none exists. In multi-project setups,
65
+ providing a public_key is required. Multi-project support is experimental - see Aeri docs.
66
+
67
+ Behavior:
68
+ - Single project: Returns existing client or creates new one
69
+ - Multi-project: Requires public_key to return specific client
70
+ - No public_key in multi-project: Returns disabled client to prevent data leakage
71
+
72
+ The function uses a singleton pattern per public_key to conserve resources and maintain state.
73
+
74
+ Args:
75
+ public_key (Optional[str]): Project identifier
76
+ - With key: Returns client for that project
77
+ - Without key: Returns single client or disabled client if multiple exist
78
+
79
+ Returns:
80
+ Aeri: Client instance in one of three states:
81
+ 1. Client for specified public_key
82
+ 2. Default client for single-project setup
83
+ 3. Disabled client when multiple projects exist without key
84
+
85
+ Security:
86
+ Disables tracing when multiple projects exist without explicit key to prevent
87
+ cross-project data leakage. Multi-project setups are experimental.
88
+
89
+ Example:
90
+ ```python
91
+ # Single project
92
+ client = get_client() # Default client
93
+
94
+ # In multi-project usage:
95
+ client_a = get_client(public_key="project_a_key") # Returns project A's client
96
+ client_b = get_client(public_key="project_b_key") # Returns project B's client
97
+
98
+ # Without specific key in multi-project setup:
99
+ client = get_client() # Returns disabled client for safety
100
+ ```
101
+ """
102
+ with AeriResourceManager._lock:
103
+ active_instances = AeriResourceManager._instances
104
+
105
+ # If no explicit public_key provided, check execution context
106
+ if not public_key:
107
+ public_key = _current_public_key.get(None)
108
+
109
+ if not public_key:
110
+ if len(active_instances) == 0:
111
+ # No clients initialized yet, create default instance
112
+ return Aeri()
113
+
114
+ if len(active_instances) == 1:
115
+ # Only one client exists, safe to use without specifying key
116
+ instance = list(active_instances.values())[0]
117
+
118
+ # Initialize with the credentials bound to the instance
119
+ # This is important if the original instance was instantiated
120
+ # via constructor arguments
121
+ return _create_client_from_instance(instance)
122
+
123
+ else:
124
+ # Multiple clients exist but no key specified - disable tracing
125
+ # to prevent cross-project data leakage
126
+ aeri_logger.warning(
127
+ "No 'aeri_public_key' passed to decorated function, but multiple aeri clients are instantiated in current process. Skipping tracing for this function to avoid cross-project leakage."
128
+ )
129
+ return Aeri(
130
+ tracing_enabled=False, public_key="fake", secret_key="fake"
131
+ )
132
+
133
+ else:
134
+ # Specific key provided, look up existing instance
135
+ target_instance: Optional[AeriResourceManager] = active_instances.get(
136
+ public_key, None
137
+ )
138
+
139
+ if target_instance is None:
140
+ # No instance found with this key - client not initialized properly
141
+ aeri_logger.warning(
142
+ f"No Aeri client with public key {public_key} has been initialized. Skipping tracing for decorated function."
143
+ )
144
+ return Aeri(
145
+ tracing_enabled=False, public_key="fake", secret_key="fake"
146
+ )
147
+
148
+ # target_instance is guaranteed to be not None at this point
149
+ return _create_client_from_instance(target_instance, public_key)