genesis-flow 1.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.
- genesis_flow-1.0.0.dist-info/METADATA +822 -0
- genesis_flow-1.0.0.dist-info/RECORD +645 -0
- genesis_flow-1.0.0.dist-info/WHEEL +5 -0
- genesis_flow-1.0.0.dist-info/entry_points.txt +19 -0
- genesis_flow-1.0.0.dist-info/licenses/LICENSE.txt +202 -0
- genesis_flow-1.0.0.dist-info/top_level.txt +1 -0
- mlflow/__init__.py +367 -0
- mlflow/__main__.py +3 -0
- mlflow/ag2/__init__.py +56 -0
- mlflow/ag2/ag2_logger.py +294 -0
- mlflow/anthropic/__init__.py +40 -0
- mlflow/anthropic/autolog.py +129 -0
- mlflow/anthropic/chat.py +144 -0
- mlflow/artifacts/__init__.py +268 -0
- mlflow/autogen/__init__.py +144 -0
- mlflow/autogen/chat.py +142 -0
- mlflow/azure/__init__.py +26 -0
- mlflow/azure/auth_handler.py +257 -0
- mlflow/azure/client.py +319 -0
- mlflow/azure/config.py +120 -0
- mlflow/azure/connection_factory.py +340 -0
- mlflow/azure/exceptions.py +27 -0
- mlflow/azure/stores.py +327 -0
- mlflow/azure/utils.py +183 -0
- mlflow/bedrock/__init__.py +45 -0
- mlflow/bedrock/_autolog.py +202 -0
- mlflow/bedrock/chat.py +122 -0
- mlflow/bedrock/stream.py +160 -0
- mlflow/bedrock/utils.py +43 -0
- mlflow/cli.py +707 -0
- mlflow/client.py +12 -0
- mlflow/config/__init__.py +56 -0
- mlflow/crewai/__init__.py +79 -0
- mlflow/crewai/autolog.py +253 -0
- mlflow/crewai/chat.py +29 -0
- mlflow/data/__init__.py +75 -0
- mlflow/data/artifact_dataset_sources.py +170 -0
- mlflow/data/code_dataset_source.py +40 -0
- mlflow/data/dataset.py +123 -0
- mlflow/data/dataset_registry.py +168 -0
- mlflow/data/dataset_source.py +110 -0
- mlflow/data/dataset_source_registry.py +219 -0
- mlflow/data/delta_dataset_source.py +167 -0
- mlflow/data/digest_utils.py +108 -0
- mlflow/data/evaluation_dataset.py +562 -0
- mlflow/data/filesystem_dataset_source.py +81 -0
- mlflow/data/http_dataset_source.py +145 -0
- mlflow/data/huggingface_dataset.py +258 -0
- mlflow/data/huggingface_dataset_source.py +118 -0
- mlflow/data/meta_dataset.py +104 -0
- mlflow/data/numpy_dataset.py +223 -0
- mlflow/data/pandas_dataset.py +231 -0
- mlflow/data/polars_dataset.py +352 -0
- mlflow/data/pyfunc_dataset_mixin.py +31 -0
- mlflow/data/schema.py +76 -0
- mlflow/data/sources.py +1 -0
- mlflow/data/spark_dataset.py +406 -0
- mlflow/data/spark_dataset_source.py +74 -0
- mlflow/data/spark_delta_utils.py +118 -0
- mlflow/data/tensorflow_dataset.py +350 -0
- mlflow/data/uc_volume_dataset_source.py +81 -0
- mlflow/db.py +27 -0
- mlflow/dspy/__init__.py +17 -0
- mlflow/dspy/autolog.py +197 -0
- mlflow/dspy/callback.py +398 -0
- mlflow/dspy/constant.py +1 -0
- mlflow/dspy/load.py +93 -0
- mlflow/dspy/save.py +393 -0
- mlflow/dspy/util.py +109 -0
- mlflow/dspy/wrapper.py +226 -0
- mlflow/entities/__init__.py +104 -0
- mlflow/entities/_mlflow_object.py +52 -0
- mlflow/entities/assessment.py +545 -0
- mlflow/entities/assessment_error.py +80 -0
- mlflow/entities/assessment_source.py +141 -0
- mlflow/entities/dataset.py +92 -0
- mlflow/entities/dataset_input.py +51 -0
- mlflow/entities/dataset_summary.py +62 -0
- mlflow/entities/document.py +48 -0
- mlflow/entities/experiment.py +109 -0
- mlflow/entities/experiment_tag.py +35 -0
- mlflow/entities/file_info.py +45 -0
- mlflow/entities/input_tag.py +35 -0
- mlflow/entities/lifecycle_stage.py +35 -0
- mlflow/entities/logged_model.py +228 -0
- mlflow/entities/logged_model_input.py +26 -0
- mlflow/entities/logged_model_output.py +32 -0
- mlflow/entities/logged_model_parameter.py +46 -0
- mlflow/entities/logged_model_status.py +74 -0
- mlflow/entities/logged_model_tag.py +33 -0
- mlflow/entities/metric.py +200 -0
- mlflow/entities/model_registry/__init__.py +29 -0
- mlflow/entities/model_registry/_model_registry_entity.py +13 -0
- mlflow/entities/model_registry/model_version.py +243 -0
- mlflow/entities/model_registry/model_version_deployment_job_run_state.py +44 -0
- mlflow/entities/model_registry/model_version_deployment_job_state.py +70 -0
- mlflow/entities/model_registry/model_version_search.py +25 -0
- mlflow/entities/model_registry/model_version_stages.py +25 -0
- mlflow/entities/model_registry/model_version_status.py +35 -0
- mlflow/entities/model_registry/model_version_tag.py +35 -0
- mlflow/entities/model_registry/prompt.py +73 -0
- mlflow/entities/model_registry/prompt_version.py +244 -0
- mlflow/entities/model_registry/registered_model.py +175 -0
- mlflow/entities/model_registry/registered_model_alias.py +35 -0
- mlflow/entities/model_registry/registered_model_deployment_job_state.py +39 -0
- mlflow/entities/model_registry/registered_model_search.py +25 -0
- mlflow/entities/model_registry/registered_model_tag.py +35 -0
- mlflow/entities/multipart_upload.py +74 -0
- mlflow/entities/param.py +49 -0
- mlflow/entities/run.py +97 -0
- mlflow/entities/run_data.py +84 -0
- mlflow/entities/run_info.py +188 -0
- mlflow/entities/run_inputs.py +59 -0
- mlflow/entities/run_outputs.py +43 -0
- mlflow/entities/run_status.py +41 -0
- mlflow/entities/run_tag.py +36 -0
- mlflow/entities/source_type.py +31 -0
- mlflow/entities/span.py +774 -0
- mlflow/entities/span_event.py +96 -0
- mlflow/entities/span_status.py +102 -0
- mlflow/entities/trace.py +317 -0
- mlflow/entities/trace_data.py +71 -0
- mlflow/entities/trace_info.py +220 -0
- mlflow/entities/trace_info_v2.py +162 -0
- mlflow/entities/trace_location.py +173 -0
- mlflow/entities/trace_state.py +39 -0
- mlflow/entities/trace_status.py +68 -0
- mlflow/entities/view_type.py +51 -0
- mlflow/environment_variables.py +866 -0
- mlflow/evaluation/__init__.py +16 -0
- mlflow/evaluation/assessment.py +369 -0
- mlflow/evaluation/evaluation.py +411 -0
- mlflow/evaluation/evaluation_tag.py +61 -0
- mlflow/evaluation/fluent.py +48 -0
- mlflow/evaluation/utils.py +201 -0
- mlflow/exceptions.py +213 -0
- mlflow/experiments.py +140 -0
- mlflow/gemini/__init__.py +81 -0
- mlflow/gemini/autolog.py +186 -0
- mlflow/gemini/chat.py +261 -0
- mlflow/genai/__init__.py +71 -0
- mlflow/genai/datasets/__init__.py +67 -0
- mlflow/genai/datasets/evaluation_dataset.py +131 -0
- mlflow/genai/evaluation/__init__.py +3 -0
- mlflow/genai/evaluation/base.py +411 -0
- mlflow/genai/evaluation/constant.py +23 -0
- mlflow/genai/evaluation/utils.py +244 -0
- mlflow/genai/judges/__init__.py +21 -0
- mlflow/genai/judges/databricks.py +404 -0
- mlflow/genai/label_schemas/__init__.py +153 -0
- mlflow/genai/label_schemas/label_schemas.py +209 -0
- mlflow/genai/labeling/__init__.py +159 -0
- mlflow/genai/labeling/labeling.py +250 -0
- mlflow/genai/optimize/__init__.py +13 -0
- mlflow/genai/optimize/base.py +198 -0
- mlflow/genai/optimize/optimizers/__init__.py +4 -0
- mlflow/genai/optimize/optimizers/base_optimizer.py +38 -0
- mlflow/genai/optimize/optimizers/dspy_mipro_optimizer.py +221 -0
- mlflow/genai/optimize/optimizers/dspy_optimizer.py +91 -0
- mlflow/genai/optimize/optimizers/utils/dspy_mipro_callback.py +76 -0
- mlflow/genai/optimize/optimizers/utils/dspy_mipro_utils.py +18 -0
- mlflow/genai/optimize/types.py +75 -0
- mlflow/genai/optimize/util.py +30 -0
- mlflow/genai/prompts/__init__.py +206 -0
- mlflow/genai/scheduled_scorers.py +431 -0
- mlflow/genai/scorers/__init__.py +26 -0
- mlflow/genai/scorers/base.py +492 -0
- mlflow/genai/scorers/builtin_scorers.py +765 -0
- mlflow/genai/scorers/scorer_utils.py +138 -0
- mlflow/genai/scorers/validation.py +165 -0
- mlflow/genai/utils/data_validation.py +146 -0
- mlflow/genai/utils/enum_utils.py +23 -0
- mlflow/genai/utils/trace_utils.py +211 -0
- mlflow/groq/__init__.py +42 -0
- mlflow/groq/_groq_autolog.py +74 -0
- mlflow/johnsnowlabs/__init__.py +888 -0
- mlflow/langchain/__init__.py +24 -0
- mlflow/langchain/api_request_parallel_processor.py +330 -0
- mlflow/langchain/autolog.py +147 -0
- mlflow/langchain/chat_agent_langgraph.py +340 -0
- mlflow/langchain/constant.py +1 -0
- mlflow/langchain/constants.py +1 -0
- mlflow/langchain/databricks_dependencies.py +444 -0
- mlflow/langchain/langchain_tracer.py +597 -0
- mlflow/langchain/model.py +919 -0
- mlflow/langchain/output_parsers.py +142 -0
- mlflow/langchain/retriever_chain.py +153 -0
- mlflow/langchain/runnables.py +527 -0
- mlflow/langchain/utils/chat.py +402 -0
- mlflow/langchain/utils/logging.py +671 -0
- mlflow/langchain/utils/serialization.py +36 -0
- mlflow/legacy_databricks_cli/__init__.py +0 -0
- mlflow/legacy_databricks_cli/configure/__init__.py +0 -0
- mlflow/legacy_databricks_cli/configure/provider.py +482 -0
- mlflow/litellm/__init__.py +175 -0
- mlflow/llama_index/__init__.py +22 -0
- mlflow/llama_index/autolog.py +55 -0
- mlflow/llama_index/chat.py +43 -0
- mlflow/llama_index/constant.py +1 -0
- mlflow/llama_index/model.py +577 -0
- mlflow/llama_index/pyfunc_wrapper.py +332 -0
- mlflow/llama_index/serialize_objects.py +188 -0
- mlflow/llama_index/tracer.py +561 -0
- mlflow/metrics/__init__.py +479 -0
- mlflow/metrics/base.py +39 -0
- mlflow/metrics/genai/__init__.py +25 -0
- mlflow/metrics/genai/base.py +101 -0
- mlflow/metrics/genai/genai_metric.py +771 -0
- mlflow/metrics/genai/metric_definitions.py +450 -0
- mlflow/metrics/genai/model_utils.py +371 -0
- mlflow/metrics/genai/prompt_template.py +68 -0
- mlflow/metrics/genai/prompts/__init__.py +0 -0
- mlflow/metrics/genai/prompts/v1.py +422 -0
- mlflow/metrics/genai/utils.py +6 -0
- mlflow/metrics/metric_definitions.py +619 -0
- mlflow/mismatch.py +34 -0
- mlflow/mistral/__init__.py +34 -0
- mlflow/mistral/autolog.py +71 -0
- mlflow/mistral/chat.py +135 -0
- mlflow/ml_package_versions.py +452 -0
- mlflow/models/__init__.py +97 -0
- mlflow/models/auth_policy.py +83 -0
- mlflow/models/cli.py +354 -0
- mlflow/models/container/__init__.py +294 -0
- mlflow/models/container/scoring_server/__init__.py +0 -0
- mlflow/models/container/scoring_server/nginx.conf +39 -0
- mlflow/models/dependencies_schemas.py +287 -0
- mlflow/models/display_utils.py +158 -0
- mlflow/models/docker_utils.py +211 -0
- mlflow/models/evaluation/__init__.py +23 -0
- mlflow/models/evaluation/_shap_patch.py +64 -0
- mlflow/models/evaluation/artifacts.py +194 -0
- mlflow/models/evaluation/base.py +1811 -0
- mlflow/models/evaluation/calibration_curve.py +109 -0
- mlflow/models/evaluation/default_evaluator.py +996 -0
- mlflow/models/evaluation/deprecated.py +23 -0
- mlflow/models/evaluation/evaluator_registry.py +80 -0
- mlflow/models/evaluation/evaluators/classifier.py +704 -0
- mlflow/models/evaluation/evaluators/default.py +233 -0
- mlflow/models/evaluation/evaluators/regressor.py +96 -0
- mlflow/models/evaluation/evaluators/shap.py +296 -0
- mlflow/models/evaluation/lift_curve.py +178 -0
- mlflow/models/evaluation/utils/metric.py +123 -0
- mlflow/models/evaluation/utils/trace.py +179 -0
- mlflow/models/evaluation/validation.py +434 -0
- mlflow/models/flavor_backend.py +93 -0
- mlflow/models/flavor_backend_registry.py +53 -0
- mlflow/models/model.py +1639 -0
- mlflow/models/model_config.py +150 -0
- mlflow/models/notebook_resources/agent_evaluation_template.html +235 -0
- mlflow/models/notebook_resources/eval_with_dataset_example.py +22 -0
- mlflow/models/notebook_resources/eval_with_synthetic_example.py +22 -0
- mlflow/models/python_api.py +369 -0
- mlflow/models/rag_signatures.py +128 -0
- mlflow/models/resources.py +321 -0
- mlflow/models/signature.py +662 -0
- mlflow/models/utils.py +2054 -0
- mlflow/models/wheeled_model.py +280 -0
- mlflow/openai/__init__.py +57 -0
- mlflow/openai/_agent_tracer.py +364 -0
- mlflow/openai/api_request_parallel_processor.py +131 -0
- mlflow/openai/autolog.py +509 -0
- mlflow/openai/constant.py +1 -0
- mlflow/openai/model.py +824 -0
- mlflow/openai/utils/chat_schema.py +367 -0
- mlflow/optuna/__init__.py +3 -0
- mlflow/optuna/storage.py +646 -0
- mlflow/plugins/__init__.py +72 -0
- mlflow/plugins/base.py +358 -0
- mlflow/plugins/builtin/__init__.py +24 -0
- mlflow/plugins/builtin/pytorch_plugin.py +150 -0
- mlflow/plugins/builtin/sklearn_plugin.py +158 -0
- mlflow/plugins/builtin/transformers_plugin.py +187 -0
- mlflow/plugins/cli.py +321 -0
- mlflow/plugins/discovery.py +340 -0
- mlflow/plugins/manager.py +465 -0
- mlflow/plugins/registry.py +316 -0
- mlflow/plugins/templates/framework_plugin_template.py +329 -0
- mlflow/prompt/constants.py +20 -0
- mlflow/prompt/promptlab_model.py +197 -0
- mlflow/prompt/registry_utils.py +248 -0
- mlflow/promptflow/__init__.py +495 -0
- mlflow/protos/__init__.py +0 -0
- mlflow/protos/assessments_pb2.py +174 -0
- mlflow/protos/databricks_artifacts_pb2.py +489 -0
- mlflow/protos/databricks_filesystem_service_pb2.py +196 -0
- mlflow/protos/databricks_managed_catalog_messages_pb2.py +95 -0
- mlflow/protos/databricks_managed_catalog_service_pb2.py +86 -0
- mlflow/protos/databricks_pb2.py +267 -0
- mlflow/protos/databricks_trace_server_pb2.py +374 -0
- mlflow/protos/databricks_uc_registry_messages_pb2.py +1249 -0
- mlflow/protos/databricks_uc_registry_service_pb2.py +170 -0
- mlflow/protos/facet_feature_statistics_pb2.py +296 -0
- mlflow/protos/internal_pb2.py +77 -0
- mlflow/protos/mlflow_artifacts_pb2.py +336 -0
- mlflow/protos/model_registry_pb2.py +1073 -0
- mlflow/protos/scalapb/__init__.py +0 -0
- mlflow/protos/scalapb/scalapb_pb2.py +104 -0
- mlflow/protos/service_pb2.py +2600 -0
- mlflow/protos/unity_catalog_oss_messages_pb2.py +457 -0
- mlflow/protos/unity_catalog_oss_service_pb2.py +130 -0
- mlflow/protos/unity_catalog_prompt_messages_pb2.py +447 -0
- mlflow/protos/unity_catalog_prompt_messages_pb2_grpc.py +24 -0
- mlflow/protos/unity_catalog_prompt_service_pb2.py +164 -0
- mlflow/protos/unity_catalog_prompt_service_pb2_grpc.py +785 -0
- mlflow/py.typed +0 -0
- mlflow/pydantic_ai/__init__.py +57 -0
- mlflow/pydantic_ai/autolog.py +173 -0
- mlflow/pyfunc/__init__.py +3844 -0
- mlflow/pyfunc/_mlflow_pyfunc_backend_predict.py +61 -0
- mlflow/pyfunc/backend.py +523 -0
- mlflow/pyfunc/context.py +78 -0
- mlflow/pyfunc/dbconnect_artifact_cache.py +144 -0
- mlflow/pyfunc/loaders/__init__.py +7 -0
- mlflow/pyfunc/loaders/chat_agent.py +117 -0
- mlflow/pyfunc/loaders/chat_model.py +125 -0
- mlflow/pyfunc/loaders/code_model.py +31 -0
- mlflow/pyfunc/loaders/responses_agent.py +112 -0
- mlflow/pyfunc/mlserver.py +46 -0
- mlflow/pyfunc/model.py +1473 -0
- mlflow/pyfunc/scoring_server/__init__.py +604 -0
- mlflow/pyfunc/scoring_server/app.py +7 -0
- mlflow/pyfunc/scoring_server/client.py +146 -0
- mlflow/pyfunc/spark_model_cache.py +48 -0
- mlflow/pyfunc/stdin_server.py +44 -0
- mlflow/pyfunc/utils/__init__.py +3 -0
- mlflow/pyfunc/utils/data_validation.py +224 -0
- mlflow/pyfunc/utils/environment.py +22 -0
- mlflow/pyfunc/utils/input_converter.py +47 -0
- mlflow/pyfunc/utils/serving_data_parser.py +11 -0
- mlflow/pytorch/__init__.py +1171 -0
- mlflow/pytorch/_lightning_autolog.py +580 -0
- mlflow/pytorch/_pytorch_autolog.py +50 -0
- mlflow/pytorch/pickle_module.py +35 -0
- mlflow/rfunc/__init__.py +42 -0
- mlflow/rfunc/backend.py +134 -0
- mlflow/runs.py +89 -0
- mlflow/server/__init__.py +302 -0
- mlflow/server/auth/__init__.py +1224 -0
- mlflow/server/auth/__main__.py +4 -0
- mlflow/server/auth/basic_auth.ini +6 -0
- mlflow/server/auth/cli.py +11 -0
- mlflow/server/auth/client.py +537 -0
- mlflow/server/auth/config.py +34 -0
- mlflow/server/auth/db/__init__.py +0 -0
- mlflow/server/auth/db/cli.py +18 -0
- mlflow/server/auth/db/migrations/__init__.py +0 -0
- mlflow/server/auth/db/migrations/alembic.ini +110 -0
- mlflow/server/auth/db/migrations/env.py +76 -0
- mlflow/server/auth/db/migrations/versions/8606fa83a998_initial_migration.py +51 -0
- mlflow/server/auth/db/migrations/versions/__init__.py +0 -0
- mlflow/server/auth/db/models.py +67 -0
- mlflow/server/auth/db/utils.py +37 -0
- mlflow/server/auth/entities.py +165 -0
- mlflow/server/auth/logo.py +14 -0
- mlflow/server/auth/permissions.py +65 -0
- mlflow/server/auth/routes.py +18 -0
- mlflow/server/auth/sqlalchemy_store.py +263 -0
- mlflow/server/graphql/__init__.py +0 -0
- mlflow/server/graphql/autogenerated_graphql_schema.py +353 -0
- mlflow/server/graphql/graphql_custom_scalars.py +24 -0
- mlflow/server/graphql/graphql_errors.py +15 -0
- mlflow/server/graphql/graphql_no_batching.py +89 -0
- mlflow/server/graphql/graphql_schema_extensions.py +74 -0
- mlflow/server/handlers.py +3217 -0
- mlflow/server/prometheus_exporter.py +17 -0
- mlflow/server/validation.py +30 -0
- mlflow/shap/__init__.py +691 -0
- mlflow/sklearn/__init__.py +1994 -0
- mlflow/sklearn/utils.py +1041 -0
- mlflow/smolagents/__init__.py +66 -0
- mlflow/smolagents/autolog.py +139 -0
- mlflow/smolagents/chat.py +29 -0
- mlflow/store/__init__.py +10 -0
- mlflow/store/_unity_catalog/__init__.py +1 -0
- mlflow/store/_unity_catalog/lineage/__init__.py +1 -0
- mlflow/store/_unity_catalog/lineage/constants.py +2 -0
- mlflow/store/_unity_catalog/registry/__init__.py +6 -0
- mlflow/store/_unity_catalog/registry/prompt_info.py +75 -0
- mlflow/store/_unity_catalog/registry/rest_store.py +1740 -0
- mlflow/store/_unity_catalog/registry/uc_oss_rest_store.py +507 -0
- mlflow/store/_unity_catalog/registry/utils.py +121 -0
- mlflow/store/artifact/__init__.py +0 -0
- mlflow/store/artifact/artifact_repo.py +472 -0
- mlflow/store/artifact/artifact_repository_registry.py +154 -0
- mlflow/store/artifact/azure_blob_artifact_repo.py +275 -0
- mlflow/store/artifact/azure_data_lake_artifact_repo.py +295 -0
- mlflow/store/artifact/cli.py +141 -0
- mlflow/store/artifact/cloud_artifact_repo.py +332 -0
- mlflow/store/artifact/databricks_artifact_repo.py +729 -0
- mlflow/store/artifact/databricks_artifact_repo_resources.py +301 -0
- mlflow/store/artifact/databricks_logged_model_artifact_repo.py +93 -0
- mlflow/store/artifact/databricks_models_artifact_repo.py +216 -0
- mlflow/store/artifact/databricks_sdk_artifact_repo.py +134 -0
- mlflow/store/artifact/databricks_sdk_models_artifact_repo.py +97 -0
- mlflow/store/artifact/dbfs_artifact_repo.py +240 -0
- mlflow/store/artifact/ftp_artifact_repo.py +132 -0
- mlflow/store/artifact/gcs_artifact_repo.py +296 -0
- mlflow/store/artifact/hdfs_artifact_repo.py +209 -0
- mlflow/store/artifact/http_artifact_repo.py +218 -0
- mlflow/store/artifact/local_artifact_repo.py +142 -0
- mlflow/store/artifact/mlflow_artifacts_repo.py +94 -0
- mlflow/store/artifact/models_artifact_repo.py +259 -0
- mlflow/store/artifact/optimized_s3_artifact_repo.py +356 -0
- mlflow/store/artifact/presigned_url_artifact_repo.py +173 -0
- mlflow/store/artifact/r2_artifact_repo.py +70 -0
- mlflow/store/artifact/runs_artifact_repo.py +265 -0
- mlflow/store/artifact/s3_artifact_repo.py +330 -0
- mlflow/store/artifact/sftp_artifact_repo.py +141 -0
- mlflow/store/artifact/uc_volume_artifact_repo.py +76 -0
- mlflow/store/artifact/unity_catalog_models_artifact_repo.py +168 -0
- mlflow/store/artifact/unity_catalog_oss_models_artifact_repo.py +168 -0
- mlflow/store/artifact/utils/__init__.py +0 -0
- mlflow/store/artifact/utils/models.py +148 -0
- mlflow/store/db/__init__.py +0 -0
- mlflow/store/db/base_sql_model.py +3 -0
- mlflow/store/db/db_types.py +10 -0
- mlflow/store/db/utils.py +314 -0
- mlflow/store/db_migrations/__init__.py +0 -0
- mlflow/store/db_migrations/alembic.ini +74 -0
- mlflow/store/db_migrations/env.py +84 -0
- mlflow/store/db_migrations/versions/0584bdc529eb_add_cascading_deletion_to_datasets_from_experiments.py +88 -0
- mlflow/store/db_migrations/versions/0a8213491aaa_drop_duplicate_killed_constraint.py +49 -0
- mlflow/store/db_migrations/versions/0c779009ac13_add_deleted_time_field_to_runs_table.py +24 -0
- mlflow/store/db_migrations/versions/181f10493468_allow_nulls_for_metric_values.py +35 -0
- mlflow/store/db_migrations/versions/27a6a02d2cf1_add_model_version_tags_table.py +38 -0
- mlflow/store/db_migrations/versions/2b4d017a5e9b_add_model_registry_tables_to_db.py +77 -0
- mlflow/store/db_migrations/versions/2d6e25af4d3e_increase_max_param_val_length.py +33 -0
- mlflow/store/db_migrations/versions/3500859a5d39_add_model_aliases_table.py +50 -0
- mlflow/store/db_migrations/versions/39d1c3be5f05_add_is_nan_constraint_for_metrics_tables_if_necessary.py +41 -0
- mlflow/store/db_migrations/versions/400f98739977_add_logged_model_tables.py +123 -0
- mlflow/store/db_migrations/versions/4465047574b1_increase_max_dataset_schema_size.py +38 -0
- mlflow/store/db_migrations/versions/451aebb31d03_add_metric_step.py +35 -0
- mlflow/store/db_migrations/versions/5b0e9adcef9c_add_cascade_deletion_to_trace_tables_fk.py +40 -0
- mlflow/store/db_migrations/versions/6953534de441_add_step_to_inputs_table.py +25 -0
- mlflow/store/db_migrations/versions/728d730b5ebd_add_registered_model_tags_table.py +38 -0
- mlflow/store/db_migrations/versions/7ac759974ad8_update_run_tags_with_larger_limit.py +36 -0
- mlflow/store/db_migrations/versions/7f2a7d5fae7d_add_datasets_inputs_input_tags_tables.py +82 -0
- mlflow/store/db_migrations/versions/84291f40a231_add_run_link_to_model_version.py +26 -0
- mlflow/store/db_migrations/versions/867495a8f9d4_add_trace_tables.py +90 -0
- mlflow/store/db_migrations/versions/89d4b8295536_create_latest_metrics_table.py +169 -0
- mlflow/store/db_migrations/versions/90e64c465722_migrate_user_column_to_tags.py +64 -0
- mlflow/store/db_migrations/versions/97727af70f4d_creation_time_last_update_time_experiments.py +25 -0
- mlflow/store/db_migrations/versions/__init__.py +0 -0
- mlflow/store/db_migrations/versions/a8c4a736bde6_allow_nulls_for_run_id.py +27 -0
- mlflow/store/db_migrations/versions/acf3f17fdcc7_add_storage_location_field_to_model_.py +29 -0
- mlflow/store/db_migrations/versions/bd07f7e963c5_create_index_on_run_uuid.py +26 -0
- mlflow/store/db_migrations/versions/bda7b8c39065_increase_model_version_tag_value_limit.py +38 -0
- mlflow/store/db_migrations/versions/c48cb773bb87_reset_default_value_for_is_nan_in_metrics_table_for_mysql.py +41 -0
- mlflow/store/db_migrations/versions/cbc13b556ace_add_v3_trace_schema_columns.py +31 -0
- mlflow/store/db_migrations/versions/cc1f77228345_change_param_value_length_to_500.py +34 -0
- mlflow/store/db_migrations/versions/cfd24bdc0731_update_run_status_constraint_with_killed.py +78 -0
- mlflow/store/db_migrations/versions/df50e92ffc5e_add_experiment_tags_table.py +38 -0
- mlflow/store/db_migrations/versions/f5a4f2784254_increase_run_tag_value_limit.py +36 -0
- mlflow/store/entities/__init__.py +3 -0
- mlflow/store/entities/paged_list.py +18 -0
- mlflow/store/model_registry/__init__.py +10 -0
- mlflow/store/model_registry/abstract_store.py +1081 -0
- mlflow/store/model_registry/base_rest_store.py +44 -0
- mlflow/store/model_registry/databricks_workspace_model_registry_rest_store.py +37 -0
- mlflow/store/model_registry/dbmodels/__init__.py +0 -0
- mlflow/store/model_registry/dbmodels/models.py +206 -0
- mlflow/store/model_registry/file_store.py +1091 -0
- mlflow/store/model_registry/rest_store.py +481 -0
- mlflow/store/model_registry/sqlalchemy_store.py +1286 -0
- mlflow/store/tracking/__init__.py +23 -0
- mlflow/store/tracking/abstract_store.py +816 -0
- mlflow/store/tracking/dbmodels/__init__.py +0 -0
- mlflow/store/tracking/dbmodels/initial_models.py +243 -0
- mlflow/store/tracking/dbmodels/models.py +1073 -0
- mlflow/store/tracking/file_store.py +2438 -0
- mlflow/store/tracking/postgres_managed_identity.py +146 -0
- mlflow/store/tracking/rest_store.py +1131 -0
- mlflow/store/tracking/sqlalchemy_store.py +2785 -0
- mlflow/system_metrics/__init__.py +61 -0
- mlflow/system_metrics/metrics/__init__.py +0 -0
- mlflow/system_metrics/metrics/base_metrics_monitor.py +32 -0
- mlflow/system_metrics/metrics/cpu_monitor.py +23 -0
- mlflow/system_metrics/metrics/disk_monitor.py +21 -0
- mlflow/system_metrics/metrics/gpu_monitor.py +71 -0
- mlflow/system_metrics/metrics/network_monitor.py +34 -0
- mlflow/system_metrics/metrics/rocm_monitor.py +123 -0
- mlflow/system_metrics/system_metrics_monitor.py +198 -0
- mlflow/tracing/__init__.py +16 -0
- mlflow/tracing/assessment.py +356 -0
- mlflow/tracing/client.py +531 -0
- mlflow/tracing/config.py +125 -0
- mlflow/tracing/constant.py +105 -0
- mlflow/tracing/destination.py +81 -0
- mlflow/tracing/display/__init__.py +40 -0
- mlflow/tracing/display/display_handler.py +196 -0
- mlflow/tracing/export/async_export_queue.py +186 -0
- mlflow/tracing/export/inference_table.py +138 -0
- mlflow/tracing/export/mlflow_v3.py +137 -0
- mlflow/tracing/export/utils.py +70 -0
- mlflow/tracing/fluent.py +1417 -0
- mlflow/tracing/processor/base_mlflow.py +199 -0
- mlflow/tracing/processor/inference_table.py +175 -0
- mlflow/tracing/processor/mlflow_v3.py +47 -0
- mlflow/tracing/processor/otel.py +73 -0
- mlflow/tracing/provider.py +487 -0
- mlflow/tracing/trace_manager.py +200 -0
- mlflow/tracing/utils/__init__.py +616 -0
- mlflow/tracing/utils/artifact_utils.py +28 -0
- mlflow/tracing/utils/copy.py +55 -0
- mlflow/tracing/utils/environment.py +55 -0
- mlflow/tracing/utils/exception.py +21 -0
- mlflow/tracing/utils/once.py +35 -0
- mlflow/tracing/utils/otlp.py +63 -0
- mlflow/tracing/utils/processor.py +54 -0
- mlflow/tracing/utils/search.py +292 -0
- mlflow/tracing/utils/timeout.py +250 -0
- mlflow/tracing/utils/token.py +19 -0
- mlflow/tracing/utils/truncation.py +124 -0
- mlflow/tracing/utils/warning.py +76 -0
- mlflow/tracking/__init__.py +39 -0
- mlflow/tracking/_model_registry/__init__.py +1 -0
- mlflow/tracking/_model_registry/client.py +764 -0
- mlflow/tracking/_model_registry/fluent.py +853 -0
- mlflow/tracking/_model_registry/registry.py +67 -0
- mlflow/tracking/_model_registry/utils.py +251 -0
- mlflow/tracking/_tracking_service/__init__.py +0 -0
- mlflow/tracking/_tracking_service/client.py +883 -0
- mlflow/tracking/_tracking_service/registry.py +56 -0
- mlflow/tracking/_tracking_service/utils.py +275 -0
- mlflow/tracking/artifact_utils.py +179 -0
- mlflow/tracking/client.py +5900 -0
- mlflow/tracking/context/__init__.py +0 -0
- mlflow/tracking/context/abstract_context.py +35 -0
- mlflow/tracking/context/databricks_cluster_context.py +15 -0
- mlflow/tracking/context/databricks_command_context.py +15 -0
- mlflow/tracking/context/databricks_job_context.py +49 -0
- mlflow/tracking/context/databricks_notebook_context.py +41 -0
- mlflow/tracking/context/databricks_repo_context.py +43 -0
- mlflow/tracking/context/default_context.py +51 -0
- mlflow/tracking/context/git_context.py +32 -0
- mlflow/tracking/context/registry.py +98 -0
- mlflow/tracking/context/system_environment_context.py +15 -0
- mlflow/tracking/default_experiment/__init__.py +1 -0
- mlflow/tracking/default_experiment/abstract_context.py +43 -0
- mlflow/tracking/default_experiment/databricks_notebook_experiment_provider.py +44 -0
- mlflow/tracking/default_experiment/registry.py +75 -0
- mlflow/tracking/fluent.py +3595 -0
- mlflow/tracking/metric_value_conversion_utils.py +93 -0
- mlflow/tracking/multimedia.py +206 -0
- mlflow/tracking/registry.py +86 -0
- mlflow/tracking/request_auth/__init__.py +0 -0
- mlflow/tracking/request_auth/abstract_request_auth_provider.py +34 -0
- mlflow/tracking/request_auth/registry.py +60 -0
- mlflow/tracking/request_header/__init__.py +0 -0
- mlflow/tracking/request_header/abstract_request_header_provider.py +36 -0
- mlflow/tracking/request_header/databricks_request_header_provider.py +38 -0
- mlflow/tracking/request_header/default_request_header_provider.py +17 -0
- mlflow/tracking/request_header/registry.py +79 -0
- mlflow/transformers/__init__.py +2982 -0
- mlflow/transformers/flavor_config.py +258 -0
- mlflow/transformers/hub_utils.py +83 -0
- mlflow/transformers/llm_inference_utils.py +468 -0
- mlflow/transformers/model_io.py +301 -0
- mlflow/transformers/peft.py +51 -0
- mlflow/transformers/signature.py +183 -0
- mlflow/transformers/torch_utils.py +55 -0
- mlflow/types/__init__.py +21 -0
- mlflow/types/agent.py +270 -0
- mlflow/types/chat.py +240 -0
- mlflow/types/llm.py +935 -0
- mlflow/types/responses.py +139 -0
- mlflow/types/responses_helpers.py +416 -0
- mlflow/types/schema.py +1505 -0
- mlflow/types/type_hints.py +647 -0
- mlflow/types/utils.py +753 -0
- mlflow/utils/__init__.py +283 -0
- mlflow/utils/_capture_modules.py +256 -0
- mlflow/utils/_capture_transformers_modules.py +75 -0
- mlflow/utils/_spark_utils.py +201 -0
- mlflow/utils/_unity_catalog_oss_utils.py +97 -0
- mlflow/utils/_unity_catalog_utils.py +479 -0
- mlflow/utils/annotations.py +218 -0
- mlflow/utils/arguments_utils.py +16 -0
- mlflow/utils/async_logging/__init__.py +1 -0
- mlflow/utils/async_logging/async_artifacts_logging_queue.py +258 -0
- mlflow/utils/async_logging/async_logging_queue.py +366 -0
- mlflow/utils/async_logging/run_artifact.py +38 -0
- mlflow/utils/async_logging/run_batch.py +58 -0
- mlflow/utils/async_logging/run_operations.py +49 -0
- mlflow/utils/autologging_utils/__init__.py +737 -0
- mlflow/utils/autologging_utils/client.py +432 -0
- mlflow/utils/autologging_utils/config.py +33 -0
- mlflow/utils/autologging_utils/events.py +294 -0
- mlflow/utils/autologging_utils/logging_and_warnings.py +328 -0
- mlflow/utils/autologging_utils/metrics_queue.py +71 -0
- mlflow/utils/autologging_utils/safety.py +1104 -0
- mlflow/utils/autologging_utils/versioning.py +95 -0
- mlflow/utils/checkpoint_utils.py +206 -0
- mlflow/utils/class_utils.py +6 -0
- mlflow/utils/cli_args.py +257 -0
- mlflow/utils/conda.py +354 -0
- mlflow/utils/credentials.py +231 -0
- mlflow/utils/data_utils.py +17 -0
- mlflow/utils/databricks_utils.py +1436 -0
- mlflow/utils/docstring_utils.py +477 -0
- mlflow/utils/doctor.py +133 -0
- mlflow/utils/download_cloud_file_chunk.py +43 -0
- mlflow/utils/env_manager.py +16 -0
- mlflow/utils/env_pack.py +131 -0
- mlflow/utils/environment.py +1009 -0
- mlflow/utils/exception_utils.py +14 -0
- mlflow/utils/file_utils.py +978 -0
- mlflow/utils/git_utils.py +77 -0
- mlflow/utils/gorilla.py +797 -0
- mlflow/utils/import_hooks/__init__.py +363 -0
- mlflow/utils/lazy_load.py +51 -0
- mlflow/utils/logging_utils.py +168 -0
- mlflow/utils/mime_type_utils.py +58 -0
- mlflow/utils/mlflow_tags.py +103 -0
- mlflow/utils/model_utils.py +486 -0
- mlflow/utils/name_utils.py +346 -0
- mlflow/utils/nfs_on_spark.py +62 -0
- mlflow/utils/openai_utils.py +164 -0
- mlflow/utils/os.py +12 -0
- mlflow/utils/oss_registry_utils.py +29 -0
- mlflow/utils/plugins.py +17 -0
- mlflow/utils/process.py +182 -0
- mlflow/utils/promptlab_utils.py +146 -0
- mlflow/utils/proto_json_utils.py +743 -0
- mlflow/utils/pydantic_utils.py +54 -0
- mlflow/utils/request_utils.py +279 -0
- mlflow/utils/requirements_utils.py +704 -0
- mlflow/utils/rest_utils.py +673 -0
- mlflow/utils/search_logged_model_utils.py +127 -0
- mlflow/utils/search_utils.py +2111 -0
- mlflow/utils/secure_loading.py +221 -0
- mlflow/utils/security_validation.py +384 -0
- mlflow/utils/server_cli_utils.py +61 -0
- mlflow/utils/spark_utils.py +15 -0
- mlflow/utils/string_utils.py +138 -0
- mlflow/utils/thread_utils.py +63 -0
- mlflow/utils/time.py +54 -0
- mlflow/utils/timeout.py +42 -0
- mlflow/utils/uri.py +572 -0
- mlflow/utils/validation.py +662 -0
- mlflow/utils/virtualenv.py +458 -0
- mlflow/utils/warnings_utils.py +25 -0
- mlflow/utils/yaml_utils.py +179 -0
- mlflow/version.py +24 -0
mlflow/tracing/fluent.py
ADDED
@@ -0,0 +1,1417 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import contextlib
|
4
|
+
import functools
|
5
|
+
import importlib
|
6
|
+
import inspect
|
7
|
+
import json
|
8
|
+
import logging
|
9
|
+
from contextvars import ContextVar
|
10
|
+
from typing import TYPE_CHECKING, Any, Callable, Generator, Literal, Optional, Union
|
11
|
+
|
12
|
+
from cachetools import TTLCache
|
13
|
+
from opentelemetry import trace as trace_api
|
14
|
+
|
15
|
+
from mlflow.entities import NoOpSpan, SpanType, Trace
|
16
|
+
from mlflow.entities.span import NO_OP_SPAN_TRACE_ID, LiveSpan, create_mlflow_span
|
17
|
+
from mlflow.entities.span_event import SpanEvent
|
18
|
+
from mlflow.entities.span_status import SpanStatusCode
|
19
|
+
from mlflow.entities.trace_state import TraceState
|
20
|
+
from mlflow.entities.trace_status import TraceStatus
|
21
|
+
from mlflow.exceptions import MlflowException
|
22
|
+
from mlflow.store.tracking import SEARCH_TRACES_DEFAULT_MAX_RESULTS
|
23
|
+
from mlflow.tracing import provider
|
24
|
+
from mlflow.tracing.client import TracingClient
|
25
|
+
from mlflow.tracing.constant import (
|
26
|
+
STREAM_CHUNK_EVENT_NAME_FORMAT,
|
27
|
+
STREAM_CHUNK_EVENT_VALUE_KEY,
|
28
|
+
SpanAttributeKey,
|
29
|
+
)
|
30
|
+
from mlflow.tracing.provider import is_tracing_enabled, safe_set_span_in_context
|
31
|
+
from mlflow.tracing.trace_manager import InMemoryTraceManager
|
32
|
+
from mlflow.tracing.utils import (
|
33
|
+
SPANS_COLUMN_NAME,
|
34
|
+
TraceJSONEncoder,
|
35
|
+
capture_function_input_args,
|
36
|
+
encode_span_id,
|
37
|
+
exclude_immutable_tags,
|
38
|
+
get_otel_attribute,
|
39
|
+
set_chat_attributes_special_case,
|
40
|
+
)
|
41
|
+
from mlflow.tracing.utils.search import extract_span_inputs_outputs, traces_to_df
|
42
|
+
from mlflow.tracing.utils.warning import request_id_backward_compatible
|
43
|
+
from mlflow.utils import get_results_from_paginated_fn
|
44
|
+
from mlflow.utils.annotations import experimental
|
45
|
+
|
46
|
+
_logger = logging.getLogger(__name__)
|
47
|
+
|
48
|
+
if TYPE_CHECKING:
|
49
|
+
import pandas
|
50
|
+
|
51
|
+
|
52
|
+
_LAST_ACTIVE_TRACE_ID_GLOBAL = None
|
53
|
+
_LAST_ACTIVE_TRACE_ID_THREAD_LOCAL = ContextVar("last_active_trace_id", default=None)
|
54
|
+
|
55
|
+
# Cache mapping between evaluation request ID to MLflow backend request ID.
|
56
|
+
# This is necessary for evaluation harness to access generated traces during
|
57
|
+
# evaluation using the dataset row ID (evaluation request ID).
|
58
|
+
_EVAL_REQUEST_ID_TO_TRACE_ID = TTLCache(maxsize=10000, ttl=3600)
|
59
|
+
|
60
|
+
|
61
|
+
def trace(
|
62
|
+
func: Optional[Callable[..., Any]] = None,
|
63
|
+
name: Optional[str] = None,
|
64
|
+
span_type: str = SpanType.UNKNOWN,
|
65
|
+
attributes: Optional[dict[str, Any]] = None,
|
66
|
+
output_reducer: Optional[Callable[[list[Any]], Any]] = None,
|
67
|
+
) -> Callable[..., Any]:
|
68
|
+
"""
|
69
|
+
A decorator that creates a new span for the decorated function.
|
70
|
+
|
71
|
+
When you decorate a function with this :py:func:`@mlflow.trace() <trace>` decorator,
|
72
|
+
a span will be created for the scope of the decorated function. The span will automatically
|
73
|
+
capture the input and output of the function. When it is applied to a method, it doesn't
|
74
|
+
capture the `self` argument. Any exception raised within the function will set the span
|
75
|
+
status to ``ERROR`` and detailed information such as exception message and stacktrace
|
76
|
+
will be recorded to the ``attributes`` field of the span.
|
77
|
+
|
78
|
+
For example, the following code will yield a span with the name ``"my_function"``,
|
79
|
+
capturing the input arguments ``x`` and ``y``, and the output of the function.
|
80
|
+
|
81
|
+
.. code-block:: python
|
82
|
+
:test:
|
83
|
+
|
84
|
+
import mlflow
|
85
|
+
|
86
|
+
|
87
|
+
@mlflow.trace
|
88
|
+
def my_function(x, y):
|
89
|
+
return x + y
|
90
|
+
|
91
|
+
This is equivalent to doing the following using the :py:func:`mlflow.start_span` context
|
92
|
+
manager, but requires less boilerplate code.
|
93
|
+
|
94
|
+
.. code-block:: python
|
95
|
+
:test:
|
96
|
+
|
97
|
+
import mlflow
|
98
|
+
|
99
|
+
|
100
|
+
def my_function(x, y):
|
101
|
+
return x + y
|
102
|
+
|
103
|
+
|
104
|
+
with mlflow.start_span("my_function") as span:
|
105
|
+
x = 1
|
106
|
+
y = 2
|
107
|
+
span.set_inputs({"x": x, "y": y})
|
108
|
+
result = my_function(x, y)
|
109
|
+
span.set_outputs({"output": result})
|
110
|
+
|
111
|
+
|
112
|
+
The @mlflow.trace decorator currently support the following types of functions:
|
113
|
+
|
114
|
+
.. list-table:: Supported Function Types
|
115
|
+
:widths: 20 30
|
116
|
+
:header-rows: 1
|
117
|
+
|
118
|
+
* - Function Type
|
119
|
+
- Supported
|
120
|
+
* - Sync
|
121
|
+
- ✅
|
122
|
+
* - Async
|
123
|
+
- ✅ (>= 2.16.0)
|
124
|
+
* - Generator
|
125
|
+
- ✅ (>= 2.20.2)
|
126
|
+
* - Async Generator
|
127
|
+
- ✅ (>= 2.20.2)
|
128
|
+
* - ClassMethod
|
129
|
+
- ✅ (>= 3.0.0)
|
130
|
+
* - StaticMethod
|
131
|
+
- ✅ (>= 3.0.0)
|
132
|
+
|
133
|
+
For more examples of using the @mlflow.trace decorator, including streaming/async
|
134
|
+
handling, see the `MLflow Tracing documentation <https://www.mlflow.org/docs/latest/tracing/api/manual-instrumentation#decorator>`_.
|
135
|
+
|
136
|
+
.. tip::
|
137
|
+
|
138
|
+
The @mlflow.trace decorator is useful when you want to trace a function defined by
|
139
|
+
yourself. However, you may also want to trace a function in external libraries. In
|
140
|
+
such case, you can use this ``mlflow.trace()`` function to directly wrap the function,
|
141
|
+
instead of using as the decorator. This will create the exact same span as the
|
142
|
+
one created by the decorator i.e. captures information from the function call.
|
143
|
+
|
144
|
+
.. code-block:: python
|
145
|
+
:test:
|
146
|
+
|
147
|
+
import math
|
148
|
+
|
149
|
+
import mlflow
|
150
|
+
|
151
|
+
mlflow.trace(math.factorial)(5)
|
152
|
+
|
153
|
+
Args:
|
154
|
+
func: The function to be decorated. Must **not** be provided when using as a decorator.
|
155
|
+
name: The name of the span. If not provided, the name of the function will be used.
|
156
|
+
span_type: The type of the span. Can be either a string or a
|
157
|
+
:py:class:`SpanType <mlflow.entities.SpanType>` enum value.
|
158
|
+
attributes: A dictionary of attributes to set on the span.
|
159
|
+
output_reducer: A function that reduces the outputs of the generator function into a
|
160
|
+
single value to be set as the span output.
|
161
|
+
"""
|
162
|
+
|
163
|
+
def decorator(fn):
|
164
|
+
# Check if the function is a classmethod or staticmethod
|
165
|
+
is_classmethod = isinstance(fn, classmethod)
|
166
|
+
is_staticmethod = isinstance(fn, staticmethod)
|
167
|
+
|
168
|
+
# Extract the original function if it's a descriptor
|
169
|
+
original_fn = fn.__func__ if is_classmethod or is_staticmethod else fn
|
170
|
+
|
171
|
+
# Apply the appropriate wrapper to the original function
|
172
|
+
if inspect.isgeneratorfunction(original_fn) or inspect.isasyncgenfunction(original_fn):
|
173
|
+
wrapped = _wrap_generator(
|
174
|
+
original_fn,
|
175
|
+
name,
|
176
|
+
span_type,
|
177
|
+
attributes,
|
178
|
+
output_reducer,
|
179
|
+
)
|
180
|
+
else:
|
181
|
+
if output_reducer is not None:
|
182
|
+
raise MlflowException.invalid_parameter_value(
|
183
|
+
"The output_reducer argument is only supported for generator functions."
|
184
|
+
)
|
185
|
+
wrapped = _wrap_function(original_fn, name, span_type, attributes)
|
186
|
+
|
187
|
+
# If the original was a descriptor, wrap the result back as the same type of descriptor
|
188
|
+
if is_classmethod:
|
189
|
+
return classmethod(wrapped)
|
190
|
+
elif is_staticmethod:
|
191
|
+
return staticmethod(wrapped)
|
192
|
+
else:
|
193
|
+
return wrapped
|
194
|
+
|
195
|
+
return decorator(func) if func else decorator
|
196
|
+
|
197
|
+
|
198
|
+
def _wrap_function(
|
199
|
+
fn: Callable[..., Any],
|
200
|
+
name: Optional[str] = None,
|
201
|
+
span_type: str = SpanType.UNKNOWN,
|
202
|
+
attributes: Optional[dict[str, Any]] = None,
|
203
|
+
) -> Callable[..., Any]:
|
204
|
+
class _WrappingContext:
|
205
|
+
# define the wrapping logic as a coroutine to avoid code duplication
|
206
|
+
# between sync and async cases
|
207
|
+
@staticmethod
|
208
|
+
def _wrapping_logic(fn, args, kwargs):
|
209
|
+
span_name = name or fn.__name__
|
210
|
+
|
211
|
+
with start_span(name=span_name, span_type=span_type, attributes=attributes) as span:
|
212
|
+
span.set_attribute(SpanAttributeKey.FUNCTION_NAME, fn.__name__)
|
213
|
+
inputs = capture_function_input_args(fn, args, kwargs)
|
214
|
+
span.set_inputs(inputs)
|
215
|
+
result = yield # sync/async function output to be sent here
|
216
|
+
span.set_outputs(result)
|
217
|
+
|
218
|
+
set_chat_attributes_special_case(span, inputs=inputs, outputs=result)
|
219
|
+
|
220
|
+
try:
|
221
|
+
yield result
|
222
|
+
except GeneratorExit:
|
223
|
+
# Swallow `GeneratorExit` raised when the generator is closed
|
224
|
+
pass
|
225
|
+
|
226
|
+
def __init__(self, fn, args, kwargs):
|
227
|
+
self.coro = self._wrapping_logic(fn, args, kwargs)
|
228
|
+
|
229
|
+
def __enter__(self):
|
230
|
+
next(self.coro)
|
231
|
+
return self.coro
|
232
|
+
|
233
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
234
|
+
# Since the function call occurs outside the coroutine,
|
235
|
+
# if an exception occurs, we need to throw it back in, so that
|
236
|
+
# we return control to the coro (in particular, so that the __exit__'s
|
237
|
+
# of start_span and OTel's use_span can execute).
|
238
|
+
if exc_type is not None:
|
239
|
+
self.coro.throw(exc_type, exc_value, traceback)
|
240
|
+
self.coro.close()
|
241
|
+
|
242
|
+
if inspect.iscoroutinefunction(fn):
|
243
|
+
|
244
|
+
async def wrapper(*args, **kwargs):
|
245
|
+
with _WrappingContext(fn, args, kwargs) as wrapping_coro:
|
246
|
+
return wrapping_coro.send(await fn(*args, **kwargs))
|
247
|
+
else:
|
248
|
+
|
249
|
+
def wrapper(*args, **kwargs):
|
250
|
+
with _WrappingContext(fn, args, kwargs) as wrapping_coro:
|
251
|
+
return wrapping_coro.send(fn(*args, **kwargs))
|
252
|
+
|
253
|
+
return _wrap_function_safe(fn, wrapper)
|
254
|
+
|
255
|
+
|
256
|
+
def _wrap_generator(
|
257
|
+
fn: Callable[..., Any],
|
258
|
+
name: Optional[str] = None,
|
259
|
+
span_type: str = SpanType.UNKNOWN,
|
260
|
+
attributes: Optional[dict[str, Any]] = None,
|
261
|
+
output_reducer: Optional[Callable[[list[Any]], Any]] = None,
|
262
|
+
) -> Callable[..., Any]:
|
263
|
+
"""
|
264
|
+
Wrap a generator function to create a span.
|
265
|
+
Generator functions need special handling because of its lazy evaluation nature.
|
266
|
+
Let's say we have a generator function like this:
|
267
|
+
```
|
268
|
+
@mlflow.trace
|
269
|
+
def generate_stream():
|
270
|
+
# B
|
271
|
+
for i in range(10):
|
272
|
+
# C
|
273
|
+
yield i * 2
|
274
|
+
# E
|
275
|
+
|
276
|
+
|
277
|
+
stream = generate_stream()
|
278
|
+
# A
|
279
|
+
for chunk in stream:
|
280
|
+
# D
|
281
|
+
pass
|
282
|
+
# F
|
283
|
+
```
|
284
|
+
The execution order is A -> B -> C -> D -> C -> D -> ... -> E -> F.
|
285
|
+
The span should only be "active" at B, C, and E, namely, when the code execution
|
286
|
+
is inside the generator function. Otherwise it will create wrong span tree, or
|
287
|
+
even worse, leak span context and pollute subsequent traces.
|
288
|
+
"""
|
289
|
+
|
290
|
+
def _start_stream_span(fn, inputs):
|
291
|
+
try:
|
292
|
+
return start_span_no_context(
|
293
|
+
name=name or fn.__name__,
|
294
|
+
parent_span=get_current_active_span(),
|
295
|
+
span_type=span_type,
|
296
|
+
attributes=attributes,
|
297
|
+
inputs=inputs,
|
298
|
+
)
|
299
|
+
except Exception as e:
|
300
|
+
_logger.debug(f"Failed to start stream span: {e}")
|
301
|
+
return NoOpSpan()
|
302
|
+
|
303
|
+
def _end_stream_span(
|
304
|
+
span: LiveSpan,
|
305
|
+
inputs: Optional[dict[str, Any]] = None,
|
306
|
+
outputs: Optional[list[Any]] = None,
|
307
|
+
output_reducer: Optional[Callable[[list[Any]], Any]] = None,
|
308
|
+
error: Optional[Exception] = None,
|
309
|
+
):
|
310
|
+
if error:
|
311
|
+
span.add_event(SpanEvent.from_exception(error))
|
312
|
+
span.end(status=SpanStatusCode.ERROR)
|
313
|
+
return
|
314
|
+
|
315
|
+
if output_reducer:
|
316
|
+
try:
|
317
|
+
outputs = output_reducer(outputs)
|
318
|
+
except Exception as e:
|
319
|
+
_logger.debug(f"Failed to reduce outputs from stream: {e}")
|
320
|
+
|
321
|
+
set_chat_attributes_special_case(span, inputs=inputs, outputs=outputs)
|
322
|
+
span.end(outputs=outputs)
|
323
|
+
|
324
|
+
def _record_chunk_event(span: LiveSpan, chunk: Any, chunk_index: int):
|
325
|
+
try:
|
326
|
+
event = SpanEvent(
|
327
|
+
name=STREAM_CHUNK_EVENT_NAME_FORMAT.format(index=chunk_index),
|
328
|
+
# OpenTelemetry SpanEvent only support str-str key-value pairs for attributes
|
329
|
+
attributes={STREAM_CHUNK_EVENT_VALUE_KEY: json.dumps(chunk, cls=TraceJSONEncoder)},
|
330
|
+
)
|
331
|
+
span.add_event(event)
|
332
|
+
except Exception as e:
|
333
|
+
_logger.debug(f"Failing to record chunk event for span {span.name}: {e}")
|
334
|
+
|
335
|
+
if inspect.isgeneratorfunction(fn):
|
336
|
+
|
337
|
+
def wrapper(*args, **kwargs):
|
338
|
+
inputs = capture_function_input_args(fn, args, kwargs)
|
339
|
+
span = _start_stream_span(fn, inputs)
|
340
|
+
generator = fn(*args, **kwargs)
|
341
|
+
|
342
|
+
i = 0
|
343
|
+
outputs = []
|
344
|
+
while True:
|
345
|
+
try:
|
346
|
+
# NB: Set the span to active only when the generator is running
|
347
|
+
with safe_set_span_in_context(span):
|
348
|
+
value = next(generator)
|
349
|
+
except StopIteration:
|
350
|
+
break
|
351
|
+
except Exception as e:
|
352
|
+
_end_stream_span(span, error=e)
|
353
|
+
raise e
|
354
|
+
else:
|
355
|
+
outputs.append(value)
|
356
|
+
_record_chunk_event(span, value, i)
|
357
|
+
yield value
|
358
|
+
i += 1
|
359
|
+
_end_stream_span(span, inputs, outputs, output_reducer)
|
360
|
+
else:
|
361
|
+
|
362
|
+
async def wrapper(*args, **kwargs):
|
363
|
+
inputs = capture_function_input_args(fn, args, kwargs)
|
364
|
+
span = _start_stream_span(fn, inputs)
|
365
|
+
generator = fn(*args, **kwargs)
|
366
|
+
|
367
|
+
i = 0
|
368
|
+
outputs = []
|
369
|
+
while True:
|
370
|
+
try:
|
371
|
+
with safe_set_span_in_context(span):
|
372
|
+
value = await generator.__anext__()
|
373
|
+
except StopAsyncIteration:
|
374
|
+
break
|
375
|
+
except Exception as e:
|
376
|
+
_end_stream_span(span, error=e)
|
377
|
+
raise e
|
378
|
+
else:
|
379
|
+
outputs.append(value)
|
380
|
+
_record_chunk_event(span, value, i)
|
381
|
+
yield value
|
382
|
+
i += 1
|
383
|
+
_end_stream_span(span, inputs, outputs, output_reducer)
|
384
|
+
|
385
|
+
return _wrap_function_safe(fn, wrapper)
|
386
|
+
|
387
|
+
|
388
|
+
def _wrap_function_safe(fn: Callable[..., Any], wrapper: Callable[..., Any]) -> Callable[..., Any]:
|
389
|
+
wrapped = functools.wraps(fn)(wrapper)
|
390
|
+
# Update the signature of the wrapper to match the signature of the original (safely)
|
391
|
+
try:
|
392
|
+
wrapped.__signature__ = inspect.signature(fn)
|
393
|
+
except Exception:
|
394
|
+
pass
|
395
|
+
return wrapped
|
396
|
+
|
397
|
+
|
398
|
+
@contextlib.contextmanager
|
399
|
+
def start_span(
|
400
|
+
name: str = "span",
|
401
|
+
span_type: Optional[str] = SpanType.UNKNOWN,
|
402
|
+
attributes: Optional[dict[str, Any]] = None,
|
403
|
+
) -> Generator[LiveSpan, None, None]:
|
404
|
+
"""
|
405
|
+
Context manager to create a new span and start it as the current span in the context.
|
406
|
+
|
407
|
+
This context manager automatically manages the span lifecycle and parent-child relationships.
|
408
|
+
The span will be ended when the context manager exits. Any exception raised within the
|
409
|
+
context manager will set the span status to ``ERROR``, and detailed information such as
|
410
|
+
exception message and stacktrace will be recorded to the ``attributes`` field of the span.
|
411
|
+
New spans can be created within the context manager, then they will be assigned as child
|
412
|
+
spans.
|
413
|
+
|
414
|
+
.. code-block:: python
|
415
|
+
:test:
|
416
|
+
|
417
|
+
import mlflow
|
418
|
+
|
419
|
+
with mlflow.start_span("my_span") as span:
|
420
|
+
x = 1
|
421
|
+
y = 2
|
422
|
+
span.set_inputs({"x": x, "y": y})
|
423
|
+
|
424
|
+
z = x + y
|
425
|
+
|
426
|
+
span.set_outputs(z)
|
427
|
+
span.set_attribute("key", "value")
|
428
|
+
# do something
|
429
|
+
|
430
|
+
When this context manager is used in the top-level scope, i.e. not within another span context,
|
431
|
+
the span will be treated as a root span. The root span doesn't have a parent reference and
|
432
|
+
**the entire trace will be logged when the root span is ended**.
|
433
|
+
|
434
|
+
|
435
|
+
.. tip::
|
436
|
+
|
437
|
+
If you want more explicit control over the trace lifecycle, you can use
|
438
|
+
the `mlflow.start_span_no_context()` API. It provides lower
|
439
|
+
level to start spans and control the parent-child relationships explicitly.
|
440
|
+
However, it is generally recommended to use this context manager as long as it satisfies
|
441
|
+
your requirements, because it requires less boilerplate code and is less error-prone.
|
442
|
+
|
443
|
+
.. note::
|
444
|
+
|
445
|
+
The context manager doesn't propagate the span context across threads by default. see
|
446
|
+
`Multi Threading <https://mlflow.org/docs/latest/tracing/api/manual-instrumentation#multi-threading>`_
|
447
|
+
for how to propagate the span context across threads.
|
448
|
+
|
449
|
+
Args:
|
450
|
+
name: The name of the span.
|
451
|
+
span_type: The type of the span. Can be either a string or
|
452
|
+
a :py:class:`SpanType <mlflow.entities.SpanType>` enum value
|
453
|
+
attributes: A dictionary of attributes to set on the span.
|
454
|
+
|
455
|
+
Returns:
|
456
|
+
Yields an :py:class:`mlflow.entities.Span` that represents the created span.
|
457
|
+
"""
|
458
|
+
try:
|
459
|
+
otel_span = provider.start_span_in_context(name)
|
460
|
+
|
461
|
+
# Create a new MLflow span and register it to the in-memory trace manager
|
462
|
+
request_id = get_otel_attribute(otel_span, SpanAttributeKey.REQUEST_ID)
|
463
|
+
mlflow_span = create_mlflow_span(otel_span, request_id, span_type)
|
464
|
+
attributes = dict(attributes) if attributes is not None else {}
|
465
|
+
mlflow_span.set_attributes(attributes)
|
466
|
+
InMemoryTraceManager.get_instance().register_span(mlflow_span)
|
467
|
+
|
468
|
+
except Exception:
|
469
|
+
_logger.debug(f"Failed to start span {name}.", exc_info=True)
|
470
|
+
mlflow_span = NoOpSpan()
|
471
|
+
yield mlflow_span
|
472
|
+
return
|
473
|
+
|
474
|
+
try:
|
475
|
+
# Setting end_on_exit = False to suppress the default span
|
476
|
+
# export and instead invoke MLflow span's end() method.
|
477
|
+
with trace_api.use_span(mlflow_span._span, end_on_exit=False):
|
478
|
+
yield mlflow_span
|
479
|
+
finally:
|
480
|
+
try:
|
481
|
+
mlflow_span.end()
|
482
|
+
except Exception:
|
483
|
+
_logger.debug(f"Failed to end span {mlflow_span.span_id}.", exc_info=True)
|
484
|
+
|
485
|
+
|
486
|
+
def start_span_no_context(
|
487
|
+
name: str,
|
488
|
+
span_type: str = SpanType.UNKNOWN,
|
489
|
+
parent_span: Optional[LiveSpan] = None,
|
490
|
+
inputs: Optional[Any] = None,
|
491
|
+
attributes: Optional[dict[str, str]] = None,
|
492
|
+
tags: Optional[dict[str, str]] = None,
|
493
|
+
experiment_id: Optional[str] = None,
|
494
|
+
start_time_ns: Optional[int] = None,
|
495
|
+
) -> LiveSpan:
|
496
|
+
"""
|
497
|
+
Start a span without attaching it to the global tracing context.
|
498
|
+
|
499
|
+
This is useful when you want to create a span without automatically linking
|
500
|
+
with a parent span and instead manually manage the parent-child relationships.
|
501
|
+
|
502
|
+
The span started with this function must be ended manually using the
|
503
|
+
`end()` method of the span object.
|
504
|
+
|
505
|
+
Args:
|
506
|
+
name: The name of the span.
|
507
|
+
span_type: The type of the span. Can be either a string or
|
508
|
+
a :py:class:`SpanType <mlflow.entities.SpanType>` enum value
|
509
|
+
parent_span: The parent span to link with. If None, the span will be treated as a root span.
|
510
|
+
inputs: The input data for the span.
|
511
|
+
attributes: A dictionary of attributes to set on the span.
|
512
|
+
tags: A dictionary of tags to set on the trace.
|
513
|
+
experiment_id: The experiment ID to associate with the trace. If not provided,
|
514
|
+
the current active experiment will be used.
|
515
|
+
start_time_ns: The start time of the span in nanoseconds. If not provided,
|
516
|
+
the current time will be used.
|
517
|
+
|
518
|
+
Returns:
|
519
|
+
A :py:class:`mlflow.entities.Span` that represents the created span.
|
520
|
+
|
521
|
+
Example:
|
522
|
+
.. code-block:: python
|
523
|
+
:test:
|
524
|
+
|
525
|
+
import mlflow
|
526
|
+
|
527
|
+
root_span = mlflow.start_span_no_context("my_trace")
|
528
|
+
|
529
|
+
# Create a child span
|
530
|
+
child_span = mlflow.start_span_no_context(
|
531
|
+
"child_span",
|
532
|
+
# Manually specify the parent span
|
533
|
+
parent_span=root_span,
|
534
|
+
)
|
535
|
+
# Do something...
|
536
|
+
child_span.end()
|
537
|
+
|
538
|
+
root_span.end()
|
539
|
+
|
540
|
+
"""
|
541
|
+
# If parent span is no-op span, the child should also be no-op too
|
542
|
+
if parent_span and parent_span.trace_id == NO_OP_SPAN_TRACE_ID:
|
543
|
+
return NoOpSpan()
|
544
|
+
|
545
|
+
try:
|
546
|
+
# Create new trace and a root span
|
547
|
+
# Once OTel span is created, SpanProcessor.on_start is invoked
|
548
|
+
# TraceInfo is created and logged into backend store inside on_start method
|
549
|
+
otel_span = provider.start_detached_span(
|
550
|
+
name,
|
551
|
+
parent=parent_span._span if parent_span else None,
|
552
|
+
start_time_ns=start_time_ns,
|
553
|
+
experiment_id=experiment_id,
|
554
|
+
)
|
555
|
+
|
556
|
+
if parent_span:
|
557
|
+
trace_id = parent_span.trace_id
|
558
|
+
else:
|
559
|
+
trace_id = get_otel_attribute(otel_span, SpanAttributeKey.REQUEST_ID)
|
560
|
+
|
561
|
+
mlflow_span = create_mlflow_span(otel_span, trace_id, span_type)
|
562
|
+
|
563
|
+
# # If the span is a no-op span i.e. tracing is disabled, do nothing
|
564
|
+
if isinstance(mlflow_span, NoOpSpan):
|
565
|
+
return mlflow_span
|
566
|
+
|
567
|
+
if inputs is not None:
|
568
|
+
mlflow_span.set_inputs(inputs)
|
569
|
+
mlflow_span.set_attributes(attributes or {})
|
570
|
+
|
571
|
+
trace_manager = InMemoryTraceManager.get_instance()
|
572
|
+
if tags := exclude_immutable_tags(tags or {}):
|
573
|
+
# Update trace tags for trace in in-memory trace manager
|
574
|
+
with trace_manager.get_trace(trace_id) as trace:
|
575
|
+
trace.info.tags.update(tags)
|
576
|
+
|
577
|
+
# Register new span in the in-memory trace manager
|
578
|
+
trace_manager.register_span(mlflow_span)
|
579
|
+
|
580
|
+
return mlflow_span
|
581
|
+
except Exception as e:
|
582
|
+
_logger.warning(
|
583
|
+
f"Failed to start span {name}: {e}. For full traceback, set logging level to debug.",
|
584
|
+
exc_info=_logger.isEnabledFor(logging.DEBUG),
|
585
|
+
)
|
586
|
+
return NoOpSpan()
|
587
|
+
|
588
|
+
|
589
|
+
@request_id_backward_compatible
|
590
|
+
def get_trace(trace_id: str) -> Optional[Trace]:
|
591
|
+
"""
|
592
|
+
Get a trace by the given request ID if it exists.
|
593
|
+
|
594
|
+
This function retrieves the trace from the in-memory buffer first, and if it doesn't exist,
|
595
|
+
it fetches the trace from the tracking store. If the trace is not found in the tracking store,
|
596
|
+
it returns None.
|
597
|
+
|
598
|
+
Args:
|
599
|
+
trace_id: The ID of the trace.
|
600
|
+
|
601
|
+
|
602
|
+
.. code-block:: python
|
603
|
+
:test:
|
604
|
+
|
605
|
+
import mlflow
|
606
|
+
|
607
|
+
|
608
|
+
with mlflow.start_span(name="span") as span:
|
609
|
+
span.set_attribute("key", "value")
|
610
|
+
|
611
|
+
trace = mlflow.get_trace(span.trace_id)
|
612
|
+
print(trace)
|
613
|
+
|
614
|
+
|
615
|
+
Returns:
|
616
|
+
A :py:class:`mlflow.entities.Trace` objects with the given request ID.
|
617
|
+
"""
|
618
|
+
# Special handling for evaluation request ID.
|
619
|
+
trace_id = _EVAL_REQUEST_ID_TO_TRACE_ID.get(trace_id) or trace_id
|
620
|
+
|
621
|
+
try:
|
622
|
+
return TracingClient().get_trace(trace_id)
|
623
|
+
except MlflowException as e:
|
624
|
+
_logger.warning(
|
625
|
+
f"Failed to get trace from the tracking store: {e}"
|
626
|
+
"For full traceback, set logging level to debug.",
|
627
|
+
exc_info=_logger.isEnabledFor(logging.DEBUG),
|
628
|
+
)
|
629
|
+
return None
|
630
|
+
|
631
|
+
|
632
|
+
def search_traces(
|
633
|
+
experiment_ids: Optional[list[str]] = None,
|
634
|
+
filter_string: Optional[str] = None,
|
635
|
+
max_results: Optional[int] = None,
|
636
|
+
order_by: Optional[list[str]] = None,
|
637
|
+
extract_fields: Optional[list[str]] = None,
|
638
|
+
run_id: Optional[str] = None,
|
639
|
+
return_type: Optional[Literal["pandas", "list"]] = None,
|
640
|
+
model_id: Optional[str] = None,
|
641
|
+
sql_warehouse_id: Optional[str] = None,
|
642
|
+
include_spans: bool = True,
|
643
|
+
) -> Union["pandas.DataFrame", list[Trace]]:
|
644
|
+
"""
|
645
|
+
Return traces that match the given list of search expressions within the experiments.
|
646
|
+
|
647
|
+
.. note::
|
648
|
+
|
649
|
+
If expected number of search results is large, consider using the
|
650
|
+
`MlflowClient.search_traces` API directly to paginate through the results. This
|
651
|
+
function returns all results in memory and may not be suitable for large result sets.
|
652
|
+
|
653
|
+
Args:
|
654
|
+
experiment_ids: List of experiment ids to scope the search. If not provided, the search
|
655
|
+
will be performed across the current active experiment.
|
656
|
+
filter_string: A search filter string.
|
657
|
+
max_results: Maximum number of traces desired. If None, all traces matching the search
|
658
|
+
expressions will be returned.
|
659
|
+
order_by: List of order_by clauses.
|
660
|
+
extract_fields: Specify fields to extract from traces using the format
|
661
|
+
``"span_name.[inputs|outputs].field_name"`` or ``"span_name.[inputs|outputs]"``.
|
662
|
+
|
663
|
+
.. note::
|
664
|
+
|
665
|
+
This parameter is only supported when the return type is set to "pandas".
|
666
|
+
|
667
|
+
For instance, ``"predict.outputs.result"`` retrieves the output ``"result"`` field from
|
668
|
+
a span named ``"predict"``, while ``"predict.outputs"`` fetches the entire outputs
|
669
|
+
dictionary, including keys ``"result"`` and ``"explanation"``.
|
670
|
+
|
671
|
+
By default, no fields are extracted into the DataFrame columns. When multiple
|
672
|
+
fields are specified, each is extracted as its own column. If an invalid field
|
673
|
+
string is provided, the function silently returns without adding that field's column.
|
674
|
+
The supported fields are limited to ``"inputs"`` and ``"outputs"`` of spans. If the
|
675
|
+
span name or field name contains a dot it must be enclosed in backticks. For example:
|
676
|
+
|
677
|
+
.. code-block:: python
|
678
|
+
|
679
|
+
# span name contains a dot
|
680
|
+
extract_fields = ["`span.name`.inputs.field"]
|
681
|
+
|
682
|
+
# field name contains a dot
|
683
|
+
extract_fields = ["span.inputs.`field.name`"]
|
684
|
+
|
685
|
+
# span name and field name contain a dot
|
686
|
+
extract_fields = ["`span.name`.inputs.`field.name`"]
|
687
|
+
|
688
|
+
run_id: A run id to scope the search. When a trace is created under an active run,
|
689
|
+
it will be associated with the run and you can filter on the run id to retrieve the
|
690
|
+
trace. See the example below for how to filter traces by run id.
|
691
|
+
|
692
|
+
return_type: The type of the return value. The following return types are supported. If
|
693
|
+
the pandas library is installed, the default return type is "pandas". Otherwise, the
|
694
|
+
default return type is "list".
|
695
|
+
|
696
|
+
- `"pandas"`: Returns a Pandas DataFrame containing information about traces
|
697
|
+
where each row represents a single trace and each column represents a field of the
|
698
|
+
trace e.g. trace_id, spans, etc.
|
699
|
+
- `"list"`: Returns a list of :py:class:`Trace <mlflow.entities.Trace>` objects.
|
700
|
+
|
701
|
+
model_id: If specified, search traces associated with the given model ID.
|
702
|
+
sql_warehouse_id: Only used in Databricks. The ID of the SQL warehouse to use for
|
703
|
+
searching traces in inference tables.
|
704
|
+
|
705
|
+
include_spans: If ``True``, include spans in the returned traces. Otherwise, only
|
706
|
+
the trace metadata is returned, e.g., trace ID, start time, end time, etc,
|
707
|
+
without any spans. Default to ``True``.
|
708
|
+
|
709
|
+
Returns:
|
710
|
+
Traces that satisfy the search expressions. Either as a list of
|
711
|
+
:py:class:`Trace <mlflow.entities.Trace>` objects or as a Pandas DataFrame,
|
712
|
+
depending on the value of the `return_type` parameter.
|
713
|
+
|
714
|
+
.. code-block:: python
|
715
|
+
:test:
|
716
|
+
:caption: Search traces with extract_fields
|
717
|
+
|
718
|
+
import mlflow
|
719
|
+
|
720
|
+
with mlflow.start_span(name="span1") as span:
|
721
|
+
span.set_inputs({"a": 1, "b": 2})
|
722
|
+
span.set_outputs({"c": 3, "d": 4})
|
723
|
+
|
724
|
+
mlflow.search_traces(
|
725
|
+
extract_fields=["span1.inputs", "span1.outputs", "span1.outputs.c"],
|
726
|
+
return_type="pandas",
|
727
|
+
)
|
728
|
+
|
729
|
+
|
730
|
+
.. code-block:: python
|
731
|
+
:test:
|
732
|
+
:caption: Search traces with extract_fields and non-dictionary span inputs and outputs
|
733
|
+
|
734
|
+
import mlflow
|
735
|
+
|
736
|
+
with mlflow.start_span(name="non_dict_span") as span:
|
737
|
+
span.set_inputs(["a", "b"])
|
738
|
+
span.set_outputs([1, 2, 3])
|
739
|
+
|
740
|
+
mlflow.search_traces(
|
741
|
+
extract_fields=["non_dict_span.inputs", "non_dict_span.outputs"],
|
742
|
+
)
|
743
|
+
|
744
|
+
.. code-block:: python
|
745
|
+
:test:
|
746
|
+
:caption: Search traces by run ID and return as a list of Trace objects
|
747
|
+
|
748
|
+
import mlflow
|
749
|
+
|
750
|
+
|
751
|
+
@mlflow.trace
|
752
|
+
def traced_func(x):
|
753
|
+
return x + 1
|
754
|
+
|
755
|
+
|
756
|
+
with mlflow.start_run() as run:
|
757
|
+
traced_func(1)
|
758
|
+
|
759
|
+
mlflow.search_traces(run_id=run.info.run_id, return_type="list")
|
760
|
+
|
761
|
+
"""
|
762
|
+
from mlflow.tracking.fluent import _get_experiment_id
|
763
|
+
|
764
|
+
# Default to "pandas" only if the pandas library is installed
|
765
|
+
if return_type is None:
|
766
|
+
try:
|
767
|
+
import pandas # noqa: F401
|
768
|
+
|
769
|
+
return_type = "pandas"
|
770
|
+
except ImportError:
|
771
|
+
return_type = "list"
|
772
|
+
|
773
|
+
if return_type not in ["pandas", "list"]:
|
774
|
+
raise MlflowException.invalid_parameter_value(
|
775
|
+
f"Invalid return type: {return_type}. Return type must be either 'pandas' or 'list'."
|
776
|
+
)
|
777
|
+
elif return_type == "list" and extract_fields:
|
778
|
+
raise MlflowException.invalid_parameter_value(
|
779
|
+
"The `extract_fields` parameter is only supported when return type is set to 'pandas'."
|
780
|
+
)
|
781
|
+
elif return_type == "pandas":
|
782
|
+
# Check if pandas is installed early to avoid unnecessary computation
|
783
|
+
if importlib.util.find_spec("pandas") is None:
|
784
|
+
raise MlflowException(
|
785
|
+
message=(
|
786
|
+
"The `pandas` library is not installed. Please install `pandas` to use"
|
787
|
+
" the `return_type='pandas'` option, or set `return_type='list'`."
|
788
|
+
),
|
789
|
+
)
|
790
|
+
|
791
|
+
if not experiment_ids:
|
792
|
+
if experiment_id := _get_experiment_id():
|
793
|
+
experiment_ids = [experiment_id]
|
794
|
+
else:
|
795
|
+
raise MlflowException(
|
796
|
+
"No active experiment found. Set an experiment using `mlflow.set_experiment`, "
|
797
|
+
"or specify the list of experiment IDs in the `experiment_ids` parameter."
|
798
|
+
)
|
799
|
+
|
800
|
+
def pagination_wrapper_func(number_to_get, next_page_token):
|
801
|
+
return TracingClient().search_traces(
|
802
|
+
experiment_ids=experiment_ids,
|
803
|
+
run_id=run_id,
|
804
|
+
max_results=number_to_get,
|
805
|
+
filter_string=filter_string,
|
806
|
+
order_by=order_by,
|
807
|
+
page_token=next_page_token,
|
808
|
+
model_id=model_id,
|
809
|
+
sql_warehouse_id=sql_warehouse_id,
|
810
|
+
include_spans=include_spans,
|
811
|
+
)
|
812
|
+
|
813
|
+
results = get_results_from_paginated_fn(
|
814
|
+
pagination_wrapper_func,
|
815
|
+
max_results_per_page=SEARCH_TRACES_DEFAULT_MAX_RESULTS,
|
816
|
+
max_results=max_results,
|
817
|
+
)
|
818
|
+
|
819
|
+
if return_type == "pandas":
|
820
|
+
results = traces_to_df(results)
|
821
|
+
if extract_fields:
|
822
|
+
results = extract_span_inputs_outputs(
|
823
|
+
traces=results,
|
824
|
+
fields=extract_fields,
|
825
|
+
col_name=SPANS_COLUMN_NAME,
|
826
|
+
)
|
827
|
+
|
828
|
+
return results
|
829
|
+
|
830
|
+
|
831
|
+
def get_current_active_span() -> Optional[LiveSpan]:
|
832
|
+
"""
|
833
|
+
Get the current active span in the global context.
|
834
|
+
|
835
|
+
.. attention::
|
836
|
+
|
837
|
+
This only works when the span is created with fluent APIs like `@mlflow.trace` or
|
838
|
+
`with mlflow.start_span`. If a span is created with the
|
839
|
+
`mlflow.start_span_no_context` APIs, it won't be
|
840
|
+
attached to the global context so this function will not return it.
|
841
|
+
|
842
|
+
|
843
|
+
.. code-block:: python
|
844
|
+
:test:
|
845
|
+
|
846
|
+
import mlflow
|
847
|
+
|
848
|
+
|
849
|
+
@mlflow.trace
|
850
|
+
def f():
|
851
|
+
span = mlflow.get_current_active_span()
|
852
|
+
span.set_attribute("key", "value")
|
853
|
+
return 0
|
854
|
+
|
855
|
+
|
856
|
+
f()
|
857
|
+
|
858
|
+
Returns:
|
859
|
+
The current active span if exists, otherwise None.
|
860
|
+
"""
|
861
|
+
otel_span = trace_api.get_current_span()
|
862
|
+
# NonRecordingSpan is returned if a tracer is not instantiated.
|
863
|
+
if otel_span is None or isinstance(otel_span, trace_api.NonRecordingSpan):
|
864
|
+
return None
|
865
|
+
|
866
|
+
trace_manager = InMemoryTraceManager.get_instance()
|
867
|
+
request_id = json.loads(otel_span.attributes.get(SpanAttributeKey.REQUEST_ID))
|
868
|
+
return trace_manager.get_span_from_id(request_id, encode_span_id(otel_span.context.span_id))
|
869
|
+
|
870
|
+
|
871
|
+
def get_active_trace_id() -> Optional[str]:
|
872
|
+
"""
|
873
|
+
Get the active trace ID in the current process.
|
874
|
+
|
875
|
+
This function is thread-safe.
|
876
|
+
|
877
|
+
Example:
|
878
|
+
|
879
|
+
.. code-block:: python
|
880
|
+
:test:
|
881
|
+
|
882
|
+
import mlflow
|
883
|
+
|
884
|
+
|
885
|
+
@mlflow.trace
|
886
|
+
def f():
|
887
|
+
trace_id = mlflow.get_active_trace_id()
|
888
|
+
print(trace_id)
|
889
|
+
|
890
|
+
|
891
|
+
f()
|
892
|
+
|
893
|
+
Returns:
|
894
|
+
The ID of the current active trace if exists, otherwise None.
|
895
|
+
"""
|
896
|
+
active_span = get_current_active_span()
|
897
|
+
if active_span:
|
898
|
+
return active_span.trace_id
|
899
|
+
return None
|
900
|
+
|
901
|
+
|
902
|
+
def get_last_active_trace_id(thread_local: bool = False) -> Optional[str]:
|
903
|
+
"""
|
904
|
+
Get the **LAST** active trace in the same process if exists.
|
905
|
+
|
906
|
+
.. warning::
|
907
|
+
|
908
|
+
This function is not thread-safe by default, returns the last active trace in
|
909
|
+
the same process. If you want to get the last active trace in the current thread,
|
910
|
+
set the `thread_local` parameter to True.
|
911
|
+
|
912
|
+
Args:
|
913
|
+
|
914
|
+
thread_local: If True, returns the last active trace in the current thread. Otherwise,
|
915
|
+
returns the last active trace in the same process. Default is False.
|
916
|
+
|
917
|
+
Returns:
|
918
|
+
The ID of the last active trace if exists, otherwise None.
|
919
|
+
|
920
|
+
.. code-block:: python
|
921
|
+
:test:
|
922
|
+
|
923
|
+
import mlflow
|
924
|
+
|
925
|
+
|
926
|
+
@mlflow.trace
|
927
|
+
def f():
|
928
|
+
pass
|
929
|
+
|
930
|
+
|
931
|
+
f()
|
932
|
+
|
933
|
+
trace_id = mlflow.get_last_active_trace_id()
|
934
|
+
|
935
|
+
# Set a tag on the trace
|
936
|
+
mlflow.set_trace_tag(trace_id, "key", "value")
|
937
|
+
|
938
|
+
# Get the full trace object
|
939
|
+
trace = mlflow.get_trace(trace_id)
|
940
|
+
"""
|
941
|
+
return (
|
942
|
+
_LAST_ACTIVE_TRACE_ID_THREAD_LOCAL.get() if thread_local else _LAST_ACTIVE_TRACE_ID_GLOBAL
|
943
|
+
)
|
944
|
+
|
945
|
+
|
946
|
+
def _set_last_active_trace_id(trace_id: str):
|
947
|
+
"""Internal function to set the last active trace ID."""
|
948
|
+
global _LAST_ACTIVE_TRACE_ID_GLOBAL
|
949
|
+
_LAST_ACTIVE_TRACE_ID_GLOBAL = trace_id
|
950
|
+
_LAST_ACTIVE_TRACE_ID_THREAD_LOCAL.set(trace_id)
|
951
|
+
|
952
|
+
|
953
|
+
def update_current_trace(
|
954
|
+
tags: Optional[dict[str, str]] = None,
|
955
|
+
metadata: Optional[dict[str, str]] = None,
|
956
|
+
client_request_id: Optional[str] = None,
|
957
|
+
request_preview: Optional[str] = None,
|
958
|
+
response_preview: Optional[str] = None,
|
959
|
+
state: Optional[Union[TraceState, str]] = None,
|
960
|
+
):
|
961
|
+
"""
|
962
|
+
Update the current active trace with the given options.
|
963
|
+
|
964
|
+
Args:
|
965
|
+
tags: A dictionary of tags to update the trace with Tags are designed for mutable values,
|
966
|
+
that can be updated after the trace is created via MLflow UI or API.
|
967
|
+
metadata: A dictionary of metadata to update the trace with. Metadata cannot be updated
|
968
|
+
once the trace is logged. It is suitable for recording immutable values like the
|
969
|
+
git hash of the application version that produced the trace.
|
970
|
+
client_request_id: Client supplied request ID to associate with the trace. This is
|
971
|
+
useful for linking the trace back to a specific request in your application or
|
972
|
+
external system. If None, the client request ID is not updated.
|
973
|
+
request_preview: A preview of the request to be shown in the Trace list view in the UI.
|
974
|
+
By default, MLflow will truncate the trace request naively by limiting the length.
|
975
|
+
This parameter allows you to specify a custom preview string.
|
976
|
+
response_preview: A preview of the response to be shown in the Trace list view in the UI.
|
977
|
+
By default, MLflow will truncate the trace response naively by limiting the length.
|
978
|
+
This parameter allows you to specify a custom preview string.
|
979
|
+
state: The state to set on the trace. Can be a TraceState enum value or string.
|
980
|
+
Only "OK" and "ERROR" are allowed. This overrides the overall trace state without
|
981
|
+
affecting the status of the current span.
|
982
|
+
|
983
|
+
Example:
|
984
|
+
|
985
|
+
You can use this function either within a function decorated with ``@mlflow.trace`` or
|
986
|
+
within the scope of the `with mlflow.start_span` context manager. If there is no active
|
987
|
+
trace found, this function will raise an exception.
|
988
|
+
|
989
|
+
Using within a function decorated with `@mlflow.trace`:
|
990
|
+
|
991
|
+
.. code-block:: python
|
992
|
+
|
993
|
+
@mlflow.trace
|
994
|
+
def my_func(x):
|
995
|
+
mlflow.update_current_trace(tags={"fruit": "apple"}, client_request_id="req-12345")
|
996
|
+
return x + 1
|
997
|
+
|
998
|
+
Using within the ``with mlflow.start_span`` context manager:
|
999
|
+
|
1000
|
+
.. code-block:: python
|
1001
|
+
|
1002
|
+
with mlflow.start_span("span"):
|
1003
|
+
mlflow.update_current_trace(tags={"fruit": "apple"}, client_request_id="req-12345")
|
1004
|
+
|
1005
|
+
Updating source information of the trace. These keys are reserved ones and MLflow populate
|
1006
|
+
them from environment information by default. You can override them if needed. Please refer
|
1007
|
+
to the MLflow Tracing documentation for the full list of reserved metadata keys.
|
1008
|
+
|
1009
|
+
.. code-block:: python
|
1010
|
+
|
1011
|
+
mlflow.update_current_trace(
|
1012
|
+
metadata={
|
1013
|
+
"mlflow.trace.session": "session-4f855da00427",
|
1014
|
+
"mlflow.trace.user": "user-id-cc156f29bcfb",
|
1015
|
+
"mlflow.source.name": "inference.py",
|
1016
|
+
"mlflow.source.git.commit": "1234567890",
|
1017
|
+
"mlflow.source.git.repoURL": "https://github.com/mlflow/mlflow",
|
1018
|
+
},
|
1019
|
+
)
|
1020
|
+
|
1021
|
+
Updating request preview:
|
1022
|
+
|
1023
|
+
.. code-block:: python
|
1024
|
+
|
1025
|
+
import mlflow
|
1026
|
+
import openai
|
1027
|
+
|
1028
|
+
|
1029
|
+
@mlflow.trace
|
1030
|
+
def predict(messages: list[dict]) -> str:
|
1031
|
+
# Customize the request preview to show the first and last messages
|
1032
|
+
custom_preview = f"{messages[0]['content'][:10]} ... {messages[-1]['content'][:10]}"
|
1033
|
+
mlflow.update_current_trace(request_preview=custom_preview)
|
1034
|
+
|
1035
|
+
# Call the model
|
1036
|
+
response = openai.chat.completions.create(
|
1037
|
+
model="o4-mini",
|
1038
|
+
messages=messages,
|
1039
|
+
)
|
1040
|
+
|
1041
|
+
return response.choices[0].message.content
|
1042
|
+
|
1043
|
+
|
1044
|
+
messages = [
|
1045
|
+
{"role": "user", "content": "Hi, how are you?"},
|
1046
|
+
{"role": "assistant", "content": "I'm good, thank you!"},
|
1047
|
+
{"role": "user", "content": "What's your name?"},
|
1048
|
+
# ... (long message history)
|
1049
|
+
{"role": "assistant", "content": "Bye!"},
|
1050
|
+
]
|
1051
|
+
predict(messages)
|
1052
|
+
|
1053
|
+
# The request preview rendered in the UI will be:
|
1054
|
+
# "Hi, how are you? ... Bye!"
|
1055
|
+
|
1056
|
+
"""
|
1057
|
+
active_span = get_current_active_span()
|
1058
|
+
|
1059
|
+
if not active_span:
|
1060
|
+
_logger.warning(
|
1061
|
+
"No active trace found. Please create a span using `mlflow.start_span` or "
|
1062
|
+
"`@mlflow.trace` before calling `mlflow.update_current_trace`.",
|
1063
|
+
)
|
1064
|
+
return
|
1065
|
+
|
1066
|
+
def _warn_non_string_values(d: dict[str, Any], field_name: str):
|
1067
|
+
non_string_items = {k: v for k, v in d.items() if not isinstance(v, str)}
|
1068
|
+
if non_string_items:
|
1069
|
+
_logger.warning(
|
1070
|
+
f"Found non-string values in {field_name}. Non-string values in {field_name} will "
|
1071
|
+
f"automatically be stringified when the trace is logged. Non-string items: "
|
1072
|
+
f"{non_string_items}"
|
1073
|
+
)
|
1074
|
+
|
1075
|
+
_warn_non_string_values(tags or {}, "tags")
|
1076
|
+
_warn_non_string_values(metadata or {}, "metadata")
|
1077
|
+
|
1078
|
+
# Update tags and client request ID for the trace stored in-memory rather than directly
|
1079
|
+
# updating the backend store. The in-memory trace will be exported when it is ended.
|
1080
|
+
# By doing this, we can avoid unnecessary server requests for each tag update.
|
1081
|
+
if request_preview is not None and not isinstance(request_preview, str):
|
1082
|
+
raise MlflowException.invalid_parameter_value(
|
1083
|
+
"The `request_preview` parameter must be a string."
|
1084
|
+
)
|
1085
|
+
if response_preview is not None and not isinstance(response_preview, str):
|
1086
|
+
raise MlflowException.invalid_parameter_value(
|
1087
|
+
"The `response_preview` parameter must be a string."
|
1088
|
+
)
|
1089
|
+
|
1090
|
+
# Update trace info for the trace stored in-memory rather than directly updating the
|
1091
|
+
# backend store. The in-memory trace will be exported when it is ended. By doing
|
1092
|
+
# this, we can avoid unnecessary server requests for each tag update.
|
1093
|
+
request_id = active_span.request_id
|
1094
|
+
with InMemoryTraceManager.get_instance().get_trace(request_id) as trace:
|
1095
|
+
if request_preview:
|
1096
|
+
trace.info.request_preview = request_preview
|
1097
|
+
if response_preview:
|
1098
|
+
trace.info.response_preview = response_preview
|
1099
|
+
if state is not None:
|
1100
|
+
|
1101
|
+
def _invalid_state_error(value):
|
1102
|
+
return MlflowException.invalid_parameter_value(
|
1103
|
+
f"State must be either 'OK' or 'ERROR', but got '{value}'."
|
1104
|
+
)
|
1105
|
+
|
1106
|
+
if state not in (TraceState.OK, TraceState.ERROR):
|
1107
|
+
raise _invalid_state_error(state)
|
1108
|
+
|
1109
|
+
trace.info.state = TraceState(state) if isinstance(state, str) else state
|
1110
|
+
|
1111
|
+
trace.info.tags.update(tags or {})
|
1112
|
+
trace.info.trace_metadata.update(metadata or {})
|
1113
|
+
if client_request_id is not None:
|
1114
|
+
trace.info.client_request_id = str(client_request_id)
|
1115
|
+
|
1116
|
+
|
1117
|
+
@request_id_backward_compatible
|
1118
|
+
def set_trace_tag(trace_id: str, key: str, value: str):
|
1119
|
+
"""
|
1120
|
+
Set a tag on the trace with the given trace ID.
|
1121
|
+
|
1122
|
+
The trace can be an active one or the one that has already ended and recorded in the
|
1123
|
+
backend. Below is an example of setting a tag on an active trace. You can replace the
|
1124
|
+
``trace_id`` parameter to set a tag on an already ended trace.
|
1125
|
+
|
1126
|
+
.. code-block:: python
|
1127
|
+
:test:
|
1128
|
+
|
1129
|
+
import mlflow
|
1130
|
+
|
1131
|
+
with mlflow.start_span(name="span") as span:
|
1132
|
+
mlflow.set_trace_tag(span.trace_id, "key", "value")
|
1133
|
+
|
1134
|
+
Args:
|
1135
|
+
trace_id: The ID of the trace to set the tag on.
|
1136
|
+
key: The string key of the tag. Must be at most 250 characters long, otherwise
|
1137
|
+
it will be truncated when stored.
|
1138
|
+
value: The string value of the tag. Must be at most 250 characters long, otherwise
|
1139
|
+
it will be truncated when stored.
|
1140
|
+
"""
|
1141
|
+
TracingClient().set_trace_tag(trace_id, key, value)
|
1142
|
+
|
1143
|
+
|
1144
|
+
@request_id_backward_compatible
|
1145
|
+
def delete_trace_tag(trace_id: str, key: str) -> None:
|
1146
|
+
"""
|
1147
|
+
Delete a tag on the trace with the given trace ID.
|
1148
|
+
|
1149
|
+
The trace can be an active one or the one that has already ended and recorded in the
|
1150
|
+
backend. Below is an example of deleting a tag on an active trace. You can replace the
|
1151
|
+
``trace_id`` parameter to delete a tag on an already ended trace.
|
1152
|
+
|
1153
|
+
.. code-block:: python
|
1154
|
+
:test:
|
1155
|
+
|
1156
|
+
import mlflow
|
1157
|
+
|
1158
|
+
with mlflow.start_span("my_span") as span:
|
1159
|
+
mlflow.set_trace_tag(span.trace_id, "key", "value")
|
1160
|
+
mlflow.delete_trace_tag(span.trace_id, "key")
|
1161
|
+
|
1162
|
+
Args:
|
1163
|
+
trace_id: The ID of the trace to delete the tag from.
|
1164
|
+
key: The string key of the tag. Must be at most 250 characters long, otherwise
|
1165
|
+
it will be truncated when stored.
|
1166
|
+
"""
|
1167
|
+
TracingClient().delete_trace_tag(trace_id, key)
|
1168
|
+
|
1169
|
+
|
1170
|
+
@experimental(version="2.17.0")
|
1171
|
+
def add_trace(trace: Union[Trace, dict[str, Any]], target: Optional[LiveSpan] = None):
|
1172
|
+
"""
|
1173
|
+
Add a completed trace object into another trace.
|
1174
|
+
|
1175
|
+
This is particularly useful when you call a remote service instrumented by
|
1176
|
+
MLflow Tracing. By using this function, you can merge the trace from the remote
|
1177
|
+
service into the current active local trace, so that you can see the full
|
1178
|
+
trace including what happens inside the remote service call.
|
1179
|
+
|
1180
|
+
The following example demonstrates how to use this function to merge a trace from a remote
|
1181
|
+
service to the current active trace in the function.
|
1182
|
+
|
1183
|
+
.. code-block:: python
|
1184
|
+
|
1185
|
+
@mlflow.trace(name="predict")
|
1186
|
+
def predict(input):
|
1187
|
+
# Call a remote service that returns a trace in the response
|
1188
|
+
resp = requests.get("https://your-service-endpoint", ...)
|
1189
|
+
|
1190
|
+
# Extract the trace from the response
|
1191
|
+
trace_json = resp.json().get("trace")
|
1192
|
+
|
1193
|
+
# Use the remote trace as a part of the current active trace.
|
1194
|
+
# It will be merged under the span "predict" and exported together when it is ended.
|
1195
|
+
mlflow.add_trace(trace_json)
|
1196
|
+
|
1197
|
+
If you have a specific target span to merge the trace under, you can pass the target span
|
1198
|
+
|
1199
|
+
.. code-block:: python
|
1200
|
+
|
1201
|
+
def predict(input):
|
1202
|
+
# Create a local span
|
1203
|
+
with mlflow.start_span(name="predict") as span:
|
1204
|
+
resp = requests.get("https://your-service-endpoint", ...)
|
1205
|
+
trace_json = resp.json().get("trace")
|
1206
|
+
|
1207
|
+
# Merge the remote trace under the span created above
|
1208
|
+
mlflow.add_trace(trace_json, target=span)
|
1209
|
+
|
1210
|
+
Args:
|
1211
|
+
trace: A :py:class:`Trace <mlflow.entities.Trace>` object or a dictionary representation
|
1212
|
+
of the trace. The trace **must** be already completed i.e. no further updates should
|
1213
|
+
be made to it. Otherwise, this function will raise an exception.
|
1214
|
+
|
1215
|
+
.. attention:
|
1216
|
+
|
1217
|
+
The spans in the trace must be ordered in a way that the parent span comes
|
1218
|
+
before its children. If the spans are not ordered correctly, this function
|
1219
|
+
will raise an exception.
|
1220
|
+
|
1221
|
+
target: The target span to merge the given trace.
|
1222
|
+
|
1223
|
+
- If provided, the trace will be merged under the target span.
|
1224
|
+
- If not provided, the trace will be merged under the current active span.
|
1225
|
+
- If not provided and there is no active span, a new span named "Remote Trace <...>"
|
1226
|
+
will be created and the trace will be merged under it.
|
1227
|
+
"""
|
1228
|
+
if not is_tracing_enabled():
|
1229
|
+
_logger.debug("Tracing is disabled. Skipping add_trace.")
|
1230
|
+
return
|
1231
|
+
|
1232
|
+
if isinstance(trace, dict):
|
1233
|
+
try:
|
1234
|
+
trace = Trace.from_dict(trace)
|
1235
|
+
except Exception as e:
|
1236
|
+
raise MlflowException.invalid_parameter_value(
|
1237
|
+
"Failed to load a trace object from the given dictionary. Please ensure the "
|
1238
|
+
f"dictionary is in the correct MLflow Trace format. Error: {e}",
|
1239
|
+
)
|
1240
|
+
elif not isinstance(trace, Trace):
|
1241
|
+
raise MlflowException.invalid_parameter_value(
|
1242
|
+
f"Invalid trace object: {type(trace)}. Please provide a valid MLflow Trace object "
|
1243
|
+
"to use it as a remote trace. You can create a Trace object from its json format by "
|
1244
|
+
"using the Trace.from_dict() method."
|
1245
|
+
)
|
1246
|
+
|
1247
|
+
if trace.info.status not in TraceStatus.end_statuses():
|
1248
|
+
raise MlflowException.invalid_parameter_value(
|
1249
|
+
"The trace must be ended before adding it to another trace. "
|
1250
|
+
f"Current status: {trace.info.status}.",
|
1251
|
+
)
|
1252
|
+
|
1253
|
+
if target_span := target or get_current_active_span():
|
1254
|
+
_merge_trace(
|
1255
|
+
trace=trace,
|
1256
|
+
target_trace_id=target_span.trace_id,
|
1257
|
+
target_parent_span_id=target_span.span_id,
|
1258
|
+
)
|
1259
|
+
else:
|
1260
|
+
# If there is no target span, create a new root span named "Remote Trace <...>"
|
1261
|
+
# and put the remote trace under it. This design aims to keep the trace export
|
1262
|
+
# logic simpler and consistent, rather than directly exporting the remote trace.
|
1263
|
+
remote_root_span = trace.data.spans[0]
|
1264
|
+
span = start_span_no_context(
|
1265
|
+
name=f"Remote Trace <{remote_root_span.name}>",
|
1266
|
+
inputs=remote_root_span.inputs,
|
1267
|
+
attributes={
|
1268
|
+
# Exclude request ID attribute not to reuse same request ID
|
1269
|
+
k: v
|
1270
|
+
for k, v in remote_root_span.attributes.items()
|
1271
|
+
if k != SpanAttributeKey.REQUEST_ID
|
1272
|
+
},
|
1273
|
+
start_time_ns=remote_root_span.start_time_ns,
|
1274
|
+
)
|
1275
|
+
_merge_trace(
|
1276
|
+
trace=trace,
|
1277
|
+
target_trace_id=span.trace_id,
|
1278
|
+
target_parent_span_id=span.span_id,
|
1279
|
+
)
|
1280
|
+
span.end(
|
1281
|
+
status=trace.info.status,
|
1282
|
+
outputs=remote_root_span.outputs,
|
1283
|
+
end_time_ns=remote_root_span.end_time_ns,
|
1284
|
+
)
|
1285
|
+
|
1286
|
+
|
1287
|
+
@experimental(version="2.21.0")
|
1288
|
+
def log_trace(
|
1289
|
+
name: str = "Task",
|
1290
|
+
request: Optional[Any] = None,
|
1291
|
+
response: Optional[Any] = None,
|
1292
|
+
intermediate_outputs: Optional[dict[str, Any]] = None,
|
1293
|
+
attributes: Optional[dict[str, Any]] = None,
|
1294
|
+
tags: Optional[dict[str, str]] = None,
|
1295
|
+
start_time_ms: Optional[int] = None,
|
1296
|
+
execution_time_ms: Optional[int] = None,
|
1297
|
+
) -> str:
|
1298
|
+
"""
|
1299
|
+
Create a trace with a single root span.
|
1300
|
+
This API is useful when you want to log an arbitrary (request, response) pair
|
1301
|
+
without structured OpenTelemetry spans. The trace is linked to the active experiment.
|
1302
|
+
|
1303
|
+
Args:
|
1304
|
+
name: The name of the trace (and the root span). Default to "Task".
|
1305
|
+
request: Input data for the entire trace. This is also set on the root span of the trace.
|
1306
|
+
response: Output data for the entire trace. This is also set on the root span of the trace.
|
1307
|
+
intermediate_outputs: A dictionary of intermediate outputs produced by the model or agent
|
1308
|
+
while handling the request. Keys are the names of the outputs,
|
1309
|
+
and values are the outputs themselves. Values must be JSON-serializable.
|
1310
|
+
attributes: A dictionary of attributes to set on the root span of the trace.
|
1311
|
+
tags: A dictionary of tags to set on the trace.
|
1312
|
+
start_time_ms: The start time of the trace in milliseconds since the UNIX epoch.
|
1313
|
+
When not specified, current time is used for start and end time of the trace.
|
1314
|
+
execution_time_ms: The execution time of the trace in milliseconds since the UNIX epoch.
|
1315
|
+
|
1316
|
+
Returns:
|
1317
|
+
The ID of the logged trace.
|
1318
|
+
|
1319
|
+
Example:
|
1320
|
+
|
1321
|
+
.. code-block:: python
|
1322
|
+
:test:
|
1323
|
+
|
1324
|
+
import time
|
1325
|
+
import mlflow
|
1326
|
+
|
1327
|
+
trace_id = mlflow.log_trace(
|
1328
|
+
request="Does mlflow support tracing?",
|
1329
|
+
response="Yes",
|
1330
|
+
intermediate_outputs={
|
1331
|
+
"retrieved_documents": ["mlflow documentation"],
|
1332
|
+
"system_prompt": ["answer the question with yes or no"],
|
1333
|
+
},
|
1334
|
+
start_time_ms=int(time.time() * 1000),
|
1335
|
+
execution_time_ms=5129,
|
1336
|
+
)
|
1337
|
+
trace = mlflow.get_trace(trace_id)
|
1338
|
+
|
1339
|
+
print(trace.data.intermediate_outputs)
|
1340
|
+
"""
|
1341
|
+
if intermediate_outputs:
|
1342
|
+
if attributes:
|
1343
|
+
attributes.update(SpanAttributeKey.INTERMEDIATE_OUTPUTS, intermediate_outputs)
|
1344
|
+
else:
|
1345
|
+
attributes = {SpanAttributeKey.INTERMEDIATE_OUTPUTS: intermediate_outputs}
|
1346
|
+
|
1347
|
+
span = start_span_no_context(
|
1348
|
+
name=name,
|
1349
|
+
inputs=request,
|
1350
|
+
attributes=attributes,
|
1351
|
+
tags=tags,
|
1352
|
+
start_time_ns=start_time_ms * 1000000 if start_time_ms else None,
|
1353
|
+
)
|
1354
|
+
span.end(
|
1355
|
+
outputs=response,
|
1356
|
+
end_time_ns=(start_time_ms + execution_time_ms) * 1000000
|
1357
|
+
if start_time_ms and execution_time_ms
|
1358
|
+
else None,
|
1359
|
+
)
|
1360
|
+
|
1361
|
+
return span.trace_id
|
1362
|
+
|
1363
|
+
|
1364
|
+
def _merge_trace(
|
1365
|
+
trace: Trace,
|
1366
|
+
target_trace_id: str,
|
1367
|
+
target_parent_span_id: str,
|
1368
|
+
):
|
1369
|
+
"""
|
1370
|
+
Merge the given trace object under an existing trace in the in-memory trace registry.
|
1371
|
+
|
1372
|
+
Args:
|
1373
|
+
trace: The trace object to be merged.
|
1374
|
+
target_trace_id: The ID of the parent trace.
|
1375
|
+
target_parent_span_id: The parent span ID, under which the child trace should be merged.
|
1376
|
+
"""
|
1377
|
+
trace_manager = InMemoryTraceManager.get_instance()
|
1378
|
+
|
1379
|
+
# The merged trace should have the same trace ID as the parent trace.
|
1380
|
+
with trace_manager.get_trace(target_trace_id) as parent_trace:
|
1381
|
+
if not parent_trace:
|
1382
|
+
_logger.warning(f"Parent trace with ID {target_trace_id} not found. Skipping merge.")
|
1383
|
+
return
|
1384
|
+
|
1385
|
+
new_trace_id = parent_trace.span_dict[target_parent_span_id]._trace_id
|
1386
|
+
|
1387
|
+
for span in trace.data.spans:
|
1388
|
+
parent_span_id = span.parent_id or target_parent_span_id
|
1389
|
+
|
1390
|
+
# NB: We clone span one by one in the order it was saved in the original trace. This
|
1391
|
+
# works upon the assumption that the parent span always comes before its children.
|
1392
|
+
# This is guaranteed in current implementation, but if it changes in the future,
|
1393
|
+
# we have to traverse the tree to determine the order.
|
1394
|
+
if not trace_manager.get_span_from_id(target_trace_id, parent_span_id):
|
1395
|
+
raise MlflowException.invalid_parameter_value(
|
1396
|
+
f"Span with ID {parent_span_id} not found. Please make sure the "
|
1397
|
+
"spans in the trace are ordered correctly i.e. the parent span comes before "
|
1398
|
+
"its children."
|
1399
|
+
)
|
1400
|
+
|
1401
|
+
cloned_span = LiveSpan.from_immutable_span(
|
1402
|
+
span=span,
|
1403
|
+
parent_span_id=parent_span_id,
|
1404
|
+
trace_id=target_trace_id,
|
1405
|
+
otel_trace_id=new_trace_id,
|
1406
|
+
)
|
1407
|
+
trace_manager.register_span(cloned_span)
|
1408
|
+
|
1409
|
+
# Merge the tags and metadata from the child trace to the parent trace.
|
1410
|
+
with trace_manager.get_trace(target_trace_id) as parent_trace:
|
1411
|
+
# Order of merging is important to ensure the parent trace's metadata is
|
1412
|
+
# not overwritten by the child trace's metadata if they have the same key.
|
1413
|
+
parent_trace.info.tags = {**trace.info.tags, **parent_trace.info.tags}
|
1414
|
+
parent_trace.info.trace_metadata = {
|
1415
|
+
**trace.info.request_metadata,
|
1416
|
+
**parent_trace.info.trace_metadata,
|
1417
|
+
}
|