opik 1.8.39__py3-none-any.whl → 1.9.71__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opik/__init__.py +19 -3
- opik/anonymizer/__init__.py +5 -0
- opik/anonymizer/anonymizer.py +12 -0
- opik/anonymizer/factory.py +80 -0
- opik/anonymizer/recursive_anonymizer.py +64 -0
- opik/anonymizer/rules.py +56 -0
- opik/anonymizer/rules_anonymizer.py +35 -0
- opik/api_objects/attachment/attachment_context.py +36 -0
- opik/api_objects/attachment/attachments_extractor.py +153 -0
- opik/api_objects/attachment/client.py +1 -0
- opik/api_objects/attachment/converters.py +2 -0
- opik/api_objects/attachment/decoder.py +18 -0
- opik/api_objects/attachment/decoder_base64.py +83 -0
- opik/api_objects/attachment/decoder_helpers.py +137 -0
- opik/api_objects/data_helpers.py +79 -0
- opik/api_objects/dataset/dataset.py +64 -4
- opik/api_objects/dataset/rest_operations.py +11 -2
- opik/api_objects/experiment/experiment.py +57 -57
- opik/api_objects/experiment/experiment_item.py +2 -1
- opik/api_objects/experiment/experiments_client.py +64 -0
- opik/api_objects/experiment/helpers.py +35 -11
- opik/api_objects/experiment/rest_operations.py +65 -5
- opik/api_objects/helpers.py +8 -5
- opik/api_objects/local_recording.py +81 -0
- opik/api_objects/opik_client.py +600 -108
- opik/api_objects/opik_query_language.py +39 -5
- opik/api_objects/prompt/__init__.py +12 -2
- opik/api_objects/prompt/base_prompt.py +69 -0
- opik/api_objects/prompt/base_prompt_template.py +29 -0
- opik/api_objects/prompt/chat/__init__.py +1 -0
- opik/api_objects/prompt/chat/chat_prompt.py +210 -0
- opik/api_objects/prompt/chat/chat_prompt_template.py +350 -0
- opik/api_objects/prompt/chat/content_renderer_registry.py +203 -0
- opik/api_objects/prompt/client.py +189 -47
- opik/api_objects/prompt/text/__init__.py +1 -0
- opik/api_objects/prompt/text/prompt.py +174 -0
- opik/api_objects/prompt/{prompt_template.py → text/prompt_template.py} +10 -6
- opik/api_objects/prompt/types.py +23 -0
- opik/api_objects/search_helpers.py +89 -0
- opik/api_objects/span/span_data.py +35 -25
- opik/api_objects/threads/threads_client.py +39 -5
- opik/api_objects/trace/trace_client.py +52 -2
- opik/api_objects/trace/trace_data.py +15 -24
- opik/api_objects/validation_helpers.py +3 -3
- opik/cli/__init__.py +5 -0
- opik/cli/__main__.py +6 -0
- opik/cli/configure.py +66 -0
- opik/cli/exports/__init__.py +131 -0
- opik/cli/exports/dataset.py +278 -0
- opik/cli/exports/experiment.py +784 -0
- opik/cli/exports/project.py +685 -0
- opik/cli/exports/prompt.py +578 -0
- opik/cli/exports/utils.py +406 -0
- opik/cli/harbor.py +39 -0
- opik/cli/healthcheck.py +21 -0
- opik/cli/imports/__init__.py +439 -0
- opik/cli/imports/dataset.py +143 -0
- opik/cli/imports/experiment.py +1192 -0
- opik/cli/imports/project.py +262 -0
- opik/cli/imports/prompt.py +177 -0
- opik/cli/imports/utils.py +280 -0
- opik/cli/main.py +49 -0
- opik/cli/proxy.py +93 -0
- opik/cli/usage_report/__init__.py +16 -0
- opik/cli/usage_report/charts.py +783 -0
- opik/cli/usage_report/cli.py +274 -0
- opik/cli/usage_report/constants.py +9 -0
- opik/cli/usage_report/extraction.py +749 -0
- opik/cli/usage_report/pdf.py +244 -0
- opik/cli/usage_report/statistics.py +78 -0
- opik/cli/usage_report/utils.py +235 -0
- opik/config.py +13 -7
- opik/configurator/configure.py +17 -0
- opik/datetime_helpers.py +12 -0
- opik/decorator/arguments_helpers.py +9 -1
- opik/decorator/base_track_decorator.py +205 -133
- opik/decorator/context_manager/span_context_manager.py +123 -0
- opik/decorator/context_manager/trace_context_manager.py +84 -0
- opik/decorator/opik_args/__init__.py +13 -0
- opik/decorator/opik_args/api_classes.py +71 -0
- opik/decorator/opik_args/helpers.py +120 -0
- opik/decorator/span_creation_handler.py +25 -6
- opik/dict_utils.py +3 -3
- opik/evaluation/__init__.py +13 -2
- opik/evaluation/engine/engine.py +272 -75
- opik/evaluation/engine/evaluation_tasks_executor.py +6 -3
- opik/evaluation/engine/helpers.py +31 -6
- opik/evaluation/engine/metrics_evaluator.py +237 -0
- opik/evaluation/evaluation_result.py +168 -2
- opik/evaluation/evaluator.py +533 -62
- opik/evaluation/metrics/__init__.py +103 -4
- opik/evaluation/metrics/aggregated_metric.py +35 -6
- opik/evaluation/metrics/base_metric.py +1 -1
- opik/evaluation/metrics/conversation/__init__.py +48 -0
- opik/evaluation/metrics/conversation/conversation_thread_metric.py +56 -2
- opik/evaluation/metrics/conversation/g_eval_wrappers.py +19 -0
- opik/evaluation/metrics/conversation/helpers.py +14 -15
- opik/evaluation/metrics/conversation/heuristics/__init__.py +14 -0
- opik/evaluation/metrics/conversation/heuristics/degeneration/__init__.py +3 -0
- opik/evaluation/metrics/conversation/heuristics/degeneration/metric.py +189 -0
- opik/evaluation/metrics/conversation/heuristics/degeneration/phrases.py +12 -0
- opik/evaluation/metrics/conversation/heuristics/knowledge_retention/__init__.py +3 -0
- opik/evaluation/metrics/conversation/heuristics/knowledge_retention/metric.py +172 -0
- opik/evaluation/metrics/conversation/llm_judges/__init__.py +32 -0
- opik/evaluation/metrics/conversation/{conversational_coherence → llm_judges/conversational_coherence}/metric.py +22 -17
- opik/evaluation/metrics/conversation/{conversational_coherence → llm_judges/conversational_coherence}/templates.py +1 -1
- opik/evaluation/metrics/conversation/llm_judges/g_eval_wrappers.py +442 -0
- opik/evaluation/metrics/conversation/{session_completeness → llm_judges/session_completeness}/metric.py +13 -7
- opik/evaluation/metrics/conversation/{session_completeness → llm_judges/session_completeness}/templates.py +1 -1
- opik/evaluation/metrics/conversation/llm_judges/user_frustration/__init__.py +0 -0
- opik/evaluation/metrics/conversation/{user_frustration → llm_judges/user_frustration}/metric.py +21 -14
- opik/evaluation/metrics/conversation/{user_frustration → llm_judges/user_frustration}/templates.py +1 -1
- opik/evaluation/metrics/conversation/types.py +4 -5
- opik/evaluation/metrics/conversation_types.py +9 -0
- opik/evaluation/metrics/heuristics/bertscore.py +107 -0
- opik/evaluation/metrics/heuristics/bleu.py +35 -15
- opik/evaluation/metrics/heuristics/chrf.py +127 -0
- opik/evaluation/metrics/heuristics/contains.py +47 -11
- opik/evaluation/metrics/heuristics/distribution_metrics.py +331 -0
- opik/evaluation/metrics/heuristics/gleu.py +113 -0
- opik/evaluation/metrics/heuristics/language_adherence.py +123 -0
- opik/evaluation/metrics/heuristics/meteor.py +119 -0
- opik/evaluation/metrics/heuristics/prompt_injection.py +150 -0
- opik/evaluation/metrics/heuristics/readability.py +129 -0
- opik/evaluation/metrics/heuristics/rouge.py +26 -9
- opik/evaluation/metrics/heuristics/spearman.py +88 -0
- opik/evaluation/metrics/heuristics/tone.py +155 -0
- opik/evaluation/metrics/heuristics/vader_sentiment.py +77 -0
- opik/evaluation/metrics/llm_judges/answer_relevance/metric.py +20 -5
- opik/evaluation/metrics/llm_judges/context_precision/metric.py +20 -6
- opik/evaluation/metrics/llm_judges/context_recall/metric.py +20 -6
- opik/evaluation/metrics/llm_judges/g_eval/__init__.py +5 -0
- opik/evaluation/metrics/llm_judges/g_eval/metric.py +219 -68
- opik/evaluation/metrics/llm_judges/g_eval/parser.py +102 -52
- opik/evaluation/metrics/llm_judges/g_eval/presets.py +209 -0
- opik/evaluation/metrics/llm_judges/g_eval_presets/__init__.py +36 -0
- opik/evaluation/metrics/llm_judges/g_eval_presets/agent_assessment.py +77 -0
- opik/evaluation/metrics/llm_judges/g_eval_presets/bias_classifier.py +181 -0
- opik/evaluation/metrics/llm_judges/g_eval_presets/compliance_risk.py +41 -0
- opik/evaluation/metrics/llm_judges/g_eval_presets/prompt_uncertainty.py +41 -0
- opik/evaluation/metrics/llm_judges/g_eval_presets/qa_suite.py +146 -0
- opik/evaluation/metrics/llm_judges/hallucination/metric.py +16 -3
- opik/evaluation/metrics/llm_judges/llm_juries/__init__.py +3 -0
- opik/evaluation/metrics/llm_judges/llm_juries/metric.py +76 -0
- opik/evaluation/metrics/llm_judges/moderation/metric.py +16 -4
- opik/evaluation/metrics/llm_judges/structure_output_compliance/__init__.py +0 -0
- opik/evaluation/metrics/llm_judges/structure_output_compliance/metric.py +144 -0
- opik/evaluation/metrics/llm_judges/structure_output_compliance/parser.py +79 -0
- opik/evaluation/metrics/llm_judges/structure_output_compliance/schema.py +15 -0
- opik/evaluation/metrics/llm_judges/structure_output_compliance/template.py +50 -0
- opik/evaluation/metrics/llm_judges/syc_eval/__init__.py +0 -0
- opik/evaluation/metrics/llm_judges/syc_eval/metric.py +252 -0
- opik/evaluation/metrics/llm_judges/syc_eval/parser.py +82 -0
- opik/evaluation/metrics/llm_judges/syc_eval/template.py +155 -0
- opik/evaluation/metrics/llm_judges/trajectory_accuracy/metric.py +20 -5
- opik/evaluation/metrics/llm_judges/usefulness/metric.py +16 -4
- opik/evaluation/metrics/ragas_metric.py +43 -23
- opik/evaluation/models/__init__.py +8 -0
- opik/evaluation/models/base_model.py +107 -1
- opik/evaluation/models/langchain/langchain_chat_model.py +15 -7
- opik/evaluation/models/langchain/message_converters.py +97 -15
- opik/evaluation/models/litellm/litellm_chat_model.py +156 -29
- opik/evaluation/models/litellm/util.py +125 -0
- opik/evaluation/models/litellm/warning_filters.py +16 -4
- opik/evaluation/models/model_capabilities.py +187 -0
- opik/evaluation/models/models_factory.py +25 -3
- opik/evaluation/preprocessing.py +92 -0
- opik/evaluation/report.py +70 -12
- opik/evaluation/rest_operations.py +49 -45
- opik/evaluation/samplers/__init__.py +4 -0
- opik/evaluation/samplers/base_dataset_sampler.py +40 -0
- opik/evaluation/samplers/random_dataset_sampler.py +48 -0
- opik/evaluation/score_statistics.py +66 -0
- opik/evaluation/scorers/__init__.py +4 -0
- opik/evaluation/scorers/scorer_function.py +55 -0
- opik/evaluation/scorers/scorer_wrapper_metric.py +130 -0
- opik/evaluation/test_case.py +3 -2
- opik/evaluation/test_result.py +1 -0
- opik/evaluation/threads/evaluator.py +31 -3
- opik/evaluation/threads/helpers.py +3 -2
- opik/evaluation/types.py +9 -1
- opik/exceptions.py +33 -0
- opik/file_upload/file_uploader.py +13 -0
- opik/file_upload/upload_options.py +2 -0
- opik/hooks/__init__.py +23 -0
- opik/hooks/anonymizer_hook.py +36 -0
- opik/hooks/httpx_client_hook.py +112 -0
- opik/httpx_client.py +12 -9
- opik/id_helpers.py +18 -0
- opik/integrations/adk/graph/subgraph_edges_builders.py +1 -2
- opik/integrations/adk/helpers.py +16 -7
- opik/integrations/adk/legacy_opik_tracer.py +7 -4
- opik/integrations/adk/opik_tracer.py +14 -1
- opik/integrations/adk/patchers/adk_otel_tracer/opik_adk_otel_tracer.py +7 -3
- opik/integrations/adk/recursive_callback_injector.py +4 -7
- opik/integrations/bedrock/converse/__init__.py +0 -0
- opik/integrations/bedrock/converse/chunks_aggregator.py +188 -0
- opik/integrations/bedrock/{converse_decorator.py → converse/converse_decorator.py} +4 -3
- opik/integrations/bedrock/invoke_agent_decorator.py +5 -4
- opik/integrations/bedrock/invoke_model/__init__.py +0 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/__init__.py +78 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/api.py +45 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/base.py +23 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/claude.py +121 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/format_detector.py +107 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/llama.py +108 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/mistral.py +118 -0
- opik/integrations/bedrock/invoke_model/chunks_aggregator/nova.py +99 -0
- opik/integrations/bedrock/invoke_model/invoke_model_decorator.py +178 -0
- opik/integrations/bedrock/invoke_model/response_types.py +34 -0
- opik/integrations/bedrock/invoke_model/stream_wrappers.py +122 -0
- opik/integrations/bedrock/invoke_model/usage_converters.py +87 -0
- opik/integrations/bedrock/invoke_model/usage_extraction.py +108 -0
- opik/integrations/bedrock/opik_tracker.py +42 -4
- opik/integrations/bedrock/types.py +19 -0
- opik/integrations/crewai/crewai_decorator.py +8 -51
- opik/integrations/crewai/opik_tracker.py +31 -10
- opik/integrations/crewai/patchers/__init__.py +5 -0
- opik/integrations/crewai/patchers/flow.py +118 -0
- opik/integrations/crewai/patchers/litellm_completion.py +30 -0
- opik/integrations/crewai/patchers/llm_client.py +207 -0
- opik/integrations/dspy/callback.py +80 -17
- opik/integrations/dspy/parsers.py +168 -0
- opik/integrations/harbor/__init__.py +17 -0
- opik/integrations/harbor/experiment_service.py +269 -0
- opik/integrations/harbor/opik_tracker.py +528 -0
- opik/integrations/haystack/opik_connector.py +2 -2
- opik/integrations/haystack/opik_tracer.py +3 -7
- opik/integrations/langchain/__init__.py +3 -1
- opik/integrations/langchain/helpers.py +96 -0
- opik/integrations/langchain/langgraph_async_context_bridge.py +131 -0
- opik/integrations/langchain/langgraph_tracer_injector.py +88 -0
- opik/integrations/langchain/opik_encoder_extension.py +1 -1
- opik/integrations/langchain/opik_tracer.py +474 -229
- opik/integrations/litellm/__init__.py +5 -0
- opik/integrations/litellm/completion_chunks_aggregator.py +115 -0
- opik/integrations/litellm/litellm_completion_decorator.py +242 -0
- opik/integrations/litellm/opik_tracker.py +43 -0
- opik/integrations/litellm/stream_patchers.py +151 -0
- opik/integrations/llama_index/callback.py +146 -107
- opik/integrations/openai/agents/opik_tracing_processor.py +1 -2
- opik/integrations/openai/openai_chat_completions_decorator.py +2 -16
- opik/integrations/openai/opik_tracker.py +1 -1
- opik/integrations/sagemaker/auth.py +5 -1
- opik/llm_usage/google_usage.py +3 -1
- opik/llm_usage/opik_usage.py +7 -8
- opik/llm_usage/opik_usage_factory.py +4 -2
- opik/logging_messages.py +6 -0
- opik/message_processing/batching/base_batcher.py +14 -21
- opik/message_processing/batching/batch_manager.py +22 -10
- opik/message_processing/batching/batch_manager_constuctors.py +10 -0
- opik/message_processing/batching/batchers.py +59 -27
- opik/message_processing/batching/flushing_thread.py +0 -3
- opik/message_processing/emulation/__init__.py +0 -0
- opik/message_processing/emulation/emulator_message_processor.py +578 -0
- opik/message_processing/emulation/local_emulator_message_processor.py +140 -0
- opik/message_processing/emulation/models.py +162 -0
- opik/message_processing/encoder_helpers.py +79 -0
- opik/message_processing/messages.py +56 -1
- opik/message_processing/preprocessing/__init__.py +0 -0
- opik/message_processing/preprocessing/attachments_preprocessor.py +70 -0
- opik/message_processing/preprocessing/batching_preprocessor.py +53 -0
- opik/message_processing/preprocessing/constants.py +1 -0
- opik/message_processing/preprocessing/file_upload_preprocessor.py +38 -0
- opik/message_processing/preprocessing/preprocessor.py +36 -0
- opik/message_processing/processors/__init__.py +0 -0
- opik/message_processing/processors/attachments_extraction_processor.py +146 -0
- opik/message_processing/processors/message_processors.py +92 -0
- opik/message_processing/processors/message_processors_chain.py +96 -0
- opik/message_processing/{message_processors.py → processors/online_message_processor.py} +85 -29
- opik/message_processing/queue_consumer.py +9 -3
- opik/message_processing/streamer.py +71 -33
- opik/message_processing/streamer_constructors.py +43 -10
- opik/opik_context.py +16 -4
- opik/plugins/pytest/hooks.py +5 -3
- opik/rest_api/__init__.py +346 -15
- opik/rest_api/alerts/__init__.py +7 -0
- opik/rest_api/alerts/client.py +667 -0
- opik/rest_api/alerts/raw_client.py +1015 -0
- opik/rest_api/alerts/types/__init__.py +7 -0
- opik/rest_api/alerts/types/get_webhook_examples_request_alert_type.py +5 -0
- opik/rest_api/annotation_queues/__init__.py +4 -0
- opik/rest_api/annotation_queues/client.py +668 -0
- opik/rest_api/annotation_queues/raw_client.py +1019 -0
- opik/rest_api/automation_rule_evaluators/client.py +34 -2
- opik/rest_api/automation_rule_evaluators/raw_client.py +24 -0
- opik/rest_api/client.py +15 -0
- opik/rest_api/dashboards/__init__.py +4 -0
- opik/rest_api/dashboards/client.py +462 -0
- opik/rest_api/dashboards/raw_client.py +648 -0
- opik/rest_api/datasets/client.py +1310 -44
- opik/rest_api/datasets/raw_client.py +2269 -358
- opik/rest_api/experiments/__init__.py +2 -2
- opik/rest_api/experiments/client.py +191 -5
- opik/rest_api/experiments/raw_client.py +301 -7
- opik/rest_api/experiments/types/__init__.py +4 -1
- opik/rest_api/experiments/types/experiment_update_status.py +5 -0
- opik/rest_api/experiments/types/experiment_update_type.py +5 -0
- opik/rest_api/experiments/types/experiment_write_status.py +5 -0
- opik/rest_api/feedback_definitions/types/find_feedback_definitions_request_type.py +1 -1
- opik/rest_api/llm_provider_key/client.py +20 -0
- opik/rest_api/llm_provider_key/raw_client.py +20 -0
- opik/rest_api/llm_provider_key/types/provider_api_key_write_provider.py +1 -1
- opik/rest_api/manual_evaluation/__init__.py +4 -0
- opik/rest_api/manual_evaluation/client.py +347 -0
- opik/rest_api/manual_evaluation/raw_client.py +543 -0
- opik/rest_api/optimizations/client.py +145 -9
- opik/rest_api/optimizations/raw_client.py +237 -13
- opik/rest_api/optimizations/types/optimization_update_status.py +3 -1
- opik/rest_api/prompts/__init__.py +2 -2
- opik/rest_api/prompts/client.py +227 -6
- opik/rest_api/prompts/raw_client.py +331 -2
- opik/rest_api/prompts/types/__init__.py +3 -1
- opik/rest_api/prompts/types/create_prompt_version_detail_template_structure.py +5 -0
- opik/rest_api/prompts/types/prompt_write_template_structure.py +5 -0
- opik/rest_api/spans/__init__.py +0 -2
- opik/rest_api/spans/client.py +238 -76
- opik/rest_api/spans/raw_client.py +307 -95
- opik/rest_api/spans/types/__init__.py +0 -2
- opik/rest_api/traces/client.py +572 -161
- opik/rest_api/traces/raw_client.py +736 -229
- opik/rest_api/types/__init__.py +352 -17
- opik/rest_api/types/aggregation_data.py +1 -0
- opik/rest_api/types/alert.py +33 -0
- opik/rest_api/types/alert_alert_type.py +5 -0
- opik/rest_api/types/alert_page_public.py +24 -0
- opik/rest_api/types/alert_public.py +33 -0
- opik/rest_api/types/alert_public_alert_type.py +5 -0
- opik/rest_api/types/alert_trigger.py +27 -0
- opik/rest_api/types/alert_trigger_config.py +28 -0
- opik/rest_api/types/alert_trigger_config_public.py +28 -0
- opik/rest_api/types/alert_trigger_config_public_type.py +10 -0
- opik/rest_api/types/alert_trigger_config_type.py +10 -0
- opik/rest_api/types/alert_trigger_config_write.py +22 -0
- opik/rest_api/types/alert_trigger_config_write_type.py +10 -0
- opik/rest_api/types/alert_trigger_event_type.py +19 -0
- opik/rest_api/types/alert_trigger_public.py +27 -0
- opik/rest_api/types/alert_trigger_public_event_type.py +19 -0
- opik/rest_api/types/alert_trigger_write.py +23 -0
- opik/rest_api/types/alert_trigger_write_event_type.py +19 -0
- opik/rest_api/types/alert_write.py +28 -0
- opik/rest_api/types/alert_write_alert_type.py +5 -0
- opik/rest_api/types/annotation_queue.py +42 -0
- opik/rest_api/types/annotation_queue_batch.py +27 -0
- opik/rest_api/types/annotation_queue_item_ids.py +19 -0
- opik/rest_api/types/annotation_queue_page_public.py +28 -0
- opik/rest_api/types/annotation_queue_public.py +38 -0
- opik/rest_api/types/annotation_queue_public_scope.py +5 -0
- opik/rest_api/types/annotation_queue_reviewer.py +20 -0
- opik/rest_api/types/annotation_queue_reviewer_public.py +20 -0
- opik/rest_api/types/annotation_queue_scope.py +5 -0
- opik/rest_api/types/annotation_queue_write.py +31 -0
- opik/rest_api/types/annotation_queue_write_scope.py +5 -0
- opik/rest_api/types/audio_url.py +19 -0
- opik/rest_api/types/audio_url_public.py +19 -0
- opik/rest_api/types/audio_url_write.py +19 -0
- opik/rest_api/types/automation_rule_evaluator.py +62 -2
- opik/rest_api/types/automation_rule_evaluator_llm_as_judge.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_llm_as_judge_public.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_llm_as_judge_write.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_object_object_public.py +155 -0
- opik/rest_api/types/automation_rule_evaluator_page_public.py +3 -2
- opik/rest_api/types/automation_rule_evaluator_public.py +57 -2
- opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_public.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_write.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python_public.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python_write.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_public.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_write.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_public.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_write.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_update.py +51 -1
- opik/rest_api/types/automation_rule_evaluator_update_llm_as_judge.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_update_span_llm_as_judge.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_update_span_user_defined_metric_python.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_update_trace_thread_llm_as_judge.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_update_trace_thread_user_defined_metric_python.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_update_user_defined_metric_python.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_public.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_write.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_write.py +51 -1
- opik/rest_api/types/boolean_feedback_definition.py +25 -0
- opik/rest_api/types/boolean_feedback_definition_create.py +20 -0
- opik/rest_api/types/boolean_feedback_definition_public.py +25 -0
- opik/rest_api/types/boolean_feedback_definition_update.py +20 -0
- opik/rest_api/types/boolean_feedback_detail.py +29 -0
- opik/rest_api/types/boolean_feedback_detail_create.py +29 -0
- opik/rest_api/types/boolean_feedback_detail_public.py +29 -0
- opik/rest_api/types/boolean_feedback_detail_update.py +29 -0
- opik/rest_api/types/dashboard_page_public.py +24 -0
- opik/rest_api/types/dashboard_public.py +30 -0
- opik/rest_api/types/dataset.py +4 -0
- opik/rest_api/types/dataset_expansion.py +42 -0
- opik/rest_api/types/dataset_expansion_response.py +39 -0
- opik/rest_api/types/dataset_item.py +2 -0
- opik/rest_api/types/dataset_item_changes_public.py +5 -0
- opik/rest_api/types/dataset_item_compare.py +2 -0
- opik/rest_api/types/dataset_item_filter.py +27 -0
- opik/rest_api/types/dataset_item_filter_operator.py +21 -0
- opik/rest_api/types/dataset_item_page_compare.py +5 -0
- opik/rest_api/types/dataset_item_page_public.py +5 -0
- opik/rest_api/types/dataset_item_public.py +2 -0
- opik/rest_api/types/dataset_item_update.py +39 -0
- opik/rest_api/types/dataset_item_write.py +1 -0
- opik/rest_api/types/dataset_public.py +4 -0
- opik/rest_api/types/dataset_public_status.py +5 -0
- opik/rest_api/types/dataset_status.py +5 -0
- opik/rest_api/types/dataset_version_diff.py +22 -0
- opik/rest_api/types/dataset_version_diff_stats.py +24 -0
- opik/rest_api/types/dataset_version_page_public.py +23 -0
- opik/rest_api/types/dataset_version_public.py +59 -0
- opik/rest_api/types/dataset_version_summary.py +46 -0
- opik/rest_api/types/dataset_version_summary_public.py +46 -0
- opik/rest_api/types/experiment.py +7 -2
- opik/rest_api/types/experiment_group_response.py +2 -0
- opik/rest_api/types/experiment_public.py +7 -2
- opik/rest_api/types/experiment_public_status.py +5 -0
- opik/rest_api/types/experiment_score.py +20 -0
- opik/rest_api/types/experiment_score_public.py +20 -0
- opik/rest_api/types/experiment_score_write.py +20 -0
- opik/rest_api/types/experiment_status.py +5 -0
- opik/rest_api/types/feedback.py +25 -1
- opik/rest_api/types/feedback_create.py +20 -1
- opik/rest_api/types/feedback_object_public.py +27 -1
- opik/rest_api/types/feedback_public.py +25 -1
- opik/rest_api/types/feedback_score_batch_item.py +2 -1
- opik/rest_api/types/feedback_score_batch_item_thread.py +2 -1
- opik/rest_api/types/feedback_score_public.py +4 -0
- opik/rest_api/types/feedback_update.py +20 -1
- opik/rest_api/types/group_content_with_aggregations.py +1 -0
- opik/rest_api/types/group_detail.py +19 -0
- opik/rest_api/types/group_details.py +20 -0
- opik/rest_api/types/guardrail.py +1 -0
- opik/rest_api/types/guardrail_write.py +1 -0
- opik/rest_api/types/ids_holder.py +19 -0
- opik/rest_api/types/image_url.py +20 -0
- opik/rest_api/types/image_url_public.py +20 -0
- opik/rest_api/types/image_url_write.py +20 -0
- opik/rest_api/types/llm_as_judge_message.py +5 -1
- opik/rest_api/types/llm_as_judge_message_content.py +26 -0
- opik/rest_api/types/llm_as_judge_message_content_public.py +26 -0
- opik/rest_api/types/llm_as_judge_message_content_write.py +26 -0
- opik/rest_api/types/llm_as_judge_message_public.py +5 -1
- opik/rest_api/types/llm_as_judge_message_write.py +5 -1
- opik/rest_api/types/llm_as_judge_model_parameters.py +3 -0
- opik/rest_api/types/llm_as_judge_model_parameters_public.py +3 -0
- opik/rest_api/types/llm_as_judge_model_parameters_write.py +3 -0
- opik/rest_api/types/manual_evaluation_request.py +38 -0
- opik/rest_api/types/manual_evaluation_request_entity_type.py +5 -0
- opik/rest_api/types/manual_evaluation_response.py +27 -0
- opik/rest_api/types/optimization.py +4 -2
- opik/rest_api/types/optimization_public.py +4 -2
- opik/rest_api/types/optimization_public_status.py +3 -1
- opik/rest_api/types/optimization_status.py +3 -1
- opik/rest_api/types/optimization_studio_config.py +27 -0
- opik/rest_api/types/optimization_studio_config_public.py +27 -0
- opik/rest_api/types/optimization_studio_config_write.py +27 -0
- opik/rest_api/types/optimization_studio_log.py +22 -0
- opik/rest_api/types/optimization_write.py +4 -2
- opik/rest_api/types/optimization_write_status.py +3 -1
- opik/rest_api/types/project.py +1 -0
- opik/rest_api/types/project_detailed.py +1 -0
- opik/rest_api/types/project_reference.py +31 -0
- opik/rest_api/types/project_reference_public.py +31 -0
- opik/rest_api/types/project_stats_summary_item.py +1 -0
- opik/rest_api/types/prompt.py +6 -0
- opik/rest_api/types/prompt_detail.py +6 -0
- opik/rest_api/types/prompt_detail_template_structure.py +5 -0
- opik/rest_api/types/prompt_public.py +6 -0
- opik/rest_api/types/prompt_public_template_structure.py +5 -0
- opik/rest_api/types/prompt_template_structure.py +5 -0
- opik/rest_api/types/prompt_version.py +3 -0
- opik/rest_api/types/prompt_version_detail.py +3 -0
- opik/rest_api/types/prompt_version_detail_template_structure.py +5 -0
- opik/rest_api/types/prompt_version_link.py +1 -0
- opik/rest_api/types/prompt_version_link_public.py +1 -0
- opik/rest_api/types/prompt_version_page_public.py +5 -0
- opik/rest_api/types/prompt_version_public.py +3 -0
- opik/rest_api/types/prompt_version_public_template_structure.py +5 -0
- opik/rest_api/types/prompt_version_template_structure.py +5 -0
- opik/rest_api/types/prompt_version_update.py +33 -0
- opik/rest_api/types/provider_api_key.py +9 -0
- opik/rest_api/types/provider_api_key_provider.py +1 -1
- opik/rest_api/types/provider_api_key_public.py +9 -0
- opik/rest_api/types/provider_api_key_public_provider.py +1 -1
- opik/rest_api/types/score_name.py +1 -0
- opik/rest_api/types/service_toggles_config.py +18 -0
- opik/rest_api/types/span.py +1 -2
- opik/rest_api/types/span_enrichment_options.py +31 -0
- opik/rest_api/types/span_experiment_item_bulk_write_view.py +1 -2
- opik/rest_api/types/span_filter.py +23 -0
- opik/rest_api/types/span_filter_operator.py +21 -0
- opik/rest_api/types/span_filter_write.py +23 -0
- opik/rest_api/types/span_filter_write_operator.py +21 -0
- opik/rest_api/types/span_llm_as_judge_code.py +27 -0
- opik/rest_api/types/span_llm_as_judge_code_public.py +27 -0
- opik/rest_api/types/span_llm_as_judge_code_write.py +27 -0
- opik/rest_api/types/span_public.py +1 -2
- opik/rest_api/types/span_update.py +46 -0
- opik/rest_api/types/span_user_defined_metric_python_code.py +20 -0
- opik/rest_api/types/span_user_defined_metric_python_code_public.py +20 -0
- opik/rest_api/types/span_user_defined_metric_python_code_write.py +20 -0
- opik/rest_api/types/span_write.py +1 -2
- opik/rest_api/types/studio_evaluation.py +20 -0
- opik/rest_api/types/studio_evaluation_public.py +20 -0
- opik/rest_api/types/studio_evaluation_write.py +20 -0
- opik/rest_api/types/studio_llm_model.py +21 -0
- opik/rest_api/types/studio_llm_model_public.py +21 -0
- opik/rest_api/types/studio_llm_model_write.py +21 -0
- opik/rest_api/types/studio_message.py +20 -0
- opik/rest_api/types/studio_message_public.py +20 -0
- opik/rest_api/types/studio_message_write.py +20 -0
- opik/rest_api/types/studio_metric.py +21 -0
- opik/rest_api/types/studio_metric_public.py +21 -0
- opik/rest_api/types/studio_metric_write.py +21 -0
- opik/rest_api/types/studio_optimizer.py +21 -0
- opik/rest_api/types/studio_optimizer_public.py +21 -0
- opik/rest_api/types/studio_optimizer_write.py +21 -0
- opik/rest_api/types/studio_prompt.py +20 -0
- opik/rest_api/types/studio_prompt_public.py +20 -0
- opik/rest_api/types/studio_prompt_write.py +20 -0
- opik/rest_api/types/trace.py +11 -2
- opik/rest_api/types/trace_enrichment_options.py +32 -0
- opik/rest_api/types/trace_experiment_item_bulk_write_view.py +1 -2
- opik/rest_api/types/trace_filter.py +23 -0
- opik/rest_api/types/trace_filter_operator.py +21 -0
- opik/rest_api/types/trace_filter_write.py +23 -0
- opik/rest_api/types/trace_filter_write_operator.py +21 -0
- opik/rest_api/types/trace_public.py +11 -2
- opik/rest_api/types/trace_thread_filter_write.py +23 -0
- opik/rest_api/types/trace_thread_filter_write_operator.py +21 -0
- opik/rest_api/types/trace_thread_identifier.py +1 -0
- opik/rest_api/types/trace_update.py +39 -0
- opik/rest_api/types/trace_write.py +1 -2
- opik/rest_api/types/value_entry.py +2 -0
- opik/rest_api/types/value_entry_compare.py +2 -0
- opik/rest_api/types/value_entry_experiment_item_bulk_write_view.py +2 -0
- opik/rest_api/types/value_entry_public.py +2 -0
- opik/rest_api/types/video_url.py +19 -0
- opik/rest_api/types/video_url_public.py +19 -0
- opik/rest_api/types/video_url_write.py +19 -0
- opik/rest_api/types/webhook.py +28 -0
- opik/rest_api/types/webhook_examples.py +19 -0
- opik/rest_api/types/webhook_public.py +28 -0
- opik/rest_api/types/webhook_test_result.py +23 -0
- opik/rest_api/types/webhook_test_result_status.py +5 -0
- opik/rest_api/types/webhook_write.py +23 -0
- opik/rest_api/types/welcome_wizard_tracking.py +22 -0
- opik/rest_api/types/workspace_configuration.py +5 -0
- opik/rest_api/welcome_wizard/__init__.py +4 -0
- opik/rest_api/welcome_wizard/client.py +195 -0
- opik/rest_api/welcome_wizard/raw_client.py +208 -0
- opik/rest_api/workspaces/client.py +14 -2
- opik/rest_api/workspaces/raw_client.py +10 -0
- opik/s3_httpx_client.py +14 -1
- opik/simulation/__init__.py +6 -0
- opik/simulation/simulated_user.py +99 -0
- opik/simulation/simulator.py +108 -0
- opik/synchronization.py +5 -6
- opik/{decorator/tracing_runtime_config.py → tracing_runtime_config.py} +6 -7
- opik/types.py +36 -0
- opik/validation/chat_prompt_messages.py +241 -0
- opik/validation/feedback_score.py +3 -3
- opik/validation/validator.py +28 -0
- opik-1.9.71.dist-info/METADATA +370 -0
- opik-1.9.71.dist-info/RECORD +1110 -0
- opik/api_objects/prompt/prompt.py +0 -112
- opik/cli.py +0 -193
- opik/hooks.py +0 -13
- opik/integrations/bedrock/chunks_aggregator.py +0 -55
- opik/integrations/bedrock/helpers.py +0 -8
- opik/rest_api/types/automation_rule_evaluator_object_public.py +0 -100
- opik/rest_api/types/json_node_experiment_item_bulk_write_view.py +0 -5
- opik-1.8.39.dist-info/METADATA +0 -339
- opik-1.8.39.dist-info/RECORD +0 -790
- /opik/{evaluation/metrics/conversation/conversational_coherence → decorator/context_manager}/__init__.py +0 -0
- /opik/evaluation/metrics/conversation/{session_completeness → llm_judges/conversational_coherence}/__init__.py +0 -0
- /opik/evaluation/metrics/conversation/{conversational_coherence → llm_judges/conversational_coherence}/schema.py +0 -0
- /opik/evaluation/metrics/conversation/{user_frustration → llm_judges/session_completeness}/__init__.py +0 -0
- /opik/evaluation/metrics/conversation/{session_completeness → llm_judges/session_completeness}/schema.py +0 -0
- /opik/evaluation/metrics/conversation/{user_frustration → llm_judges/user_frustration}/schema.py +0 -0
- /opik/integrations/bedrock/{stream_wrappers.py → converse/stream_wrappers.py} +0 -0
- /opik/rest_api/{spans/types → types}/span_update_type.py +0 -0
- {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/WHEEL +0 -0
- {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/entry_points.txt +0 -0
- {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/licenses/LICENSE +0 -0
- {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Optional
|
|
1
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
2
|
+
import json
|
|
3
|
+
import dataclasses
|
|
2
4
|
|
|
5
|
+
import opik.exceptions
|
|
3
6
|
from opik.rest_api import client as rest_client
|
|
4
7
|
from opik.rest_api import core as rest_api_core
|
|
5
|
-
from opik.rest_api.types import prompt_version_detail
|
|
8
|
+
from opik.rest_api.types import prompt_version_detail
|
|
9
|
+
from . import types as prompt_types
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
|
|
12
|
+
@dataclasses.dataclass
|
|
13
|
+
class PromptSearchResult:
|
|
14
|
+
"""Result from searching prompts, containing name, template structure, and latest version details."""
|
|
15
|
+
|
|
16
|
+
name: str
|
|
17
|
+
template_structure: str
|
|
18
|
+
prompt_version_detail: prompt_version_detail.PromptVersionDetail
|
|
8
19
|
|
|
9
20
|
|
|
10
21
|
class PromptClient:
|
|
@@ -16,7 +27,8 @@ class PromptClient:
|
|
|
16
27
|
name: str,
|
|
17
28
|
prompt: str,
|
|
18
29
|
metadata: Optional[Dict[str, Any]],
|
|
19
|
-
type: PromptType = PromptType.MUSTACHE,
|
|
30
|
+
type: prompt_types.PromptType = prompt_types.PromptType.MUSTACHE,
|
|
31
|
+
template_structure: str = "text",
|
|
20
32
|
) -> prompt_version_detail.PromptVersionDetail:
|
|
21
33
|
"""
|
|
22
34
|
Creates the prompt detail for the given prompt name and template.
|
|
@@ -24,20 +36,59 @@ class PromptClient:
|
|
|
24
36
|
Parameters:
|
|
25
37
|
- name: The name of the prompt.
|
|
26
38
|
- prompt: The template content for the prompt.
|
|
39
|
+
- metadata: Optional metadata for the prompt.
|
|
40
|
+
- type: The template type (MUSTACHE or JINJA2).
|
|
41
|
+
- template_structure: Either "text" (default) or "chat".
|
|
27
42
|
|
|
28
43
|
Returns:
|
|
29
44
|
- A Prompt object for the provided prompt name and template.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
- PromptTemplateStructureMismatch: If a prompt with the same name already exists but has a different
|
|
48
|
+
template_structure (e.g., trying to create a text prompt when a chat prompt exists, or vice versa).
|
|
49
|
+
Template structure is immutable after prompt creation.
|
|
30
50
|
"""
|
|
31
51
|
prompt_version = self._get_latest_version(name)
|
|
32
52
|
|
|
53
|
+
# For chat prompts, compare parsed JSON to avoid formatting differences
|
|
54
|
+
templates_equal = False
|
|
55
|
+
|
|
56
|
+
if prompt_version is not None:
|
|
57
|
+
if prompt_version.template_structure != template_structure:
|
|
58
|
+
raise opik.exceptions.PromptTemplateStructureMismatch(
|
|
59
|
+
prompt_name=name,
|
|
60
|
+
existing_structure=prompt_version.template_structure,
|
|
61
|
+
attempted_structure=template_structure,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if template_structure == "chat":
|
|
65
|
+
try:
|
|
66
|
+
existing_messages = json.loads(prompt_version.template)
|
|
67
|
+
new_messages = json.loads(prompt)
|
|
68
|
+
templates_equal = existing_messages == new_messages
|
|
69
|
+
except (json.JSONDecodeError, TypeError):
|
|
70
|
+
templates_equal = prompt_version.template == prompt
|
|
71
|
+
else:
|
|
72
|
+
templates_equal = prompt_version.template == prompt
|
|
73
|
+
|
|
74
|
+
# Create a new version if:
|
|
75
|
+
# - No version exists yet (new prompt)
|
|
76
|
+
# - Template content has changed
|
|
77
|
+
# - Metadata has changed
|
|
78
|
+
# - Type has changed
|
|
79
|
+
# Note: template_structure is immutable and used by the backend only if it is the first prompt version.
|
|
33
80
|
if (
|
|
34
81
|
prompt_version is None
|
|
35
|
-
or
|
|
82
|
+
or not templates_equal
|
|
36
83
|
or prompt_version.metadata != metadata
|
|
37
84
|
or prompt_version.type != type.value
|
|
38
85
|
):
|
|
39
86
|
prompt_version = self._create_new_version(
|
|
40
|
-
name=name,
|
|
87
|
+
name=name,
|
|
88
|
+
prompt=prompt,
|
|
89
|
+
type=type,
|
|
90
|
+
metadata=metadata,
|
|
91
|
+
template_structure=template_structure,
|
|
41
92
|
)
|
|
42
93
|
|
|
43
94
|
return prompt_version
|
|
@@ -46,8 +97,9 @@ class PromptClient:
|
|
|
46
97
|
self,
|
|
47
98
|
name: str,
|
|
48
99
|
prompt: str,
|
|
49
|
-
type: PromptVersionDetailType,
|
|
100
|
+
type: prompt_version_detail.PromptVersionDetailType,
|
|
50
101
|
metadata: Optional[Dict[str, Any]],
|
|
102
|
+
template_structure: str = "text",
|
|
51
103
|
) -> prompt_version_detail.PromptVersionDetail:
|
|
52
104
|
new_prompt_version_detail_data = prompt_version_detail.PromptVersionDetail(
|
|
53
105
|
template=prompt,
|
|
@@ -58,6 +110,7 @@ class PromptClient:
|
|
|
58
110
|
self._rest_client.prompts.create_prompt_version(
|
|
59
111
|
name=name,
|
|
60
112
|
version=new_prompt_version_detail_data,
|
|
113
|
+
template_structure=template_structure,
|
|
61
114
|
)
|
|
62
115
|
)
|
|
63
116
|
return new_prompt_version_detail
|
|
@@ -65,20 +118,13 @@ class PromptClient:
|
|
|
65
118
|
def _get_latest_version(
|
|
66
119
|
self, name: str
|
|
67
120
|
) -> Optional[prompt_version_detail.PromptVersionDetail]:
|
|
68
|
-
|
|
69
|
-
prompt_latest_version = self._rest_client.prompts.retrieve_prompt_version(
|
|
70
|
-
name=name
|
|
71
|
-
)
|
|
72
|
-
return prompt_latest_version
|
|
73
|
-
except rest_api_core.ApiError as e:
|
|
74
|
-
if e.status_code != 404:
|
|
75
|
-
raise e
|
|
76
|
-
return None
|
|
121
|
+
return self.get_prompt(name=name, commit=None)
|
|
77
122
|
|
|
78
123
|
def get_prompt(
|
|
79
124
|
self,
|
|
80
125
|
name: str,
|
|
81
126
|
commit: Optional[str] = None,
|
|
127
|
+
raise_if_not_template_structure: Optional[str] = None,
|
|
82
128
|
) -> Optional[prompt_version_detail.PromptVersionDetail]:
|
|
83
129
|
"""
|
|
84
130
|
Retrieve the prompt detail for a given prompt name and commit version.
|
|
@@ -86,6 +132,7 @@ class PromptClient:
|
|
|
86
132
|
Parameters:
|
|
87
133
|
name: The name of the prompt.
|
|
88
134
|
commit: An optional commit version of the prompt. If not provided, the latest version is retrieved.
|
|
135
|
+
raise_if_not_template_structure: Optional template structure validation. If provided and doesn't match, raises PromptTemplateStructureMismatch.
|
|
89
136
|
|
|
90
137
|
Returns:
|
|
91
138
|
Prompt: The details of the specified prompt.
|
|
@@ -95,17 +142,35 @@ class PromptClient:
|
|
|
95
142
|
name=name,
|
|
96
143
|
commit=commit,
|
|
97
144
|
)
|
|
98
|
-
return prompt_version
|
|
99
145
|
|
|
146
|
+
should_skip_validation = (
|
|
147
|
+
prompt_version.template_structure is None
|
|
148
|
+
and raise_if_not_template_structure == "text"
|
|
149
|
+
)
|
|
150
|
+
if should_skip_validation:
|
|
151
|
+
return prompt_version
|
|
152
|
+
|
|
153
|
+
# Client-side validation for template_structure if requested and not skipped
|
|
154
|
+
if (
|
|
155
|
+
raise_if_not_template_structure is not None
|
|
156
|
+
and prompt_version.template_structure != raise_if_not_template_structure
|
|
157
|
+
):
|
|
158
|
+
raise opik.exceptions.PromptTemplateStructureMismatch(
|
|
159
|
+
prompt_name=name,
|
|
160
|
+
existing_structure=prompt_version.template_structure,
|
|
161
|
+
attempted_structure=raise_if_not_template_structure,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return prompt_version
|
|
100
165
|
except rest_api_core.ApiError as e:
|
|
101
166
|
if e.status_code != 404:
|
|
102
167
|
raise e
|
|
103
|
-
|
|
168
|
+
# 400, 404 - not found
|
|
104
169
|
return None
|
|
105
170
|
|
|
106
171
|
# TODO: Need to add support for prompt name in the BE so we don't
|
|
107
172
|
# need to retrieve the prompt id
|
|
108
|
-
def
|
|
173
|
+
def get_all_prompt_versions(
|
|
109
174
|
self, name: str
|
|
110
175
|
) -> List[prompt_version_detail.PromptVersionDetail]:
|
|
111
176
|
"""
|
|
@@ -134,42 +199,119 @@ class PromptClient:
|
|
|
134
199
|
raise ValueError("No prompts found for name: " + name)
|
|
135
200
|
|
|
136
201
|
prompt_id = filtered_prompt_list[0]
|
|
202
|
+
return self._get_prompt_versions_by_id_paginated(prompt_id)
|
|
137
203
|
|
|
138
|
-
|
|
139
|
-
|
|
204
|
+
except rest_api_core.ApiError as e:
|
|
205
|
+
if e.status_code != 404:
|
|
206
|
+
raise e
|
|
207
|
+
|
|
208
|
+
return []
|
|
209
|
+
|
|
210
|
+
def _get_prompt_versions_by_id_paginated(
|
|
211
|
+
self, prompt_id: str
|
|
212
|
+
) -> List[prompt_version_detail.PromptVersionDetail]:
|
|
213
|
+
page = 1
|
|
214
|
+
size = 100
|
|
215
|
+
prompts: List[prompt_version_detail.PromptVersionDetail] = []
|
|
216
|
+
while True:
|
|
217
|
+
prompt_versions_page = self._rest_client.prompts.get_prompt_versions(
|
|
218
|
+
id=prompt_id, page=page, size=size
|
|
219
|
+
).content
|
|
220
|
+
|
|
221
|
+
versions = prompt_versions_page or []
|
|
222
|
+
prompts.extend(
|
|
223
|
+
[
|
|
224
|
+
# Converting to PromptVersionDetail for consistency with other methods.
|
|
225
|
+
# TODO: backend should implement non-frontend endpoint which will return PromptVersionDetail objects
|
|
226
|
+
prompt_version_detail.PromptVersionDetail(
|
|
227
|
+
id=version.id,
|
|
228
|
+
prompt_id=version.prompt_id,
|
|
229
|
+
template=version.template,
|
|
230
|
+
type=version.type,
|
|
231
|
+
metadata=version.metadata,
|
|
232
|
+
commit=version.commit,
|
|
233
|
+
created_at=version.created_at,
|
|
234
|
+
created_by=version.created_by,
|
|
235
|
+
)
|
|
236
|
+
for version in versions
|
|
237
|
+
]
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if len(versions) < size:
|
|
241
|
+
break
|
|
242
|
+
page += 1
|
|
243
|
+
|
|
244
|
+
return prompts
|
|
245
|
+
|
|
246
|
+
def search_prompts(
|
|
247
|
+
self,
|
|
248
|
+
*,
|
|
249
|
+
name: Optional[str] = None,
|
|
250
|
+
parsed_filters: Optional[List[Dict[str, Any]]] = None,
|
|
251
|
+
) -> List[PromptSearchResult]:
|
|
252
|
+
"""
|
|
253
|
+
Search prompt containers by optional name substring and filters, then
|
|
254
|
+
return the latest version detail for each matched prompt container.
|
|
255
|
+
|
|
256
|
+
Parameters:
|
|
257
|
+
name: Optional substring of the prompt name to search for.
|
|
258
|
+
parsed_filters: List of parsed filters (OQL) that will be stringified for the backend.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
List[PromptSearchResult]: Each result contains name, template_structure, and prompt_version_detail.
|
|
262
|
+
"""
|
|
263
|
+
try:
|
|
264
|
+
filters_str = (
|
|
265
|
+
json.dumps(parsed_filters) if parsed_filters is not None else None
|
|
266
|
+
)
|
|
140
267
|
|
|
141
|
-
|
|
268
|
+
# Page through all prompt containers and collect name + template_structure
|
|
269
|
+
page = 1
|
|
270
|
+
size = 1000
|
|
271
|
+
prompt_info: List[Tuple[str, str]] = [] # (name, template_structure)
|
|
142
272
|
while True:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
[
|
|
149
|
-
# Converting to PromptVersionDetail for consistency with other methods.
|
|
150
|
-
# TODO: backend should implement non-frontend endpoint which will return PromptVersionDetail objects
|
|
151
|
-
prompt_version_detail.PromptVersionDetail(
|
|
152
|
-
id=version.id,
|
|
153
|
-
prompt_id=version.prompt_id,
|
|
154
|
-
template=version.template,
|
|
155
|
-
type=version.type,
|
|
156
|
-
metadata=version.metadata,
|
|
157
|
-
commit=version.commit,
|
|
158
|
-
created_at=version.created_at,
|
|
159
|
-
created_by=version.created_by,
|
|
160
|
-
)
|
|
161
|
-
for version in prompt_versions
|
|
162
|
-
]
|
|
273
|
+
prompts_page = self._rest_client.prompts.get_prompts(
|
|
274
|
+
page=page,
|
|
275
|
+
size=size,
|
|
276
|
+
name=name,
|
|
277
|
+
filters=filters_str,
|
|
163
278
|
)
|
|
164
|
-
|
|
165
|
-
if len(
|
|
279
|
+
content = prompts_page.content or []
|
|
280
|
+
if len(content) == 0:
|
|
281
|
+
break
|
|
282
|
+
prompt_info.extend(
|
|
283
|
+
[(p.name, p.template_structure or "text") for p in content]
|
|
284
|
+
)
|
|
285
|
+
if len(content) < size:
|
|
166
286
|
break
|
|
167
287
|
page += 1
|
|
168
288
|
|
|
169
|
-
|
|
289
|
+
if len(prompt_info) == 0:
|
|
290
|
+
return []
|
|
291
|
+
|
|
292
|
+
# Retrieve latest version for each container name
|
|
293
|
+
results: List[PromptSearchResult] = []
|
|
294
|
+
for prompt_name, template_structure in prompt_info:
|
|
295
|
+
try:
|
|
296
|
+
latest_version = self._rest_client.prompts.retrieve_prompt_version(
|
|
297
|
+
name=prompt_name,
|
|
298
|
+
)
|
|
299
|
+
results.append(
|
|
300
|
+
PromptSearchResult(
|
|
301
|
+
name=prompt_name,
|
|
302
|
+
template_structure=template_structure,
|
|
303
|
+
prompt_version_detail=latest_version,
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
except rest_api_core.ApiError as e:
|
|
307
|
+
# Skip prompts that can't be retrieved (e.g., deleted between search and retrieval)
|
|
308
|
+
if e.status_code == 404:
|
|
309
|
+
continue
|
|
310
|
+
raise e
|
|
311
|
+
|
|
312
|
+
return results
|
|
170
313
|
|
|
171
314
|
except rest_api_core.ApiError as e:
|
|
172
315
|
if e.status_code != 404:
|
|
173
316
|
raise e
|
|
174
|
-
|
|
175
|
-
return []
|
|
317
|
+
return []
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Empty - all exports handled by parent __init__.py
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Dict, Optional, Union, List
|
|
5
|
+
from typing_extensions import override
|
|
6
|
+
from opik.rest_api import types as rest_api_types
|
|
7
|
+
from . import prompt_template
|
|
8
|
+
from .. import types as prompt_types
|
|
9
|
+
from .. import client as prompt_client
|
|
10
|
+
from .. import base_prompt
|
|
11
|
+
|
|
12
|
+
LOGGER = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Prompt(base_prompt.BasePrompt):
|
|
16
|
+
"""
|
|
17
|
+
Prompt class represents a prompt with a name, prompt text/template and commit hash.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
name: str,
|
|
23
|
+
prompt: str,
|
|
24
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
25
|
+
type: prompt_types.PromptType = prompt_types.PromptType.MUSTACHE,
|
|
26
|
+
validate_placeholders: bool = True,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Initializes a new instance of the class with the given parameters.
|
|
30
|
+
Creates a new text prompt using the opik client and sets the initial state of the instance attributes based on the created prompt.
|
|
31
|
+
|
|
32
|
+
Parameters:
|
|
33
|
+
name: The name for the prompt.
|
|
34
|
+
prompt: The template for the prompt.
|
|
35
|
+
metadata: Optional metadata for the prompt.
|
|
36
|
+
type: The template type (MUSTACHE or JINJA2).
|
|
37
|
+
validate_placeholders: Whether to validate template placeholders.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
PromptTemplateStructureMismatch: If a chat prompt with the same name already exists (template structure is immutable).
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
self._template = prompt_template.PromptTemplate(
|
|
44
|
+
template=prompt, type=type, validate_placeholders=validate_placeholders
|
|
45
|
+
)
|
|
46
|
+
self._name = name
|
|
47
|
+
self._metadata = metadata
|
|
48
|
+
self._type = type
|
|
49
|
+
|
|
50
|
+
self._sync_with_backend()
|
|
51
|
+
|
|
52
|
+
def _sync_with_backend(self) -> None:
|
|
53
|
+
from opik.api_objects import opik_client
|
|
54
|
+
|
|
55
|
+
opik_client_ = opik_client.get_client_cached()
|
|
56
|
+
prompt_client_ = prompt_client.PromptClient(opik_client_.rest_client)
|
|
57
|
+
prompt_version = prompt_client_.create_prompt(
|
|
58
|
+
name=self._name,
|
|
59
|
+
prompt=self._template.text,
|
|
60
|
+
metadata=self._metadata,
|
|
61
|
+
type=self._type,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
self._commit = prompt_version.commit
|
|
65
|
+
self.__internal_api__prompt_id__ = prompt_version.prompt_id
|
|
66
|
+
self.__internal_api__version_id__ = prompt_version.id
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
@override
|
|
70
|
+
def name(self) -> str:
|
|
71
|
+
"""The name of the prompt."""
|
|
72
|
+
return self._name
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def prompt(self) -> str:
|
|
76
|
+
"""The latest template of the prompt."""
|
|
77
|
+
return str(self._template)
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
@override
|
|
81
|
+
def commit(self) -> Optional[str]:
|
|
82
|
+
"""The commit hash of the prompt."""
|
|
83
|
+
return self._commit
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
@override
|
|
87
|
+
def metadata(self) -> Optional[Dict[str, Any]]:
|
|
88
|
+
"""The metadata dictionary associated with the prompt"""
|
|
89
|
+
return copy.deepcopy(self._metadata)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
@override
|
|
93
|
+
def type(self) -> prompt_types.PromptType:
|
|
94
|
+
"""The prompt type of the prompt."""
|
|
95
|
+
return self._type
|
|
96
|
+
|
|
97
|
+
@override
|
|
98
|
+
def format(self, **kwargs: Any) -> Union[str, List[Dict[str, Any]]]:
|
|
99
|
+
"""
|
|
100
|
+
Replaces placeholders in the template with provided keyword arguments.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
**kwargs: Arbitrary keyword arguments where the key represents the placeholder
|
|
104
|
+
in the template and the value is the value to replace the placeholder with.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
A string with all placeholders replaced by their corresponding values from kwargs.
|
|
108
|
+
"""
|
|
109
|
+
is_playground_chat_prompt = (
|
|
110
|
+
self._metadata is not None
|
|
111
|
+
and self._metadata.get("created_from") == "opik_ui"
|
|
112
|
+
and self._metadata.get("type") == "messages_json"
|
|
113
|
+
)
|
|
114
|
+
formatted_string = self._template.format(**kwargs)
|
|
115
|
+
|
|
116
|
+
if is_playground_chat_prompt:
|
|
117
|
+
try:
|
|
118
|
+
return json.loads(formatted_string)
|
|
119
|
+
except json.JSONDecodeError:
|
|
120
|
+
LOGGER.error(
|
|
121
|
+
f"Failed to parse JSON string: {formatted_string}. Make sure chat prompt is valid JSON. Returning the raw string."
|
|
122
|
+
)
|
|
123
|
+
return formatted_string
|
|
124
|
+
|
|
125
|
+
return formatted_string
|
|
126
|
+
|
|
127
|
+
@override
|
|
128
|
+
def __internal_api__to_info_dict__(self) -> Dict[str, Any]:
|
|
129
|
+
"""
|
|
130
|
+
Convert the prompt to an info dictionary for serialization.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Dictionary containing prompt metadata and version information.
|
|
134
|
+
"""
|
|
135
|
+
info_dict: Dict[str, Any] = {
|
|
136
|
+
"name": self.name,
|
|
137
|
+
"version": {
|
|
138
|
+
"template": self.prompt,
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if self.__internal_api__prompt_id__ is not None:
|
|
143
|
+
info_dict["id"] = self.__internal_api__prompt_id__
|
|
144
|
+
|
|
145
|
+
if self.commit is not None:
|
|
146
|
+
info_dict["version"]["commit"] = self.commit
|
|
147
|
+
|
|
148
|
+
if self.__internal_api__version_id__ is not None:
|
|
149
|
+
info_dict["version"]["id"] = self.__internal_api__version_id__
|
|
150
|
+
|
|
151
|
+
return info_dict
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def from_fern_prompt_version(
|
|
155
|
+
cls,
|
|
156
|
+
name: str,
|
|
157
|
+
prompt_version: rest_api_types.PromptVersionDetail,
|
|
158
|
+
) -> "Prompt":
|
|
159
|
+
# will not call __init__ to avoid API calls, create new instance with __new__
|
|
160
|
+
prompt = cls.__new__(cls)
|
|
161
|
+
|
|
162
|
+
prompt.__internal_api__version_id__ = prompt_version.id
|
|
163
|
+
prompt.__internal_api__prompt_id__ = prompt_version.prompt_id
|
|
164
|
+
|
|
165
|
+
prompt._name = name
|
|
166
|
+
prompt._template = prompt_template.PromptTemplate(
|
|
167
|
+
template=prompt_version.template,
|
|
168
|
+
type=prompt_types.PromptType(prompt_version.type)
|
|
169
|
+
or prompt_types.PromptType.MUSTACHE,
|
|
170
|
+
)
|
|
171
|
+
prompt._commit = prompt_version.commit
|
|
172
|
+
prompt._metadata = prompt_version.metadata
|
|
173
|
+
prompt._type = prompt_version.type
|
|
174
|
+
return prompt
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from typing import Any, Set
|
|
3
|
+
from typing_extensions import override
|
|
3
4
|
import jinja2
|
|
4
5
|
|
|
5
6
|
import opik.exceptions as exceptions
|
|
6
|
-
from
|
|
7
|
+
from .. import types as prompt_types
|
|
8
|
+
from .. import base_prompt_template
|
|
7
9
|
|
|
8
10
|
|
|
9
|
-
class PromptTemplate:
|
|
11
|
+
class PromptTemplate(base_prompt_template.BasePromptTemplate):
|
|
10
12
|
def __init__(
|
|
11
13
|
self,
|
|
12
14
|
template: str,
|
|
13
15
|
validate_placeholders: bool = True,
|
|
14
|
-
type: PromptType = PromptType.MUSTACHE,
|
|
16
|
+
type: prompt_types.PromptType = prompt_types.PromptType.MUSTACHE,
|
|
15
17
|
) -> None:
|
|
16
18
|
self._template = template
|
|
17
19
|
self._type = type
|
|
@@ -21,8 +23,9 @@ class PromptTemplate:
|
|
|
21
23
|
def text(self) -> str:
|
|
22
24
|
return self._template
|
|
23
25
|
|
|
26
|
+
@override
|
|
24
27
|
def format(self, **kwargs: Any) -> str:
|
|
25
|
-
if self._type == PromptType.MUSTACHE:
|
|
28
|
+
if self._type == prompt_types.PromptType.MUSTACHE:
|
|
26
29
|
template = self._template
|
|
27
30
|
placeholders = _extract_mustache_placeholder_keys(self._template)
|
|
28
31
|
kwargs_keys: Set[str] = set(kwargs.keys())
|
|
@@ -33,9 +36,10 @@ class PromptTemplate:
|
|
|
33
36
|
)
|
|
34
37
|
|
|
35
38
|
for key, value in kwargs.items():
|
|
36
|
-
|
|
39
|
+
replacement = "" if value is None else str(value)
|
|
40
|
+
template = template.replace(f"{{{{{key}}}}}", replacement)
|
|
37
41
|
|
|
38
|
-
elif self._type == PromptType.JINJA2:
|
|
42
|
+
elif self._type == prompt_types.PromptType.JINJA2:
|
|
39
43
|
template = jinja2.Template(self._template).render(**kwargs)
|
|
40
44
|
else:
|
|
41
45
|
template = self._template
|
opik/api_objects/prompt/types.py
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, List, Literal, Mapping, Optional, Union, Set
|
|
4
|
+
|
|
1
5
|
import enum
|
|
2
6
|
|
|
3
7
|
|
|
4
8
|
class PromptType(str, enum.Enum):
|
|
5
9
|
MUSTACHE = "mustache"
|
|
6
10
|
JINJA2 = "jinja2"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Core multimodal/chat prompt related types
|
|
14
|
+
MessageContent = Union[str, List[Dict[str, Any]]]
|
|
15
|
+
ContentPart = Dict[str, Any]
|
|
16
|
+
RendererFn = Callable[[ContentPart, Dict[str, Any], PromptType], Optional[ContentPart]]
|
|
17
|
+
ModalityName = Literal["vision", "video"]
|
|
18
|
+
SupportedModalities = Mapping[ModalityName, bool]
|
|
19
|
+
ModalitySet = Set[ModalityName]
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"PromptType",
|
|
23
|
+
"MessageContent",
|
|
24
|
+
"ContentPart",
|
|
25
|
+
"RendererFn",
|
|
26
|
+
"ModalityName",
|
|
27
|
+
"SupportedModalities",
|
|
28
|
+
"ModalitySet",
|
|
29
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from typing import Optional, List, Callable, Any
|
|
2
|
+
|
|
3
|
+
from opik import synchronization
|
|
4
|
+
from opik.api_objects import rest_stream_parser
|
|
5
|
+
from opik.api_objects.helpers import OptionalFilterParsedItemList
|
|
6
|
+
from opik.rest_api import client as rest_api_client
|
|
7
|
+
from opik.rest_api.types import span_public, trace_public
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def search_spans_with_filters(
|
|
11
|
+
rest_client: rest_api_client.OpikApi,
|
|
12
|
+
trace_id: Optional[str],
|
|
13
|
+
project_name: str,
|
|
14
|
+
filters: Optional[OptionalFilterParsedItemList],
|
|
15
|
+
max_results: int,
|
|
16
|
+
truncate: bool,
|
|
17
|
+
) -> List[span_public.SpanPublic]:
|
|
18
|
+
spans = rest_stream_parser.read_and_parse_full_stream(
|
|
19
|
+
read_source=lambda current_batch_size,
|
|
20
|
+
last_retrieved_id: rest_client.spans.search_spans(
|
|
21
|
+
trace_id=trace_id,
|
|
22
|
+
project_name=project_name,
|
|
23
|
+
filters=filters,
|
|
24
|
+
limit=current_batch_size,
|
|
25
|
+
truncate=truncate,
|
|
26
|
+
last_retrieved_id=last_retrieved_id,
|
|
27
|
+
),
|
|
28
|
+
max_results=max_results,
|
|
29
|
+
parsed_item_class=span_public.SpanPublic,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return spans
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def search_traces_with_filters(
|
|
36
|
+
rest_client: rest_api_client.OpikApi,
|
|
37
|
+
project_name: Optional[str],
|
|
38
|
+
filters: Optional[OptionalFilterParsedItemList],
|
|
39
|
+
max_results: int,
|
|
40
|
+
truncate: bool,
|
|
41
|
+
) -> List[trace_public.TracePublic]:
|
|
42
|
+
traces = rest_stream_parser.read_and_parse_full_stream(
|
|
43
|
+
read_source=lambda current_batch_size,
|
|
44
|
+
last_retrieved_id: rest_client.traces.search_traces(
|
|
45
|
+
project_name=project_name,
|
|
46
|
+
filters=filters,
|
|
47
|
+
limit=current_batch_size,
|
|
48
|
+
truncate=truncate,
|
|
49
|
+
last_retrieved_id=last_retrieved_id,
|
|
50
|
+
),
|
|
51
|
+
max_results=max_results,
|
|
52
|
+
parsed_item_class=trace_public.TracePublic,
|
|
53
|
+
)
|
|
54
|
+
return traces
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def search_and_wait_for_done(
|
|
58
|
+
search_functor: Callable[[], List[Any]],
|
|
59
|
+
wait_for_at_least: int,
|
|
60
|
+
wait_for_timeout: int,
|
|
61
|
+
sleep_time: float,
|
|
62
|
+
) -> List[Any]:
|
|
63
|
+
"""
|
|
64
|
+
The expected behavior is to keep making repeated calls until either the specified number of
|
|
65
|
+
results is found or the timeout is reached. The function will then return the best possible
|
|
66
|
+
attempt results to meet these conditions.
|
|
67
|
+
Args:
|
|
68
|
+
search_functor: The function to call to retrieve the results.
|
|
69
|
+
wait_for_at_least: The minimum number of results to return.
|
|
70
|
+
wait_for_timeout: The timeout for waiting for results.
|
|
71
|
+
sleep_time: The time to sleep between calls to search_functor.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
The function returns the results of the best possible attempt to meet both waiting conditions.
|
|
75
|
+
"""
|
|
76
|
+
result: List[Any] = []
|
|
77
|
+
|
|
78
|
+
def search() -> List[Any]:
|
|
79
|
+
nonlocal result
|
|
80
|
+
result = search_functor()
|
|
81
|
+
return result
|
|
82
|
+
|
|
83
|
+
synchronization.wait_for_done(
|
|
84
|
+
check_function=lambda: len(search()) >= wait_for_at_least,
|
|
85
|
+
timeout=wait_for_timeout,
|
|
86
|
+
sleep_time=sleep_time,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return result
|