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
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from opik.message_processing import messages
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MessagePreprocessor(abc.ABC):
|
|
8
|
+
"""
|
|
9
|
+
Abstract base class for message preprocessing.
|
|
10
|
+
|
|
11
|
+
This class provides a common interface for pre-processing messages, allowing
|
|
12
|
+
derived classes to implement custom preprocessing logic tailored to specific
|
|
13
|
+
requirements. Instances of this class cannot be created directly; it must be
|
|
14
|
+
subclassed with the `preprocess` method implemented.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@abc.abstractmethod
|
|
18
|
+
def preprocess(
|
|
19
|
+
self, message: Optional[messages.BaseMessage]
|
|
20
|
+
) -> Optional[messages.BaseMessage]:
|
|
21
|
+
"""
|
|
22
|
+
Processes and preprocesses the given message to prepare it for further operations.
|
|
23
|
+
|
|
24
|
+
This is an abstract method and needs to be implemented in any concrete subclass. The
|
|
25
|
+
preprocessing step is typically used for transformations or checks on the given input
|
|
26
|
+
message before further processing.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
message: The input message to be preprocessed. This can
|
|
30
|
+
optionally be None.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The processed message after preprocessing. Returns None if the input message is None
|
|
34
|
+
or if a message was fully consumed here and no further processing is required.
|
|
35
|
+
"""
|
|
36
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Optional, NamedTuple, List, Literal, cast
|
|
3
|
+
|
|
4
|
+
from opik.api_objects.attachment import (
|
|
5
|
+
attachments_extractor,
|
|
6
|
+
attachment_context,
|
|
7
|
+
converters,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from . import message_processors
|
|
11
|
+
from ..preprocessing import constants
|
|
12
|
+
from .. import messages, streamer
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
LOGGER = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EntityDetails(NamedTuple):
|
|
19
|
+
entity_type: Literal["span", "trace"]
|
|
20
|
+
entity_id: str
|
|
21
|
+
project_name: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AttachmentsExtractionProcessor(message_processors.BaseMessageProcessor):
|
|
25
|
+
"""
|
|
26
|
+
Class for processing message attachments through extraction and further handling.
|
|
27
|
+
|
|
28
|
+
The AttachmentsExtractionProcessor class is designed to handle attachments from incoming
|
|
29
|
+
messages. It checks the type of messages and processes them if they support
|
|
30
|
+
attachments. This includes extracting attachment data, replacing them with references,
|
|
31
|
+
and streaming processed or original messages through a pipeline. The class provides a
|
|
32
|
+
mechanism to toggle processing activity and ensures proper handling of messages with
|
|
33
|
+
embedded attachment information.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
min_attachment_size: int,
|
|
39
|
+
messages_streamer: streamer.Streamer,
|
|
40
|
+
url_override: str,
|
|
41
|
+
is_active: bool = True,
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
Initializes an object with essential components for managing message streaming
|
|
45
|
+
and attachment extraction.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
min_attachment_size: Minimum size for an attachment to be extracted.
|
|
49
|
+
messages_streamer: The streamer that is responsible for managing
|
|
50
|
+
messages broadcasts.
|
|
51
|
+
url_override: A custom URL to override default configurations if set.
|
|
52
|
+
is_active: Indicator of whether this instance is active. Default is True.
|
|
53
|
+
"""
|
|
54
|
+
self._is_active = is_active
|
|
55
|
+
self.extractor = attachments_extractor.AttachmentsExtractor(min_attachment_size)
|
|
56
|
+
self.messages_streamer = messages_streamer
|
|
57
|
+
self._url_override = url_override
|
|
58
|
+
|
|
59
|
+
self.attachment_attributes = ["input", "output", "metadata"]
|
|
60
|
+
|
|
61
|
+
def is_active(self) -> bool:
|
|
62
|
+
return self._is_active
|
|
63
|
+
|
|
64
|
+
def process(self, message: messages.BaseMessage) -> None:
|
|
65
|
+
if not isinstance(message, messages.AttachmentSupportingMessage):
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
if self._is_active:
|
|
69
|
+
# do attachment processing only if the processor is active
|
|
70
|
+
try:
|
|
71
|
+
self._process_attachments_in_message(message.original_message)
|
|
72
|
+
except Exception as ex:
|
|
73
|
+
LOGGER.error(
|
|
74
|
+
"Failed to process attachment support message: %s", ex, exc_info=ex
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# put the original message into the streamer for further processing
|
|
78
|
+
original_message = message.original_message
|
|
79
|
+
setattr(original_message, constants.MARKER_ATTRIBUTE_NAME, True)
|
|
80
|
+
self.messages_streamer.put(original_message)
|
|
81
|
+
|
|
82
|
+
def _process_attachments_in_message(self, original: messages.BaseMessage) -> None:
|
|
83
|
+
entity_details = entity_type_from_attachment_message(original)
|
|
84
|
+
if entity_details is None:
|
|
85
|
+
LOGGER.error(
|
|
86
|
+
"Failed to extract entity details from message - %s. Skipping embedded attachments processing.",
|
|
87
|
+
original.__class__.__name__,
|
|
88
|
+
)
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
attachments = []
|
|
92
|
+
|
|
93
|
+
for attribute in self.attachment_attributes:
|
|
94
|
+
if getattr(original, attribute, None):
|
|
95
|
+
results = self.extractor.extract_and_replace(
|
|
96
|
+
data=getattr(original, attribute),
|
|
97
|
+
entity_type=entity_details.entity_type,
|
|
98
|
+
entity_id=entity_details.entity_id,
|
|
99
|
+
project_name=entity_details.project_name,
|
|
100
|
+
context=cast(Literal["input", "output", "metadata"], attribute),
|
|
101
|
+
)
|
|
102
|
+
attachments.extend(results)
|
|
103
|
+
|
|
104
|
+
if len(attachments) > 0:
|
|
105
|
+
LOGGER.debug(
|
|
106
|
+
"Extracted %d attachments from %s (entity: %s/%s)",
|
|
107
|
+
len(attachments),
|
|
108
|
+
original.__class__.__name__,
|
|
109
|
+
entity_details.entity_type,
|
|
110
|
+
entity_details.entity_id,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
self._process_attachments(attachments)
|
|
114
|
+
else:
|
|
115
|
+
LOGGER.debug(
|
|
116
|
+
"No attachments found in the message - %s.", original.__class__.__name__
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def _process_attachments(
|
|
120
|
+
self, attachments: List[attachment_context.AttachmentWithContext]
|
|
121
|
+
) -> None:
|
|
122
|
+
for attachment in attachments:
|
|
123
|
+
create_attachment_message = converters.attachment_to_message(
|
|
124
|
+
attachment_data=attachment.attachment_data,
|
|
125
|
+
entity_type=attachment.entity_type,
|
|
126
|
+
entity_id=attachment.entity_id,
|
|
127
|
+
project_name=attachment.project_name,
|
|
128
|
+
url_override=self._url_override,
|
|
129
|
+
delete_after_upload=True, # make sure to delete attachments after upload to avoid leaking space and data
|
|
130
|
+
)
|
|
131
|
+
self.messages_streamer.put(create_attachment_message)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def entity_type_from_attachment_message(
|
|
135
|
+
message: messages.BaseMessage,
|
|
136
|
+
) -> Optional[EntityDetails]:
|
|
137
|
+
if isinstance(message, (messages.CreateSpanMessage, messages.UpdateSpanMessage)):
|
|
138
|
+
return EntityDetails("span", message.span_id, project_name=message.project_name)
|
|
139
|
+
elif isinstance(
|
|
140
|
+
message, (messages.CreateTraceMessage, messages.UpdateTraceMessage)
|
|
141
|
+
):
|
|
142
|
+
return EntityDetails(
|
|
143
|
+
"trace", message.trace_id, project_name=message.project_name
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
return None
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import logging
|
|
3
|
+
from typing import List, Optional, TypeVar, Type
|
|
4
|
+
|
|
5
|
+
from .. import messages
|
|
6
|
+
|
|
7
|
+
import opik.exceptions
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
LOGGER = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseMessageProcessor(abc.ABC):
|
|
16
|
+
@abc.abstractmethod
|
|
17
|
+
def process(self, message: messages.BaseMessage) -> None:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
@abc.abstractmethod
|
|
21
|
+
def is_active(self) -> bool:
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ChainedMessageProcessor(BaseMessageProcessor):
|
|
26
|
+
"""
|
|
27
|
+
Processes messages through a chain of message processors.
|
|
28
|
+
|
|
29
|
+
This class allows for the sequential processing of a message by a list of
|
|
30
|
+
`BaseMessageProcessor` instances. Each processor in the chain is invoked in the
|
|
31
|
+
order provided. If an exception occurs during the processing by a specific
|
|
32
|
+
processor, it is logged, and the process continues with the next processor in
|
|
33
|
+
the chain.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, processors: List[BaseMessageProcessor]) -> None:
|
|
37
|
+
self._processors = processors
|
|
38
|
+
|
|
39
|
+
def is_active(self) -> bool:
|
|
40
|
+
return True
|
|
41
|
+
|
|
42
|
+
def process(self, message: messages.BaseMessage) -> None:
|
|
43
|
+
rate_limit_error: Optional[opik.exceptions.OpikCloudRequestsRateLimited] = None
|
|
44
|
+
|
|
45
|
+
for processor in self._processors:
|
|
46
|
+
try:
|
|
47
|
+
processor.process(message)
|
|
48
|
+
except opik.exceptions.OpikCloudRequestsRateLimited as ex:
|
|
49
|
+
rate_limit_error = ex
|
|
50
|
+
except Exception as ex:
|
|
51
|
+
LOGGER.error(
|
|
52
|
+
"Unexpected error while processing message: %s with message processor: %s",
|
|
53
|
+
ex,
|
|
54
|
+
type(processor),
|
|
55
|
+
exc_info=True,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Rate limit error is a special case that is handled by the caller.
|
|
59
|
+
if rate_limit_error is not None:
|
|
60
|
+
raise rate_limit_error
|
|
61
|
+
|
|
62
|
+
def get_processor_by_type(self, processor_type: Type[T]) -> Optional[T]:
|
|
63
|
+
"""
|
|
64
|
+
Retrieves a processor from the available processors that matches the specified type.
|
|
65
|
+
|
|
66
|
+
This method iterates through the list of processors and checks if any of them is
|
|
67
|
+
an instance of the given class type. If a match is found, it returns the processor.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
processor_type: Concrete processor class to search for.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
The processor matching the specified type if found, else None.
|
|
74
|
+
"""
|
|
75
|
+
for processor in self._processors:
|
|
76
|
+
if isinstance(processor, processor_type):
|
|
77
|
+
return processor
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
def add_first(self, processor: BaseMessageProcessor) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Inserts a processor at the first position in the list of processors.
|
|
83
|
+
|
|
84
|
+
This method allows prioritizing a given processor by placing it
|
|
85
|
+
at the beginning of the internal processor list. As a result, the
|
|
86
|
+
provided processor will be executed before others.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
processor: The message processor to be
|
|
90
|
+
added to the front of the processor list.
|
|
91
|
+
"""
|
|
92
|
+
self._processors.insert(0, processor)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from opik.rest_api import client as rest_api_client
|
|
5
|
+
|
|
6
|
+
from . import (
|
|
7
|
+
message_processors,
|
|
8
|
+
online_message_processor,
|
|
9
|
+
)
|
|
10
|
+
from ..emulation import local_emulator_message_processor
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
LOGGER = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_message_processors_chain(
|
|
17
|
+
rest_client: rest_api_client.OpikApi,
|
|
18
|
+
) -> message_processors.ChainedMessageProcessor:
|
|
19
|
+
"""
|
|
20
|
+
Creates a chain of message processors by combining an online processor and a
|
|
21
|
+
local emulator processor. The chain is primarily useful for processing messages
|
|
22
|
+
in a sequence where each processor in the chain contributes its functionality.
|
|
23
|
+
|
|
24
|
+
The online processor is initialized using the provided REST API client. The local
|
|
25
|
+
emulator processor is included but remains inactive by default. The constructed
|
|
26
|
+
chain ensures combined and streamlined processing, accommodating both online
|
|
27
|
+
and local simulation needs based on evaluation activation.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
rest_client: REST API client instance used to configure the online message
|
|
31
|
+
processor.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
A chained message processor containing the online and local emulator processors.
|
|
35
|
+
"""
|
|
36
|
+
online = online_message_processor.OpikMessageProcessor(rest_client=rest_client)
|
|
37
|
+
# is not active by default - will be activated during evaluation
|
|
38
|
+
local = local_emulator_message_processor.LocalEmulatorMessageProcessor(active=False)
|
|
39
|
+
|
|
40
|
+
return message_processors.ChainedMessageProcessor(processors=[online, local])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def toggle_local_emulator_message_processor(
|
|
44
|
+
active: bool, chain: message_processors.ChainedMessageProcessor, reset: bool = True
|
|
45
|
+
) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Toggles the state of the Local Emulator Message Processor within a given
|
|
48
|
+
ChainedMessageProcessor. This function either activates or deactivates the
|
|
49
|
+
processor based on the `active` parameter and resets its state if being
|
|
50
|
+
activated. Logs a warning if the Local Emulator Message Processor is not
|
|
51
|
+
found in the chain.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
active: Determines whether to activate or deactivate the Local
|
|
55
|
+
Emulator Message Processor. If True, the processor is activated.
|
|
56
|
+
chain: The message processor
|
|
57
|
+
chain containing the Local Emulator Message Processor to be toggled.
|
|
58
|
+
reset: Determines whether to reset the Local Emulator Message Processor.
|
|
59
|
+
This can be used to clear the state of the Local Emulator before
|
|
60
|
+
evaluation. Also, it can be used to clean up the state of the Local Emulator
|
|
61
|
+
after evaluation to release system resources (memory).
|
|
62
|
+
"""
|
|
63
|
+
local = chain.get_processor_by_type(
|
|
64
|
+
local_emulator_message_processor.LocalEmulatorMessageProcessor
|
|
65
|
+
)
|
|
66
|
+
if local is None:
|
|
67
|
+
LOGGER.warning("Local emulator message processor not found in the chain.")
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
if reset:
|
|
71
|
+
local.reset()
|
|
72
|
+
|
|
73
|
+
local.set_active(active=active)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_local_emulator_message_processor(
|
|
77
|
+
chain: message_processors.ChainedMessageProcessor,
|
|
78
|
+
) -> Optional[local_emulator_message_processor.LocalEmulatorMessageProcessor]:
|
|
79
|
+
"""
|
|
80
|
+
Retrieves the local emulator message processor from a given chain of message processors.
|
|
81
|
+
|
|
82
|
+
This function searches through the provided chain and looks for a processor of type
|
|
83
|
+
LocalEmulatorMessageProcessor. If one is found, it is returned; otherwise, None is returned.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
chain: A chain of message processors that may contain a
|
|
87
|
+
LocalEmulatorMessageProcessor.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
The LocalEmulatorMessageProcessor if found in the chain,
|
|
91
|
+
otherwise None.
|
|
92
|
+
"""
|
|
93
|
+
local = chain.get_processor_by_type(
|
|
94
|
+
local_emulator_message_processor.LocalEmulatorMessageProcessor
|
|
95
|
+
)
|
|
96
|
+
return local
|
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import abc
|
|
2
1
|
import logging
|
|
3
|
-
from typing import
|
|
2
|
+
from typing import Callable, Dict, Type, Any
|
|
3
|
+
|
|
4
4
|
import pydantic
|
|
5
5
|
import tenacity
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
from . import
|
|
10
|
-
from
|
|
11
|
-
from .. import dict_utils
|
|
12
|
-
from ..rate_limit import rate_limit
|
|
13
|
-
from ..rest_api.types import (
|
|
7
|
+
from opik import dict_utils, exceptions, logging_messages
|
|
8
|
+
from opik.rate_limit import rate_limit
|
|
9
|
+
from opik.rest_api import client as rest_api_client, core as rest_api_core
|
|
10
|
+
from opik.rest_api.types import (
|
|
14
11
|
feedback_score_batch_item,
|
|
15
|
-
guardrail,
|
|
16
12
|
feedback_score_batch_item_thread,
|
|
13
|
+
guardrail,
|
|
14
|
+
experiment_item,
|
|
17
15
|
)
|
|
18
|
-
|
|
19
|
-
from
|
|
16
|
+
|
|
17
|
+
from . import message_processors
|
|
18
|
+
from .. import encoder_helpers, messages
|
|
20
19
|
|
|
21
20
|
LOGGER = logging.getLogger(__name__)
|
|
22
21
|
|
|
@@ -24,21 +23,16 @@ LOGGER = logging.getLogger(__name__)
|
|
|
24
23
|
MessageProcessingHandler = Callable[[messages.BaseMessage], None]
|
|
25
24
|
|
|
26
25
|
|
|
27
|
-
class
|
|
28
|
-
@abc.abstractmethod
|
|
29
|
-
def process(
|
|
30
|
-
self,
|
|
31
|
-
message: messages.BaseMessage,
|
|
32
|
-
) -> None:
|
|
33
|
-
pass
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class OpikMessageProcessor(BaseMessageProcessor):
|
|
26
|
+
class OpikMessageProcessor(message_processors.BaseMessageProcessor):
|
|
37
27
|
def __init__(
|
|
38
|
-
self,
|
|
28
|
+
self,
|
|
29
|
+
rest_client: rest_api_client.OpikApi,
|
|
30
|
+
batch_memory_limit_mb: int = 50,
|
|
31
|
+
active: bool = True,
|
|
39
32
|
):
|
|
40
33
|
self._rest_client = rest_client
|
|
41
34
|
self._batch_memory_limit_mb = batch_memory_limit_mb
|
|
35
|
+
self._is_active = active
|
|
42
36
|
|
|
43
37
|
self._handlers: Dict[Type, MessageProcessingHandler] = {
|
|
44
38
|
messages.CreateSpanMessage: self._process_create_span_message, # type: ignore
|
|
@@ -51,9 +45,17 @@ class OpikMessageProcessor(BaseMessageProcessor):
|
|
|
51
45
|
messages.CreateSpansBatchMessage: self._process_create_spans_batch_message, # type: ignore
|
|
52
46
|
messages.CreateTraceBatchMessage: self._process_create_traces_batch_message, # type: ignore
|
|
53
47
|
messages.GuardrailBatchMessage: self._process_guardrail_batch_message, # type: ignore
|
|
48
|
+
messages.CreateExperimentItemsBatchMessage: self._process_create_experiment_items_batch_message, # type: ignore
|
|
49
|
+
messages.AttachmentSupportingMessage: self._noop_handler, # type: ignore
|
|
54
50
|
}
|
|
55
51
|
|
|
52
|
+
def is_active(self) -> bool:
|
|
53
|
+
return self._is_active
|
|
54
|
+
|
|
56
55
|
def process(self, message: messages.BaseMessage) -> None:
|
|
56
|
+
if not self.is_active():
|
|
57
|
+
return
|
|
58
|
+
|
|
57
59
|
message_type = type(message)
|
|
58
60
|
handler = self._handlers.get(message_type)
|
|
59
61
|
if handler is None:
|
|
@@ -92,6 +94,7 @@ class OpikMessageProcessor(BaseMessageProcessor):
|
|
|
92
94
|
f"{cause.__class__.__name__} - {cause}",
|
|
93
95
|
extra={"error_tracking_extra": error_tracking_extra},
|
|
94
96
|
)
|
|
97
|
+
LOGGER.warning(logging_messages.MAKE_SURE_OPIK_IS_CONFIGURED_CORRECTLY)
|
|
95
98
|
except pydantic.ValidationError as validation_error:
|
|
96
99
|
error_tracking_extra = _generate_error_tracking_extra(
|
|
97
100
|
validation_error, message
|
|
@@ -112,6 +115,7 @@ class OpikMessageProcessor(BaseMessageProcessor):
|
|
|
112
115
|
exc_info=True,
|
|
113
116
|
extra={"error_tracking_extra": error_tracking_extra},
|
|
114
117
|
)
|
|
118
|
+
LOGGER.warning(logging_messages.MAKE_SURE_OPIK_IS_CONFIGURED_CORRECTLY)
|
|
115
119
|
|
|
116
120
|
def _process_create_span_message(
|
|
117
121
|
self,
|
|
@@ -121,7 +125,12 @@ class OpikMessageProcessor(BaseMessageProcessor):
|
|
|
121
125
|
cleaned_create_span_kwargs = dict_utils.remove_none_from_dict(
|
|
122
126
|
create_span_kwargs
|
|
123
127
|
)
|
|
124
|
-
cleaned_create_span_kwargs =
|
|
128
|
+
cleaned_create_span_kwargs = encoder_helpers.encode_and_anonymize(
|
|
129
|
+
cleaned_create_span_kwargs,
|
|
130
|
+
fields_to_anonymize=message.fields_to_anonymize(),
|
|
131
|
+
object_type="span",
|
|
132
|
+
)
|
|
133
|
+
|
|
125
134
|
LOGGER.debug("Create span request: %s", cleaned_create_span_kwargs)
|
|
126
135
|
self._rest_client.spans.create_span(**cleaned_create_span_kwargs)
|
|
127
136
|
|
|
@@ -133,7 +142,12 @@ class OpikMessageProcessor(BaseMessageProcessor):
|
|
|
133
142
|
cleaned_create_trace_kwargs = dict_utils.remove_none_from_dict(
|
|
134
143
|
create_trace_kwargs
|
|
135
144
|
)
|
|
136
|
-
cleaned_create_trace_kwargs =
|
|
145
|
+
cleaned_create_trace_kwargs = encoder_helpers.encode_and_anonymize(
|
|
146
|
+
cleaned_create_trace_kwargs,
|
|
147
|
+
fields_to_anonymize=message.fields_to_anonymize(),
|
|
148
|
+
object_type="trace",
|
|
149
|
+
)
|
|
150
|
+
|
|
137
151
|
LOGGER.debug("Create trace request: %s", cleaned_create_trace_kwargs)
|
|
138
152
|
self._rest_client.traces.create_trace(**cleaned_create_trace_kwargs)
|
|
139
153
|
|
|
@@ -146,7 +160,12 @@ class OpikMessageProcessor(BaseMessageProcessor):
|
|
|
146
160
|
cleaned_update_span_kwargs = dict_utils.remove_none_from_dict(
|
|
147
161
|
update_span_kwargs
|
|
148
162
|
)
|
|
149
|
-
cleaned_update_span_kwargs =
|
|
163
|
+
cleaned_update_span_kwargs = encoder_helpers.encode_and_anonymize(
|
|
164
|
+
cleaned_update_span_kwargs,
|
|
165
|
+
fields_to_anonymize=message.fields_to_anonymize(),
|
|
166
|
+
object_type="span",
|
|
167
|
+
)
|
|
168
|
+
|
|
150
169
|
LOGGER.debug("Update span request: %s", cleaned_update_span_kwargs)
|
|
151
170
|
self._rest_client.spans.update_span(**cleaned_update_span_kwargs)
|
|
152
171
|
|
|
@@ -159,7 +178,12 @@ class OpikMessageProcessor(BaseMessageProcessor):
|
|
|
159
178
|
cleaned_update_trace_kwargs = dict_utils.remove_none_from_dict(
|
|
160
179
|
update_trace_kwargs
|
|
161
180
|
)
|
|
162
|
-
cleaned_update_trace_kwargs =
|
|
181
|
+
cleaned_update_trace_kwargs = encoder_helpers.encode_and_anonymize(
|
|
182
|
+
cleaned_update_trace_kwargs,
|
|
183
|
+
fields_to_anonymize=message.fields_to_anonymize(),
|
|
184
|
+
object_type="trace",
|
|
185
|
+
)
|
|
186
|
+
|
|
163
187
|
LOGGER.debug("Update trace request: %s", cleaned_update_trace_kwargs)
|
|
164
188
|
self._rest_client.traces.update_trace(**cleaned_update_trace_kwargs)
|
|
165
189
|
LOGGER.debug("Sent trace %s", message.trace_id)
|
|
@@ -253,6 +277,35 @@ class OpikMessageProcessor(BaseMessageProcessor):
|
|
|
253
277
|
|
|
254
278
|
self._rest_client.guardrails.create_guardrails(guardrails=batch)
|
|
255
279
|
|
|
280
|
+
def _process_create_experiment_items_batch_message(
|
|
281
|
+
self,
|
|
282
|
+
message: messages.CreateExperimentItemsBatchMessage,
|
|
283
|
+
) -> None:
|
|
284
|
+
experiment_items_batch = [
|
|
285
|
+
experiment_item.ExperimentItem(
|
|
286
|
+
id=item.id,
|
|
287
|
+
experiment_id=item.experiment_id,
|
|
288
|
+
dataset_item_id=item.dataset_item_id,
|
|
289
|
+
trace_id=item.trace_id,
|
|
290
|
+
)
|
|
291
|
+
for item in message.batch
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
LOGGER.debug(
|
|
295
|
+
"Create experiment items batch request of size %d",
|
|
296
|
+
len(experiment_items_batch),
|
|
297
|
+
)
|
|
298
|
+
self._rest_client.experiments.create_experiment_items(
|
|
299
|
+
experiment_items=experiment_items_batch
|
|
300
|
+
)
|
|
301
|
+
LOGGER.debug(
|
|
302
|
+
"Sent experiment items batch of size %d", len(experiment_items_batch)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def _noop_handler(self, message: messages.BaseMessage) -> None:
|
|
306
|
+
# just ignore the message
|
|
307
|
+
pass
|
|
308
|
+
|
|
256
309
|
|
|
257
310
|
def _generate_error_tracking_extra(
|
|
258
311
|
exception: Exception, message: messages.BaseMessage
|
|
@@ -260,8 +313,11 @@ def _generate_error_tracking_extra(
|
|
|
260
313
|
result: Dict[str, Any] = {"exception": exception}
|
|
261
314
|
|
|
262
315
|
if isinstance(exception, rest_api_core.ApiError):
|
|
263
|
-
fingerprint = [
|
|
264
|
-
|
|
316
|
+
fingerprint = [
|
|
317
|
+
type(message).__name__,
|
|
318
|
+
type(exception).__name__,
|
|
319
|
+
str(exception.status_code),
|
|
320
|
+
]
|
|
265
321
|
result["fingerprint"] = fingerprint
|
|
266
322
|
result["status_code"] = exception.status_code
|
|
267
323
|
|
|
@@ -4,7 +4,8 @@ import time
|
|
|
4
4
|
from queue import Empty
|
|
5
5
|
from typing import Optional
|
|
6
6
|
|
|
7
|
-
from . import
|
|
7
|
+
from . import message_queue, messages
|
|
8
|
+
from .processors import message_processors
|
|
8
9
|
from .. import exceptions, _logging
|
|
9
10
|
|
|
10
11
|
LOGGER = logging.getLogger(__name__)
|
|
@@ -49,8 +50,9 @@ class QueueConsumer(threading.Thread):
|
|
|
49
50
|
|
|
50
51
|
if message is None:
|
|
51
52
|
return
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
|
|
54
|
+
if message.delivery_time <= now:
|
|
55
|
+
self._process_message(message)
|
|
54
56
|
else:
|
|
55
57
|
# put a message back to keep an order in the queue
|
|
56
58
|
self._push_message_back(message)
|
|
@@ -87,4 +89,8 @@ class QueueConsumer(threading.Thread):
|
|
|
87
89
|
"The message queue size limit has been reached. The current message has been returned to the queue, and the newest message has been discarded.",
|
|
88
90
|
logger=LOGGER,
|
|
89
91
|
)
|
|
92
|
+
message.delivery_attempts += 1
|
|
90
93
|
self._message_queue.put_back(message)
|
|
94
|
+
|
|
95
|
+
def _process_message(self, message: messages.BaseMessage) -> None:
|
|
96
|
+
self._message_processor.process(message)
|