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,76 @@
|
|
|
1
|
+
"""Span filter predicates for controlling OpenTelemetry span export.
|
|
2
|
+
|
|
3
|
+
This module provides composable filter functions that determine which spans
|
|
4
|
+
the AeriSpanProcessor forwards to the Aeri backend.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from opentelemetry.sdk.trace import ReadableSpan
|
|
8
|
+
|
|
9
|
+
from aeri._client.constants import AERI_TRACER_NAME
|
|
10
|
+
|
|
11
|
+
KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES = frozenset(
|
|
12
|
+
{
|
|
13
|
+
AERI_TRACER_NAME,
|
|
14
|
+
"agent_framework",
|
|
15
|
+
"ai",
|
|
16
|
+
"haystack",
|
|
17
|
+
"langsmith",
|
|
18
|
+
"litellm",
|
|
19
|
+
"openinference",
|
|
20
|
+
"opentelemetry.instrumentation.anthropic",
|
|
21
|
+
"strands-agents",
|
|
22
|
+
"vllm",
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
"""Known instrumentation scope namespace prefixes.
|
|
26
|
+
|
|
27
|
+
Prefix matching is boundary-aware:
|
|
28
|
+
- exact match (``scope == prefix``)
|
|
29
|
+
- direct descendant scopes (``scope.startswith(prefix + ".")``)
|
|
30
|
+
|
|
31
|
+
Please create a Github issue in https://github.com/aeri/aeri if you'd like to expand this default allow list.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_aeri_span(span: ReadableSpan) -> bool:
|
|
36
|
+
"""Return whether the span was created by the Aeri SDK tracer."""
|
|
37
|
+
return (
|
|
38
|
+
span.instrumentation_scope is not None
|
|
39
|
+
and span.instrumentation_scope.name == AERI_TRACER_NAME
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_genai_span(span: ReadableSpan) -> bool:
|
|
44
|
+
"""Return whether the span has any ``gen_ai.*`` semantic convention attribute."""
|
|
45
|
+
if span.attributes is None:
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
return any(
|
|
49
|
+
isinstance(key, str) and key.startswith("gen_ai")
|
|
50
|
+
for key in span.attributes.keys()
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _matches_scope_prefix(scope_name: str, prefix: str) -> bool:
|
|
55
|
+
"""Return whether a scope matches a prefix using namespace boundaries."""
|
|
56
|
+
return scope_name == prefix or scope_name.startswith(f"{prefix}.")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_known_llm_instrumentor(span: ReadableSpan) -> bool:
|
|
60
|
+
"""Return whether the span comes from a known LLM instrumentation scope."""
|
|
61
|
+
if span.instrumentation_scope is None:
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
scope_name = span.instrumentation_scope.name
|
|
65
|
+
|
|
66
|
+
return any(
|
|
67
|
+
_matches_scope_prefix(scope_name, prefix)
|
|
68
|
+
for prefix in KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def is_default_export_span(span: ReadableSpan) -> bool:
|
|
73
|
+
"""Return whether a span should be exported by default."""
|
|
74
|
+
return (
|
|
75
|
+
is_aeri_span(span) or is_genai_span(span) or is_known_llm_instrumentor(span)
|
|
76
|
+
)
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Span processor for Aeri OpenTelemetry integration.
|
|
2
|
+
|
|
3
|
+
This module defines the AeriSpanProcessor class, which extends OpenTelemetry's
|
|
4
|
+
BatchSpanProcessor with Aeri-specific functionality. It handles exporting
|
|
5
|
+
spans to the Aeri API with proper authentication and filtering.
|
|
6
|
+
|
|
7
|
+
Key features:
|
|
8
|
+
- HTTP-based span export to Aeri API
|
|
9
|
+
- Basic authentication with Aeri API keys
|
|
10
|
+
- Configurable batch processing behavior
|
|
11
|
+
- Project-scoped span filtering to prevent cross-project data leakage
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import base64
|
|
15
|
+
import os
|
|
16
|
+
from typing import Callable, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
from opentelemetry import context as context_api
|
|
19
|
+
from opentelemetry.context import Context
|
|
20
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
21
|
+
from opentelemetry.sdk.trace import ReadableSpan, Span
|
|
22
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
23
|
+
from opentelemetry.trace import format_span_id
|
|
24
|
+
|
|
25
|
+
from aeri._client.environment_variables import (
|
|
26
|
+
AERI_FLUSH_AT,
|
|
27
|
+
AERI_FLUSH_INTERVAL,
|
|
28
|
+
AERI_OTEL_TRACES_EXPORT_PATH,
|
|
29
|
+
)
|
|
30
|
+
from aeri._client.propagation import _get_propagated_attributes_from_context
|
|
31
|
+
from aeri._client.span_filter import is_default_export_span, is_aeri_span
|
|
32
|
+
from aeri._client.utils import span_formatter
|
|
33
|
+
from aeri.logger import aeri_logger
|
|
34
|
+
from aeri.version import __version__ as aeri_version
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AeriSpanProcessor(BatchSpanProcessor):
|
|
38
|
+
"""OpenTelemetry span processor that exports spans to the Aeri API.
|
|
39
|
+
|
|
40
|
+
This processor extends OpenTelemetry's BatchSpanProcessor with Aeri-specific functionality:
|
|
41
|
+
1. Project-scoped span filtering to prevent cross-project data leakage
|
|
42
|
+
2. Instrumentation scope filtering to block spans from specific libraries/frameworks
|
|
43
|
+
3. Configurable batch processing parameters for optimal performance
|
|
44
|
+
4. HTTP-based span export to the Aeri OTLP endpoint
|
|
45
|
+
5. Debug logging for span processing operations
|
|
46
|
+
6. Authentication with Aeri API using Basic Auth
|
|
47
|
+
|
|
48
|
+
The processor is designed to efficiently handle large volumes of spans with
|
|
49
|
+
minimal overhead, while ensuring spans are only sent to the correct project.
|
|
50
|
+
It integrates with OpenTelemetry's standard span lifecycle, adding Aeri-specific
|
|
51
|
+
filtering and export capabilities.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
*,
|
|
57
|
+
public_key: str,
|
|
58
|
+
secret_key: str,
|
|
59
|
+
base_url: str,
|
|
60
|
+
timeout: Optional[int] = None,
|
|
61
|
+
flush_at: Optional[int] = None,
|
|
62
|
+
flush_interval: Optional[float] = None,
|
|
63
|
+
blocked_instrumentation_scopes: Optional[List[str]] = None,
|
|
64
|
+
should_export_span: Optional[Callable[[ReadableSpan], bool]] = None,
|
|
65
|
+
additional_headers: Optional[Dict[str, str]] = None,
|
|
66
|
+
):
|
|
67
|
+
self.public_key = public_key
|
|
68
|
+
self.blocked_instrumentation_scopes = (
|
|
69
|
+
blocked_instrumentation_scopes
|
|
70
|
+
if blocked_instrumentation_scopes is not None
|
|
71
|
+
else []
|
|
72
|
+
)
|
|
73
|
+
self._should_export_span = should_export_span or is_default_export_span
|
|
74
|
+
|
|
75
|
+
env_flush_at = os.environ.get(AERI_FLUSH_AT, None)
|
|
76
|
+
flush_at = flush_at or int(env_flush_at) if env_flush_at is not None else None
|
|
77
|
+
|
|
78
|
+
env_flush_interval = os.environ.get(AERI_FLUSH_INTERVAL, None)
|
|
79
|
+
flush_interval = (
|
|
80
|
+
flush_interval or float(env_flush_interval)
|
|
81
|
+
if env_flush_interval is not None
|
|
82
|
+
else None
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
basic_auth_header = "Basic " + base64.b64encode(
|
|
86
|
+
f"{public_key}:{secret_key}".encode("utf-8")
|
|
87
|
+
).decode("ascii")
|
|
88
|
+
|
|
89
|
+
# Prepare default headers
|
|
90
|
+
default_headers = {
|
|
91
|
+
"Authorization": basic_auth_header,
|
|
92
|
+
"x-aeri-sdk-name": "python",
|
|
93
|
+
"x-aeri-sdk-version": aeri_version,
|
|
94
|
+
"x-aeri-public-key": public_key,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Merge additional headers if provided
|
|
98
|
+
headers = {**default_headers, **(additional_headers or {})}
|
|
99
|
+
|
|
100
|
+
traces_export_path = os.environ.get(AERI_OTEL_TRACES_EXPORT_PATH, None)
|
|
101
|
+
|
|
102
|
+
endpoint = (
|
|
103
|
+
f"{base_url}/{traces_export_path}"
|
|
104
|
+
if traces_export_path
|
|
105
|
+
else f"{base_url}/api/public/otel/v1/traces"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
aeri_span_exporter = OTLPSpanExporter(
|
|
109
|
+
endpoint=endpoint,
|
|
110
|
+
headers=headers,
|
|
111
|
+
timeout=timeout,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
super().__init__(
|
|
115
|
+
span_exporter=aeri_span_exporter,
|
|
116
|
+
export_timeout_millis=timeout * 1_000 if timeout else None,
|
|
117
|
+
max_export_batch_size=flush_at,
|
|
118
|
+
schedule_delay_millis=flush_interval * 1_000
|
|
119
|
+
if flush_interval is not None
|
|
120
|
+
else None,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None:
|
|
124
|
+
context = parent_context or context_api.get_current()
|
|
125
|
+
propagated_attributes = _get_propagated_attributes_from_context(context)
|
|
126
|
+
|
|
127
|
+
if propagated_attributes:
|
|
128
|
+
span.set_attributes(propagated_attributes)
|
|
129
|
+
|
|
130
|
+
aeri_logger.debug(
|
|
131
|
+
f"Propagated {len(propagated_attributes)} attributes to span '{format_span_id(span.context.span_id)}': {propagated_attributes}"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return super().on_start(span, parent_context)
|
|
135
|
+
|
|
136
|
+
def on_end(self, span: ReadableSpan) -> None:
|
|
137
|
+
# Only export spans that belong to the scoped project
|
|
138
|
+
# This is important to not send spans to wrong project in multi-project setups
|
|
139
|
+
if is_aeri_span(span) and not self._is_aeri_project_span(span):
|
|
140
|
+
aeri_logger.debug(
|
|
141
|
+
f"Security: Span rejected - belongs to project '{span.instrumentation_scope.attributes.get('public_key') if span.instrumentation_scope and span.instrumentation_scope.attributes else None}' but processor is for '{self.public_key}'. "
|
|
142
|
+
f"This prevents cross-project data leakage in multi-project environments."
|
|
143
|
+
)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
# Do not export spans from blocked instrumentation scopes
|
|
147
|
+
if self._is_blocked_instrumentation_scope(span):
|
|
148
|
+
aeri_logger.debug(
|
|
149
|
+
"Trace: Dropping span due to blocked instrumentation scope | "
|
|
150
|
+
f"span_name='{span.name}' | "
|
|
151
|
+
f"instrumentation_scope='{self._get_scope_name(span)}'"
|
|
152
|
+
)
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
# Apply custom or default span filter
|
|
156
|
+
try:
|
|
157
|
+
should_export = self._should_export_span(span)
|
|
158
|
+
except Exception as error:
|
|
159
|
+
aeri_logger.error(
|
|
160
|
+
"Trace: should_export_span callback raised an error. "
|
|
161
|
+
f"Dropping span name='{span.name}' scope='{self._get_scope_name(span)}'. "
|
|
162
|
+
f"Error: {error}"
|
|
163
|
+
)
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
if not should_export:
|
|
167
|
+
aeri_logger.debug(
|
|
168
|
+
"Trace: Dropping span due to should_export_span filter | "
|
|
169
|
+
f"span_name='{span.name}' | "
|
|
170
|
+
f"instrumentation_scope='{self._get_scope_name(span)}'"
|
|
171
|
+
)
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
aeri_logger.debug(
|
|
175
|
+
f"Trace: Processing span name='{span._name}' | Full details:\n{span_formatter(span)}"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
super().on_end(span)
|
|
179
|
+
|
|
180
|
+
def _is_blocked_instrumentation_scope(self, span: ReadableSpan) -> bool:
|
|
181
|
+
return (
|
|
182
|
+
span.instrumentation_scope is not None
|
|
183
|
+
and span.instrumentation_scope.name in self.blocked_instrumentation_scopes
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def _is_aeri_project_span(self, span: ReadableSpan) -> bool:
|
|
187
|
+
if not is_aeri_span(span):
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
if span.instrumentation_scope is not None:
|
|
191
|
+
public_key_on_span = (
|
|
192
|
+
span.instrumentation_scope.attributes.get("public_key", None)
|
|
193
|
+
if span.instrumentation_scope.attributes
|
|
194
|
+
else None
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return public_key_on_span == self.public_key
|
|
198
|
+
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
@staticmethod
|
|
202
|
+
def _get_scope_name(span: ReadableSpan) -> Optional[str]:
|
|
203
|
+
if span.instrumentation_scope is None:
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
return span.instrumentation_scope.name
|
aeri/_client/utils.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Utility functions for Aeri OpenTelemetry integration.
|
|
2
|
+
|
|
3
|
+
This module provides utility functions for working with OpenTelemetry spans,
|
|
4
|
+
including formatting and serialization of span data, and async execution helpers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import threading
|
|
10
|
+
from hashlib import sha256
|
|
11
|
+
from typing import Any, Coroutine
|
|
12
|
+
|
|
13
|
+
from opentelemetry import trace as otel_trace_api
|
|
14
|
+
from opentelemetry.sdk import util
|
|
15
|
+
from opentelemetry.sdk.trace import ReadableSpan
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def span_formatter(span: ReadableSpan) -> str:
|
|
19
|
+
parent_id = (
|
|
20
|
+
otel_trace_api.format_span_id(span.parent.span_id) if span.parent else None
|
|
21
|
+
)
|
|
22
|
+
start_time = util.ns_to_iso_str(span._start_time) if span._start_time else None
|
|
23
|
+
end_time = util.ns_to_iso_str(span._end_time) if span._end_time else None
|
|
24
|
+
status = {
|
|
25
|
+
"status_code": str(span._status.status_code.name),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if span._status.description:
|
|
29
|
+
status["description"] = span._status.description
|
|
30
|
+
|
|
31
|
+
context = (
|
|
32
|
+
{
|
|
33
|
+
"trace_id": otel_trace_api.format_trace_id(span._context.trace_id),
|
|
34
|
+
"span_id": otel_trace_api.format_span_id(span._context.span_id),
|
|
35
|
+
"trace_state": repr(span._context.trace_state),
|
|
36
|
+
}
|
|
37
|
+
if span._context
|
|
38
|
+
else None
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
instrumentationScope = json.loads(
|
|
42
|
+
span._instrumentation_scope.to_json() if span._instrumentation_scope else "{}"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
json.dumps(
|
|
47
|
+
{
|
|
48
|
+
"name": span._name,
|
|
49
|
+
"context": context,
|
|
50
|
+
"kind": str(span.kind),
|
|
51
|
+
"parent_id": parent_id,
|
|
52
|
+
"start_time": start_time,
|
|
53
|
+
"end_time": end_time,
|
|
54
|
+
"status": status,
|
|
55
|
+
"attributes": span._format_attributes(span._attributes),
|
|
56
|
+
"events": span._format_events(span._events),
|
|
57
|
+
"links": span._format_links(span._links),
|
|
58
|
+
"resource": json.loads(span.resource.to_json()),
|
|
59
|
+
"instrumentationScope": instrumentationScope,
|
|
60
|
+
},
|
|
61
|
+
indent=2,
|
|
62
|
+
)
|
|
63
|
+
+ "\n"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class _RunAsyncThread(threading.Thread):
|
|
68
|
+
"""Helper thread class for running async coroutines in a separate thread."""
|
|
69
|
+
|
|
70
|
+
def __init__(self, coro: Coroutine[Any, Any, Any]) -> None:
|
|
71
|
+
self.coro = coro
|
|
72
|
+
self.result: Any = None
|
|
73
|
+
self.exception: Exception | None = None
|
|
74
|
+
super().__init__()
|
|
75
|
+
|
|
76
|
+
def run(self) -> None:
|
|
77
|
+
try:
|
|
78
|
+
self.result = asyncio.run(self.coro)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
self.exception = e
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def run_async_safely(coro: Coroutine[Any, Any, Any]) -> Any:
|
|
84
|
+
"""Safely run an async coroutine, handling existing event loops.
|
|
85
|
+
|
|
86
|
+
This function detects if there's already a running event loop and uses
|
|
87
|
+
a separate thread if needed to avoid the "asyncio.run() cannot be called
|
|
88
|
+
from a running event loop" error. This is particularly useful in environments
|
|
89
|
+
like Jupyter notebooks, FastAPI applications, or other async frameworks.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
coro: The coroutine to run
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
The result of the coroutine
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
Any exception raised by the coroutine
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
```python
|
|
102
|
+
# Works in both sync and async contexts
|
|
103
|
+
async def my_async_function():
|
|
104
|
+
await asyncio.sleep(1)
|
|
105
|
+
return "done"
|
|
106
|
+
|
|
107
|
+
result = run_async_safely(my_async_function())
|
|
108
|
+
```
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
# Check if there's already a running event loop
|
|
112
|
+
loop = asyncio.get_running_loop()
|
|
113
|
+
except RuntimeError:
|
|
114
|
+
# No running loop, safe to use asyncio.run()
|
|
115
|
+
return asyncio.run(coro)
|
|
116
|
+
|
|
117
|
+
if loop and loop.is_running():
|
|
118
|
+
# There's a running loop, use a separate thread
|
|
119
|
+
thread = _RunAsyncThread(coro)
|
|
120
|
+
thread.start()
|
|
121
|
+
thread.join()
|
|
122
|
+
|
|
123
|
+
if thread.exception:
|
|
124
|
+
raise thread.exception
|
|
125
|
+
return thread.result
|
|
126
|
+
else:
|
|
127
|
+
# Loop exists but not running, safe to use asyncio.run()
|
|
128
|
+
return asyncio.run(coro)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_sha256_hash_hex(value: Any) -> str:
|
|
132
|
+
return sha256(value.encode("utf-8")).digest().hex()
|