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.
- aeri/__init__.py +72 -0
- aeri/_client/_validation.py +204 -0
- aeri/_client/attributes.py +188 -0
- aeri/_client/client.py +3761 -0
- aeri/_client/constants.py +65 -0
- aeri/_client/datasets.py +302 -0
- aeri/_client/environment_variables.py +158 -0
- aeri/_client/get_client.py +149 -0
- aeri/_client/observe.py +661 -0
- aeri/_client/propagation.py +475 -0
- aeri/_client/resource_manager.py +510 -0
- aeri/_client/span.py +1519 -0
- aeri/_client/span_filter.py +76 -0
- aeri/_client/span_processor.py +206 -0
- aeri/_client/utils.py +132 -0
- aeri/_task_manager/media_manager.py +331 -0
- aeri/_task_manager/media_upload_consumer.py +44 -0
- aeri/_task_manager/media_upload_queue.py +12 -0
- aeri/_task_manager/score_ingestion_consumer.py +208 -0
- aeri/_task_manager/task_manager.py +475 -0
- aeri/_utils/__init__.py +19 -0
- aeri/_utils/environment.py +34 -0
- aeri/_utils/error_logging.py +47 -0
- aeri/_utils/parse_error.py +99 -0
- aeri/_utils/prompt_cache.py +188 -0
- aeri/_utils/request.py +137 -0
- aeri/_utils/serializer.py +205 -0
- aeri/api/.fern/metadata.json +14 -0
- aeri/api/__init__.py +836 -0
- aeri/api/annotation_queues/__init__.py +82 -0
- aeri/api/annotation_queues/client.py +1111 -0
- aeri/api/annotation_queues/raw_client.py +2288 -0
- aeri/api/annotation_queues/types/__init__.py +84 -0
- aeri/api/annotation_queues/types/annotation_queue.py +28 -0
- aeri/api/annotation_queues/types/annotation_queue_assignment_request.py +16 -0
- aeri/api/annotation_queues/types/annotation_queue_item.py +34 -0
- aeri/api/annotation_queues/types/annotation_queue_object_type.py +26 -0
- aeri/api/annotation_queues/types/annotation_queue_status.py +22 -0
- aeri/api/annotation_queues/types/create_annotation_queue_assignment_response.py +18 -0
- aeri/api/annotation_queues/types/create_annotation_queue_item_request.py +25 -0
- aeri/api/annotation_queues/types/create_annotation_queue_request.py +20 -0
- aeri/api/annotation_queues/types/delete_annotation_queue_assignment_response.py +14 -0
- aeri/api/annotation_queues/types/delete_annotation_queue_item_response.py +15 -0
- aeri/api/annotation_queues/types/paginated_annotation_queue_items.py +17 -0
- aeri/api/annotation_queues/types/paginated_annotation_queues.py +17 -0
- aeri/api/annotation_queues/types/update_annotation_queue_item_request.py +15 -0
- aeri/api/blob_storage_integrations/__init__.py +73 -0
- aeri/api/blob_storage_integrations/client.py +550 -0
- aeri/api/blob_storage_integrations/raw_client.py +976 -0
- aeri/api/blob_storage_integrations/types/__init__.py +77 -0
- aeri/api/blob_storage_integrations/types/blob_storage_export_frequency.py +26 -0
- aeri/api/blob_storage_integrations/types/blob_storage_export_mode.py +26 -0
- aeri/api/blob_storage_integrations/types/blob_storage_integration_deletion_response.py +14 -0
- aeri/api/blob_storage_integrations/types/blob_storage_integration_file_type.py +26 -0
- aeri/api/blob_storage_integrations/types/blob_storage_integration_response.py +64 -0
- aeri/api/blob_storage_integrations/types/blob_storage_integration_status_response.py +50 -0
- aeri/api/blob_storage_integrations/types/blob_storage_integration_type.py +26 -0
- aeri/api/blob_storage_integrations/types/blob_storage_integrations_response.py +15 -0
- aeri/api/blob_storage_integrations/types/blob_storage_sync_status.py +47 -0
- aeri/api/blob_storage_integrations/types/create_blob_storage_integration_request.py +91 -0
- aeri/api/client.py +679 -0
- aeri/api/comments/__init__.py +44 -0
- aeri/api/comments/client.py +407 -0
- aeri/api/comments/raw_client.py +750 -0
- aeri/api/comments/types/__init__.py +46 -0
- aeri/api/comments/types/create_comment_request.py +47 -0
- aeri/api/comments/types/create_comment_response.py +17 -0
- aeri/api/comments/types/get_comments_response.py +17 -0
- aeri/api/commons/__init__.py +210 -0
- aeri/api/commons/errors/__init__.py +56 -0
- aeri/api/commons/errors/access_denied_error.py +12 -0
- aeri/api/commons/errors/error.py +12 -0
- aeri/api/commons/errors/method_not_allowed_error.py +12 -0
- aeri/api/commons/errors/not_found_error.py +12 -0
- aeri/api/commons/errors/unauthorized_error.py +12 -0
- aeri/api/commons/types/__init__.py +190 -0
- aeri/api/commons/types/base_score.py +90 -0
- aeri/api/commons/types/base_score_v1.py +70 -0
- aeri/api/commons/types/boolean_score.py +26 -0
- aeri/api/commons/types/boolean_score_v1.py +26 -0
- aeri/api/commons/types/categorical_score.py +26 -0
- aeri/api/commons/types/categorical_score_v1.py +26 -0
- aeri/api/commons/types/comment.py +36 -0
- aeri/api/commons/types/comment_object_type.py +30 -0
- aeri/api/commons/types/config_category.py +15 -0
- aeri/api/commons/types/correction_score.py +26 -0
- aeri/api/commons/types/create_score_value.py +5 -0
- aeri/api/commons/types/dataset.py +49 -0
- aeri/api/commons/types/dataset_item.py +58 -0
- aeri/api/commons/types/dataset_run.py +63 -0
- aeri/api/commons/types/dataset_run_item.py +40 -0
- aeri/api/commons/types/dataset_run_with_items.py +19 -0
- aeri/api/commons/types/dataset_status.py +22 -0
- aeri/api/commons/types/map_value.py +11 -0
- aeri/api/commons/types/model.py +125 -0
- aeri/api/commons/types/model_price.py +14 -0
- aeri/api/commons/types/model_usage_unit.py +42 -0
- aeri/api/commons/types/numeric_score.py +17 -0
- aeri/api/commons/types/numeric_score_v1.py +17 -0
- aeri/api/commons/types/observation.py +142 -0
- aeri/api/commons/types/observation_level.py +30 -0
- aeri/api/commons/types/observation_v2.py +235 -0
- aeri/api/commons/types/observations_view.py +89 -0
- aeri/api/commons/types/pricing_tier.py +91 -0
- aeri/api/commons/types/pricing_tier_condition.py +68 -0
- aeri/api/commons/types/pricing_tier_input.py +76 -0
- aeri/api/commons/types/pricing_tier_operator.py +42 -0
- aeri/api/commons/types/score.py +201 -0
- aeri/api/commons/types/score_config.py +66 -0
- aeri/api/commons/types/score_config_data_type.py +26 -0
- aeri/api/commons/types/score_data_type.py +30 -0
- aeri/api/commons/types/score_source.py +26 -0
- aeri/api/commons/types/score_v1.py +131 -0
- aeri/api/commons/types/session.py +25 -0
- aeri/api/commons/types/session_with_traces.py +15 -0
- aeri/api/commons/types/trace.py +84 -0
- aeri/api/commons/types/trace_with_details.py +43 -0
- aeri/api/commons/types/trace_with_full_details.py +45 -0
- aeri/api/commons/types/usage.py +59 -0
- aeri/api/core/__init__.py +111 -0
- aeri/api/core/api_error.py +23 -0
- aeri/api/core/client_wrapper.py +141 -0
- aeri/api/core/datetime_utils.py +30 -0
- aeri/api/core/enum.py +20 -0
- aeri/api/core/file.py +70 -0
- aeri/api/core/force_multipart.py +18 -0
- aeri/api/core/http_client.py +711 -0
- aeri/api/core/http_response.py +55 -0
- aeri/api/core/http_sse/__init__.py +48 -0
- aeri/api/core/http_sse/_api.py +114 -0
- aeri/api/core/http_sse/_decoders.py +66 -0
- aeri/api/core/http_sse/_exceptions.py +7 -0
- aeri/api/core/http_sse/_models.py +17 -0
- aeri/api/core/jsonable_encoder.py +102 -0
- aeri/api/core/pydantic_utilities.py +310 -0
- aeri/api/core/query_encoder.py +60 -0
- aeri/api/core/remove_none_from_dict.py +11 -0
- aeri/api/core/request_options.py +35 -0
- aeri/api/core/serialization.py +282 -0
- aeri/api/dataset_items/__init__.py +52 -0
- aeri/api/dataset_items/client.py +499 -0
- aeri/api/dataset_items/raw_client.py +973 -0
- aeri/api/dataset_items/types/__init__.py +50 -0
- aeri/api/dataset_items/types/create_dataset_item_request.py +37 -0
- aeri/api/dataset_items/types/delete_dataset_item_response.py +17 -0
- aeri/api/dataset_items/types/paginated_dataset_items.py +17 -0
- aeri/api/dataset_run_items/__init__.py +43 -0
- aeri/api/dataset_run_items/client.py +323 -0
- aeri/api/dataset_run_items/raw_client.py +547 -0
- aeri/api/dataset_run_items/types/__init__.py +44 -0
- aeri/api/dataset_run_items/types/create_dataset_run_item_request.py +51 -0
- aeri/api/dataset_run_items/types/paginated_dataset_run_items.py +17 -0
- aeri/api/datasets/__init__.py +55 -0
- aeri/api/datasets/client.py +661 -0
- aeri/api/datasets/raw_client.py +1368 -0
- aeri/api/datasets/types/__init__.py +53 -0
- aeri/api/datasets/types/create_dataset_request.py +31 -0
- aeri/api/datasets/types/delete_dataset_run_response.py +14 -0
- aeri/api/datasets/types/paginated_dataset_runs.py +17 -0
- aeri/api/datasets/types/paginated_datasets.py +17 -0
- aeri/api/health/__init__.py +44 -0
- aeri/api/health/client.py +112 -0
- aeri/api/health/errors/__init__.py +42 -0
- aeri/api/health/errors/service_unavailable_error.py +13 -0
- aeri/api/health/raw_client.py +227 -0
- aeri/api/health/types/__init__.py +40 -0
- aeri/api/health/types/health_response.py +30 -0
- aeri/api/ingestion/__init__.py +169 -0
- aeri/api/ingestion/client.py +221 -0
- aeri/api/ingestion/raw_client.py +293 -0
- aeri/api/ingestion/types/__init__.py +169 -0
- aeri/api/ingestion/types/base_event.py +27 -0
- aeri/api/ingestion/types/create_event_body.py +14 -0
- aeri/api/ingestion/types/create_event_event.py +15 -0
- aeri/api/ingestion/types/create_generation_body.py +40 -0
- aeri/api/ingestion/types/create_generation_event.py +15 -0
- aeri/api/ingestion/types/create_observation_event.py +15 -0
- aeri/api/ingestion/types/create_span_body.py +19 -0
- aeri/api/ingestion/types/create_span_event.py +15 -0
- aeri/api/ingestion/types/ingestion_error.py +17 -0
- aeri/api/ingestion/types/ingestion_event.py +155 -0
- aeri/api/ingestion/types/ingestion_response.py +17 -0
- aeri/api/ingestion/types/ingestion_success.py +15 -0
- aeri/api/ingestion/types/ingestion_usage.py +8 -0
- aeri/api/ingestion/types/observation_body.py +53 -0
- aeri/api/ingestion/types/observation_type.py +54 -0
- aeri/api/ingestion/types/open_ai_completion_usage_schema.py +26 -0
- aeri/api/ingestion/types/open_ai_response_usage_schema.py +24 -0
- aeri/api/ingestion/types/open_ai_usage.py +28 -0
- aeri/api/ingestion/types/optional_observation_body.py +36 -0
- aeri/api/ingestion/types/score_body.py +75 -0
- aeri/api/ingestion/types/score_event.py +15 -0
- aeri/api/ingestion/types/sdk_log_body.py +14 -0
- aeri/api/ingestion/types/sdk_log_event.py +15 -0
- aeri/api/ingestion/types/trace_body.py +36 -0
- aeri/api/ingestion/types/trace_event.py +15 -0
- aeri/api/ingestion/types/update_event_body.py +14 -0
- aeri/api/ingestion/types/update_generation_body.py +40 -0
- aeri/api/ingestion/types/update_generation_event.py +15 -0
- aeri/api/ingestion/types/update_observation_event.py +15 -0
- aeri/api/ingestion/types/update_span_body.py +19 -0
- aeri/api/ingestion/types/update_span_event.py +15 -0
- aeri/api/ingestion/types/usage_details.py +10 -0
- aeri/api/legacy/__init__.py +61 -0
- aeri/api/legacy/client.py +105 -0
- aeri/api/legacy/metrics_v1/__init__.py +40 -0
- aeri/api/legacy/metrics_v1/client.py +214 -0
- aeri/api/legacy/metrics_v1/raw_client.py +322 -0
- aeri/api/legacy/metrics_v1/types/__init__.py +40 -0
- aeri/api/legacy/metrics_v1/types/metrics_response.py +19 -0
- aeri/api/legacy/observations_v1/__init__.py +43 -0
- aeri/api/legacy/observations_v1/client.py +523 -0
- aeri/api/legacy/observations_v1/raw_client.py +759 -0
- aeri/api/legacy/observations_v1/types/__init__.py +44 -0
- aeri/api/legacy/observations_v1/types/observations.py +17 -0
- aeri/api/legacy/observations_v1/types/observations_views.py +17 -0
- aeri/api/legacy/raw_client.py +13 -0
- aeri/api/legacy/score_v1/__init__.py +43 -0
- aeri/api/legacy/score_v1/client.py +329 -0
- aeri/api/legacy/score_v1/raw_client.py +545 -0
- aeri/api/legacy/score_v1/types/__init__.py +44 -0
- aeri/api/legacy/score_v1/types/create_score_request.py +75 -0
- aeri/api/legacy/score_v1/types/create_score_response.py +17 -0
- aeri/api/llm_connections/__init__.py +55 -0
- aeri/api/llm_connections/client.py +311 -0
- aeri/api/llm_connections/raw_client.py +541 -0
- aeri/api/llm_connections/types/__init__.py +53 -0
- aeri/api/llm_connections/types/llm_adapter.py +38 -0
- aeri/api/llm_connections/types/llm_connection.py +77 -0
- aeri/api/llm_connections/types/paginated_llm_connections.py +17 -0
- aeri/api/llm_connections/types/upsert_llm_connection_request.py +69 -0
- aeri/api/media/__init__.py +58 -0
- aeri/api/media/client.py +427 -0
- aeri/api/media/raw_client.py +739 -0
- aeri/api/media/types/__init__.py +56 -0
- aeri/api/media/types/get_media_response.py +55 -0
- aeri/api/media/types/get_media_upload_url_request.py +51 -0
- aeri/api/media/types/get_media_upload_url_response.py +28 -0
- aeri/api/media/types/media_content_type.py +232 -0
- aeri/api/media/types/patch_media_body.py +43 -0
- aeri/api/metrics/__init__.py +40 -0
- aeri/api/metrics/client.py +422 -0
- aeri/api/metrics/raw_client.py +530 -0
- aeri/api/metrics/types/__init__.py +40 -0
- aeri/api/metrics/types/metrics_v2response.py +19 -0
- aeri/api/models/__init__.py +43 -0
- aeri/api/models/client.py +523 -0
- aeri/api/models/raw_client.py +993 -0
- aeri/api/models/types/__init__.py +44 -0
- aeri/api/models/types/create_model_request.py +103 -0
- aeri/api/models/types/paginated_models.py +17 -0
- aeri/api/observations/__init__.py +43 -0
- aeri/api/observations/client.py +522 -0
- aeri/api/observations/raw_client.py +641 -0
- aeri/api/observations/types/__init__.py +44 -0
- aeri/api/observations/types/observations_v2meta.py +21 -0
- aeri/api/observations/types/observations_v2response.py +28 -0
- aeri/api/opentelemetry/__init__.py +67 -0
- aeri/api/opentelemetry/client.py +276 -0
- aeri/api/opentelemetry/raw_client.py +291 -0
- aeri/api/opentelemetry/types/__init__.py +65 -0
- aeri/api/opentelemetry/types/otel_attribute.py +27 -0
- aeri/api/opentelemetry/types/otel_attribute_value.py +46 -0
- aeri/api/opentelemetry/types/otel_resource.py +24 -0
- aeri/api/opentelemetry/types/otel_resource_span.py +32 -0
- aeri/api/opentelemetry/types/otel_scope.py +34 -0
- aeri/api/opentelemetry/types/otel_scope_span.py +28 -0
- aeri/api/opentelemetry/types/otel_span.py +76 -0
- aeri/api/opentelemetry/types/otel_trace_response.py +16 -0
- aeri/api/organizations/__init__.py +73 -0
- aeri/api/organizations/client.py +756 -0
- aeri/api/organizations/raw_client.py +1707 -0
- aeri/api/organizations/types/__init__.py +71 -0
- aeri/api/organizations/types/delete_membership_request.py +16 -0
- aeri/api/organizations/types/membership_deletion_response.py +17 -0
- aeri/api/organizations/types/membership_request.py +18 -0
- aeri/api/organizations/types/membership_response.py +20 -0
- aeri/api/organizations/types/membership_role.py +30 -0
- aeri/api/organizations/types/memberships_response.py +15 -0
- aeri/api/organizations/types/organization_api_key.py +31 -0
- aeri/api/organizations/types/organization_api_keys_response.py +19 -0
- aeri/api/organizations/types/organization_project.py +25 -0
- aeri/api/organizations/types/organization_projects_response.py +15 -0
- aeri/api/projects/__init__.py +67 -0
- aeri/api/projects/client.py +760 -0
- aeri/api/projects/raw_client.py +1577 -0
- aeri/api/projects/types/__init__.py +65 -0
- aeri/api/projects/types/api_key_deletion_response.py +18 -0
- aeri/api/projects/types/api_key_list.py +23 -0
- aeri/api/projects/types/api_key_response.py +30 -0
- aeri/api/projects/types/api_key_summary.py +35 -0
- aeri/api/projects/types/organization.py +22 -0
- aeri/api/projects/types/project.py +34 -0
- aeri/api/projects/types/project_deletion_response.py +15 -0
- aeri/api/projects/types/projects.py +15 -0
- aeri/api/prompt_version/__init__.py +4 -0
- aeri/api/prompt_version/client.py +157 -0
- aeri/api/prompt_version/raw_client.py +264 -0
- aeri/api/prompts/__init__.py +100 -0
- aeri/api/prompts/client.py +550 -0
- aeri/api/prompts/raw_client.py +987 -0
- aeri/api/prompts/types/__init__.py +96 -0
- aeri/api/prompts/types/base_prompt.py +42 -0
- aeri/api/prompts/types/chat_message.py +17 -0
- aeri/api/prompts/types/chat_message_type.py +15 -0
- aeri/api/prompts/types/chat_message_with_placeholders.py +8 -0
- aeri/api/prompts/types/chat_prompt.py +15 -0
- aeri/api/prompts/types/create_chat_prompt_request.py +37 -0
- aeri/api/prompts/types/create_chat_prompt_type.py +15 -0
- aeri/api/prompts/types/create_prompt_request.py +8 -0
- aeri/api/prompts/types/create_text_prompt_request.py +36 -0
- aeri/api/prompts/types/create_text_prompt_type.py +15 -0
- aeri/api/prompts/types/placeholder_message.py +16 -0
- aeri/api/prompts/types/placeholder_message_type.py +15 -0
- aeri/api/prompts/types/prompt.py +58 -0
- aeri/api/prompts/types/prompt_meta.py +35 -0
- aeri/api/prompts/types/prompt_meta_list_response.py +17 -0
- aeri/api/prompts/types/prompt_type.py +20 -0
- aeri/api/prompts/types/text_prompt.py +14 -0
- aeri/api/scim/__init__.py +94 -0
- aeri/api/scim/client.py +686 -0
- aeri/api/scim/raw_client.py +1528 -0
- aeri/api/scim/types/__init__.py +92 -0
- aeri/api/scim/types/authentication_scheme.py +20 -0
- aeri/api/scim/types/bulk_config.py +22 -0
- aeri/api/scim/types/empty_response.py +16 -0
- aeri/api/scim/types/filter_config.py +17 -0
- aeri/api/scim/types/resource_meta.py +17 -0
- aeri/api/scim/types/resource_type.py +27 -0
- aeri/api/scim/types/resource_types_response.py +21 -0
- aeri/api/scim/types/schema_extension.py +17 -0
- aeri/api/scim/types/schema_resource.py +19 -0
- aeri/api/scim/types/schemas_response.py +21 -0
- aeri/api/scim/types/scim_email.py +16 -0
- aeri/api/scim/types/scim_feature_support.py +14 -0
- aeri/api/scim/types/scim_name.py +14 -0
- aeri/api/scim/types/scim_user.py +24 -0
- aeri/api/scim/types/scim_users_list_response.py +25 -0
- aeri/api/scim/types/service_provider_config.py +36 -0
- aeri/api/scim/types/user_meta.py +20 -0
- aeri/api/score_configs/__init__.py +44 -0
- aeri/api/score_configs/client.py +526 -0
- aeri/api/score_configs/raw_client.py +1012 -0
- aeri/api/score_configs/types/__init__.py +46 -0
- aeri/api/score_configs/types/create_score_config_request.py +46 -0
- aeri/api/score_configs/types/score_configs.py +17 -0
- aeri/api/score_configs/types/update_score_config_request.py +53 -0
- aeri/api/scores/__init__.py +76 -0
- aeri/api/scores/client.py +420 -0
- aeri/api/scores/raw_client.py +656 -0
- aeri/api/scores/types/__init__.py +76 -0
- aeri/api/scores/types/get_scores_response.py +17 -0
- aeri/api/scores/types/get_scores_response_data.py +211 -0
- aeri/api/scores/types/get_scores_response_data_boolean.py +15 -0
- aeri/api/scores/types/get_scores_response_data_categorical.py +15 -0
- aeri/api/scores/types/get_scores_response_data_correction.py +15 -0
- aeri/api/scores/types/get_scores_response_data_numeric.py +15 -0
- aeri/api/scores/types/get_scores_response_trace_data.py +38 -0
- aeri/api/sessions/__init__.py +40 -0
- aeri/api/sessions/client.py +262 -0
- aeri/api/sessions/raw_client.py +500 -0
- aeri/api/sessions/types/__init__.py +40 -0
- aeri/api/sessions/types/paginated_sessions.py +17 -0
- aeri/api/trace/__init__.py +44 -0
- aeri/api/trace/client.py +728 -0
- aeri/api/trace/raw_client.py +1208 -0
- aeri/api/trace/types/__init__.py +46 -0
- aeri/api/trace/types/delete_trace_response.py +14 -0
- aeri/api/trace/types/sort.py +14 -0
- aeri/api/trace/types/traces.py +17 -0
- aeri/api/utils/__init__.py +44 -0
- aeri/api/utils/pagination/__init__.py +40 -0
- aeri/api/utils/pagination/types/__init__.py +40 -0
- aeri/api/utils/pagination/types/meta_response.py +38 -0
- aeri/batch_evaluation.py +1643 -0
- aeri/experiment.py +1044 -0
- aeri/langchain/CallbackHandler.py +1377 -0
- aeri/langchain/__init__.py +5 -0
- aeri/langchain/utils.py +212 -0
- aeri/logger.py +28 -0
- aeri/media.py +352 -0
- aeri/model.py +477 -0
- aeri/openai.py +1124 -0
- aeri/py.typed +0 -0
- aeri/span_filter.py +17 -0
- aeri/types.py +79 -0
- aeri/version.py +3 -0
- aeri_python-4.0.0.dist-info/METADATA +51 -0
- aeri_python-4.0.0.dist-info/RECORD +391 -0
- aeri_python-4.0.0.dist-info/WHEEL +4 -0
- aeri_python-4.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
"""Attribute propagation utilities for Aeri OpenTelemetry integration.
|
|
2
|
+
|
|
3
|
+
This module provides the `propagate_attributes` context manager for setting trace-level
|
|
4
|
+
attributes (user_id, session_id, metadata) that automatically propagate to all child spans
|
|
5
|
+
within the context.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, Generator, List, Literal, Optional, TypedDict, Union, cast
|
|
9
|
+
|
|
10
|
+
from opentelemetry import baggage
|
|
11
|
+
from opentelemetry import (
|
|
12
|
+
baggage as otel_baggage_api,
|
|
13
|
+
)
|
|
14
|
+
from opentelemetry import (
|
|
15
|
+
context as otel_context_api,
|
|
16
|
+
)
|
|
17
|
+
from opentelemetry import (
|
|
18
|
+
trace as otel_trace_api,
|
|
19
|
+
)
|
|
20
|
+
from opentelemetry.util._decorator import (
|
|
21
|
+
_AgnosticContextManager,
|
|
22
|
+
_agnosticcontextmanager,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from aeri._client.attributes import AeriOtelSpanAttributes
|
|
26
|
+
from aeri._client.constants import AERI_SDK_EXPERIMENT_ENVIRONMENT
|
|
27
|
+
from aeri.logger import aeri_logger
|
|
28
|
+
|
|
29
|
+
PropagatedKeys = Literal[
|
|
30
|
+
"user_id",
|
|
31
|
+
"session_id",
|
|
32
|
+
"metadata",
|
|
33
|
+
"version",
|
|
34
|
+
"tags",
|
|
35
|
+
"trace_name",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
InternalPropagatedKeys = Literal[
|
|
39
|
+
"experiment_id",
|
|
40
|
+
"experiment_name",
|
|
41
|
+
"experiment_metadata",
|
|
42
|
+
"experiment_dataset_id",
|
|
43
|
+
"experiment_item_id",
|
|
44
|
+
"experiment_item_metadata",
|
|
45
|
+
"experiment_item_root_observation_id",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
propagated_keys: List[Union[PropagatedKeys, InternalPropagatedKeys]] = [
|
|
49
|
+
"user_id",
|
|
50
|
+
"session_id",
|
|
51
|
+
"metadata",
|
|
52
|
+
"version",
|
|
53
|
+
"tags",
|
|
54
|
+
"trace_name",
|
|
55
|
+
"experiment_id",
|
|
56
|
+
"experiment_name",
|
|
57
|
+
"experiment_metadata",
|
|
58
|
+
"experiment_dataset_id",
|
|
59
|
+
"experiment_item_id",
|
|
60
|
+
"experiment_item_metadata",
|
|
61
|
+
"experiment_item_root_observation_id",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class PropagatedExperimentAttributes(TypedDict):
|
|
66
|
+
experiment_id: str
|
|
67
|
+
experiment_name: str
|
|
68
|
+
experiment_metadata: Optional[str]
|
|
69
|
+
experiment_dataset_id: Optional[str]
|
|
70
|
+
experiment_item_id: str
|
|
71
|
+
experiment_item_metadata: Optional[str]
|
|
72
|
+
experiment_item_root_observation_id: str
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def propagate_attributes(
|
|
76
|
+
*,
|
|
77
|
+
user_id: Optional[str] = None,
|
|
78
|
+
session_id: Optional[str] = None,
|
|
79
|
+
metadata: Optional[Dict[str, str]] = None,
|
|
80
|
+
version: Optional[str] = None,
|
|
81
|
+
tags: Optional[List[str]] = None,
|
|
82
|
+
trace_name: Optional[str] = None,
|
|
83
|
+
as_baggage: bool = False,
|
|
84
|
+
) -> _AgnosticContextManager[Any]:
|
|
85
|
+
"""Propagate trace-level attributes to all spans created within this context.
|
|
86
|
+
|
|
87
|
+
This context manager sets attributes on the currently active span AND automatically
|
|
88
|
+
propagates them to all new child spans created within the context. This is the
|
|
89
|
+
recommended way to set trace-level attributes like user_id, session_id, and metadata
|
|
90
|
+
dimensions that should be consistently applied across all observations in a trace.
|
|
91
|
+
|
|
92
|
+
**IMPORTANT**: Call this as early as possible within your trace/workflow. Only the
|
|
93
|
+
currently active span and spans created after entering this context will have these
|
|
94
|
+
attributes. Pre-existing spans will NOT be retroactively updated.
|
|
95
|
+
|
|
96
|
+
**Why this matters**: Aeri aggregation queries (e.g., total cost by user_id,
|
|
97
|
+
filtering by session_id) only include observations that have the attribute set.
|
|
98
|
+
If you call `propagate_attributes` late in your workflow, earlier spans won't be
|
|
99
|
+
included in aggregations for that attribute.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
user_id: User identifier to associate with all spans in this context.
|
|
103
|
+
Must be US-ASCII string, ≤200 characters. Use this to track which user
|
|
104
|
+
generated each trace and enable e.g. per-user cost/performance analysis.
|
|
105
|
+
session_id: Session identifier to associate with all spans in this context.
|
|
106
|
+
Must be US-ASCII string, ≤200 characters. Use this to group related traces
|
|
107
|
+
within a user session (e.g., a conversation thread, multi-turn interaction).
|
|
108
|
+
metadata: Additional key-value metadata to propagate to all spans.
|
|
109
|
+
- Keys and values must be US-ASCII strings
|
|
110
|
+
- All values must be ≤200 characters
|
|
111
|
+
- Use for dimensions like internal correlating identifiers
|
|
112
|
+
- AVOID: large payloads, sensitive data, non-string values (will be dropped with warning)
|
|
113
|
+
version: Version identfier for parts of your application that are independently versioned, e.g. agents
|
|
114
|
+
tags: List of tags to categorize the group of observations
|
|
115
|
+
trace_name: Name to assign to the trace. Must be US-ASCII string, ≤200 characters.
|
|
116
|
+
Use this to set a consistent trace name for all spans created within this context.
|
|
117
|
+
as_baggage: If True, propagates attributes using OpenTelemetry baggage for
|
|
118
|
+
cross-process/service propagation. **Security warning**: When enabled,
|
|
119
|
+
attribute values are added to HTTP headers on ALL outbound requests.
|
|
120
|
+
Only enable if values are safe to transmit via HTTP headers and you need
|
|
121
|
+
cross-service tracing. Default: False.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Context manager that propagates attributes to all child spans.
|
|
125
|
+
|
|
126
|
+
Example:
|
|
127
|
+
Basic usage with user and session tracking:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from aeri import Aeri
|
|
131
|
+
|
|
132
|
+
aeri = Aeri()
|
|
133
|
+
|
|
134
|
+
# Set attributes early in the trace
|
|
135
|
+
with aeri.start_as_current_observation(name="user_workflow") as span:
|
|
136
|
+
with aeri.propagate_attributes(
|
|
137
|
+
user_id="user_123",
|
|
138
|
+
session_id="session_abc",
|
|
139
|
+
metadata={"experiment": "variant_a", "environment": "production"}
|
|
140
|
+
):
|
|
141
|
+
# All spans created here will have user_id, session_id, and metadata
|
|
142
|
+
with aeri.start_observation(name="llm_call") as llm_span:
|
|
143
|
+
# This span inherits: user_id, session_id, experiment, environment
|
|
144
|
+
...
|
|
145
|
+
|
|
146
|
+
with aeri.start_generation(name="completion") as gen:
|
|
147
|
+
# This span also inherits all attributes
|
|
148
|
+
...
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Late propagation (anti-pattern):
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
with aeri.start_as_current_observation(name="workflow") as span:
|
|
155
|
+
# These spans WON'T have user_id
|
|
156
|
+
early_span = aeri.start_observation(name="early_work")
|
|
157
|
+
early_span.end()
|
|
158
|
+
|
|
159
|
+
# Set attributes in the middle
|
|
160
|
+
with aeri.propagate_attributes(user_id="user_123"):
|
|
161
|
+
# Only spans created AFTER this point will have user_id
|
|
162
|
+
late_span = aeri.start_observation(name="late_work")
|
|
163
|
+
late_span.end()
|
|
164
|
+
|
|
165
|
+
# Result: Aggregations by user_id will miss "early_work" span
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Cross-service propagation with baggage (advanced):
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
# Service A - originating service
|
|
172
|
+
with aeri.start_as_current_observation(name="api_request"):
|
|
173
|
+
with aeri.propagate_attributes(
|
|
174
|
+
user_id="user_123",
|
|
175
|
+
session_id="session_abc",
|
|
176
|
+
as_baggage=True # Propagate via HTTP headers
|
|
177
|
+
):
|
|
178
|
+
# Make HTTP request to Service B
|
|
179
|
+
response = requests.get("https://service-b.example.com/api")
|
|
180
|
+
# user_id and session_id are now in HTTP headers
|
|
181
|
+
|
|
182
|
+
# Service B - downstream service
|
|
183
|
+
# OpenTelemetry will automatically extract baggage from HTTP headers
|
|
184
|
+
# and propagate to spans in Service B
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Note:
|
|
188
|
+
- **Validation**: All attribute values (user_id, session_id, metadata values)
|
|
189
|
+
must be strings ≤200 characters. Invalid values will be dropped with a
|
|
190
|
+
warning logged. Ensure values meet constraints before calling.
|
|
191
|
+
- **OpenTelemetry**: This uses OpenTelemetry context propagation under the hood,
|
|
192
|
+
making it compatible with other OTel-instrumented libraries.
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
No exceptions are raised. Invalid values are logged as warnings and dropped.
|
|
196
|
+
"""
|
|
197
|
+
return _propagate_attributes(
|
|
198
|
+
user_id=user_id,
|
|
199
|
+
session_id=session_id,
|
|
200
|
+
metadata=metadata,
|
|
201
|
+
version=version,
|
|
202
|
+
tags=tags,
|
|
203
|
+
trace_name=trace_name,
|
|
204
|
+
as_baggage=as_baggage,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@_agnosticcontextmanager
|
|
209
|
+
def _propagate_attributes(
|
|
210
|
+
*,
|
|
211
|
+
user_id: Optional[str] = None,
|
|
212
|
+
session_id: Optional[str] = None,
|
|
213
|
+
metadata: Optional[Dict[str, str]] = None,
|
|
214
|
+
version: Optional[str] = None,
|
|
215
|
+
tags: Optional[List[str]] = None,
|
|
216
|
+
trace_name: Optional[str] = None,
|
|
217
|
+
as_baggage: bool = False,
|
|
218
|
+
experiment: Optional[PropagatedExperimentAttributes] = None,
|
|
219
|
+
) -> Generator[Any, Any, Any]:
|
|
220
|
+
context = otel_context_api.get_current()
|
|
221
|
+
current_span = otel_trace_api.get_current_span()
|
|
222
|
+
|
|
223
|
+
propagated_string_attributes: Dict[str, Optional[Union[str, List[str]]]] = {
|
|
224
|
+
"user_id": user_id,
|
|
225
|
+
"session_id": session_id,
|
|
226
|
+
"version": version,
|
|
227
|
+
"tags": tags,
|
|
228
|
+
"trace_name": trace_name,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
propagated_string_attributes = propagated_string_attributes | (
|
|
232
|
+
cast(Dict[str, Union[str, List[str], None]], experiment) or {}
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Filter out None values
|
|
236
|
+
propagated_string_attributes = {
|
|
237
|
+
k: v for k, v in propagated_string_attributes.items() if v is not None
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
for key, value in propagated_string_attributes.items():
|
|
241
|
+
validated_value = _validate_propagated_value(value=value, key=key)
|
|
242
|
+
|
|
243
|
+
if validated_value is not None:
|
|
244
|
+
context = _set_propagated_attribute(
|
|
245
|
+
key=key,
|
|
246
|
+
value=validated_value,
|
|
247
|
+
context=context,
|
|
248
|
+
span=current_span,
|
|
249
|
+
as_baggage=as_baggage,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
if metadata is not None:
|
|
253
|
+
validated_metadata: Dict[str, str] = {}
|
|
254
|
+
|
|
255
|
+
for key, value in metadata.items():
|
|
256
|
+
if _validate_string_value(value=value, key=f"metadata.{key}"):
|
|
257
|
+
validated_metadata[key] = value
|
|
258
|
+
|
|
259
|
+
if validated_metadata:
|
|
260
|
+
context = _set_propagated_attribute(
|
|
261
|
+
key="metadata",
|
|
262
|
+
value=validated_metadata,
|
|
263
|
+
context=context,
|
|
264
|
+
span=current_span,
|
|
265
|
+
as_baggage=as_baggage,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Activate context, execute, and detach context
|
|
269
|
+
token = otel_context_api.attach(context=context)
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
yield
|
|
273
|
+
|
|
274
|
+
finally:
|
|
275
|
+
otel_context_api.detach(token)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _get_propagated_attributes_from_context(
|
|
279
|
+
context: otel_context_api.Context,
|
|
280
|
+
) -> Dict[str, Union[str, List[str]]]:
|
|
281
|
+
propagated_attributes: Dict[str, Union[str, List[str]]] = {}
|
|
282
|
+
|
|
283
|
+
# Handle baggage
|
|
284
|
+
baggage_entries = baggage.get_all(context=context)
|
|
285
|
+
for baggage_key, baggage_value in baggage_entries.items():
|
|
286
|
+
if baggage_key.startswith(AERI_BAGGAGE_PREFIX):
|
|
287
|
+
span_key = _get_span_key_from_baggage_key(baggage_key)
|
|
288
|
+
|
|
289
|
+
if span_key:
|
|
290
|
+
propagated_attributes[span_key] = (
|
|
291
|
+
baggage_value
|
|
292
|
+
if isinstance(baggage_value, (str, list))
|
|
293
|
+
else str(baggage_value)
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# Handle OTEL context
|
|
297
|
+
for key in propagated_keys:
|
|
298
|
+
context_key = _get_propagated_context_key(key)
|
|
299
|
+
value = otel_context_api.get_value(key=context_key, context=context)
|
|
300
|
+
|
|
301
|
+
if value is None:
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
if isinstance(value, dict):
|
|
305
|
+
# Handle metadata
|
|
306
|
+
for k, v in value.items():
|
|
307
|
+
span_key = f"{AeriOtelSpanAttributes.TRACE_METADATA}.{k}"
|
|
308
|
+
propagated_attributes[span_key] = v
|
|
309
|
+
|
|
310
|
+
else:
|
|
311
|
+
span_key = _get_propagated_span_key(key)
|
|
312
|
+
|
|
313
|
+
propagated_attributes[span_key] = (
|
|
314
|
+
value if isinstance(value, (str, list)) else str(value)
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
if (
|
|
318
|
+
AeriOtelSpanAttributes.EXPERIMENT_ITEM_ROOT_OBSERVATION_ID
|
|
319
|
+
in propagated_attributes
|
|
320
|
+
):
|
|
321
|
+
propagated_attributes[AeriOtelSpanAttributes.ENVIRONMENT] = (
|
|
322
|
+
AERI_SDK_EXPERIMENT_ENVIRONMENT
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
return propagated_attributes
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _set_propagated_attribute(
|
|
329
|
+
*,
|
|
330
|
+
key: str,
|
|
331
|
+
value: Union[str, List[str], Dict[str, str]],
|
|
332
|
+
context: otel_context_api.Context,
|
|
333
|
+
span: otel_trace_api.Span,
|
|
334
|
+
as_baggage: bool,
|
|
335
|
+
) -> otel_context_api.Context:
|
|
336
|
+
# Get key names
|
|
337
|
+
context_key = _get_propagated_context_key(key)
|
|
338
|
+
span_key = _get_propagated_span_key(key)
|
|
339
|
+
baggage_key = _get_propagated_baggage_key(key)
|
|
340
|
+
|
|
341
|
+
# Merge metadata with previously set metadata keys
|
|
342
|
+
if isinstance(value, dict):
|
|
343
|
+
existing_metadata_in_context = cast(
|
|
344
|
+
dict, otel_context_api.get_value(context_key) or {}
|
|
345
|
+
)
|
|
346
|
+
value = existing_metadata_in_context | value
|
|
347
|
+
|
|
348
|
+
# Merge tags with previously set tags
|
|
349
|
+
if isinstance(value, list):
|
|
350
|
+
existing_tags_in_context = cast(
|
|
351
|
+
list, otel_context_api.get_value(context_key) or []
|
|
352
|
+
)
|
|
353
|
+
merged_tags = list(existing_tags_in_context)
|
|
354
|
+
merged_tags.extend(tag for tag in value if tag not in existing_tags_in_context)
|
|
355
|
+
|
|
356
|
+
value = merged_tags
|
|
357
|
+
|
|
358
|
+
# Set in context
|
|
359
|
+
context = otel_context_api.set_value(
|
|
360
|
+
key=context_key,
|
|
361
|
+
value=value,
|
|
362
|
+
context=context,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Set on current span
|
|
366
|
+
if span is not None and span.is_recording():
|
|
367
|
+
if isinstance(value, dict):
|
|
368
|
+
# Handle metadata
|
|
369
|
+
for k, v in value.items():
|
|
370
|
+
span.set_attribute(
|
|
371
|
+
key=f"{AeriOtelSpanAttributes.TRACE_METADATA}.{k}",
|
|
372
|
+
value=v,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
else:
|
|
376
|
+
span.set_attribute(key=span_key, value=value)
|
|
377
|
+
|
|
378
|
+
# Set on baggage
|
|
379
|
+
if as_baggage:
|
|
380
|
+
if isinstance(value, dict):
|
|
381
|
+
# Handle metadata
|
|
382
|
+
for k, v in value.items():
|
|
383
|
+
context = otel_baggage_api.set_baggage(
|
|
384
|
+
name=f"{baggage_key}_{k}", value=v, context=context
|
|
385
|
+
)
|
|
386
|
+
else:
|
|
387
|
+
context = otel_baggage_api.set_baggage(
|
|
388
|
+
name=baggage_key, value=value, context=context
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
return context
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _validate_propagated_value(
|
|
395
|
+
*, value: Any, key: str
|
|
396
|
+
) -> Optional[Union[str, List[str]]]:
|
|
397
|
+
if isinstance(value, list):
|
|
398
|
+
validated_values = [
|
|
399
|
+
v for v in value if _validate_string_value(key=key, value=v)
|
|
400
|
+
]
|
|
401
|
+
|
|
402
|
+
return validated_values if len(validated_values) > 0 else None
|
|
403
|
+
|
|
404
|
+
if not isinstance(value, str):
|
|
405
|
+
aeri_logger.warning( # type: ignore
|
|
406
|
+
f"Propagated attribute '{key}' value is not a string. Dropping value."
|
|
407
|
+
)
|
|
408
|
+
return None
|
|
409
|
+
|
|
410
|
+
if len(value) > 200:
|
|
411
|
+
aeri_logger.warning(
|
|
412
|
+
f"Propagated attribute '{key}' value is over 200 characters ({len(value)} chars). Dropping value."
|
|
413
|
+
)
|
|
414
|
+
return None
|
|
415
|
+
|
|
416
|
+
return value
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _validate_string_value(*, value: str, key: str) -> bool:
|
|
420
|
+
if not isinstance(value, str):
|
|
421
|
+
aeri_logger.warning( # type: ignore
|
|
422
|
+
f"Propagated attribute '{key}' value is not a string. Dropping value."
|
|
423
|
+
)
|
|
424
|
+
return False
|
|
425
|
+
|
|
426
|
+
if len(value) > 200:
|
|
427
|
+
aeri_logger.warning(
|
|
428
|
+
f"Propagated attribute '{key}' value is over 200 characters ({len(value)} chars). Dropping value."
|
|
429
|
+
)
|
|
430
|
+
return False
|
|
431
|
+
|
|
432
|
+
return True
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _get_propagated_context_key(key: str) -> str:
|
|
436
|
+
return f"aeri.propagated.{key}"
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
AERI_BAGGAGE_PREFIX = "aeri_"
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _get_propagated_baggage_key(key: str) -> str:
|
|
443
|
+
return f"{AERI_BAGGAGE_PREFIX}{key}"
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _get_span_key_from_baggage_key(key: str) -> Optional[str]:
|
|
447
|
+
if not key.startswith(AERI_BAGGAGE_PREFIX):
|
|
448
|
+
return None
|
|
449
|
+
|
|
450
|
+
# Remove prefix to get the actual key name
|
|
451
|
+
suffix = key[len(AERI_BAGGAGE_PREFIX) :]
|
|
452
|
+
|
|
453
|
+
if suffix.startswith("metadata_"):
|
|
454
|
+
metadata_key = suffix[len("metadata_") :]
|
|
455
|
+
|
|
456
|
+
return _get_propagated_span_key(metadata_key)
|
|
457
|
+
|
|
458
|
+
return _get_propagated_span_key(suffix)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _get_propagated_span_key(key: str) -> str:
|
|
462
|
+
return {
|
|
463
|
+
"session_id": AeriOtelSpanAttributes.TRACE_SESSION_ID,
|
|
464
|
+
"user_id": AeriOtelSpanAttributes.TRACE_USER_ID,
|
|
465
|
+
"version": AeriOtelSpanAttributes.VERSION,
|
|
466
|
+
"tags": AeriOtelSpanAttributes.TRACE_TAGS,
|
|
467
|
+
"trace_name": AeriOtelSpanAttributes.TRACE_NAME,
|
|
468
|
+
"experiment_id": AeriOtelSpanAttributes.EXPERIMENT_ID,
|
|
469
|
+
"experiment_name": AeriOtelSpanAttributes.EXPERIMENT_NAME,
|
|
470
|
+
"experiment_metadata": AeriOtelSpanAttributes.EXPERIMENT_METADATA,
|
|
471
|
+
"experiment_dataset_id": AeriOtelSpanAttributes.EXPERIMENT_DATASET_ID,
|
|
472
|
+
"experiment_item_id": AeriOtelSpanAttributes.EXPERIMENT_ITEM_ID,
|
|
473
|
+
"experiment_item_metadata": AeriOtelSpanAttributes.EXPERIMENT_ITEM_METADATA,
|
|
474
|
+
"experiment_item_root_observation_id": AeriOtelSpanAttributes.EXPERIMENT_ITEM_ROOT_OBSERVATION_ID,
|
|
475
|
+
}.get(key) or f"{AeriOtelSpanAttributes.TRACE_METADATA}.{key}"
|