opik 1.8.39__py3-none-any.whl → 1.9.71__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.
- opik/__init__.py +19 -3
- opik/anonymizer/__init__.py +5 -0
- opik/anonymizer/anonymizer.py +12 -0
- opik/anonymizer/factory.py +80 -0
- opik/anonymizer/recursive_anonymizer.py +64 -0
- opik/anonymizer/rules.py +56 -0
- opik/anonymizer/rules_anonymizer.py +35 -0
- opik/api_objects/attachment/attachment_context.py +36 -0
- opik/api_objects/attachment/attachments_extractor.py +153 -0
- opik/api_objects/attachment/client.py +1 -0
- opik/api_objects/attachment/converters.py +2 -0
- opik/api_objects/attachment/decoder.py +18 -0
- opik/api_objects/attachment/decoder_base64.py +83 -0
- opik/api_objects/attachment/decoder_helpers.py +137 -0
- opik/api_objects/data_helpers.py +79 -0
- opik/api_objects/dataset/dataset.py +64 -4
- opik/api_objects/dataset/rest_operations.py +11 -2
- opik/api_objects/experiment/experiment.py +57 -57
- opik/api_objects/experiment/experiment_item.py +2 -1
- opik/api_objects/experiment/experiments_client.py +64 -0
- opik/api_objects/experiment/helpers.py +35 -11
- opik/api_objects/experiment/rest_operations.py +65 -5
- opik/api_objects/helpers.py +8 -5
- opik/api_objects/local_recording.py +81 -0
- opik/api_objects/opik_client.py +600 -108
- opik/api_objects/opik_query_language.py +39 -5
- opik/api_objects/prompt/__init__.py +12 -2
- opik/api_objects/prompt/base_prompt.py +69 -0
- opik/api_objects/prompt/base_prompt_template.py +29 -0
- opik/api_objects/prompt/chat/__init__.py +1 -0
- opik/api_objects/prompt/chat/chat_prompt.py +210 -0
- opik/api_objects/prompt/chat/chat_prompt_template.py +350 -0
- opik/api_objects/prompt/chat/content_renderer_registry.py +203 -0
- opik/api_objects/prompt/client.py +189 -47
- opik/api_objects/prompt/text/__init__.py +1 -0
- opik/api_objects/prompt/text/prompt.py +174 -0
- opik/api_objects/prompt/{prompt_template.py → text/prompt_template.py} +10 -6
- opik/api_objects/prompt/types.py +23 -0
- opik/api_objects/search_helpers.py +89 -0
- opik/api_objects/span/span_data.py +35 -25
- opik/api_objects/threads/threads_client.py +39 -5
- opik/api_objects/trace/trace_client.py +52 -2
- opik/api_objects/trace/trace_data.py +15 -24
- opik/api_objects/validation_helpers.py +3 -3
- opik/cli/__init__.py +5 -0
- opik/cli/__main__.py +6 -0
- opik/cli/configure.py +66 -0
- opik/cli/exports/__init__.py +131 -0
- opik/cli/exports/dataset.py +278 -0
- opik/cli/exports/experiment.py +784 -0
- opik/cli/exports/project.py +685 -0
- opik/cli/exports/prompt.py +578 -0
- opik/cli/exports/utils.py +406 -0
- opik/cli/harbor.py +39 -0
- opik/cli/healthcheck.py +21 -0
- opik/cli/imports/__init__.py +439 -0
- opik/cli/imports/dataset.py +143 -0
- opik/cli/imports/experiment.py +1192 -0
- opik/cli/imports/project.py +262 -0
- opik/cli/imports/prompt.py +177 -0
- opik/cli/imports/utils.py +280 -0
- opik/cli/main.py +49 -0
- opik/cli/proxy.py +93 -0
- opik/cli/usage_report/__init__.py +16 -0
- opik/cli/usage_report/charts.py +783 -0
- opik/cli/usage_report/cli.py +274 -0
- opik/cli/usage_report/constants.py +9 -0
- opik/cli/usage_report/extraction.py +749 -0
- opik/cli/usage_report/pdf.py +244 -0
- opik/cli/usage_report/statistics.py +78 -0
- opik/cli/usage_report/utils.py +235 -0
- opik/config.py +13 -7
- opik/configurator/configure.py +17 -0
- opik/datetime_helpers.py +12 -0
- opik/decorator/arguments_helpers.py +9 -1
- opik/decorator/base_track_decorator.py +205 -133
- opik/decorator/context_manager/span_context_manager.py +123 -0
- opik/decorator/context_manager/trace_context_manager.py +84 -0
- opik/decorator/opik_args/__init__.py +13 -0
- opik/decorator/opik_args/api_classes.py +71 -0
- opik/decorator/opik_args/helpers.py +120 -0
- opik/decorator/span_creation_handler.py +25 -6
- opik/dict_utils.py +3 -3
- opik/evaluation/__init__.py +13 -2
- opik/evaluation/engine/engine.py +272 -75
- opik/evaluation/engine/evaluation_tasks_executor.py +6 -3
- opik/evaluation/engine/helpers.py +31 -6
- opik/evaluation/engine/metrics_evaluator.py +237 -0
- opik/evaluation/evaluation_result.py +168 -2
- opik/evaluation/evaluator.py +533 -62
- opik/evaluation/metrics/__init__.py +103 -4
- opik/evaluation/metrics/aggregated_metric.py +35 -6
- opik/evaluation/metrics/base_metric.py +1 -1
- opik/evaluation/metrics/conversation/__init__.py +48 -0
- opik/evaluation/metrics/conversation/conversation_thread_metric.py +56 -2
- opik/evaluation/metrics/conversation/g_eval_wrappers.py +19 -0
- opik/evaluation/metrics/conversation/helpers.py +14 -15
- opik/evaluation/metrics/conversation/heuristics/__init__.py +14 -0
- opik/evaluation/metrics/conversation/heuristics/degeneration/__init__.py +3 -0
- opik/evaluation/metrics/conversation/heuristics/degeneration/metric.py +189 -0
- opik/evaluation/metrics/conversation/heuristics/degeneration/phrases.py +12 -0
- opik/evaluation/metrics/conversation/heuristics/knowledge_retention/__init__.py +3 -0
- opik/evaluation/metrics/conversation/heuristics/knowledge_retention/metric.py +172 -0
- opik/evaluation/metrics/conversation/llm_judges/__init__.py +32 -0
- opik/evaluation/metrics/conversation/{conversational_coherence → llm_judges/conversational_coherence}/metric.py +22 -17
- opik/evaluation/metrics/conversation/{conversational_coherence → llm_judges/conversational_coherence}/templates.py +1 -1
- opik/evaluation/metrics/conversation/llm_judges/g_eval_wrappers.py +442 -0
- opik/evaluation/metrics/conversation/{session_completeness → llm_judges/session_completeness}/metric.py +13 -7
- opik/evaluation/metrics/conversation/{session_completeness → llm_judges/session_completeness}/templates.py +1 -1
- opik/evaluation/metrics/conversation/llm_judges/user_frustration/__init__.py +0 -0
- opik/evaluation/metrics/conversation/{user_frustration → llm_judges/user_frustration}/metric.py +21 -14
- opik/evaluation/metrics/conversation/{user_frustration → llm_judges/user_frustration}/templates.py +1 -1
- opik/evaluation/metrics/conversation/types.py +4 -5
- opik/evaluation/metrics/conversation_types.py +9 -0
- opik/evaluation/metrics/heuristics/bertscore.py +107 -0
- opik/evaluation/metrics/heuristics/bleu.py +35 -15
- opik/evaluation/metrics/heuristics/chrf.py +127 -0
- opik/evaluation/metrics/heuristics/contains.py +47 -11
- opik/evaluation/metrics/heuristics/distribution_metrics.py +331 -0
- opik/evaluation/metrics/heuristics/gleu.py +113 -0
- opik/evaluation/metrics/heuristics/language_adherence.py +123 -0
- opik/evaluation/metrics/heuristics/meteor.py +119 -0
- opik/evaluation/metrics/heuristics/prompt_injection.py +150 -0
- opik/evaluation/metrics/heuristics/readability.py +129 -0
- opik/evaluation/metrics/heuristics/rouge.py +26 -9
- opik/evaluation/metrics/heuristics/spearman.py +88 -0
- opik/evaluation/metrics/heuristics/tone.py +155 -0
- opik/evaluation/metrics/heuristics/vader_sentiment.py +77 -0
- opik/evaluation/metrics/llm_judges/answer_relevance/metric.py +20 -5
- opik/evaluation/metrics/llm_judges/context_precision/metric.py +20 -6
- opik/evaluation/metrics/llm_judges/context_recall/metric.py +20 -6
- opik/evaluation/metrics/llm_judges/g_eval/__init__.py +5 -0
- opik/evaluation/metrics/llm_judges/g_eval/metric.py +219 -68
- opik/evaluation/metrics/llm_judges/g_eval/parser.py +102 -52
- opik/evaluation/metrics/llm_judges/g_eval/presets.py +209 -0
- opik/evaluation/metrics/llm_judges/g_eval_presets/__init__.py +36 -0
- opik/evaluation/metrics/llm_judges/g_eval_presets/agent_assessment.py +77 -0
- opik/evaluation/metrics/llm_judges/g_eval_presets/bias_classifier.py +181 -0
- opik/evaluation/metrics/llm_judges/g_eval_presets/compliance_risk.py +41 -0
- opik/evaluation/metrics/llm_judges/g_eval_presets/prompt_uncertainty.py +41 -0
- opik/evaluation/metrics/llm_judges/g_eval_presets/qa_suite.py +146 -0
- opik/evaluation/metrics/llm_judges/hallucination/metric.py +16 -3
- opik/evaluation/metrics/llm_judges/llm_juries/__init__.py +3 -0
- opik/evaluation/metrics/llm_judges/llm_juries/metric.py +76 -0
- opik/evaluation/metrics/llm_judges/moderation/metric.py +16 -4
- opik/evaluation/metrics/llm_judges/structure_output_compliance/__init__.py +0 -0
- opik/evaluation/metrics/llm_judges/structure_output_compliance/metric.py +144 -0
- opik/evaluation/metrics/llm_judges/structure_output_compliance/parser.py +79 -0
- opik/evaluation/metrics/llm_judges/structure_output_compliance/schema.py +15 -0
- opik/evaluation/metrics/llm_judges/structure_output_compliance/template.py +50 -0
- opik/evaluation/metrics/llm_judges/syc_eval/__init__.py +0 -0
- opik/evaluation/metrics/llm_judges/syc_eval/metric.py +252 -0
- opik/evaluation/metrics/llm_judges/syc_eval/parser.py +82 -0
- opik/evaluation/metrics/llm_judges/syc_eval/template.py +155 -0
- opik/evaluation/metrics/llm_judges/trajectory_accuracy/metric.py +20 -5
- opik/evaluation/metrics/llm_judges/usefulness/metric.py +16 -4
- opik/evaluation/metrics/ragas_metric.py +43 -23
- opik/evaluation/models/__init__.py +8 -0
- opik/evaluation/models/base_model.py +107 -1
- opik/evaluation/models/langchain/langchain_chat_model.py +15 -7
- opik/evaluation/models/langchain/message_converters.py +97 -15
- opik/evaluation/models/litellm/litellm_chat_model.py +156 -29
- opik/evaluation/models/litellm/util.py +125 -0
- opik/evaluation/models/litellm/warning_filters.py +16 -4
- opik/evaluation/models/model_capabilities.py +187 -0
- opik/evaluation/models/models_factory.py +25 -3
- opik/evaluation/preprocessing.py +92 -0
- opik/evaluation/report.py +70 -12
- opik/evaluation/rest_operations.py +49 -45
- opik/evaluation/samplers/__init__.py +4 -0
- opik/evaluation/samplers/base_dataset_sampler.py +40 -0
- opik/evaluation/samplers/random_dataset_sampler.py +48 -0
- opik/evaluation/score_statistics.py +66 -0
- opik/evaluation/scorers/__init__.py +4 -0
- opik/evaluation/scorers/scorer_function.py +55 -0
- opik/evaluation/scorers/scorer_wrapper_metric.py +130 -0
- opik/evaluation/test_case.py +3 -2
- opik/evaluation/test_result.py +1 -0
- opik/evaluation/threads/evaluator.py +31 -3
- opik/evaluation/threads/helpers.py +3 -2
- opik/evaluation/types.py +9 -1
- opik/exceptions.py +33 -0
- opik/file_upload/file_uploader.py +13 -0
- opik/file_upload/upload_options.py +2 -0
- opik/hooks/__init__.py +23 -0
- opik/hooks/anonymizer_hook.py +36 -0
- opik/hooks/httpx_client_hook.py +112 -0
- opik/httpx_client.py +12 -9
- opik/id_helpers.py +18 -0
- opik/integrations/adk/graph/subgraph_edges_builders.py +1 -2
- opik/integrations/adk/helpers.py +16 -7
- opik/integrations/adk/legacy_opik_tracer.py +7 -4
- opik/integrations/adk/opik_tracer.py +14 -1
- opik/integrations/adk/patchers/adk_otel_tracer/opik_adk_otel_tracer.py +7 -3
- opik/integrations/adk/recursive_callback_injector.py +4 -7
- opik/integrations/bedrock/converse/__init__.py +0 -0
- opik/integrations/bedrock/converse/chunks_aggregator.py +188 -0
- opik/integrations/bedrock/{converse_decorator.py → converse/converse_decorator.py} +4 -3
- opik/integrations/bedrock/invoke_agent_decorator.py +5 -4
- opik/integrations/bedrock/invoke_model/__init__.py +0 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/__init__.py +78 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/api.py +45 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/base.py +23 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/claude.py +121 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/format_detector.py +107 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/llama.py +108 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/mistral.py +118 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/nova.py +99 -0
- opik/integrations/bedrock/invoke_model/invoke_model_decorator.py +178 -0
- opik/integrations/bedrock/invoke_model/response_types.py +34 -0
- opik/integrations/bedrock/invoke_model/stream_wrappers.py +122 -0
- opik/integrations/bedrock/invoke_model/usage_converters.py +87 -0
- opik/integrations/bedrock/invoke_model/usage_extraction.py +108 -0
- opik/integrations/bedrock/opik_tracker.py +42 -4
- opik/integrations/bedrock/types.py +19 -0
- opik/integrations/crewai/crewai_decorator.py +8 -51
- opik/integrations/crewai/opik_tracker.py +31 -10
- opik/integrations/crewai/patchers/__init__.py +5 -0
- opik/integrations/crewai/patchers/flow.py +118 -0
- opik/integrations/crewai/patchers/litellm_completion.py +30 -0
- opik/integrations/crewai/patchers/llm_client.py +207 -0
- opik/integrations/dspy/callback.py +80 -17
- opik/integrations/dspy/parsers.py +168 -0
- opik/integrations/harbor/__init__.py +17 -0
- opik/integrations/harbor/experiment_service.py +269 -0
- opik/integrations/harbor/opik_tracker.py +528 -0
- opik/integrations/haystack/opik_connector.py +2 -2
- opik/integrations/haystack/opik_tracer.py +3 -7
- opik/integrations/langchain/__init__.py +3 -1
- opik/integrations/langchain/helpers.py +96 -0
- opik/integrations/langchain/langgraph_async_context_bridge.py +131 -0
- opik/integrations/langchain/langgraph_tracer_injector.py +88 -0
- opik/integrations/langchain/opik_encoder_extension.py +1 -1
- opik/integrations/langchain/opik_tracer.py +474 -229
- opik/integrations/litellm/__init__.py +5 -0
- opik/integrations/litellm/completion_chunks_aggregator.py +115 -0
- opik/integrations/litellm/litellm_completion_decorator.py +242 -0
- opik/integrations/litellm/opik_tracker.py +43 -0
- opik/integrations/litellm/stream_patchers.py +151 -0
- opik/integrations/llama_index/callback.py +146 -107
- opik/integrations/openai/agents/opik_tracing_processor.py +1 -2
- opik/integrations/openai/openai_chat_completions_decorator.py +2 -16
- opik/integrations/openai/opik_tracker.py +1 -1
- opik/integrations/sagemaker/auth.py +5 -1
- opik/llm_usage/google_usage.py +3 -1
- opik/llm_usage/opik_usage.py +7 -8
- opik/llm_usage/opik_usage_factory.py +4 -2
- opik/logging_messages.py +6 -0
- opik/message_processing/batching/base_batcher.py +14 -21
- opik/message_processing/batching/batch_manager.py +22 -10
- opik/message_processing/batching/batch_manager_constuctors.py +10 -0
- opik/message_processing/batching/batchers.py +59 -27
- opik/message_processing/batching/flushing_thread.py +0 -3
- opik/message_processing/emulation/__init__.py +0 -0
- opik/message_processing/emulation/emulator_message_processor.py +578 -0
- opik/message_processing/emulation/local_emulator_message_processor.py +140 -0
- opik/message_processing/emulation/models.py +162 -0
- opik/message_processing/encoder_helpers.py +79 -0
- opik/message_processing/messages.py +56 -1
- opik/message_processing/preprocessing/__init__.py +0 -0
- opik/message_processing/preprocessing/attachments_preprocessor.py +70 -0
- opik/message_processing/preprocessing/batching_preprocessor.py +53 -0
- opik/message_processing/preprocessing/constants.py +1 -0
- opik/message_processing/preprocessing/file_upload_preprocessor.py +38 -0
- opik/message_processing/preprocessing/preprocessor.py +36 -0
- opik/message_processing/processors/__init__.py +0 -0
- opik/message_processing/processors/attachments_extraction_processor.py +146 -0
- opik/message_processing/processors/message_processors.py +92 -0
- opik/message_processing/processors/message_processors_chain.py +96 -0
- opik/message_processing/{message_processors.py → processors/online_message_processor.py} +85 -29
- opik/message_processing/queue_consumer.py +9 -3
- opik/message_processing/streamer.py +71 -33
- opik/message_processing/streamer_constructors.py +43 -10
- opik/opik_context.py +16 -4
- opik/plugins/pytest/hooks.py +5 -3
- opik/rest_api/__init__.py +346 -15
- opik/rest_api/alerts/__init__.py +7 -0
- opik/rest_api/alerts/client.py +667 -0
- opik/rest_api/alerts/raw_client.py +1015 -0
- opik/rest_api/alerts/types/__init__.py +7 -0
- opik/rest_api/alerts/types/get_webhook_examples_request_alert_type.py +5 -0
- opik/rest_api/annotation_queues/__init__.py +4 -0
- opik/rest_api/annotation_queues/client.py +668 -0
- opik/rest_api/annotation_queues/raw_client.py +1019 -0
- opik/rest_api/automation_rule_evaluators/client.py +34 -2
- opik/rest_api/automation_rule_evaluators/raw_client.py +24 -0
- opik/rest_api/client.py +15 -0
- opik/rest_api/dashboards/__init__.py +4 -0
- opik/rest_api/dashboards/client.py +462 -0
- opik/rest_api/dashboards/raw_client.py +648 -0
- opik/rest_api/datasets/client.py +1310 -44
- opik/rest_api/datasets/raw_client.py +2269 -358
- opik/rest_api/experiments/__init__.py +2 -2
- opik/rest_api/experiments/client.py +191 -5
- opik/rest_api/experiments/raw_client.py +301 -7
- opik/rest_api/experiments/types/__init__.py +4 -1
- opik/rest_api/experiments/types/experiment_update_status.py +5 -0
- opik/rest_api/experiments/types/experiment_update_type.py +5 -0
- opik/rest_api/experiments/types/experiment_write_status.py +5 -0
- opik/rest_api/feedback_definitions/types/find_feedback_definitions_request_type.py +1 -1
- opik/rest_api/llm_provider_key/client.py +20 -0
- opik/rest_api/llm_provider_key/raw_client.py +20 -0
- opik/rest_api/llm_provider_key/types/provider_api_key_write_provider.py +1 -1
- opik/rest_api/manual_evaluation/__init__.py +4 -0
- opik/rest_api/manual_evaluation/client.py +347 -0
- opik/rest_api/manual_evaluation/raw_client.py +543 -0
- opik/rest_api/optimizations/client.py +145 -9
- opik/rest_api/optimizations/raw_client.py +237 -13
- opik/rest_api/optimizations/types/optimization_update_status.py +3 -1
- opik/rest_api/prompts/__init__.py +2 -2
- opik/rest_api/prompts/client.py +227 -6
- opik/rest_api/prompts/raw_client.py +331 -2
- opik/rest_api/prompts/types/__init__.py +3 -1
- opik/rest_api/prompts/types/create_prompt_version_detail_template_structure.py +5 -0
- opik/rest_api/prompts/types/prompt_write_template_structure.py +5 -0
- opik/rest_api/spans/__init__.py +0 -2
- opik/rest_api/spans/client.py +238 -76
- opik/rest_api/spans/raw_client.py +307 -95
- opik/rest_api/spans/types/__init__.py +0 -2
- opik/rest_api/traces/client.py +572 -161
- opik/rest_api/traces/raw_client.py +736 -229
- opik/rest_api/types/__init__.py +352 -17
- opik/rest_api/types/aggregation_data.py +1 -0
- opik/rest_api/types/alert.py +33 -0
- opik/rest_api/types/alert_alert_type.py +5 -0
- opik/rest_api/types/alert_page_public.py +24 -0
- opik/rest_api/types/alert_public.py +33 -0
- opik/rest_api/types/alert_public_alert_type.py +5 -0
- opik/rest_api/types/alert_trigger.py +27 -0
- opik/rest_api/types/alert_trigger_config.py +28 -0
- opik/rest_api/types/alert_trigger_config_public.py +28 -0
- opik/rest_api/types/alert_trigger_config_public_type.py +10 -0
- opik/rest_api/types/alert_trigger_config_type.py +10 -0
- opik/rest_api/types/alert_trigger_config_write.py +22 -0
- opik/rest_api/types/alert_trigger_config_write_type.py +10 -0
- opik/rest_api/types/alert_trigger_event_type.py +19 -0
- opik/rest_api/types/alert_trigger_public.py +27 -0
- opik/rest_api/types/alert_trigger_public_event_type.py +19 -0
- opik/rest_api/types/alert_trigger_write.py +23 -0
- opik/rest_api/types/alert_trigger_write_event_type.py +19 -0
- opik/rest_api/types/alert_write.py +28 -0
- opik/rest_api/types/alert_write_alert_type.py +5 -0
- opik/rest_api/types/annotation_queue.py +42 -0
- opik/rest_api/types/annotation_queue_batch.py +27 -0
- opik/rest_api/types/annotation_queue_item_ids.py +19 -0
- opik/rest_api/types/annotation_queue_page_public.py +28 -0
- opik/rest_api/types/annotation_queue_public.py +38 -0
- opik/rest_api/types/annotation_queue_public_scope.py +5 -0
- opik/rest_api/types/annotation_queue_reviewer.py +20 -0
- opik/rest_api/types/annotation_queue_reviewer_public.py +20 -0
- opik/rest_api/types/annotation_queue_scope.py +5 -0
- opik/rest_api/types/annotation_queue_write.py +31 -0
- opik/rest_api/types/annotation_queue_write_scope.py +5 -0
- opik/rest_api/types/audio_url.py +19 -0
- opik/rest_api/types/audio_url_public.py +19 -0
- opik/rest_api/types/audio_url_write.py +19 -0
- opik/rest_api/types/automation_rule_evaluator.py +62 -2
- opik/rest_api/types/automation_rule_evaluator_llm_as_judge.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_llm_as_judge_public.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_llm_as_judge_write.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_object_object_public.py +155 -0
- opik/rest_api/types/automation_rule_evaluator_page_public.py +3 -2
- opik/rest_api/types/automation_rule_evaluator_public.py +57 -2
- opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_public.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_write.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python_public.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python_write.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_public.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_write.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_public.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_write.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_update.py +51 -1
- opik/rest_api/types/automation_rule_evaluator_update_llm_as_judge.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_update_span_llm_as_judge.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_update_span_user_defined_metric_python.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_update_trace_thread_llm_as_judge.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_update_trace_thread_user_defined_metric_python.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_update_user_defined_metric_python.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_public.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_write.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_write.py +51 -1
- opik/rest_api/types/boolean_feedback_definition.py +25 -0
- opik/rest_api/types/boolean_feedback_definition_create.py +20 -0
- opik/rest_api/types/boolean_feedback_definition_public.py +25 -0
- opik/rest_api/types/boolean_feedback_definition_update.py +20 -0
- opik/rest_api/types/boolean_feedback_detail.py +29 -0
- opik/rest_api/types/boolean_feedback_detail_create.py +29 -0
- opik/rest_api/types/boolean_feedback_detail_public.py +29 -0
- opik/rest_api/types/boolean_feedback_detail_update.py +29 -0
- opik/rest_api/types/dashboard_page_public.py +24 -0
- opik/rest_api/types/dashboard_public.py +30 -0
- opik/rest_api/types/dataset.py +4 -0
- opik/rest_api/types/dataset_expansion.py +42 -0
- opik/rest_api/types/dataset_expansion_response.py +39 -0
- opik/rest_api/types/dataset_item.py +2 -0
- opik/rest_api/types/dataset_item_changes_public.py +5 -0
- opik/rest_api/types/dataset_item_compare.py +2 -0
- opik/rest_api/types/dataset_item_filter.py +27 -0
- opik/rest_api/types/dataset_item_filter_operator.py +21 -0
- opik/rest_api/types/dataset_item_page_compare.py +5 -0
- opik/rest_api/types/dataset_item_page_public.py +5 -0
- opik/rest_api/types/dataset_item_public.py +2 -0
- opik/rest_api/types/dataset_item_update.py +39 -0
- opik/rest_api/types/dataset_item_write.py +1 -0
- opik/rest_api/types/dataset_public.py +4 -0
- opik/rest_api/types/dataset_public_status.py +5 -0
- opik/rest_api/types/dataset_status.py +5 -0
- opik/rest_api/types/dataset_version_diff.py +22 -0
- opik/rest_api/types/dataset_version_diff_stats.py +24 -0
- opik/rest_api/types/dataset_version_page_public.py +23 -0
- opik/rest_api/types/dataset_version_public.py +59 -0
- opik/rest_api/types/dataset_version_summary.py +46 -0
- opik/rest_api/types/dataset_version_summary_public.py +46 -0
- opik/rest_api/types/experiment.py +7 -2
- opik/rest_api/types/experiment_group_response.py +2 -0
- opik/rest_api/types/experiment_public.py +7 -2
- opik/rest_api/types/experiment_public_status.py +5 -0
- opik/rest_api/types/experiment_score.py +20 -0
- opik/rest_api/types/experiment_score_public.py +20 -0
- opik/rest_api/types/experiment_score_write.py +20 -0
- opik/rest_api/types/experiment_status.py +5 -0
- opik/rest_api/types/feedback.py +25 -1
- opik/rest_api/types/feedback_create.py +20 -1
- opik/rest_api/types/feedback_object_public.py +27 -1
- opik/rest_api/types/feedback_public.py +25 -1
- opik/rest_api/types/feedback_score_batch_item.py +2 -1
- opik/rest_api/types/feedback_score_batch_item_thread.py +2 -1
- opik/rest_api/types/feedback_score_public.py +4 -0
- opik/rest_api/types/feedback_update.py +20 -1
- opik/rest_api/types/group_content_with_aggregations.py +1 -0
- opik/rest_api/types/group_detail.py +19 -0
- opik/rest_api/types/group_details.py +20 -0
- opik/rest_api/types/guardrail.py +1 -0
- opik/rest_api/types/guardrail_write.py +1 -0
- opik/rest_api/types/ids_holder.py +19 -0
- opik/rest_api/types/image_url.py +20 -0
- opik/rest_api/types/image_url_public.py +20 -0
- opik/rest_api/types/image_url_write.py +20 -0
- opik/rest_api/types/llm_as_judge_message.py +5 -1
- opik/rest_api/types/llm_as_judge_message_content.py +26 -0
- opik/rest_api/types/llm_as_judge_message_content_public.py +26 -0
- opik/rest_api/types/llm_as_judge_message_content_write.py +26 -0
- opik/rest_api/types/llm_as_judge_message_public.py +5 -1
- opik/rest_api/types/llm_as_judge_message_write.py +5 -1
- opik/rest_api/types/llm_as_judge_model_parameters.py +3 -0
- opik/rest_api/types/llm_as_judge_model_parameters_public.py +3 -0
- opik/rest_api/types/llm_as_judge_model_parameters_write.py +3 -0
- opik/rest_api/types/manual_evaluation_request.py +38 -0
- opik/rest_api/types/manual_evaluation_request_entity_type.py +5 -0
- opik/rest_api/types/manual_evaluation_response.py +27 -0
- opik/rest_api/types/optimization.py +4 -2
- opik/rest_api/types/optimization_public.py +4 -2
- opik/rest_api/types/optimization_public_status.py +3 -1
- opik/rest_api/types/optimization_status.py +3 -1
- opik/rest_api/types/optimization_studio_config.py +27 -0
- opik/rest_api/types/optimization_studio_config_public.py +27 -0
- opik/rest_api/types/optimization_studio_config_write.py +27 -0
- opik/rest_api/types/optimization_studio_log.py +22 -0
- opik/rest_api/types/optimization_write.py +4 -2
- opik/rest_api/types/optimization_write_status.py +3 -1
- opik/rest_api/types/project.py +1 -0
- opik/rest_api/types/project_detailed.py +1 -0
- opik/rest_api/types/project_reference.py +31 -0
- opik/rest_api/types/project_reference_public.py +31 -0
- opik/rest_api/types/project_stats_summary_item.py +1 -0
- opik/rest_api/types/prompt.py +6 -0
- opik/rest_api/types/prompt_detail.py +6 -0
- opik/rest_api/types/prompt_detail_template_structure.py +5 -0
- opik/rest_api/types/prompt_public.py +6 -0
- opik/rest_api/types/prompt_public_template_structure.py +5 -0
- opik/rest_api/types/prompt_template_structure.py +5 -0
- opik/rest_api/types/prompt_version.py +3 -0
- opik/rest_api/types/prompt_version_detail.py +3 -0
- opik/rest_api/types/prompt_version_detail_template_structure.py +5 -0
- opik/rest_api/types/prompt_version_link.py +1 -0
- opik/rest_api/types/prompt_version_link_public.py +1 -0
- opik/rest_api/types/prompt_version_page_public.py +5 -0
- opik/rest_api/types/prompt_version_public.py +3 -0
- opik/rest_api/types/prompt_version_public_template_structure.py +5 -0
- opik/rest_api/types/prompt_version_template_structure.py +5 -0
- opik/rest_api/types/prompt_version_update.py +33 -0
- opik/rest_api/types/provider_api_key.py +9 -0
- opik/rest_api/types/provider_api_key_provider.py +1 -1
- opik/rest_api/types/provider_api_key_public.py +9 -0
- opik/rest_api/types/provider_api_key_public_provider.py +1 -1
- opik/rest_api/types/score_name.py +1 -0
- opik/rest_api/types/service_toggles_config.py +18 -0
- opik/rest_api/types/span.py +1 -2
- opik/rest_api/types/span_enrichment_options.py +31 -0
- opik/rest_api/types/span_experiment_item_bulk_write_view.py +1 -2
- opik/rest_api/types/span_filter.py +23 -0
- opik/rest_api/types/span_filter_operator.py +21 -0
- opik/rest_api/types/span_filter_write.py +23 -0
- opik/rest_api/types/span_filter_write_operator.py +21 -0
- opik/rest_api/types/span_llm_as_judge_code.py +27 -0
- opik/rest_api/types/span_llm_as_judge_code_public.py +27 -0
- opik/rest_api/types/span_llm_as_judge_code_write.py +27 -0
- opik/rest_api/types/span_public.py +1 -2
- opik/rest_api/types/span_update.py +46 -0
- opik/rest_api/types/span_user_defined_metric_python_code.py +20 -0
- opik/rest_api/types/span_user_defined_metric_python_code_public.py +20 -0
- opik/rest_api/types/span_user_defined_metric_python_code_write.py +20 -0
- opik/rest_api/types/span_write.py +1 -2
- opik/rest_api/types/studio_evaluation.py +20 -0
- opik/rest_api/types/studio_evaluation_public.py +20 -0
- opik/rest_api/types/studio_evaluation_write.py +20 -0
- opik/rest_api/types/studio_llm_model.py +21 -0
- opik/rest_api/types/studio_llm_model_public.py +21 -0
- opik/rest_api/types/studio_llm_model_write.py +21 -0
- opik/rest_api/types/studio_message.py +20 -0
- opik/rest_api/types/studio_message_public.py +20 -0
- opik/rest_api/types/studio_message_write.py +20 -0
- opik/rest_api/types/studio_metric.py +21 -0
- opik/rest_api/types/studio_metric_public.py +21 -0
- opik/rest_api/types/studio_metric_write.py +21 -0
- opik/rest_api/types/studio_optimizer.py +21 -0
- opik/rest_api/types/studio_optimizer_public.py +21 -0
- opik/rest_api/types/studio_optimizer_write.py +21 -0
- opik/rest_api/types/studio_prompt.py +20 -0
- opik/rest_api/types/studio_prompt_public.py +20 -0
- opik/rest_api/types/studio_prompt_write.py +20 -0
- opik/rest_api/types/trace.py +11 -2
- opik/rest_api/types/trace_enrichment_options.py +32 -0
- opik/rest_api/types/trace_experiment_item_bulk_write_view.py +1 -2
- opik/rest_api/types/trace_filter.py +23 -0
- opik/rest_api/types/trace_filter_operator.py +21 -0
- opik/rest_api/types/trace_filter_write.py +23 -0
- opik/rest_api/types/trace_filter_write_operator.py +21 -0
- opik/rest_api/types/trace_public.py +11 -2
- opik/rest_api/types/trace_thread_filter_write.py +23 -0
- opik/rest_api/types/trace_thread_filter_write_operator.py +21 -0
- opik/rest_api/types/trace_thread_identifier.py +1 -0
- opik/rest_api/types/trace_update.py +39 -0
- opik/rest_api/types/trace_write.py +1 -2
- opik/rest_api/types/value_entry.py +2 -0
- opik/rest_api/types/value_entry_compare.py +2 -0
- opik/rest_api/types/value_entry_experiment_item_bulk_write_view.py +2 -0
- opik/rest_api/types/value_entry_public.py +2 -0
- opik/rest_api/types/video_url.py +19 -0
- opik/rest_api/types/video_url_public.py +19 -0
- opik/rest_api/types/video_url_write.py +19 -0
- opik/rest_api/types/webhook.py +28 -0
- opik/rest_api/types/webhook_examples.py +19 -0
- opik/rest_api/types/webhook_public.py +28 -0
- opik/rest_api/types/webhook_test_result.py +23 -0
- opik/rest_api/types/webhook_test_result_status.py +5 -0
- opik/rest_api/types/webhook_write.py +23 -0
- opik/rest_api/types/welcome_wizard_tracking.py +22 -0
- opik/rest_api/types/workspace_configuration.py +5 -0
- opik/rest_api/welcome_wizard/__init__.py +4 -0
- opik/rest_api/welcome_wizard/client.py +195 -0
- opik/rest_api/welcome_wizard/raw_client.py +208 -0
- opik/rest_api/workspaces/client.py +14 -2
- opik/rest_api/workspaces/raw_client.py +10 -0
- opik/s3_httpx_client.py +14 -1
- opik/simulation/__init__.py +6 -0
- opik/simulation/simulated_user.py +99 -0
- opik/simulation/simulator.py +108 -0
- opik/synchronization.py +5 -6
- opik/{decorator/tracing_runtime_config.py → tracing_runtime_config.py} +6 -7
- opik/types.py +36 -0
- opik/validation/chat_prompt_messages.py +241 -0
- opik/validation/feedback_score.py +3 -3
- opik/validation/validator.py +28 -0
- opik-1.9.71.dist-info/METADATA +370 -0
- opik-1.9.71.dist-info/RECORD +1110 -0
- opik/api_objects/prompt/prompt.py +0 -112
- opik/cli.py +0 -193
- opik/hooks.py +0 -13
- opik/integrations/bedrock/chunks_aggregator.py +0 -55
- opik/integrations/bedrock/helpers.py +0 -8
- opik/rest_api/types/automation_rule_evaluator_object_public.py +0 -100
- opik/rest_api/types/json_node_experiment_item_bulk_write_view.py +0 -5
- opik-1.8.39.dist-info/METADATA +0 -339
- opik-1.8.39.dist-info/RECORD +0 -790
- /opik/{evaluation/metrics/conversation/conversational_coherence → decorator/context_manager}/__init__.py +0 -0
- /opik/evaluation/metrics/conversation/{session_completeness → llm_judges/conversational_coherence}/__init__.py +0 -0
- /opik/evaluation/metrics/conversation/{conversational_coherence → llm_judges/conversational_coherence}/schema.py +0 -0
- /opik/evaluation/metrics/conversation/{user_frustration → llm_judges/session_completeness}/__init__.py +0 -0
- /opik/evaluation/metrics/conversation/{session_completeness → llm_judges/session_completeness}/schema.py +0 -0
- /opik/evaluation/metrics/conversation/{user_frustration → llm_judges/user_frustration}/schema.py +0 -0
- /opik/integrations/bedrock/{stream_wrappers.py → converse/stream_wrappers.py} +0 -0
- /opik/rest_api/{spans/types → types}/span_update_type.py +0 -0
- {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/WHEEL +0 -0
- {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/entry_points.txt +0 -0
- {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/licenses/LICENSE +0 -0
- {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/top_level.txt +0 -0
|
@@ -1,31 +1,40 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import datetime
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
Dict,
|
|
6
|
+
List,
|
|
7
|
+
Literal,
|
|
8
|
+
Optional,
|
|
9
|
+
Set,
|
|
10
|
+
TYPE_CHECKING,
|
|
11
|
+
cast,
|
|
12
|
+
Callable,
|
|
13
|
+
NamedTuple,
|
|
14
|
+
)
|
|
4
15
|
import contextvars
|
|
16
|
+
from uuid import UUID
|
|
17
|
+
|
|
5
18
|
from langchain_core import language_models
|
|
6
19
|
from langchain_core.tracers import BaseTracer
|
|
7
20
|
from langchain_core.tracers.schemas import Run
|
|
8
21
|
|
|
9
|
-
import
|
|
10
|
-
import opik.llm_usage as llm_usage
|
|
22
|
+
from opik import context_storage, dict_utils, llm_usage, tracing_runtime_config
|
|
11
23
|
from opik.api_objects import span, trace
|
|
24
|
+
from opik.decorator import arguments_helpers, span_creation_handler
|
|
12
25
|
from opik.types import DistributedTraceHeadersDict, ErrorInfoDict
|
|
13
26
|
from opik.validation import parameters_validator
|
|
14
27
|
from . import (
|
|
15
28
|
base_llm_patcher,
|
|
29
|
+
helpers as langchain_helpers,
|
|
16
30
|
opik_encoder_extension,
|
|
17
31
|
provider_usage_extractors,
|
|
18
32
|
)
|
|
19
33
|
|
|
20
34
|
from ...api_objects import helpers, opik_client
|
|
21
|
-
import opik.context_storage as context_storage
|
|
22
|
-
import opik.decorator.tracing_runtime_config as tracing_runtime_config
|
|
23
35
|
|
|
24
36
|
if TYPE_CHECKING:
|
|
25
|
-
from uuid import UUID
|
|
26
|
-
|
|
27
37
|
from langchain_core.runnables.graph import Graph
|
|
28
|
-
|
|
29
38
|
from langchain_core.messages import BaseMessage
|
|
30
39
|
|
|
31
40
|
LOGGER = logging.getLogger(__name__)
|
|
@@ -36,6 +45,20 @@ language_models.BaseLLM.dict = base_llm_patcher.base_llm_dict_patched()
|
|
|
36
45
|
|
|
37
46
|
SpanType = Literal["llm", "tool", "general"]
|
|
38
47
|
|
|
48
|
+
# A callable that receives an error string and returns True if the error should be skipped,
|
|
49
|
+
# or False otherwise.
|
|
50
|
+
SkipErrorCallback = Callable[[str], bool]
|
|
51
|
+
|
|
52
|
+
# Placeholder output dictionary used when errors are intentionally skipped
|
|
53
|
+
# via the skip_error_callback. This signals that the output was not produced
|
|
54
|
+
# due to a handled/ignored error during execution.
|
|
55
|
+
ERROR_SKIPPED_OUTPUTS = {"warning": "Error output skipped by skip_error_callback."}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TrackRootRunResult(NamedTuple):
|
|
59
|
+
new_trace_data: Optional[trace.TraceData]
|
|
60
|
+
new_span_data: Optional[span.SpanData]
|
|
61
|
+
|
|
39
62
|
|
|
40
63
|
def _get_span_type(run: Dict[str, Any]) -> SpanType:
|
|
41
64
|
if run.get("run_type") in ["llm", "tool"]:
|
|
@@ -47,15 +70,16 @@ def _get_span_type(run: Dict[str, Any]) -> SpanType:
|
|
|
47
70
|
return cast(SpanType, "general")
|
|
48
71
|
|
|
49
72
|
|
|
50
|
-
|
|
51
|
-
""
|
|
73
|
+
def _is_root_run(run_dict: Dict[str, Any]) -> bool:
|
|
74
|
+
return run_dict.get("parent_run_id") is None
|
|
52
75
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
76
|
+
|
|
77
|
+
def _get_run_metadata(run_dict: Dict[str, Any]) -> Dict[str, Any]:
|
|
78
|
+
return run_dict["extra"].get("metadata", {})
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class OpikTracer(BaseTracer):
|
|
82
|
+
"""Langchain Opik Tracer."""
|
|
59
83
|
|
|
60
84
|
def __init__(
|
|
61
85
|
self,
|
|
@@ -65,8 +89,35 @@ class OpikTracer(BaseTracer):
|
|
|
65
89
|
project_name: Optional[str] = None,
|
|
66
90
|
distributed_headers: Optional[DistributedTraceHeadersDict] = None,
|
|
67
91
|
thread_id: Optional[str] = None,
|
|
92
|
+
skip_error_callback: Optional[SkipErrorCallback] = None,
|
|
93
|
+
opik_context_read_only_mode: bool = False,
|
|
68
94
|
**kwargs: Any,
|
|
69
95
|
) -> None:
|
|
96
|
+
"""
|
|
97
|
+
Initializes an instance of the class with various parameters for traces, metadata, and project configuration.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
tags: List of tags associated with logged traces.
|
|
101
|
+
metadata: Dictionary containing metadata information for logged traces.
|
|
102
|
+
graph: A LangGraph Graph object for representing dependencies or flow
|
|
103
|
+
to track the Graph Definition in Opik.
|
|
104
|
+
project_name: Name of the project associated with the traces.
|
|
105
|
+
distributed_headers: Headers for distributed tracing context.
|
|
106
|
+
thread_id: Unique identifier for the conversational thread
|
|
107
|
+
to be associated with traces.
|
|
108
|
+
skip_error_callback : Callback function to handle skip errors logic.
|
|
109
|
+
Allows defining custom logic for handling errors that are intentionally skipped.
|
|
110
|
+
Please note that in traces/spans where errors are intentionally skipped,
|
|
111
|
+
the output will be replaced with `ERROR_SKIPPED_OUTPUTS`. You can provide
|
|
112
|
+
the output manually using `opik_context.get_current_span_data().update(output=...)`.
|
|
113
|
+
opik_context_read_only_mode: Whether to adding/popping spans/traces to/from the context storage.
|
|
114
|
+
* If False (default), OpikTracer will add created spans/traces to the opik context, so if there is a @track-decorated
|
|
115
|
+
function called inside the LangChain runnable, it will be attached to it's parent span from LangChain automatically.
|
|
116
|
+
* If True, OpikTracer will not modify the context storage and only create spans/traces from LangChain's Run objects.
|
|
117
|
+
This might be useful when the environment doesn't support proper context isolation for concurrent operations and you
|
|
118
|
+
want to avoid modifying the Opik context stack due to unsafety.
|
|
119
|
+
**kwargs: Additional arguments passed to the parent class constructor.
|
|
120
|
+
"""
|
|
70
121
|
validator = parameters_validator.create_validator(
|
|
71
122
|
method_name="__init__", class_name=self.__class__.__name__
|
|
72
123
|
)
|
|
@@ -82,23 +133,26 @@ class OpikTracer(BaseTracer):
|
|
|
82
133
|
self._trace_default_metadata["created_from"] = "langchain"
|
|
83
134
|
|
|
84
135
|
if graph:
|
|
85
|
-
self.
|
|
86
|
-
"format": "mermaid",
|
|
87
|
-
"data": graph.draw_mermaid(),
|
|
88
|
-
}
|
|
136
|
+
self.set_graph(graph)
|
|
89
137
|
|
|
90
138
|
self._trace_default_tags = tags
|
|
91
139
|
|
|
92
|
-
self._span_data_map: Dict[
|
|
140
|
+
self._span_data_map: Dict[UUID, span.SpanData] = {}
|
|
93
141
|
"""Map from run id to span data."""
|
|
94
142
|
|
|
95
|
-
self._created_traces_data_map: Dict[
|
|
143
|
+
self._created_traces_data_map: Dict[UUID, trace.TraceData] = {}
|
|
96
144
|
"""Map from run id to trace data."""
|
|
97
145
|
|
|
98
146
|
self._created_traces: List[trace.Trace] = []
|
|
99
147
|
|
|
100
148
|
self._externally_created_traces_ids: Set[str] = set()
|
|
101
149
|
|
|
150
|
+
self._skipped_langgraph_root_run_ids: Set[UUID] = set()
|
|
151
|
+
"""Set of run IDs for LangGraph root runs where we skip creating the span."""
|
|
152
|
+
|
|
153
|
+
self._langgraph_parent_span_ids: Dict[UUID, Optional[str]] = {}
|
|
154
|
+
"""Map from LangGraph root run ID to parent span ID (None if attached to trace)."""
|
|
155
|
+
|
|
102
156
|
self._project_name = project_name
|
|
103
157
|
|
|
104
158
|
self._distributed_headers = distributed_headers
|
|
@@ -113,54 +167,100 @@ class OpikTracer(BaseTracer):
|
|
|
113
167
|
Optional[str]
|
|
114
168
|
] = contextvars.ContextVar("root_run_external_parent_span_id", default=None)
|
|
115
169
|
|
|
170
|
+
self._skip_error_callback = skip_error_callback
|
|
171
|
+
|
|
172
|
+
self._opik_context_read_only_mode = opik_context_read_only_mode
|
|
173
|
+
|
|
174
|
+
def set_graph(self, graph: "Graph") -> None:
|
|
175
|
+
"""
|
|
176
|
+
Set the LangGraph graph structure for visualization in Opik traces.
|
|
177
|
+
|
|
178
|
+
This method extracts the graph structure and stores it in trace metadata,
|
|
179
|
+
allowing the graph to be visualized in the Opik UI.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
graph: A LangGraph Graph object (typically obtained via graph.get_graph(xray=True)).
|
|
183
|
+
"""
|
|
184
|
+
self._trace_default_metadata["_opik_graph_definition"] = {
|
|
185
|
+
"format": "mermaid",
|
|
186
|
+
"data": graph.draw_mermaid(),
|
|
187
|
+
}
|
|
188
|
+
|
|
116
189
|
def _is_opik_span_created_by_this_tracer(self, span_id: str) -> bool:
|
|
117
|
-
return any(
|
|
190
|
+
return any(span_.id == span_id for span_ in self._span_data_map.values())
|
|
118
191
|
|
|
119
192
|
def _is_opik_trace_created_by_this_tracer(self, trace_id: str) -> bool:
|
|
120
193
|
return any(
|
|
121
|
-
|
|
194
|
+
trace_.id == trace_id for trace_ in self._created_traces_data_map.values()
|
|
122
195
|
)
|
|
123
196
|
|
|
124
|
-
def _persist_run(self, run:
|
|
197
|
+
def _persist_run(self, run: Run) -> None:
|
|
125
198
|
run_dict: Dict[str, Any] = run.dict()
|
|
126
199
|
|
|
127
200
|
error_info: Optional[ErrorInfoDict]
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
201
|
+
trace_additional_metadata: Dict[str, Any] = {}
|
|
202
|
+
|
|
203
|
+
error_str = run_dict.get("error")
|
|
204
|
+
outputs = None
|
|
205
|
+
error_info = None
|
|
206
|
+
|
|
207
|
+
if error_str is not None:
|
|
208
|
+
if not self._should_skip_error(error_str):
|
|
209
|
+
error_info = ErrorInfoDict(
|
|
210
|
+
exception_type="Exception",
|
|
211
|
+
traceback=error_str,
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
outputs = ERROR_SKIPPED_OUTPUTS
|
|
215
|
+
elif (outputs := run_dict.get("outputs")) is not None:
|
|
216
|
+
outputs, trace_additional_metadata = (
|
|
217
|
+
langchain_helpers.split_big_langgraph_outputs(outputs)
|
|
218
|
+
)
|
|
137
219
|
|
|
138
|
-
|
|
220
|
+
if not self._opik_context_read_only_mode:
|
|
221
|
+
self._ensure_no_hanging_opik_tracer_spans()
|
|
139
222
|
|
|
223
|
+
span_data = self._span_data_map.get(run.id)
|
|
140
224
|
if (
|
|
141
|
-
span_data
|
|
142
|
-
|
|
225
|
+
span_data is None
|
|
226
|
+
or span_data.trace_id not in self._externally_created_traces_ids
|
|
143
227
|
):
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
228
|
+
self._finalize_trace(
|
|
229
|
+
run_id=run.id,
|
|
230
|
+
run_dict=run_dict,
|
|
231
|
+
trace_additional_metadata=trace_additional_metadata,
|
|
232
|
+
outputs=outputs,
|
|
233
|
+
error_info=error_info,
|
|
234
|
+
)
|
|
149
235
|
|
|
150
|
-
|
|
236
|
+
def _finalize_trace(
|
|
237
|
+
self,
|
|
238
|
+
run_id: UUID,
|
|
239
|
+
run_dict: Dict[str, Any],
|
|
240
|
+
trace_additional_metadata: Optional[Dict[str, Any]],
|
|
241
|
+
outputs: Optional[Dict[str, Any]],
|
|
242
|
+
error_info: Optional[ErrorInfoDict],
|
|
243
|
+
) -> None:
|
|
244
|
+
trace_data = self._created_traces_data_map.get(run_id)
|
|
245
|
+
if trace_data is None:
|
|
246
|
+
LOGGER.warning(
|
|
247
|
+
f"Trace data for run '{run_id}' not found in the traces data map. Skipping processing of _finalize_trace."
|
|
248
|
+
)
|
|
249
|
+
return
|
|
151
250
|
|
|
152
|
-
|
|
153
|
-
|
|
251
|
+
# workaround for `.astream()` method usage
|
|
252
|
+
if trace_data.input == {"input": ""}:
|
|
253
|
+
trace_data.input = run_dict["inputs"]
|
|
154
254
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
trace_data.input = run_dict["inputs"]
|
|
255
|
+
if trace_additional_metadata:
|
|
256
|
+
trace_data.update(metadata=trace_additional_metadata)
|
|
158
257
|
|
|
159
|
-
|
|
160
|
-
|
|
258
|
+
trace_data.init_end_time().update(output=outputs, error_info=error_info)
|
|
259
|
+
trace_ = self._opik_client.trace(**trace_data.as_parameters)
|
|
161
260
|
|
|
162
|
-
|
|
163
|
-
|
|
261
|
+
assert trace_ is not None
|
|
262
|
+
self._created_traces.append(trace_)
|
|
263
|
+
if not self._opik_context_read_only_mode:
|
|
164
264
|
self._opik_context_storage.pop_trace_data(ensure_id=trace_data.id)
|
|
165
265
|
|
|
166
266
|
def _ensure_no_hanging_opik_tracer_spans(self) -> None:
|
|
@@ -178,173 +278,170 @@ class OpikTracer(BaseTracer):
|
|
|
178
278
|
)
|
|
179
279
|
|
|
180
280
|
def _track_root_run(
|
|
181
|
-
self, run_dict: Dict[str, Any]
|
|
182
|
-
) ->
|
|
183
|
-
run_metadata = run_dict
|
|
281
|
+
self, run_dict: Dict[str, Any], allow_duplicating_root_span: bool
|
|
282
|
+
) -> TrackRootRunResult:
|
|
283
|
+
run_metadata = _get_run_metadata(run_dict)
|
|
184
284
|
root_metadata = dict_utils.deepmerge(self._trace_default_metadata, run_metadata)
|
|
185
285
|
self._update_thread_id_from_metadata(run_dict)
|
|
186
286
|
|
|
187
|
-
|
|
188
|
-
new_span_data = self._attach_span_to_distributed_headers(
|
|
189
|
-
run_dict=run_dict,
|
|
190
|
-
root_metadata=root_metadata,
|
|
191
|
-
)
|
|
192
|
-
return None, new_span_data
|
|
193
|
-
|
|
287
|
+
# Track the parent span ID for LangGraph cleanup later
|
|
194
288
|
current_span_data = self._opik_context_storage.top_span_data()
|
|
195
|
-
|
|
289
|
+
parent_span_id_when_langgraph_started = (
|
|
196
290
|
current_span_data.id if current_span_data is not None else None
|
|
197
291
|
)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
run_dict=run_dict,
|
|
201
|
-
current_span_data=current_span_data,
|
|
202
|
-
root_metadata=root_metadata,
|
|
203
|
-
)
|
|
204
|
-
return None, new_span_data
|
|
205
|
-
|
|
206
|
-
current_trace_data = self._opik_context_storage.get_trace_data()
|
|
207
|
-
if current_trace_data is not None:
|
|
208
|
-
new_span_data = self._attach_span_to_existing_trace(
|
|
209
|
-
run_dict=run_dict,
|
|
210
|
-
current_trace_data=current_trace_data,
|
|
211
|
-
root_metadata=root_metadata,
|
|
212
|
-
)
|
|
213
|
-
return None, new_span_data
|
|
214
|
-
|
|
215
|
-
return self._initialize_span_and_trace_from_scratch(
|
|
216
|
-
run_dict=run_dict, root_metadata=root_metadata
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
def _initialize_span_and_trace_from_scratch(
|
|
220
|
-
self, run_dict: Dict[str, Any], root_metadata: Dict[str, Any]
|
|
221
|
-
) -> Tuple[trace.TraceData, span.SpanData]:
|
|
222
|
-
trace_data = trace.TraceData(
|
|
223
|
-
name=run_dict["name"],
|
|
224
|
-
input=run_dict["inputs"],
|
|
225
|
-
metadata=root_metadata,
|
|
226
|
-
tags=self._trace_default_tags,
|
|
227
|
-
project_name=self._project_name,
|
|
228
|
-
thread_id=self._thread_id,
|
|
292
|
+
self._root_run_external_parent_span_id.set(
|
|
293
|
+
parent_span_id_when_langgraph_started
|
|
229
294
|
)
|
|
230
295
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
span_data = span.SpanData(
|
|
234
|
-
trace_id=trace_data.id,
|
|
235
|
-
parent_span_id=None,
|
|
296
|
+
start_span_arguments = arguments_helpers.StartSpanParameters(
|
|
236
297
|
name=run_dict["name"],
|
|
237
298
|
input=run_dict["inputs"],
|
|
238
299
|
type=_get_span_type(run_dict),
|
|
239
|
-
metadata=root_metadata,
|
|
240
300
|
tags=self._trace_default_tags,
|
|
301
|
+
metadata=root_metadata,
|
|
241
302
|
project_name=self._project_name,
|
|
303
|
+
thread_id=self._thread_id,
|
|
242
304
|
)
|
|
243
305
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def _attach_span_to_existing_span(
|
|
249
|
-
self,
|
|
250
|
-
run_dict: Dict[str, Any],
|
|
251
|
-
current_span_data: span.SpanData,
|
|
252
|
-
root_metadata: Dict[str, Any],
|
|
253
|
-
) -> span.SpanData:
|
|
254
|
-
project_name = helpers.resolve_child_span_project_name(
|
|
255
|
-
current_span_data.project_name,
|
|
256
|
-
self._project_name,
|
|
306
|
+
span_creation_result = span_creation_handler.create_span_respecting_context(
|
|
307
|
+
start_span_arguments=start_span_arguments,
|
|
308
|
+
distributed_trace_headers=self._distributed_headers,
|
|
309
|
+
opik_context_storage=self._opik_context_storage,
|
|
257
310
|
)
|
|
258
311
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
metadata=root_metadata,
|
|
265
|
-
tags=self._trace_default_tags,
|
|
266
|
-
project_name=project_name,
|
|
267
|
-
type=_get_span_type(run_dict),
|
|
312
|
+
trace_created_externally = (
|
|
313
|
+
span_creation_result.trace_data is None
|
|
314
|
+
and not self._is_opik_trace_created_by_this_tracer(
|
|
315
|
+
span_creation_result.span_data.trace_id
|
|
316
|
+
)
|
|
268
317
|
)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
return span_data
|
|
318
|
+
if trace_created_externally:
|
|
319
|
+
self._externally_created_traces_ids.add(
|
|
320
|
+
span_creation_result.span_data.trace_id
|
|
321
|
+
)
|
|
274
322
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
root_metadata: Dict[str, Any],
|
|
280
|
-
) -> span.SpanData:
|
|
281
|
-
project_name = helpers.resolve_child_span_project_name(
|
|
282
|
-
current_trace_data.project_name,
|
|
283
|
-
self._project_name,
|
|
323
|
+
should_skip_root_span_creation = (
|
|
324
|
+
span_creation_result.trace_data is not None
|
|
325
|
+
and _is_root_run(run_dict)
|
|
326
|
+
and not allow_duplicating_root_span
|
|
284
327
|
)
|
|
328
|
+
if should_skip_root_span_creation:
|
|
329
|
+
return TrackRootRunResult(
|
|
330
|
+
new_trace_data=span_creation_result.trace_data,
|
|
331
|
+
new_span_data=None,
|
|
332
|
+
)
|
|
285
333
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
name=run_dict["name"],
|
|
290
|
-
input=run_dict["inputs"],
|
|
291
|
-
metadata=root_metadata,
|
|
292
|
-
tags=self._trace_default_tags,
|
|
293
|
-
project_name=project_name,
|
|
294
|
-
type=_get_span_type(run_dict),
|
|
334
|
+
return TrackRootRunResult(
|
|
335
|
+
new_trace_data=span_creation_result.trace_data,
|
|
336
|
+
new_span_data=span_creation_result.span_data,
|
|
295
337
|
)
|
|
296
|
-
self._span_data_map[run_dict["id"]] = span_data
|
|
297
|
-
if not self._is_opik_trace_created_by_this_tracer(current_trace_data.id):
|
|
298
|
-
self._externally_created_traces_ids.add(current_trace_data.id)
|
|
299
|
-
return span_data
|
|
300
338
|
|
|
301
|
-
def
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if self._distributed_headers is None:
|
|
307
|
-
raise ValueError("Distributed headers are not set")
|
|
308
|
-
|
|
309
|
-
span_data = span.SpanData(
|
|
310
|
-
trace_id=self._distributed_headers["opik_trace_id"],
|
|
311
|
-
parent_span_id=self._distributed_headers["opik_parent_span_id"],
|
|
312
|
-
name=run_dict["name"],
|
|
313
|
-
input=run_dict["inputs"],
|
|
314
|
-
metadata=root_metadata,
|
|
315
|
-
tags=self._trace_default_tags,
|
|
316
|
-
project_name=self._project_name,
|
|
317
|
-
type=_get_span_type(run_dict),
|
|
318
|
-
)
|
|
319
|
-
self._span_data_map[run_dict["id"]] = span_data
|
|
320
|
-
self._externally_created_traces_ids.add(span_data.trace_id)
|
|
321
|
-
return span_data
|
|
339
|
+
def _process_start_span(self, run: Run, allow_duplicating_root_span: bool) -> None:
|
|
340
|
+
try:
|
|
341
|
+
self._process_start_span_unsafe(run, allow_duplicating_root_span)
|
|
342
|
+
except Exception as e:
|
|
343
|
+
LOGGER.error("Failed during _process_start_span: %s", e, exc_info=True)
|
|
322
344
|
|
|
323
|
-
def
|
|
345
|
+
def _process_start_span_unsafe(
|
|
346
|
+
self, run: Run, allow_duplicating_root_span: bool
|
|
347
|
+
) -> None:
|
|
324
348
|
run_dict: Dict[str, Any] = run.dict()
|
|
325
|
-
new_span_data: span.SpanData
|
|
326
|
-
new_trace_data: Optional[trace.TraceData] = None
|
|
327
349
|
|
|
328
350
|
if not run.parent_run_id:
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
351
|
+
self._create_root_trace_and_span(
|
|
352
|
+
run_id=run.id,
|
|
353
|
+
run_dict=run_dict,
|
|
354
|
+
allow_duplicating_root_span=allow_duplicating_root_span,
|
|
355
|
+
)
|
|
356
|
+
return
|
|
357
|
+
|
|
358
|
+
# Check if the parent is a skipped LangGraph/LangChain root run.
|
|
359
|
+
# If so, attach children directly to trace.
|
|
360
|
+
# Otherwise, attach to the parent span.
|
|
361
|
+
if run.parent_run_id in self._skipped_langgraph_root_run_ids:
|
|
362
|
+
self._attach_span_to_local_or_distributed_trace(
|
|
363
|
+
run_id=run.id,
|
|
364
|
+
parent_run_id=run.parent_run_id,
|
|
365
|
+
run_dict=run_dict,
|
|
366
|
+
)
|
|
367
|
+
else:
|
|
368
|
+
self._attach_span_to_parent_span(
|
|
369
|
+
run_id=run.id, parent_run_id=run.parent_run_id, run_dict=run_dict
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
def _create_root_trace_and_span(
|
|
373
|
+
self, run_id: UUID, run_dict: Dict[str, Any], allow_duplicating_root_span: bool
|
|
374
|
+
) -> None:
|
|
375
|
+
"""
|
|
376
|
+
Creates a root trace and span for a given run and stores the relevant trace and span
|
|
377
|
+
data in local storage for future reference.
|
|
378
|
+
|
|
379
|
+
The new span is only created if no new trace is created, i.e., when attached to an existing span
|
|
380
|
+
or distributed headers. If a new trace is created, the span is skipped and only the
|
|
381
|
+
trace data is stored in local storage for future reference.
|
|
382
|
+
"""
|
|
383
|
+
# This is the first run for the chain.
|
|
384
|
+
root_run_result = self._track_root_run(run_dict, allow_duplicating_root_span)
|
|
385
|
+
if root_run_result.new_trace_data is not None:
|
|
386
|
+
if not self._opik_context_read_only_mode:
|
|
387
|
+
self._opik_context_storage.set_trace_data(
|
|
388
|
+
root_run_result.new_trace_data
|
|
389
|
+
)
|
|
338
390
|
|
|
339
|
-
self._opik_context_storage.add_span_data(new_span_data)
|
|
340
391
|
if (
|
|
341
392
|
self._opik_client.config.log_start_trace_span
|
|
342
393
|
and tracing_runtime_config.is_tracing_active()
|
|
343
394
|
):
|
|
344
|
-
self._opik_client.
|
|
345
|
-
|
|
395
|
+
self._opik_client.trace(
|
|
396
|
+
**root_run_result.new_trace_data.as_start_parameters
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# If this is a LangGraph/LangChain root run under fresh trace, skip creating the span
|
|
400
|
+
if root_run_result.new_span_data is None:
|
|
401
|
+
# Mark this run as skipped and store trace data for child runs
|
|
402
|
+
self._skipped_langgraph_root_run_ids.add(run_id)
|
|
403
|
+
|
|
404
|
+
# Store parent span ID if LangGraph was attached to the existing span
|
|
405
|
+
parent_span_id = self._root_run_external_parent_span_id.get()
|
|
406
|
+
self._langgraph_parent_span_ids[run_id] = parent_span_id
|
|
407
|
+
|
|
408
|
+
# Store trace data if we created a new trace but skip span data
|
|
409
|
+
if root_run_result.new_trace_data is not None:
|
|
410
|
+
self._save_span_trace_data_to_local_maps(
|
|
411
|
+
run_id=run_id,
|
|
412
|
+
span_data=None,
|
|
413
|
+
trace_data=root_run_result.new_trace_data,
|
|
414
|
+
)
|
|
415
|
+
else:
|
|
416
|
+
# save new span and trace data to local maps to be able to retrieve them later
|
|
417
|
+
self._save_span_trace_data_to_local_maps(
|
|
418
|
+
run_id=run_id,
|
|
419
|
+
span_data=root_run_result.new_span_data,
|
|
420
|
+
trace_data=root_run_result.new_trace_data,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
if not self._opik_context_read_only_mode:
|
|
424
|
+
self._opik_context_storage.add_span_data(root_run_result.new_span_data)
|
|
425
|
+
|
|
426
|
+
if (
|
|
427
|
+
self._opik_client.config.log_start_trace_span
|
|
428
|
+
and tracing_runtime_config.is_tracing_active()
|
|
429
|
+
):
|
|
430
|
+
self._opik_client.span(
|
|
431
|
+
**root_run_result.new_span_data.as_start_parameters
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def _attach_span_to_parent_span(
|
|
435
|
+
self, run_id: UUID, parent_run_id: UUID, run_dict: Dict[str, Any]
|
|
436
|
+
) -> None:
|
|
437
|
+
"""
|
|
438
|
+
Attaches child span to a parent span and updates relevant context storage.
|
|
346
439
|
|
|
347
|
-
|
|
440
|
+
This method is responsible for creating a new span data object associated with a
|
|
441
|
+
run, linking it to the parent span data, and saving it to local and external maps.
|
|
442
|
+
Additionally, it updates the context storage and logs the span if tracing is active.
|
|
443
|
+
"""
|
|
444
|
+
parent_span_data = self._span_data_map[parent_run_id]
|
|
348
445
|
|
|
349
446
|
project_name = helpers.resolve_child_span_project_name(
|
|
350
447
|
parent_span_data.project_name,
|
|
@@ -355,31 +452,133 @@ class OpikTracer(BaseTracer):
|
|
|
355
452
|
trace_id=parent_span_data.trace_id,
|
|
356
453
|
parent_span_id=parent_span_data.id,
|
|
357
454
|
input=run_dict["inputs"],
|
|
358
|
-
metadata=run_dict
|
|
359
|
-
name=
|
|
455
|
+
metadata=_get_run_metadata(run_dict),
|
|
456
|
+
name=run_dict["name"],
|
|
360
457
|
type=_get_span_type(run_dict),
|
|
361
458
|
project_name=project_name,
|
|
362
459
|
)
|
|
363
460
|
new_span_data.update(metadata={"created_from": "langchain"})
|
|
364
461
|
|
|
365
|
-
self.
|
|
462
|
+
self._save_span_trace_data_to_local_maps(
|
|
463
|
+
run_id=run_id,
|
|
464
|
+
span_data=new_span_data,
|
|
465
|
+
trace_data=None,
|
|
466
|
+
)
|
|
366
467
|
|
|
367
468
|
if new_span_data.trace_id not in self._externally_created_traces_ids:
|
|
368
|
-
self._created_traces_data_map[
|
|
369
|
-
|
|
469
|
+
self._created_traces_data_map[run_id] = self._created_traces_data_map[
|
|
470
|
+
parent_run_id
|
|
370
471
|
]
|
|
371
472
|
|
|
372
|
-
self.
|
|
473
|
+
if not self._opik_context_read_only_mode:
|
|
474
|
+
self._opik_context_storage.add_span_data(new_span_data)
|
|
475
|
+
|
|
373
476
|
if (
|
|
374
477
|
self._opik_client.config.log_start_trace_span
|
|
375
478
|
and tracing_runtime_config.is_tracing_active()
|
|
376
479
|
):
|
|
377
480
|
self._opik_client.span(**new_span_data.as_start_parameters)
|
|
378
481
|
|
|
379
|
-
def
|
|
482
|
+
def _attach_span_to_local_or_distributed_trace(
|
|
483
|
+
self, run_id: UUID, parent_run_id: UUID, run_dict: Dict[str, Any]
|
|
484
|
+
) -> None:
|
|
485
|
+
"""
|
|
486
|
+
Attaches child span directly to a trace by checking trace data or distributed
|
|
487
|
+
headers and creates new span data based on the provided run information.
|
|
488
|
+
"""
|
|
489
|
+
# Check if we have trace data (new trace) or distributed headers
|
|
490
|
+
if parent_run_id in self._created_traces_data_map:
|
|
491
|
+
# LangGraph created a new trace - attach children directly to trace
|
|
492
|
+
trace_data = self._created_traces_data_map[parent_run_id]
|
|
493
|
+
project_name = helpers.resolve_child_span_project_name(
|
|
494
|
+
trace_data.project_name,
|
|
495
|
+
self._project_name,
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
new_span_data = span.SpanData(
|
|
499
|
+
trace_id=trace_data.id,
|
|
500
|
+
parent_span_id=None, # Direct child of trace
|
|
501
|
+
input=run_dict["inputs"],
|
|
502
|
+
metadata=_get_run_metadata(run_dict),
|
|
503
|
+
name=run_dict["name"],
|
|
504
|
+
type=_get_span_type(run_dict),
|
|
505
|
+
project_name=project_name,
|
|
506
|
+
)
|
|
507
|
+
if new_span_data.trace_id not in self._externally_created_traces_ids:
|
|
508
|
+
self._created_traces_data_map[run_id] = trace_data
|
|
509
|
+
|
|
510
|
+
elif self._distributed_headers:
|
|
511
|
+
# LangGraph with distributed headers - attach to distributed trace
|
|
512
|
+
new_span_data = span.SpanData(
|
|
513
|
+
trace_id=self._distributed_headers["opik_trace_id"],
|
|
514
|
+
parent_span_id=self._distributed_headers["opik_parent_span_id"],
|
|
515
|
+
name=run_dict["name"],
|
|
516
|
+
input=run_dict["inputs"],
|
|
517
|
+
metadata=_get_run_metadata(run_dict),
|
|
518
|
+
tags=self._trace_default_tags,
|
|
519
|
+
project_name=self._project_name,
|
|
520
|
+
type=_get_span_type(run_dict),
|
|
521
|
+
)
|
|
522
|
+
self._externally_created_traces_ids.add(new_span_data.trace_id)
|
|
523
|
+
|
|
524
|
+
elif (
|
|
525
|
+
current_trace_data := self._opik_context_storage.get_trace_data()
|
|
526
|
+
) is not None:
|
|
527
|
+
# LangGraph attached to existing trace - attach children directly to trace
|
|
528
|
+
project_name = helpers.resolve_child_span_project_name(
|
|
529
|
+
current_trace_data.project_name,
|
|
530
|
+
self._project_name,
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
new_span_data = span.SpanData(
|
|
534
|
+
trace_id=current_trace_data.id,
|
|
535
|
+
parent_span_id=None,
|
|
536
|
+
name=run_dict["name"],
|
|
537
|
+
input=run_dict["inputs"],
|
|
538
|
+
metadata=_get_run_metadata(run_dict),
|
|
539
|
+
tags=self._trace_default_tags,
|
|
540
|
+
project_name=project_name,
|
|
541
|
+
type=_get_span_type(run_dict),
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
if not self._is_opik_trace_created_by_this_tracer(current_trace_data.id):
|
|
545
|
+
self._externally_created_traces_ids.add(current_trace_data.id)
|
|
546
|
+
else:
|
|
547
|
+
LOGGER.warning(
|
|
548
|
+
f"Cannot find trace data or distributed headers for LangGraph child run '{run_id}'"
|
|
549
|
+
)
|
|
550
|
+
return
|
|
551
|
+
|
|
552
|
+
new_span_data.update(metadata={"created_from": "langchain"})
|
|
553
|
+
self._save_span_trace_data_to_local_maps(
|
|
554
|
+
run_id=run_id,
|
|
555
|
+
span_data=new_span_data,
|
|
556
|
+
trace_data=None,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
if not self._opik_context_read_only_mode:
|
|
560
|
+
self._opik_context_storage.add_span_data(new_span_data)
|
|
561
|
+
|
|
562
|
+
if (
|
|
563
|
+
self._opik_client.config.log_start_trace_span
|
|
564
|
+
and tracing_runtime_config.is_tracing_active()
|
|
565
|
+
):
|
|
566
|
+
self._opik_client.span(**new_span_data.as_start_parameters)
|
|
567
|
+
|
|
568
|
+
def _process_end_span(self, run: Run) -> None:
|
|
569
|
+
span_data = None
|
|
380
570
|
try:
|
|
381
|
-
|
|
571
|
+
# Skip processing if this is a skipped LangGraph root run
|
|
572
|
+
if run.id in self._skipped_langgraph_root_run_ids:
|
|
573
|
+
return
|
|
574
|
+
|
|
575
|
+
if run.id not in self._span_data_map:
|
|
576
|
+
LOGGER.warning(
|
|
577
|
+
f"Span data for run '{run.id}' not found in the span data map. Skipping processing of end span."
|
|
578
|
+
)
|
|
579
|
+
return
|
|
382
580
|
span_data = self._span_data_map[run.id]
|
|
581
|
+
run_dict: Dict[str, Any] = run.dict()
|
|
383
582
|
|
|
384
583
|
usage_info = provider_usage_extractors.try_extract_provider_usage_data(
|
|
385
584
|
run_dict
|
|
@@ -391,8 +590,15 @@ class OpikTracer(BaseTracer):
|
|
|
391
590
|
if span_data.input == {"input": ""}:
|
|
392
591
|
span_data.input = run_dict["inputs"]
|
|
393
592
|
|
|
593
|
+
filtered_output, additional_metadata = (
|
|
594
|
+
langchain_helpers.split_big_langgraph_outputs(run_dict["outputs"])
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
if additional_metadata:
|
|
598
|
+
span_data.update(metadata=additional_metadata)
|
|
599
|
+
|
|
394
600
|
span_data.init_end_time().update(
|
|
395
|
-
output=
|
|
601
|
+
output=filtered_output,
|
|
396
602
|
usage=(
|
|
397
603
|
usage_info.usage.provider_usage.model_dump()
|
|
398
604
|
if isinstance(usage_info.usage, llm_usage.OpikUsage)
|
|
@@ -407,42 +613,78 @@ class OpikTracer(BaseTracer):
|
|
|
407
613
|
except Exception as e:
|
|
408
614
|
LOGGER.error(f"Failed during _process_end_span: {e}", exc_info=True)
|
|
409
615
|
finally:
|
|
410
|
-
self.
|
|
411
|
-
|
|
616
|
+
if span_data is not None and not self._opik_context_read_only_mode:
|
|
617
|
+
self._opik_context_storage.trim_span_data_stack_to_certain_span(
|
|
618
|
+
span_id=span_data.id
|
|
619
|
+
)
|
|
620
|
+
self._opik_context_storage.pop_span_data(ensure_id=span_data.id)
|
|
621
|
+
|
|
622
|
+
def _should_skip_error(self, error_str: str) -> bool:
|
|
623
|
+
if self._skip_error_callback is None:
|
|
624
|
+
return False
|
|
625
|
+
|
|
626
|
+
return self._skip_error_callback(error_str)
|
|
627
|
+
|
|
628
|
+
def _process_end_span_with_error(self, run: Run) -> None:
|
|
629
|
+
# Skip processing if this is a skipped LangGraph root run
|
|
630
|
+
if run.id in self._skipped_langgraph_root_run_ids:
|
|
631
|
+
return
|
|
632
|
+
|
|
633
|
+
if run.id not in self._span_data_map:
|
|
634
|
+
LOGGER.warning(
|
|
635
|
+
f"Span data for run '{run.id}' not found in the span data map. Skipping processing of _process_end_span_with_error."
|
|
412
636
|
)
|
|
413
|
-
|
|
637
|
+
return
|
|
414
638
|
|
|
415
|
-
|
|
639
|
+
span_data = None
|
|
416
640
|
try:
|
|
417
641
|
run_dict: Dict[str, Any] = run.dict()
|
|
418
642
|
span_data = self._span_data_map[run.id]
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
643
|
+
error_str = run_dict["error"]
|
|
644
|
+
|
|
645
|
+
if self._should_skip_error(error_str):
|
|
646
|
+
span_data.init_end_time().update(output=ERROR_SKIPPED_OUTPUTS)
|
|
647
|
+
else:
|
|
648
|
+
error_info = ErrorInfoDict(
|
|
649
|
+
exception_type="Exception",
|
|
650
|
+
traceback=error_str,
|
|
651
|
+
)
|
|
652
|
+
span_data.init_end_time().update(
|
|
653
|
+
output=None,
|
|
654
|
+
error_info=error_info,
|
|
655
|
+
)
|
|
423
656
|
|
|
424
|
-
span_data.init_end_time().update(
|
|
425
|
-
output=None,
|
|
426
|
-
error_info=error_info,
|
|
427
|
-
)
|
|
428
657
|
if tracing_runtime_config.is_tracing_active():
|
|
429
658
|
self._opik_client.span(**span_data.as_parameters)
|
|
430
659
|
except Exception as e:
|
|
431
660
|
LOGGER.debug(f"Failed during _process_end_span_with_error: {e}")
|
|
432
661
|
finally:
|
|
433
|
-
self.
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
662
|
+
if span_data is not None and not self._opik_context_read_only_mode:
|
|
663
|
+
self._opik_context_storage.trim_span_data_stack_to_certain_span(
|
|
664
|
+
span_id=span_data.id
|
|
665
|
+
)
|
|
666
|
+
self._opik_context_storage.pop_span_data(ensure_id=span_data.id)
|
|
437
667
|
|
|
438
668
|
def _update_thread_id_from_metadata(self, run_dict: Dict[str, Any]) -> None:
|
|
439
669
|
if not self._thread_id:
|
|
440
670
|
# We want to default to any manually set thread_id, so only update if self._thread_id is not already set
|
|
441
|
-
thread_id = run_dict
|
|
671
|
+
thread_id = _get_run_metadata(run_dict).get("thread_id")
|
|
442
672
|
|
|
443
673
|
if thread_id:
|
|
444
674
|
self._thread_id = thread_id
|
|
445
675
|
|
|
676
|
+
def _save_span_trace_data_to_local_maps(
|
|
677
|
+
self,
|
|
678
|
+
run_id: UUID,
|
|
679
|
+
span_data: Optional[span.SpanData],
|
|
680
|
+
trace_data: Optional[trace.TraceData],
|
|
681
|
+
) -> None:
|
|
682
|
+
if span_data is not None:
|
|
683
|
+
self._span_data_map[run_id] = span_data
|
|
684
|
+
|
|
685
|
+
if trace_data is not None:
|
|
686
|
+
self._created_traces_data_map[run_id] = trace_data
|
|
687
|
+
|
|
446
688
|
def flush(self) -> None:
|
|
447
689
|
"""
|
|
448
690
|
Flush to ensure all data is sent to the Opik server.
|
|
@@ -458,31 +700,34 @@ class OpikTracer(BaseTracer):
|
|
|
458
700
|
"""
|
|
459
701
|
return self._created_traces
|
|
460
702
|
|
|
703
|
+
def get_current_span_data_for_run(self, run_id: UUID) -> Optional[span.SpanData]:
|
|
704
|
+
return self._span_data_map.get(run_id)
|
|
705
|
+
|
|
461
706
|
def _skip_tracking(self) -> bool:
|
|
462
707
|
if not tracing_runtime_config.is_tracing_active():
|
|
463
708
|
return True
|
|
464
709
|
|
|
465
710
|
return False
|
|
466
711
|
|
|
467
|
-
def _on_llm_start(self, run:
|
|
712
|
+
def _on_llm_start(self, run: Run) -> None:
|
|
468
713
|
"""Process the LLM Run upon start."""
|
|
469
714
|
if self._skip_tracking():
|
|
470
715
|
return
|
|
471
716
|
|
|
472
|
-
self._process_start_span(run)
|
|
717
|
+
self._process_start_span(run, allow_duplicating_root_span=True)
|
|
473
718
|
|
|
474
719
|
def on_chat_model_start(
|
|
475
720
|
self,
|
|
476
721
|
serialized: Dict[str, Any],
|
|
477
722
|
messages: List[List["BaseMessage"]],
|
|
478
723
|
*,
|
|
479
|
-
run_id:
|
|
724
|
+
run_id: UUID,
|
|
480
725
|
tags: Optional[List[str]] = None,
|
|
481
|
-
parent_run_id: Optional[
|
|
726
|
+
parent_run_id: Optional[UUID] = None,
|
|
482
727
|
metadata: Optional[Dict[str, Any]] = None,
|
|
483
728
|
name: Optional[str] = None,
|
|
484
729
|
**kwargs: Any,
|
|
485
|
-
) ->
|
|
730
|
+
) -> Run:
|
|
486
731
|
"""Start a trace for an LLM run.
|
|
487
732
|
|
|
488
733
|
Duplicated from Langchain tracer, it is disabled by default in all tracers, see https://github.com/langchain-ai/langchain/blob/fdda1aaea14b257845a19023e8af5e20140ec9fe/libs/core/langchain_core/callbacks/manager.py#L270-L289 and https://github.com/langchain-ai/langchain/blob/fdda1aaea14b257845a19023e8af5e20140ec9fe/libs/core/langchain_core/tracers/core.py#L168-L180
|
|
@@ -524,63 +769,63 @@ class OpikTracer(BaseTracer):
|
|
|
524
769
|
self._on_chat_model_start(chat_model_run)
|
|
525
770
|
return chat_model_run
|
|
526
771
|
|
|
527
|
-
def _on_chat_model_start(self, run:
|
|
772
|
+
def _on_chat_model_start(self, run: Run) -> None:
|
|
528
773
|
"""Process the Chat Model Run upon start."""
|
|
529
774
|
if self._skip_tracking():
|
|
530
775
|
return
|
|
531
776
|
|
|
532
|
-
self._process_start_span(run)
|
|
777
|
+
self._process_start_span(run, allow_duplicating_root_span=True)
|
|
533
778
|
|
|
534
|
-
def _on_llm_end(self, run:
|
|
779
|
+
def _on_llm_end(self, run: Run) -> None:
|
|
535
780
|
"""Process the LLM Run."""
|
|
536
781
|
if self._skip_tracking():
|
|
537
782
|
return
|
|
538
783
|
|
|
539
784
|
self._process_end_span(run)
|
|
540
785
|
|
|
541
|
-
def _on_llm_error(self, run:
|
|
786
|
+
def _on_llm_error(self, run: Run) -> None:
|
|
542
787
|
"""Process the LLM Run upon error."""
|
|
543
788
|
if self._skip_tracking():
|
|
544
789
|
return
|
|
545
790
|
|
|
546
791
|
self._process_end_span_with_error(run)
|
|
547
792
|
|
|
548
|
-
def _on_chain_start(self, run:
|
|
793
|
+
def _on_chain_start(self, run: Run) -> None:
|
|
549
794
|
"""Process the Chain Run upon start."""
|
|
550
795
|
if self._skip_tracking():
|
|
551
796
|
return
|
|
552
797
|
|
|
553
|
-
self._process_start_span(run)
|
|
798
|
+
self._process_start_span(run, allow_duplicating_root_span=False)
|
|
554
799
|
|
|
555
|
-
def _on_chain_end(self, run:
|
|
800
|
+
def _on_chain_end(self, run: Run) -> None:
|
|
556
801
|
"""Process the Chain Run."""
|
|
557
802
|
if self._skip_tracking():
|
|
558
803
|
return
|
|
559
804
|
|
|
560
805
|
self._process_end_span(run)
|
|
561
806
|
|
|
562
|
-
def _on_chain_error(self, run:
|
|
807
|
+
def _on_chain_error(self, run: Run) -> None:
|
|
563
808
|
"""Process the Chain Run upon error."""
|
|
564
809
|
if self._skip_tracking():
|
|
565
810
|
return
|
|
566
811
|
|
|
567
812
|
self._process_end_span_with_error(run)
|
|
568
813
|
|
|
569
|
-
def _on_tool_start(self, run:
|
|
814
|
+
def _on_tool_start(self, run: Run) -> None:
|
|
570
815
|
"""Process the Tool Run upon start."""
|
|
571
816
|
if self._skip_tracking():
|
|
572
817
|
return
|
|
573
818
|
|
|
574
|
-
self._process_start_span(run)
|
|
819
|
+
self._process_start_span(run, allow_duplicating_root_span=True)
|
|
575
820
|
|
|
576
|
-
def _on_tool_end(self, run:
|
|
821
|
+
def _on_tool_end(self, run: Run) -> None:
|
|
577
822
|
"""Process the Tool Run."""
|
|
578
823
|
if self._skip_tracking():
|
|
579
824
|
return
|
|
580
825
|
|
|
581
826
|
self._process_end_span(run)
|
|
582
827
|
|
|
583
|
-
def _on_tool_error(self, run:
|
|
828
|
+
def _on_tool_error(self, run: Run) -> None:
|
|
584
829
|
"""Process the Tool Run upon error."""
|
|
585
830
|
if self._skip_tracking():
|
|
586
831
|
return
|