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,137 @@
|
|
|
1
|
+
import mimetypes
|
|
2
|
+
import random
|
|
3
|
+
import time
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# The attachment file name regex
|
|
8
|
+
ATTACHMENT_FILE_NAME_REGEX = r"(?:input|output|metadata)-attachment-\d+-\d+-sdk\.\w+"
|
|
9
|
+
ATTACHMENT_FILE_NAME_PLACEHOLDER_REGEX = (
|
|
10
|
+
r"\[((?:input|output|metadata)-attachment-\d+-\d+-sdk\.\w+)\]"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_file_extension(mime_type: str) -> str:
|
|
15
|
+
"""Convert MIME type to file extension.
|
|
16
|
+
|
|
17
|
+
Mirrors the Java getFileExtension() method in AttachmentStripperService.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
mime_type: The MIME type (e.g., "image/png", "application/pdf")
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
File extension without a leading dot (e.g., "png", "pdf")
|
|
24
|
+
"""
|
|
25
|
+
if not mime_type:
|
|
26
|
+
return "bin"
|
|
27
|
+
|
|
28
|
+
# Try to get extension from mimetypes module
|
|
29
|
+
extension = mimetypes.guess_extension(mime_type, strict=False)
|
|
30
|
+
|
|
31
|
+
if extension:
|
|
32
|
+
# Remove the leading dot
|
|
33
|
+
extension = extension.lstrip(".")
|
|
34
|
+
# Handle special cases where mimetypes returns less common extensions
|
|
35
|
+
if mime_type == "image/jpeg" and extension == "jpe":
|
|
36
|
+
return "jpg"
|
|
37
|
+
return extension
|
|
38
|
+
|
|
39
|
+
# Fallback: extract from the MIME type (e.g., "image/png" -> "png")
|
|
40
|
+
if "/" in mime_type:
|
|
41
|
+
subtype = mime_type.split("/")[1]
|
|
42
|
+
# Handle special cases like "svg+xml" -> "svg"
|
|
43
|
+
if "+" in subtype:
|
|
44
|
+
subtype = subtype.split("+")[0]
|
|
45
|
+
# Remove any parameters (e.g., "jpeg; charset=utf-8" -> "jpeg")
|
|
46
|
+
subtype = subtype.split(";")[0].strip()
|
|
47
|
+
return subtype
|
|
48
|
+
|
|
49
|
+
return "bin"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def detect_mime_type(data: bytes) -> Optional[str]:
|
|
53
|
+
"""Detect MIME type from byte content using magic bytes.
|
|
54
|
+
|
|
55
|
+
This provides basic MIME type detection similar to Apache Tika in the Java implementation.
|
|
56
|
+
It checks common file format magic bytes.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
data: The byte data to analyze
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Detected MIME type string, or "application/octet-stream" if unknown
|
|
63
|
+
"""
|
|
64
|
+
if len(data) < 4:
|
|
65
|
+
return "application/octet-stream"
|
|
66
|
+
|
|
67
|
+
# Check common file format magic bytes
|
|
68
|
+
# PNG
|
|
69
|
+
if data[:8] == b"\x89PNG\r\n\x1a\n":
|
|
70
|
+
return "image/png"
|
|
71
|
+
|
|
72
|
+
# JPEG
|
|
73
|
+
if data[:2] == b"\xff\xd8" and data[-2:] == b"\xff\xd9":
|
|
74
|
+
return "image/jpeg"
|
|
75
|
+
|
|
76
|
+
# GIF
|
|
77
|
+
if data[:6] in (b"GIF87a", b"GIF89a"):
|
|
78
|
+
return "image/gif"
|
|
79
|
+
|
|
80
|
+
# PDF
|
|
81
|
+
if data[:4] == b"%PDF":
|
|
82
|
+
return "application/pdf"
|
|
83
|
+
|
|
84
|
+
# WebP
|
|
85
|
+
if data[:4] == b"RIFF" and data[8:12] == b"WEBP":
|
|
86
|
+
return "image/webp"
|
|
87
|
+
|
|
88
|
+
# SVG (XML-based, check for SVG tag)
|
|
89
|
+
try:
|
|
90
|
+
text = data[:1024].decode("utf-8", errors="ignore")
|
|
91
|
+
if "<svg" in text.lower():
|
|
92
|
+
return "image/svg+xml"
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
# MP4
|
|
97
|
+
if len(data) >= 12 and data[4:8] == b"ftyp":
|
|
98
|
+
return "video/mp4"
|
|
99
|
+
|
|
100
|
+
# JSON
|
|
101
|
+
try:
|
|
102
|
+
text = data[:100].decode("utf-8", errors="strict").strip()
|
|
103
|
+
if text.startswith("{") or text.startswith("["):
|
|
104
|
+
return "application/json"
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
# Default to octet-stream for unknown types
|
|
109
|
+
return "application/octet-stream"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def create_attachment_filename(context: str, extension: str) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Generates a unique attachment filename based on the provided context and file extension.
|
|
115
|
+
|
|
116
|
+
This function creates a filename by combining the given context, a randomly generated
|
|
117
|
+
prefix to ensure uniqueness, the current timestamp in milliseconds, and the provided
|
|
118
|
+
file extension. The generated filename aligns with the backend convention for naming
|
|
119
|
+
attachments, which includes specific formatting and structure.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
context: The context to use as the base for the filename (e.g., "input",
|
|
123
|
+
"output", or "metadata").
|
|
124
|
+
extension: The file extension to use for the filename (e.g., "png",
|
|
125
|
+
"jpg", "txt").
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
A generated filename string in the format
|
|
129
|
+
"{context}-attachment-{random_prefix}-{timestamp}.{extension}".
|
|
130
|
+
"""
|
|
131
|
+
# The backend has the following naming convention: r"\\[((?:input|output|metadata)-attachment-\\d+-\\d+\\.\\w+)\\]"
|
|
132
|
+
# Example: [input-attachment-1-1704067200000.png]
|
|
133
|
+
|
|
134
|
+
timestamp = int(round(time.time() * 1000))
|
|
135
|
+
# we need to generate a large enough random prefix to avoid collisions
|
|
136
|
+
random_prefix = random.randint(1, 99999999)
|
|
137
|
+
return f"{context}-attachment-{random_prefix}-{timestamp}-sdk.{extension}"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from typing import Optional, Any, Dict, List, Union
|
|
2
|
+
|
|
3
|
+
import pydantic
|
|
4
|
+
|
|
5
|
+
from opik import dict_utils
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def merge_tags(
|
|
9
|
+
existing_tags: Optional[List[str]], new_tags: Optional[List[str]]
|
|
10
|
+
) -> Optional[List[str]]:
|
|
11
|
+
"""Merge tag lists, preserving existing tags and adding new ones.
|
|
12
|
+
|
|
13
|
+
If both existing_tags and new_tags are None or empty, return None."""
|
|
14
|
+
if existing_tags is None and new_tags is None:
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
result = list(existing_tags or [])
|
|
18
|
+
if new_tags:
|
|
19
|
+
for tag in new_tags:
|
|
20
|
+
if tag not in result:
|
|
21
|
+
result.append(tag)
|
|
22
|
+
|
|
23
|
+
return result if result else None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def merge_metadata(
|
|
27
|
+
existing_metadata: Optional[Dict[str, Any]],
|
|
28
|
+
new_metadata: Optional[Union[Dict[str, Any], pydantic.BaseModel]],
|
|
29
|
+
prompts: Optional[List[Dict[str, Any]]] = None,
|
|
30
|
+
) -> Optional[Dict[str, Any]]:
|
|
31
|
+
"""Merge the existing metadata dictionary with new data, with new values taking precedence.
|
|
32
|
+
|
|
33
|
+
If both existing_metadata and new_metadata are None or empty, return None.
|
|
34
|
+
"""
|
|
35
|
+
if prompts is not None:
|
|
36
|
+
new_metadata = new_metadata or {}
|
|
37
|
+
new_metadata["opik_prompts"] = prompts
|
|
38
|
+
|
|
39
|
+
return _merge_dictionary_with_data(existing_metadata, new_data=new_metadata)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def merge_inputs(
|
|
43
|
+
existing_inputs: Optional[Dict[str, Any]],
|
|
44
|
+
new_inputs: Optional[Union[Dict[str, Any], pydantic.BaseModel]],
|
|
45
|
+
) -> Optional[Dict[str, Any]]:
|
|
46
|
+
"""Merge the existing input dictionary with new data, with new values taking precedence.
|
|
47
|
+
|
|
48
|
+
If both existing_inputs and new_inputs are None or empty, return None."""
|
|
49
|
+
return _merge_dictionary_with_data(existing_inputs, new_data=new_inputs)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def merge_outputs(
|
|
53
|
+
existing_outputs: Optional[Dict[str, Any]],
|
|
54
|
+
new_outputs: Optional[Union[Dict[str, Any], pydantic.BaseModel]],
|
|
55
|
+
) -> Optional[Dict[str, Any]]:
|
|
56
|
+
"""Merge the existing output dictionary with new data, with new values taking precedence.
|
|
57
|
+
|
|
58
|
+
If both existing_outputs and new_outputs are None or empty, return None."""
|
|
59
|
+
return _merge_dictionary_with_data(existing_outputs, new_data=new_outputs)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _merge_dictionary_with_data(
|
|
63
|
+
existing_dict: Optional[Dict[str, Any]],
|
|
64
|
+
new_data: Optional[Union[Dict[str, Any], pydantic.BaseModel]],
|
|
65
|
+
) -> Optional[Dict[str, Any]]:
|
|
66
|
+
"""Merge the dictionary with new data, with new values taking precedence.
|
|
67
|
+
|
|
68
|
+
If both existing_dict and new_data are None or empty, return None."""
|
|
69
|
+
if existing_dict is None and new_data is None:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
if isinstance(new_data, pydantic.BaseModel):
|
|
73
|
+
new_data = new_data.model_dump()
|
|
74
|
+
|
|
75
|
+
result = dict(existing_dict or {})
|
|
76
|
+
if new_data:
|
|
77
|
+
result = dict_utils.deepmerge(result, new_data)
|
|
78
|
+
|
|
79
|
+
return result if result else None
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import functools
|
|
3
|
-
|
|
3
|
+
import time
|
|
4
|
+
from typing import Optional, Any, List, Dict, Sequence, Set, TYPE_CHECKING, Callable
|
|
4
5
|
|
|
5
6
|
from opik.api_objects import rest_stream_parser
|
|
6
7
|
from opik.rest_api import client as rest_api_client
|
|
7
8
|
from opik.rest_api.types import dataset_item_write as rest_dataset_item
|
|
9
|
+
from opik.rest_api.core.api_error import ApiError
|
|
8
10
|
from opik.message_processing.batching import sequence_splitter
|
|
11
|
+
from opik.rate_limit import rate_limit
|
|
9
12
|
import opik.exceptions as exceptions
|
|
10
13
|
import opik.config as config
|
|
11
14
|
from opik.rest_client_configurator import retry_decorator
|
|
@@ -18,6 +21,54 @@ if TYPE_CHECKING:
|
|
|
18
21
|
LOGGER = logging.getLogger(__name__)
|
|
19
22
|
|
|
20
23
|
|
|
24
|
+
def _ensure_rest_api_call_respecting_rate_limit(
|
|
25
|
+
rest_callable: Callable[[], Any],
|
|
26
|
+
) -> Any:
|
|
27
|
+
"""
|
|
28
|
+
Execute a REST API call with automatic retry on rate limit (429) errors.
|
|
29
|
+
|
|
30
|
+
This function handles HTTP 429 rate limit errors by waiting for the duration
|
|
31
|
+
specified in the response headers and retrying the request. Regular retries
|
|
32
|
+
for other errors are handled by the underlying rest client.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
rest_callable: A callable that performs the REST API call.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
The result of the successful REST API call.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ApiError: If the error is not a 429 rate limit error.
|
|
42
|
+
"""
|
|
43
|
+
while True:
|
|
44
|
+
try:
|
|
45
|
+
result = rest_callable()
|
|
46
|
+
return result
|
|
47
|
+
except ApiError as exception:
|
|
48
|
+
if exception.status_code == 429:
|
|
49
|
+
# Parse rate limit headers to get retry delay
|
|
50
|
+
if exception.headers is not None:
|
|
51
|
+
rate_limiter = rate_limit.parse_rate_limit(exception.headers)
|
|
52
|
+
if rate_limiter is not None:
|
|
53
|
+
retry_after = rate_limiter.retry_after()
|
|
54
|
+
LOGGER.info(
|
|
55
|
+
"Rate limited (HTTP 429), retrying in %s seconds",
|
|
56
|
+
retry_after,
|
|
57
|
+
)
|
|
58
|
+
time.sleep(retry_after)
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# Fallback: wait 1 second if no header available
|
|
62
|
+
LOGGER.info(
|
|
63
|
+
"Rate limited (HTTP 429) with no retry-after header, retrying in 1 second"
|
|
64
|
+
)
|
|
65
|
+
time.sleep(1)
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
# Re-raise if not a 429 error
|
|
69
|
+
raise
|
|
70
|
+
|
|
71
|
+
|
|
21
72
|
class Dataset:
|
|
22
73
|
def __init__(
|
|
23
74
|
self,
|
|
@@ -52,6 +103,17 @@ class Dataset:
|
|
|
52
103
|
"""The description of the dataset."""
|
|
53
104
|
return self._description
|
|
54
105
|
|
|
106
|
+
def _insert_batch_with_retry(
|
|
107
|
+
self, batch: List[rest_dataset_item.DatasetItemWrite]
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Insert a batch of dataset items with automatic retry on rate limit errors."""
|
|
110
|
+
_ensure_rest_api_call_respecting_rate_limit(
|
|
111
|
+
lambda: self._rest_client.datasets.create_or_update_dataset_items(
|
|
112
|
+
dataset_name=self._name, items=batch
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
LOGGER.debug("Successfully sent dataset items batch of size %d", len(batch))
|
|
116
|
+
|
|
55
117
|
def __internal_api__insert_items_as_dataclasses__(
|
|
56
118
|
self, items: List[dataset_item.DatasetItem]
|
|
57
119
|
) -> None:
|
|
@@ -90,9 +152,7 @@ class Dataset:
|
|
|
90
152
|
|
|
91
153
|
for batch in batches:
|
|
92
154
|
LOGGER.debug("Sending dataset items batch of size %d", len(batch))
|
|
93
|
-
self.
|
|
94
|
-
dataset_name=self._name, items=batch
|
|
95
|
-
)
|
|
155
|
+
self._insert_batch_with_retry(batch)
|
|
96
156
|
|
|
97
157
|
def insert(self, items: Sequence[Dict[str, Any]]) -> None:
|
|
98
158
|
"""
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from typing import List
|
|
2
4
|
from opik.rest_api import OpikApi
|
|
3
5
|
import opik.exceptions as exceptions
|
|
6
|
+
from opik.message_processing import streamer
|
|
4
7
|
from . import dataset
|
|
5
8
|
from .. import experiment
|
|
9
|
+
from ..experiment import experiments_client
|
|
6
10
|
from ...rest_api.core.api_error import ApiError
|
|
7
11
|
|
|
8
12
|
|
|
@@ -55,7 +59,11 @@ def get_dataset_id(rest_client: OpikApi, dataset_name: str) -> str:
|
|
|
55
59
|
|
|
56
60
|
|
|
57
61
|
def get_dataset_experiments(
|
|
58
|
-
rest_client: OpikApi,
|
|
62
|
+
rest_client: OpikApi,
|
|
63
|
+
dataset_id: str,
|
|
64
|
+
max_results: int,
|
|
65
|
+
streamer: streamer.Streamer,
|
|
66
|
+
experiments_client: experiments_client.ExperimentsClient,
|
|
59
67
|
) -> List[experiment.Experiment]:
|
|
60
68
|
page_size = 100
|
|
61
69
|
experiments: List[experiment.Experiment] = []
|
|
@@ -78,7 +86,8 @@ def get_dataset_experiments(
|
|
|
78
86
|
name=experiment_.name,
|
|
79
87
|
dataset_name=experiment_.dataset_name,
|
|
80
88
|
rest_client=rest_client,
|
|
81
|
-
|
|
89
|
+
streamer=streamer,
|
|
90
|
+
experiments_client=experiments_client,
|
|
82
91
|
)
|
|
83
92
|
)
|
|
84
93
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
import logging
|
|
3
|
-
from typing import List, Optional
|
|
3
|
+
from typing import List, Optional, TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
import opik.rest_api
|
|
6
5
|
from opik.message_processing.batching import sequence_splitter
|
|
6
|
+
from opik.message_processing import messages, streamer
|
|
7
7
|
from opik.rest_api import client as rest_api_client
|
|
8
|
-
from opik.rest_api
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
|
|
8
|
+
from opik.rest_api import types as rest_api_types
|
|
9
|
+
from . import experiment_item, experiments_client
|
|
10
|
+
from .. import constants, helpers
|
|
11
|
+
from ...api_objects.prompt import base_prompt
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from opik.evaluation.metrics import score_result
|
|
13
15
|
|
|
14
16
|
LOGGER = logging.getLogger(__name__)
|
|
15
17
|
|
|
@@ -21,13 +23,17 @@ class Experiment:
|
|
|
21
23
|
name: Optional[str],
|
|
22
24
|
dataset_name: str,
|
|
23
25
|
rest_client: rest_api_client.OpikApi,
|
|
24
|
-
|
|
26
|
+
streamer: streamer.Streamer,
|
|
27
|
+
experiments_client: experiments_client.ExperimentsClient,
|
|
28
|
+
prompts: Optional[List[base_prompt.BasePrompt]] = None,
|
|
25
29
|
) -> None:
|
|
26
30
|
self._id = id
|
|
27
31
|
self._name = name
|
|
28
32
|
self._dataset_name = dataset_name
|
|
29
33
|
self._rest_client = rest_client
|
|
30
34
|
self._prompts = prompts
|
|
35
|
+
self._streamer = streamer
|
|
36
|
+
self._experiments_client = experiments_client
|
|
31
37
|
|
|
32
38
|
@property
|
|
33
39
|
def id(self) -> str:
|
|
@@ -57,7 +63,7 @@ class Experiment:
|
|
|
57
63
|
def experiments_rest_client(self) -> rest_api_client.ExperimentsClient:
|
|
58
64
|
return self._rest_client.experiments
|
|
59
65
|
|
|
60
|
-
def get_experiment_data(self) -> experiment_public.ExperimentPublic:
|
|
66
|
+
def get_experiment_data(self) -> rest_api_types.experiment_public.ExperimentPublic:
|
|
61
67
|
return self._rest_client.experiments.get_experiment_by_id(id=self.id)
|
|
62
68
|
|
|
63
69
|
def insert(
|
|
@@ -74,8 +80,8 @@ class Experiment:
|
|
|
74
80
|
Returns:
|
|
75
81
|
None
|
|
76
82
|
"""
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
experiment_item_messages = [
|
|
84
|
+
messages.ExperimentItemMessage(
|
|
79
85
|
id=helpers.generate_id(),
|
|
80
86
|
experiment_id=self.id,
|
|
81
87
|
dataset_item_id=item.dataset_item_id,
|
|
@@ -84,68 +90,62 @@ class Experiment:
|
|
|
84
90
|
for item in experiment_items_references
|
|
85
91
|
]
|
|
86
92
|
|
|
93
|
+
# Split into batches for the streamer
|
|
87
94
|
batches = sequence_splitter.split_into_batches(
|
|
88
|
-
|
|
95
|
+
experiment_item_messages,
|
|
96
|
+
max_length=constants.FEEDBACK_SCORES_MAX_BATCH_SIZE,
|
|
89
97
|
)
|
|
90
98
|
|
|
91
99
|
for batch in batches:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
experiment_items=batch,
|
|
100
|
+
create_experiment_items_batch_message = (
|
|
101
|
+
messages.CreateExperimentItemsBatchMessage(batch=batch)
|
|
95
102
|
)
|
|
96
|
-
|
|
103
|
+
self._streamer.put(create_experiment_items_batch_message)
|
|
97
104
|
|
|
98
105
|
def get_items(
|
|
99
106
|
self,
|
|
100
|
-
max_results: Optional[int] =
|
|
107
|
+
max_results: Optional[int] = 10000,
|
|
101
108
|
truncate: bool = False,
|
|
102
109
|
) -> List[experiment_item.ExperimentItemContent]:
|
|
103
110
|
"""
|
|
104
|
-
Retrieves and returns a list of experiment items
|
|
105
|
-
truncate the results for each batch.
|
|
106
|
-
|
|
107
|
-
This method streams experiment items from a backend service in chunks up to the specified `max_results`
|
|
108
|
-
or until the available items are exhausted. It handles batch-wise retrieval and parsing, ensuring the client
|
|
109
|
-
receives a list of `ExperimentItemContent` objects, while respecting the constraints on maximum retrieval size
|
|
110
|
-
from the backend. If truncation is enabled, the backend may return truncated details for each item.
|
|
111
|
+
Retrieves and returns a list of experiment items for this experiment.
|
|
111
112
|
|
|
112
113
|
Args:
|
|
113
|
-
max_results: Maximum number of experiment items to retrieve.
|
|
114
|
-
truncate: Whether to truncate the items returned by the backend.
|
|
114
|
+
max_results: Maximum number of experiment items to retrieve. Defaults to 10000 if not specified.
|
|
115
|
+
truncate: Whether to truncate the items returned by the backend. Defaults to False.
|
|
115
116
|
|
|
117
|
+
Returns:
|
|
118
|
+
List of ExperimentItemContent objects for this experiment.
|
|
116
119
|
"""
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
items_stream = self._rest_client.experiments.stream_experiment_items(
|
|
129
|
-
experiment_name=self.name,
|
|
130
|
-
limit=current_batch_size,
|
|
131
|
-
last_retrieved_id=result[-1].id if len(result) > 0 else None,
|
|
132
|
-
truncate=truncate,
|
|
133
|
-
)
|
|
120
|
+
if max_results is None:
|
|
121
|
+
max_results = 10000 # TODO: remove this once we have a proper way to get all experiment items
|
|
122
|
+
|
|
123
|
+
return self._experiments_client.find_experiment_items_for_dataset(
|
|
124
|
+
dataset_name=self.dataset_name,
|
|
125
|
+
experiment_ids=[self.id],
|
|
126
|
+
truncate=truncate,
|
|
127
|
+
max_results=max_results,
|
|
128
|
+
)
|
|
134
129
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
130
|
+
def log_experiment_scores(
|
|
131
|
+
self,
|
|
132
|
+
score_results: List["score_result.ScoreResult"],
|
|
133
|
+
) -> None:
|
|
134
|
+
"""Log experiment-level scores to the backend."""
|
|
135
|
+
experiment_scores: List[rest_api_types.ExperimentScore] = []
|
|
141
136
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
)
|
|
146
|
-
result.append(converted_item)
|
|
137
|
+
for score_result_ in score_results:
|
|
138
|
+
if score_result_.scoring_failed:
|
|
139
|
+
continue
|
|
147
140
|
|
|
148
|
-
|
|
149
|
-
|
|
141
|
+
experiment_score = rest_api_types.ExperimentScore(
|
|
142
|
+
name=score_result_.name,
|
|
143
|
+
value=score_result_.value,
|
|
144
|
+
)
|
|
145
|
+
experiment_scores.append(experiment_score)
|
|
150
146
|
|
|
151
|
-
|
|
147
|
+
if experiment_scores:
|
|
148
|
+
self._rest_client.experiments.update_experiment(
|
|
149
|
+
id=self.id,
|
|
150
|
+
experiment_scores=experiment_scores,
|
|
151
|
+
)
|
|
@@ -24,6 +24,7 @@ class ExperimentItemContent:
|
|
|
24
24
|
def from_rest_experiment_item_compare(
|
|
25
25
|
cls,
|
|
26
26
|
value: experiment_item_compare.ExperimentItemCompare,
|
|
27
|
+
dataset_item_data: Optional[Dict[str, Any]] = None,
|
|
27
28
|
) -> "ExperimentItemContent":
|
|
28
29
|
if value.feedback_scores is None:
|
|
29
30
|
feedback_scores: List[FeedbackScoreDict] = []
|
|
@@ -42,7 +43,7 @@ class ExperimentItemContent:
|
|
|
42
43
|
id=value.id,
|
|
43
44
|
trace_id=value.trace_id,
|
|
44
45
|
dataset_item_id=value.dataset_item_id,
|
|
45
|
-
dataset_item_data=value.input,
|
|
46
|
+
dataset_item_data=dataset_item_data if dataset_item_data else value.input,
|
|
46
47
|
evaluation_task_output=value.output,
|
|
47
48
|
feedback_scores=feedback_scores,
|
|
48
49
|
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from . import rest_operations, experiment_item
|
|
5
|
+
from .. import opik_query_language
|
|
6
|
+
from ...rest_api import client as rest_api_client
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ExperimentsClient:
|
|
10
|
+
"""Client for managing and querying experiments in bulk."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, rest_client: rest_api_client.OpikApi):
|
|
13
|
+
self._rest_client = rest_client
|
|
14
|
+
|
|
15
|
+
def find_experiment_items_for_dataset(
|
|
16
|
+
self,
|
|
17
|
+
dataset_name: str,
|
|
18
|
+
experiment_ids: List[str],
|
|
19
|
+
truncate: bool = True,
|
|
20
|
+
max_results: int = 1000,
|
|
21
|
+
filter_string: Optional[str] = None,
|
|
22
|
+
) -> List[experiment_item.ExperimentItemContent]:
|
|
23
|
+
"""
|
|
24
|
+
Find experiment items associated with a specific dataset among a list of experiments.
|
|
25
|
+
|
|
26
|
+
This method queries the dataset for experiment items matching the
|
|
27
|
+
criteria provided by the input parameters. It leverages the
|
|
28
|
+
ExperimentsClient to perform the underlying operation.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
dataset_name: Name of the dataset to query for experiment items.
|
|
32
|
+
experiment_ids: List of experiment IDs to filter the results.
|
|
33
|
+
filter_string: Optional filter string to refine the
|
|
34
|
+
query based on additional criteria (dataset fields, feedback scores, etc.).
|
|
35
|
+
truncate: Whether to truncate image data stored in input, output,
|
|
36
|
+
or metadata. Defaults to True.
|
|
37
|
+
max_results: Maximum number of results to return. Defaults to 1000.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
A list of experiment item content objects that match the criteria.
|
|
41
|
+
"""
|
|
42
|
+
# prepare filter expression
|
|
43
|
+
if filter_string is not None:
|
|
44
|
+
filter_expression = json.dumps(
|
|
45
|
+
opik_query_language.OpikQueryLanguage(
|
|
46
|
+
filter_string
|
|
47
|
+
).get_filter_expressions()
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
filter_expression = None
|
|
51
|
+
|
|
52
|
+
# get dataset id
|
|
53
|
+
dataset_id = self._rest_client.datasets.get_dataset_by_identifier(
|
|
54
|
+
dataset_name=dataset_name
|
|
55
|
+
).id
|
|
56
|
+
|
|
57
|
+
return rest_operations.find_experiment_items_for_dataset(
|
|
58
|
+
dataset_id=dataset_id,
|
|
59
|
+
experiment_ids=experiment_ids,
|
|
60
|
+
rest_client=self._rest_client,
|
|
61
|
+
max_results=max_results,
|
|
62
|
+
truncate=truncate,
|
|
63
|
+
filter_expression=filter_expression,
|
|
64
|
+
)
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import copy
|
|
1
2
|
import logging
|
|
2
|
-
import opik.jsonable_encoder as jsonable_encoder
|
|
3
3
|
from typing import Any, Dict, List, Mapping, Optional, Tuple
|
|
4
|
-
|
|
5
|
-
from
|
|
4
|
+
|
|
5
|
+
from opik import id_helpers
|
|
6
|
+
import opik.jsonable_encoder as jsonable_encoder
|
|
7
|
+
|
|
8
|
+
from ..prompt import base_prompt
|
|
9
|
+
|
|
6
10
|
|
|
7
11
|
LOGGER = logging.getLogger(__name__)
|
|
8
12
|
|
|
@@ -11,7 +15,7 @@ PromptVersion = Dict[str, str]
|
|
|
11
15
|
|
|
12
16
|
def build_metadata_and_prompt_versions(
|
|
13
17
|
experiment_config: Optional[Dict[str, Any]],
|
|
14
|
-
prompts: Optional[List[
|
|
18
|
+
prompts: Optional[List[base_prompt.BasePrompt]],
|
|
15
19
|
) -> Tuple[Optional[Dict[str, Any]], Optional[List[PromptVersion]]]:
|
|
16
20
|
prompt_versions: Optional[List[PromptVersion]] = None
|
|
17
21
|
|
|
@@ -35,11 +39,22 @@ def build_metadata_and_prompt_versions(
|
|
|
35
39
|
|
|
36
40
|
if prompts is not None and len(prompts) > 0:
|
|
37
41
|
prompt_versions = []
|
|
38
|
-
experiment_config["prompts"] =
|
|
42
|
+
experiment_config["prompts"] = {}
|
|
39
43
|
|
|
40
|
-
for
|
|
41
|
-
prompt_versions.append({"id":
|
|
42
|
-
|
|
44
|
+
for prompt_obj in prompts:
|
|
45
|
+
prompt_versions.append({"id": prompt_obj.__internal_api__version_id__})
|
|
46
|
+
# Use __internal_api__to_info_dict__() to get the prompt content in a consistent way
|
|
47
|
+
prompt_info = prompt_obj.__internal_api__to_info_dict__()
|
|
48
|
+
# Extract the template/messages from the version dict
|
|
49
|
+
if "version" in prompt_info:
|
|
50
|
+
if "template" in prompt_info["version"]:
|
|
51
|
+
experiment_config["prompts"][prompt_obj.name] = prompt_info[
|
|
52
|
+
"version"
|
|
53
|
+
]["template"]
|
|
54
|
+
elif "messages" in prompt_info["version"]:
|
|
55
|
+
experiment_config["prompts"][prompt_obj.name] = prompt_info[
|
|
56
|
+
"version"
|
|
57
|
+
]["messages"]
|
|
43
58
|
|
|
44
59
|
if experiment_config == {}:
|
|
45
60
|
return None, None
|
|
@@ -50,9 +65,9 @@ def build_metadata_and_prompt_versions(
|
|
|
50
65
|
|
|
51
66
|
|
|
52
67
|
def handle_prompt_args(
|
|
53
|
-
prompt: Optional[
|
|
54
|
-
prompts: Optional[List[
|
|
55
|
-
) -> Optional[List[
|
|
68
|
+
prompt: Optional[base_prompt.BasePrompt] = None,
|
|
69
|
+
prompts: Optional[List[base_prompt.BasePrompt]] = None,
|
|
70
|
+
) -> Optional[List[base_prompt.BasePrompt]]:
|
|
56
71
|
if prompts is not None and len(prompts) > 0 and prompt is not None:
|
|
57
72
|
LOGGER.warning(
|
|
58
73
|
"Arguments `prompt` and `prompts` are mutually exclusive, `prompts` will be used`."
|
|
@@ -63,3 +78,12 @@ def handle_prompt_args(
|
|
|
63
78
|
prompts = None
|
|
64
79
|
|
|
65
80
|
return prompts
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def generate_unique_experiment_name(experiment_name_prefix: Optional[str]) -> str:
|
|
84
|
+
if experiment_name_prefix is None:
|
|
85
|
+
return id_helpers.generate_random_alphanumeric_string(12)
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
f"{experiment_name_prefix}-{id_helpers.generate_random_alphanumeric_string(6)}"
|
|
89
|
+
)
|