opik 1.6.4__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 +33 -2
- 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/__init__.py +5 -0
- opik/api_objects/attachment/attachment.py +20 -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 +220 -0
- opik/api_objects/attachment/converters.py +51 -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/conversation/__init__.py +0 -0
- opik/api_objects/conversation/conversation_factory.py +43 -0
- opik/api_objects/conversation/conversation_thread.py +49 -0
- opik/api_objects/data_helpers.py +79 -0
- opik/api_objects/dataset/dataset.py +107 -45
- opik/api_objects/dataset/rest_operations.py +12 -3
- opik/api_objects/experiment/experiment.py +81 -45
- 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 +88 -19
- opik/api_objects/helpers.py +104 -7
- opik/api_objects/local_recording.py +81 -0
- opik/api_objects/opik_client.py +872 -174
- opik/api_objects/opik_query_language.py +136 -18
- opik/api_objects/optimization/__init__.py +3 -0
- opik/api_objects/optimization/optimization.py +39 -0
- opik/api_objects/prompt/__init__.py +13 -1
- 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 +193 -41
- opik/api_objects/prompt/text/__init__.py +1 -0
- opik/api_objects/prompt/text/prompt.py +174 -0
- opik/api_objects/prompt/text/prompt_template.py +55 -0
- opik/api_objects/prompt/types.py +29 -0
- opik/api_objects/rest_stream_parser.py +98 -0
- opik/api_objects/search_helpers.py +89 -0
- opik/api_objects/span/span_client.py +165 -45
- opik/api_objects/span/span_data.py +136 -25
- opik/api_objects/threads/__init__.py +0 -0
- opik/api_objects/threads/threads_client.py +185 -0
- opik/api_objects/trace/trace_client.py +72 -36
- opik/api_objects/trace/trace_data.py +112 -26
- 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 +62 -4
- opik/configurator/configure.py +45 -6
- opik/configurator/opik_rest_helpers.py +4 -1
- opik/context_storage.py +164 -65
- opik/datetime_helpers.py +12 -0
- opik/decorator/arguments_helpers.py +9 -1
- opik/decorator/base_track_decorator.py +298 -146
- opik/decorator/context_manager/__init__.py +0 -0
- opik/decorator/context_manager/span_context_manager.py +123 -0
- opik/decorator/context_manager/trace_context_manager.py +84 -0
- opik/decorator/generator_wrappers.py +3 -2
- opik/decorator/inspect_helpers.py +11 -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 +49 -21
- opik/decorator/tracker.py +9 -1
- opik/dict_utils.py +3 -3
- opik/environment.py +13 -1
- opik/error_tracking/api.py +1 -1
- opik/error_tracking/before_send.py +6 -5
- opik/error_tracking/environment_details.py +29 -7
- opik/error_tracking/error_filtering/filter_by_response_status_code.py +42 -0
- opik/error_tracking/error_filtering/filter_chain_builder.py +14 -3
- opik/evaluation/__init__.py +14 -2
- opik/evaluation/engine/engine.py +280 -82
- opik/evaluation/engine/evaluation_tasks_executor.py +15 -10
- opik/evaluation/engine/helpers.py +34 -9
- opik/evaluation/engine/metrics_evaluator.py +237 -0
- opik/evaluation/engine/types.py +5 -4
- opik/evaluation/evaluation_result.py +169 -2
- opik/evaluation/evaluator.py +659 -58
- opik/evaluation/metrics/__init__.py +121 -6
- opik/evaluation/metrics/aggregated_metric.py +92 -0
- opik/evaluation/metrics/arguments_helpers.py +15 -21
- opik/evaluation/metrics/arguments_validator.py +38 -0
- opik/evaluation/metrics/base_metric.py +20 -10
- opik/evaluation/metrics/conversation/__init__.py +48 -0
- opik/evaluation/metrics/conversation/conversation_thread_metric.py +79 -0
- opik/evaluation/metrics/conversation/conversation_turns_factory.py +39 -0
- opik/evaluation/metrics/conversation/g_eval_wrappers.py +19 -0
- opik/evaluation/metrics/conversation/helpers.py +84 -0
- 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/llm_judges/conversational_coherence/__init__.py +0 -0
- opik/evaluation/metrics/conversation/llm_judges/conversational_coherence/metric.py +274 -0
- opik/evaluation/metrics/conversation/llm_judges/conversational_coherence/schema.py +16 -0
- opik/evaluation/metrics/conversation/llm_judges/conversational_coherence/templates.py +95 -0
- opik/evaluation/metrics/conversation/llm_judges/g_eval_wrappers.py +442 -0
- opik/evaluation/metrics/conversation/llm_judges/session_completeness/__init__.py +0 -0
- opik/evaluation/metrics/conversation/llm_judges/session_completeness/metric.py +295 -0
- opik/evaluation/metrics/conversation/llm_judges/session_completeness/schema.py +22 -0
- opik/evaluation/metrics/conversation/llm_judges/session_completeness/templates.py +139 -0
- opik/evaluation/metrics/conversation/llm_judges/user_frustration/__init__.py +0 -0
- opik/evaluation/metrics/conversation/llm_judges/user_frustration/metric.py +277 -0
- opik/evaluation/metrics/conversation/llm_judges/user_frustration/schema.py +16 -0
- opik/evaluation/metrics/conversation/llm_judges/user_frustration/templates.py +135 -0
- opik/evaluation/metrics/conversation/types.py +34 -0
- opik/evaluation/metrics/conversation_types.py +9 -0
- opik/evaluation/metrics/heuristics/bertscore.py +107 -0
- opik/evaluation/metrics/heuristics/bleu.py +43 -16
- opik/evaluation/metrics/heuristics/chrf.py +127 -0
- opik/evaluation/metrics/heuristics/contains.py +50 -11
- opik/evaluation/metrics/heuristics/distribution_metrics.py +331 -0
- opik/evaluation/metrics/heuristics/equals.py +4 -1
- opik/evaluation/metrics/heuristics/gleu.py +113 -0
- opik/evaluation/metrics/heuristics/is_json.py +9 -3
- opik/evaluation/metrics/heuristics/language_adherence.py +123 -0
- opik/evaluation/metrics/heuristics/levenshtein_ratio.py +6 -5
- 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/regex_match.py +4 -1
- opik/evaluation/metrics/heuristics/rouge.py +148 -0
- opik/evaluation/metrics/heuristics/sentiment.py +98 -0
- 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 +27 -30
- opik/evaluation/metrics/llm_judges/answer_relevance/parser.py +27 -0
- opik/evaluation/metrics/llm_judges/answer_relevance/templates.py +10 -10
- opik/evaluation/metrics/llm_judges/context_precision/metric.py +28 -31
- opik/evaluation/metrics/llm_judges/context_precision/parser.py +27 -0
- opik/evaluation/metrics/llm_judges/context_precision/template.py +7 -7
- opik/evaluation/metrics/llm_judges/context_recall/metric.py +27 -31
- opik/evaluation/metrics/llm_judges/context_recall/parser.py +27 -0
- opik/evaluation/metrics/llm_judges/context_recall/template.py +7 -7
- opik/evaluation/metrics/llm_judges/factuality/metric.py +7 -26
- opik/evaluation/metrics/llm_judges/factuality/parser.py +35 -0
- opik/evaluation/metrics/llm_judges/factuality/template.py +1 -1
- opik/evaluation/metrics/llm_judges/g_eval/__init__.py +5 -0
- opik/evaluation/metrics/llm_judges/g_eval/metric.py +244 -113
- opik/evaluation/metrics/llm_judges/g_eval/parser.py +161 -0
- 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 +23 -27
- opik/evaluation/metrics/llm_judges/hallucination/parser.py +29 -0
- opik/evaluation/metrics/llm_judges/hallucination/template.py +2 -4
- 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 +23 -28
- opik/evaluation/metrics/llm_judges/moderation/parser.py +27 -0
- opik/evaluation/metrics/llm_judges/moderation/template.py +2 -2
- opik/evaluation/metrics/llm_judges/parsing_helpers.py +26 -0
- 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/__init__.py +3 -0
- opik/evaluation/metrics/llm_judges/trajectory_accuracy/metric.py +171 -0
- opik/evaluation/metrics/llm_judges/trajectory_accuracy/parser.py +38 -0
- opik/evaluation/metrics/llm_judges/trajectory_accuracy/templates.py +65 -0
- opik/evaluation/metrics/llm_judges/usefulness/metric.py +23 -32
- opik/evaluation/metrics/llm_judges/usefulness/parser.py +28 -0
- opik/evaluation/metrics/ragas_metric.py +112 -0
- opik/evaluation/models/__init__.py +10 -0
- opik/evaluation/models/base_model.py +140 -18
- opik/evaluation/models/langchain/__init__.py +3 -0
- opik/evaluation/models/langchain/langchain_chat_model.py +166 -0
- opik/evaluation/models/langchain/message_converters.py +106 -0
- opik/evaluation/models/langchain/opik_monitoring.py +23 -0
- opik/evaluation/models/litellm/litellm_chat_model.py +186 -40
- opik/evaluation/models/litellm/opik_monitor.py +24 -21
- 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/__init__.py +0 -0
- opik/evaluation/threads/context_helper.py +32 -0
- opik/evaluation/threads/evaluation_engine.py +181 -0
- opik/evaluation/threads/evaluation_result.py +18 -0
- opik/evaluation/threads/evaluator.py +120 -0
- opik/evaluation/threads/helpers.py +51 -0
- opik/evaluation/types.py +9 -1
- opik/exceptions.py +116 -3
- opik/file_upload/__init__.py +0 -0
- opik/file_upload/base_upload_manager.py +39 -0
- opik/file_upload/file_upload_monitor.py +14 -0
- opik/file_upload/file_uploader.py +141 -0
- opik/file_upload/mime_type.py +9 -0
- opik/file_upload/s3_multipart_upload/__init__.py +0 -0
- opik/file_upload/s3_multipart_upload/file_parts_strategy.py +89 -0
- opik/file_upload/s3_multipart_upload/s3_file_uploader.py +86 -0
- opik/file_upload/s3_multipart_upload/s3_upload_error.py +29 -0
- opik/file_upload/thread_pool.py +17 -0
- opik/file_upload/upload_client.py +114 -0
- opik/file_upload/upload_manager.py +255 -0
- opik/file_upload/upload_options.py +37 -0
- opik/format_helpers.py +17 -0
- opik/guardrails/__init__.py +4 -0
- opik/guardrails/guardrail.py +157 -0
- opik/guardrails/guards/__init__.py +5 -0
- opik/guardrails/guards/guard.py +17 -0
- opik/guardrails/guards/pii.py +47 -0
- opik/guardrails/guards/topic.py +76 -0
- opik/guardrails/rest_api_client.py +34 -0
- opik/guardrails/schemas.py +24 -0
- opik/guardrails/tracing.py +61 -0
- opik/healthcheck/__init__.py +2 -1
- opik/healthcheck/checks.py +2 -2
- opik/healthcheck/rich_representation.py +1 -1
- 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 +75 -4
- opik/id_helpers.py +18 -0
- opik/integrations/adk/__init__.py +14 -0
- opik/integrations/adk/callback_context_info_extractors.py +32 -0
- opik/integrations/adk/graph/__init__.py +0 -0
- opik/integrations/adk/graph/mermaid_graph_builder.py +128 -0
- opik/integrations/adk/graph/nodes.py +101 -0
- opik/integrations/adk/graph/subgraph_edges_builders.py +41 -0
- opik/integrations/adk/helpers.py +48 -0
- opik/integrations/adk/legacy_opik_tracer.py +381 -0
- opik/integrations/adk/opik_tracer.py +370 -0
- opik/integrations/adk/patchers/__init__.py +4 -0
- opik/integrations/adk/patchers/adk_otel_tracer/__init__.py +0 -0
- opik/integrations/adk/patchers/adk_otel_tracer/llm_span_helpers.py +30 -0
- opik/integrations/adk/patchers/adk_otel_tracer/opik_adk_otel_tracer.py +201 -0
- opik/integrations/adk/patchers/litellm_wrappers.py +91 -0
- opik/integrations/adk/patchers/llm_response_wrapper.py +105 -0
- opik/integrations/adk/patchers/patchers.py +64 -0
- opik/integrations/adk/recursive_callback_injector.py +126 -0
- opik/integrations/aisuite/aisuite_decorator.py +8 -3
- opik/integrations/aisuite/opik_tracker.py +1 -0
- opik/integrations/anthropic/messages_create_decorator.py +8 -3
- opik/integrations/anthropic/opik_tracker.py +0 -1
- 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} +18 -8
- opik/integrations/bedrock/invoke_agent_decorator.py +12 -7
- 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 +43 -4
- opik/integrations/bedrock/types.py +19 -0
- opik/integrations/crewai/crewai_decorator.py +34 -56
- 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 +246 -84
- opik/integrations/dspy/graph.py +88 -0
- opik/integrations/dspy/parsers.py +168 -0
- opik/integrations/genai/encoder_extension.py +2 -6
- opik/integrations/genai/generate_content_decorator.py +20 -13
- opik/integrations/guardrails/guardrails_decorator.py +4 -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/constants.py +35 -0
- opik/integrations/haystack/converters.py +1 -2
- opik/integrations/haystack/opik_connector.py +28 -6
- opik/integrations/haystack/opik_span_bridge.py +284 -0
- opik/integrations/haystack/opik_tracer.py +124 -222
- 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 +2 -2
- opik/integrations/langchain/opik_tracer.py +641 -206
- opik/integrations/langchain/provider_usage_extractors/__init__.py +5 -0
- opik/integrations/langchain/provider_usage_extractors/anthropic_usage_extractor.py +101 -0
- opik/integrations/langchain/provider_usage_extractors/anthropic_vertexai_usage_extractor.py +67 -0
- opik/integrations/langchain/provider_usage_extractors/bedrock_usage_extractor.py +94 -0
- opik/integrations/langchain/provider_usage_extractors/google_generative_ai_usage_extractor.py +109 -0
- opik/integrations/langchain/provider_usage_extractors/groq_usage_extractor.py +92 -0
- opik/integrations/langchain/provider_usage_extractors/langchain_run_helpers/__init__.py +15 -0
- opik/integrations/langchain/provider_usage_extractors/langchain_run_helpers/helpers.py +134 -0
- opik/integrations/langchain/provider_usage_extractors/langchain_run_helpers/langchain_usage.py +163 -0
- opik/integrations/langchain/provider_usage_extractors/openai_usage_extractor.py +124 -0
- opik/integrations/langchain/provider_usage_extractors/provider_usage_extractor_protocol.py +29 -0
- opik/integrations/langchain/provider_usage_extractors/usage_extractor.py +48 -0
- opik/integrations/langchain/provider_usage_extractors/vertexai_usage_extractor.py +109 -0
- 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 +179 -78
- opik/integrations/llama_index/event_parsing_utils.py +29 -9
- opik/integrations/openai/agents/opik_tracing_processor.py +204 -32
- opik/integrations/openai/agents/span_data_parsers.py +15 -6
- opik/integrations/openai/chat_completion_chunks_aggregator.py +1 -1
- opik/integrations/openai/{openai_decorator.py → openai_chat_completions_decorator.py} +45 -35
- opik/integrations/openai/openai_responses_decorator.py +158 -0
- opik/integrations/openai/opik_tracker.py +94 -13
- opik/integrations/openai/response_events_aggregator.py +36 -0
- opik/integrations/openai/stream_patchers.py +125 -15
- opik/integrations/sagemaker/auth.py +5 -1
- opik/jsonable_encoder.py +29 -1
- opik/llm_usage/base_original_provider_usage.py +15 -8
- opik/llm_usage/bedrock_usage.py +8 -2
- opik/llm_usage/google_usage.py +6 -1
- opik/llm_usage/llm_usage_info.py +6 -0
- opik/llm_usage/{openai_usage.py → openai_chat_completions_usage.py} +2 -12
- opik/llm_usage/{openai_agent_usage.py → openai_responses_usage.py} +7 -15
- opik/llm_usage/opik_usage.py +36 -10
- opik/llm_usage/opik_usage_factory.py +35 -19
- opik/logging_messages.py +19 -7
- opik/message_processing/arguments_utils.py +22 -0
- opik/message_processing/batching/base_batcher.py +45 -17
- opik/message_processing/batching/batch_manager.py +22 -10
- opik/message_processing/batching/batch_manager_constuctors.py +36 -11
- opik/message_processing/batching/batchers.py +167 -44
- opik/message_processing/batching/flushing_thread.py +0 -3
- opik/message_processing/batching/sequence_splitter.py +50 -5
- 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/message_queue.py +79 -0
- opik/message_processing/messages.py +154 -12
- 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/processors/online_message_processor.py +324 -0
- opik/message_processing/queue_consumer.py +61 -13
- opik/message_processing/streamer.py +102 -31
- opik/message_processing/streamer_constructors.py +67 -12
- opik/opik_context.py +103 -11
- opik/plugins/pytest/decorator.py +2 -2
- opik/plugins/pytest/experiment_runner.py +3 -2
- opik/plugins/pytest/hooks.py +6 -4
- opik/rate_limit/__init__.py +0 -0
- opik/rate_limit/rate_limit.py +25 -0
- opik/rest_api/__init__.py +643 -11
- 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/attachments/__init__.py +17 -0
- opik/rest_api/attachments/client.py +752 -0
- opik/rest_api/attachments/raw_client.py +1125 -0
- opik/rest_api/attachments/types/__init__.py +15 -0
- opik/rest_api/attachments/types/attachment_list_request_entity_type.py +5 -0
- opik/rest_api/attachments/types/download_attachment_request_entity_type.py +5 -0
- opik/rest_api/attachments/types/start_multipart_upload_request_entity_type.py +5 -0
- opik/rest_api/attachments/types/upload_attachment_request_entity_type.py +5 -0
- opik/rest_api/automation_rule_evaluators/__init__.py +2 -0
- opik/rest_api/automation_rule_evaluators/client.py +182 -1162
- opik/rest_api/automation_rule_evaluators/raw_client.py +598 -0
- opik/rest_api/chat_completions/__init__.py +2 -0
- opik/rest_api/chat_completions/client.py +115 -149
- opik/rest_api/chat_completions/raw_client.py +339 -0
- opik/rest_api/check/__init__.py +2 -0
- opik/rest_api/check/client.py +88 -106
- opik/rest_api/check/raw_client.py +258 -0
- opik/rest_api/client.py +112 -212
- opik/rest_api/core/__init__.py +5 -0
- opik/rest_api/core/api_error.py +12 -6
- opik/rest_api/core/client_wrapper.py +4 -14
- opik/rest_api/core/datetime_utils.py +1 -3
- opik/rest_api/core/file.py +2 -5
- opik/rest_api/core/http_client.py +42 -120
- opik/rest_api/core/http_response.py +55 -0
- opik/rest_api/core/jsonable_encoder.py +1 -4
- opik/rest_api/core/pydantic_utilities.py +79 -147
- opik/rest_api/core/query_encoder.py +1 -3
- opik/rest_api/core/serialization.py +10 -10
- 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/__init__.py +5 -0
- opik/rest_api/datasets/client.py +1638 -1091
- opik/rest_api/datasets/raw_client.py +3389 -0
- opik/rest_api/datasets/types/__init__.py +8 -0
- opik/rest_api/datasets/types/dataset_update_visibility.py +5 -0
- opik/rest_api/datasets/types/dataset_write_visibility.py +5 -0
- opik/rest_api/errors/__init__.py +2 -0
- opik/rest_api/errors/bad_request_error.py +4 -3
- opik/rest_api/errors/conflict_error.py +4 -3
- opik/rest_api/errors/forbidden_error.py +4 -2
- opik/rest_api/errors/not_found_error.py +4 -3
- opik/rest_api/errors/not_implemented_error.py +4 -3
- opik/rest_api/errors/unauthorized_error.py +4 -3
- opik/rest_api/errors/unprocessable_entity_error.py +4 -3
- opik/rest_api/experiments/__init__.py +5 -0
- opik/rest_api/experiments/client.py +676 -752
- opik/rest_api/experiments/raw_client.py +1872 -0
- opik/rest_api/experiments/types/__init__.py +10 -0
- 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/experiments/types/experiment_write_type.py +5 -0
- opik/rest_api/feedback_definitions/__init__.py +2 -0
- opik/rest_api/feedback_definitions/client.py +96 -370
- opik/rest_api/feedback_definitions/raw_client.py +541 -0
- opik/rest_api/feedback_definitions/types/__init__.py +2 -0
- opik/rest_api/feedback_definitions/types/find_feedback_definitions_request_type.py +1 -3
- opik/rest_api/guardrails/__init__.py +4 -0
- opik/rest_api/guardrails/client.py +104 -0
- opik/rest_api/guardrails/raw_client.py +102 -0
- opik/rest_api/llm_provider_key/__init__.py +2 -0
- opik/rest_api/llm_provider_key/client.py +166 -440
- opik/rest_api/llm_provider_key/raw_client.py +643 -0
- opik/rest_api/llm_provider_key/types/__init__.py +2 -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/open_telemetry_ingestion/__init__.py +2 -0
- opik/rest_api/open_telemetry_ingestion/client.py +38 -63
- opik/rest_api/open_telemetry_ingestion/raw_client.py +88 -0
- opik/rest_api/optimizations/__init__.py +7 -0
- opik/rest_api/optimizations/client.py +704 -0
- opik/rest_api/optimizations/raw_client.py +920 -0
- opik/rest_api/optimizations/types/__init__.py +7 -0
- opik/rest_api/optimizations/types/optimization_update_status.py +7 -0
- opik/rest_api/projects/__init__.py +10 -1
- opik/rest_api/projects/client.py +180 -855
- opik/rest_api/projects/raw_client.py +1216 -0
- opik/rest_api/projects/types/__init__.py +11 -4
- opik/rest_api/projects/types/project_metric_request_public_interval.py +1 -3
- opik/rest_api/projects/types/project_metric_request_public_metric_type.py +11 -1
- opik/rest_api/projects/types/project_update_visibility.py +5 -0
- opik/rest_api/projects/types/project_write_visibility.py +5 -0
- opik/rest_api/prompts/__init__.py +4 -2
- opik/rest_api/prompts/client.py +381 -970
- opik/rest_api/prompts/raw_client.py +1634 -0
- opik/rest_api/prompts/types/__init__.py +5 -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/raw_client.py +156 -0
- opik/rest_api/redirect/__init__.py +4 -0
- opik/rest_api/redirect/client.py +375 -0
- opik/rest_api/redirect/raw_client.py +566 -0
- opik/rest_api/service_toggles/__init__.py +4 -0
- opik/rest_api/service_toggles/client.py +91 -0
- opik/rest_api/service_toggles/raw_client.py +93 -0
- opik/rest_api/spans/__init__.py +2 -0
- opik/rest_api/spans/client.py +659 -1354
- opik/rest_api/spans/raw_client.py +2383 -0
- opik/rest_api/spans/types/__init__.py +2 -0
- opik/rest_api/spans/types/find_feedback_score_names_1_request_type.py +1 -3
- opik/rest_api/spans/types/get_span_stats_request_type.py +1 -3
- opik/rest_api/spans/types/get_spans_by_project_request_type.py +1 -3
- opik/rest_api/spans/types/span_search_stream_request_public_type.py +1 -3
- opik/rest_api/system_usage/__init__.py +2 -0
- opik/rest_api/system_usage/client.py +157 -216
- opik/rest_api/system_usage/raw_client.py +455 -0
- opik/rest_api/traces/__init__.py +2 -0
- opik/rest_api/traces/client.py +2102 -1625
- opik/rest_api/traces/raw_client.py +4144 -0
- opik/rest_api/types/__init__.py +629 -24
- opik/rest_api/types/aggregation_data.py +27 -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/{json_schema_element.py → annotation_queue_item_ids.py} +5 -7
- 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/{workspace_metadata.py → annotation_queue_reviewer.py} +6 -7
- 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/assistant_message.py +7 -8
- opik/rest_api/types/assistant_message_role.py +1 -3
- opik/rest_api/types/attachment.py +22 -0
- opik/rest_api/types/attachment_page.py +28 -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 +160 -0
- opik/rest_api/types/automation_rule_evaluator_llm_as_judge.py +6 -6
- opik/rest_api/types/automation_rule_evaluator_llm_as_judge_public.py +6 -6
- opik/rest_api/types/automation_rule_evaluator_llm_as_judge_write.py +6 -6
- opik/rest_api/types/automation_rule_evaluator_object_object_public.py +155 -0
- opik/rest_api/types/automation_rule_evaluator_page_public.py +6 -6
- opik/rest_api/types/automation_rule_evaluator_public.py +155 -0
- 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 +22 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_public.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_write.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_public.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_write.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_update.py +143 -0
- opik/rest_api/types/automation_rule_evaluator_update_llm_as_judge.py +6 -6
- 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 +22 -0
- opik/rest_api/types/automation_rule_evaluator_update_trace_thread_user_defined_metric_python.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_update_user_defined_metric_python.py +6 -6
- opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python.py +6 -6
- opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_public.py +6 -6
- opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_write.py +6 -6
- opik/rest_api/types/automation_rule_evaluator_write.py +143 -0
- opik/rest_api/types/avg_value_stat_public.py +3 -5
- opik/rest_api/types/batch_delete.py +3 -5
- opik/rest_api/types/batch_delete_by_project.py +20 -0
- opik/rest_api/types/bi_information.py +3 -5
- opik/rest_api/types/bi_information_response.py +4 -6
- 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/categorical_feedback_definition.py +5 -7
- opik/rest_api/types/categorical_feedback_definition_create.py +4 -6
- opik/rest_api/types/categorical_feedback_definition_public.py +5 -7
- opik/rest_api/types/categorical_feedback_definition_update.py +4 -6
- opik/rest_api/types/categorical_feedback_detail.py +3 -5
- opik/rest_api/types/categorical_feedback_detail_create.py +3 -5
- opik/rest_api/types/categorical_feedback_detail_public.py +3 -5
- opik/rest_api/types/categorical_feedback_detail_update.py +3 -5
- opik/rest_api/types/chat_completion_choice.py +4 -6
- opik/rest_api/types/chat_completion_response.py +5 -6
- opik/rest_api/types/check.py +22 -0
- opik/rest_api/types/{json_node_compare.py → check_name.py} +1 -1
- opik/rest_api/types/check_public.py +22 -0
- opik/rest_api/types/check_public_name.py +5 -0
- opik/rest_api/types/check_public_result.py +5 -0
- opik/rest_api/types/check_result.py +5 -0
- opik/rest_api/types/chunked_output_json_node.py +4 -6
- opik/rest_api/types/chunked_output_json_node_public.py +4 -6
- opik/rest_api/types/chunked_output_json_node_public_type.py +6 -10
- opik/rest_api/types/chunked_output_json_node_type.py +6 -10
- opik/rest_api/types/column.py +8 -10
- opik/rest_api/types/column_compare.py +8 -10
- opik/rest_api/types/column_public.py +8 -10
- opik/rest_api/types/column_types_item.py +1 -3
- opik/rest_api/types/comment.py +4 -6
- opik/rest_api/types/comment_compare.py +4 -6
- opik/rest_api/types/comment_public.py +4 -6
- opik/rest_api/types/complete_multipart_upload_request.py +33 -0
- opik/rest_api/types/complete_multipart_upload_request_entity_type.py +5 -0
- opik/rest_api/types/completion_tokens_details.py +3 -5
- opik/rest_api/types/count_value_stat_public.py +3 -5
- opik/rest_api/types/dashboard_page_public.py +24 -0
- opik/rest_api/types/dashboard_public.py +30 -0
- opik/rest_api/types/data_point_double.py +21 -0
- opik/rest_api/types/data_point_number_public.py +3 -5
- opik/rest_api/types/dataset.py +14 -6
- 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 +9 -8
- opik/rest_api/types/dataset_item_batch.py +3 -5
- opik/rest_api/types/dataset_item_changes_public.py +5 -0
- opik/rest_api/types/dataset_item_compare.py +9 -8
- opik/rest_api/types/dataset_item_compare_source.py +1 -3
- 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 +10 -7
- opik/rest_api/types/dataset_item_page_public.py +10 -7
- opik/rest_api/types/dataset_item_public.py +9 -8
- opik/rest_api/types/dataset_item_public_source.py +1 -3
- opik/rest_api/types/dataset_item_source.py +1 -3
- opik/rest_api/types/dataset_item_update.py +39 -0
- opik/rest_api/types/dataset_item_write.py +5 -6
- opik/rest_api/types/dataset_item_write_source.py +1 -3
- opik/rest_api/types/dataset_page_public.py +9 -6
- opik/rest_api/types/dataset_public.py +14 -6
- opik/rest_api/types/dataset_public_status.py +5 -0
- opik/rest_api/types/dataset_public_visibility.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/dataset_visibility.py +5 -0
- opik/rest_api/types/delete_attachments_request.py +23 -0
- opik/rest_api/types/delete_attachments_request_entity_type.py +5 -0
- opik/rest_api/types/delete_feedback_score.py +4 -5
- opik/rest_api/types/delete_ids_holder.py +19 -0
- opik/rest_api/types/delta.py +7 -9
- opik/rest_api/types/error_count_with_deviation.py +21 -0
- opik/rest_api/types/error_count_with_deviation_detailed.py +21 -0
- opik/rest_api/types/error_info.py +3 -5
- opik/rest_api/types/error_info_experiment_item_bulk_write_view.py +21 -0
- opik/rest_api/types/error_info_public.py +3 -5
- opik/rest_api/types/error_info_write.py +3 -5
- opik/rest_api/types/error_message.py +3 -5
- opik/rest_api/types/error_message_detail.py +3 -5
- opik/rest_api/types/error_message_detailed.py +3 -5
- opik/rest_api/types/error_message_public.py +3 -5
- opik/rest_api/types/experiment.py +21 -10
- opik/rest_api/types/experiment_group_aggregations_response.py +20 -0
- opik/rest_api/types/experiment_group_response.py +22 -0
- opik/rest_api/types/experiment_item.py +14 -11
- opik/rest_api/types/experiment_item_bulk_record.py +27 -0
- opik/rest_api/types/experiment_item_bulk_record_experiment_item_bulk_write_view.py +27 -0
- opik/rest_api/types/experiment_item_bulk_upload.py +27 -0
- opik/rest_api/types/experiment_item_compare.py +14 -11
- opik/rest_api/types/experiment_item_compare_trace_visibility_mode.py +5 -0
- opik/rest_api/types/experiment_item_public.py +6 -6
- opik/rest_api/types/experiment_item_public_trace_visibility_mode.py +5 -0
- opik/rest_api/types/experiment_item_trace_visibility_mode.py +5 -0
- opik/rest_api/types/experiment_page_public.py +9 -6
- opik/rest_api/types/experiment_public.py +21 -10
- opik/rest_api/types/experiment_public_status.py +5 -0
- opik/rest_api/types/experiment_public_type.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/experiment_type.py +5 -0
- opik/rest_api/types/export_trace_service_request.py +5 -0
- opik/rest_api/types/feedback.py +40 -27
- opik/rest_api/types/feedback_create.py +27 -13
- opik/rest_api/types/feedback_definition_page_public.py +4 -6
- opik/rest_api/types/feedback_object_public.py +40 -27
- opik/rest_api/types/feedback_public.py +40 -27
- opik/rest_api/types/feedback_score.py +7 -7
- opik/rest_api/types/feedback_score_average.py +3 -5
- opik/rest_api/types/feedback_score_average_detailed.py +3 -5
- opik/rest_api/types/feedback_score_average_public.py +3 -5
- opik/rest_api/types/feedback_score_batch.py +4 -6
- opik/rest_api/types/feedback_score_batch_item.py +6 -6
- opik/rest_api/types/feedback_score_batch_item_source.py +1 -3
- opik/rest_api/types/feedback_score_batch_item_thread.py +32 -0
- opik/rest_api/types/feedback_score_batch_item_thread_source.py +5 -0
- opik/rest_api/types/feedback_score_compare.py +7 -7
- opik/rest_api/types/feedback_score_compare_source.py +1 -3
- opik/rest_api/types/feedback_score_experiment_item_bulk_write_view.py +31 -0
- opik/rest_api/types/feedback_score_experiment_item_bulk_write_view_source.py +5 -0
- opik/rest_api/types/feedback_score_names.py +4 -6
- opik/rest_api/types/feedback_score_public.py +11 -7
- opik/rest_api/types/feedback_score_public_source.py +1 -3
- opik/rest_api/types/feedback_score_source.py +1 -3
- opik/rest_api/types/feedback_update.py +27 -13
- opik/rest_api/types/function.py +4 -7
- opik/rest_api/types/function_call.py +3 -5
- opik/rest_api/types/group_content.py +19 -0
- opik/rest_api/types/group_content_with_aggregations.py +21 -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 +34 -0
- opik/rest_api/types/guardrail_batch.py +20 -0
- opik/rest_api/types/guardrail_name.py +5 -0
- opik/rest_api/types/guardrail_result.py +5 -0
- opik/rest_api/types/guardrail_write.py +33 -0
- opik/rest_api/types/guardrail_write_name.py +5 -0
- opik/rest_api/types/guardrail_write_result.py +5 -0
- opik/rest_api/types/guardrails_validation.py +21 -0
- opik/rest_api/types/guardrails_validation_public.py +21 -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/json_list_string.py +7 -0
- opik/rest_api/types/json_list_string_compare.py +7 -0
- opik/rest_api/types/json_list_string_experiment_item_bulk_write_view.py +7 -0
- opik/rest_api/types/json_list_string_public.py +7 -0
- opik/rest_api/types/json_list_string_write.py +7 -0
- opik/rest_api/types/json_schema.py +5 -8
- opik/rest_api/types/llm_as_judge_code.py +8 -12
- opik/rest_api/types/llm_as_judge_code_public.py +8 -12
- opik/rest_api/types/llm_as_judge_code_write.py +8 -12
- opik/rest_api/types/llm_as_judge_message.py +9 -7
- 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 +9 -7
- opik/rest_api/types/llm_as_judge_message_public_role.py +1 -1
- opik/rest_api/types/llm_as_judge_message_role.py +1 -1
- opik/rest_api/types/llm_as_judge_message_write.py +9 -7
- opik/rest_api/types/llm_as_judge_message_write_role.py +1 -1
- opik/rest_api/types/llm_as_judge_model_parameters.py +6 -5
- opik/rest_api/types/llm_as_judge_model_parameters_public.py +6 -5
- opik/rest_api/types/llm_as_judge_model_parameters_write.py +6 -5
- opik/rest_api/types/llm_as_judge_output_schema.py +4 -6
- opik/rest_api/types/llm_as_judge_output_schema_public.py +4 -6
- opik/rest_api/types/llm_as_judge_output_schema_public_type.py +1 -3
- opik/rest_api/types/llm_as_judge_output_schema_type.py +1 -3
- opik/rest_api/types/llm_as_judge_output_schema_write.py +4 -6
- opik/rest_api/types/llm_as_judge_output_schema_write_type.py +1 -3
- opik/rest_api/types/log_item.py +5 -7
- opik/rest_api/types/log_item_level.py +1 -3
- opik/rest_api/types/log_page.py +4 -6
- 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/multipart_upload_part.py +20 -0
- opik/rest_api/types/numerical_feedback_definition.py +5 -7
- opik/rest_api/types/numerical_feedback_definition_create.py +4 -6
- opik/rest_api/types/numerical_feedback_definition_public.py +5 -7
- opik/rest_api/types/numerical_feedback_definition_update.py +4 -6
- opik/rest_api/types/numerical_feedback_detail.py +3 -5
- opik/rest_api/types/numerical_feedback_detail_create.py +3 -5
- opik/rest_api/types/numerical_feedback_detail_public.py +3 -5
- opik/rest_api/types/numerical_feedback_detail_update.py +3 -5
- opik/rest_api/types/optimization.py +37 -0
- opik/rest_api/types/optimization_page_public.py +28 -0
- opik/rest_api/types/optimization_public.py +37 -0
- opik/rest_api/types/optimization_public_status.py +7 -0
- opik/rest_api/types/optimization_status.py +7 -0
- 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 +30 -0
- opik/rest_api/types/optimization_write_status.py +7 -0
- opik/rest_api/types/page_columns.py +4 -6
- opik/rest_api/types/percentage_value_stat_public.py +4 -6
- opik/rest_api/types/percentage_values.py +8 -16
- opik/rest_api/types/percentage_values_detailed.py +8 -16
- opik/rest_api/types/percentage_values_public.py +8 -16
- opik/rest_api/types/project.py +12 -7
- opik/rest_api/types/project_detailed.py +12 -7
- opik/rest_api/types/project_detailed_visibility.py +5 -0
- opik/rest_api/types/project_metric_response_public.py +5 -9
- opik/rest_api/types/project_metric_response_public_interval.py +1 -3
- opik/rest_api/types/project_metric_response_public_metric_type.py +11 -1
- opik/rest_api/types/project_page_public.py +8 -10
- opik/rest_api/types/project_public.py +6 -6
- opik/rest_api/types/project_public_visibility.py +5 -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_stat_item_object_public.py +8 -17
- opik/rest_api/types/project_stats_public.py +4 -6
- opik/rest_api/types/project_stats_summary.py +4 -6
- opik/rest_api/types/project_stats_summary_item.py +9 -6
- opik/rest_api/types/project_visibility.py +5 -0
- opik/rest_api/types/prompt.py +12 -7
- opik/rest_api/types/prompt_detail.py +12 -7
- opik/rest_api/types/prompt_detail_template_structure.py +5 -0
- opik/rest_api/types/prompt_page_public.py +9 -6
- opik/rest_api/types/prompt_public.py +11 -6
- 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_tokens_details.py +19 -0
- opik/rest_api/types/prompt_version.py +7 -6
- opik/rest_api/types/prompt_version_detail.py +7 -6
- opik/rest_api/types/prompt_version_detail_template_structure.py +5 -0
- opik/rest_api/types/prompt_version_link.py +4 -5
- opik/rest_api/types/prompt_version_link_public.py +4 -5
- opik/rest_api/types/prompt_version_link_write.py +3 -5
- opik/rest_api/types/prompt_version_page_public.py +9 -6
- opik/rest_api/types/prompt_version_public.py +7 -6
- 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 +18 -8
- opik/rest_api/types/provider_api_key_page_public.py +27 -0
- opik/rest_api/types/provider_api_key_provider.py +1 -1
- opik/rest_api/types/provider_api_key_public.py +18 -8
- opik/rest_api/types/provider_api_key_public_provider.py +1 -1
- opik/rest_api/types/response_format.py +5 -7
- opik/rest_api/types/response_format_type.py +1 -3
- opik/rest_api/types/result.py +21 -0
- opik/rest_api/types/results_number_public.py +4 -6
- opik/rest_api/types/score_name.py +4 -5
- opik/rest_api/types/service_toggles_config.py +44 -0
- opik/rest_api/types/span.py +13 -15
- opik/rest_api/types/span_batch.py +4 -6
- opik/rest_api/types/span_enrichment_options.py +31 -0
- opik/rest_api/types/span_experiment_item_bulk_write_view.py +39 -0
- opik/rest_api/types/span_experiment_item_bulk_write_view_type.py +5 -0
- 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_public.py +4 -6
- opik/rest_api/types/span_filter_public_operator.py +2 -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_page_public.py +9 -6
- opik/rest_api/types/span_public.py +19 -16
- opik/rest_api/types/span_public_type.py +1 -1
- opik/rest_api/types/span_type.py +1 -1
- opik/rest_api/types/span_update.py +46 -0
- opik/rest_api/types/span_update_type.py +5 -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 +13 -14
- opik/rest_api/types/span_write_type.py +1 -1
- opik/rest_api/types/spans_count_response.py +20 -0
- opik/rest_api/types/start_multipart_upload_response.py +20 -0
- opik/rest_api/types/stream_options.py +3 -5
- 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/tool.py +4 -6
- opik/rest_api/types/tool_call.py +4 -6
- opik/rest_api/types/trace.py +26 -12
- opik/rest_api/types/trace_batch.py +4 -6
- opik/rest_api/types/trace_count_response.py +4 -6
- opik/rest_api/types/trace_enrichment_options.py +32 -0
- opik/rest_api/types/trace_experiment_item_bulk_write_view.py +41 -0
- 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_public.py +23 -0
- opik/rest_api/types/trace_filter_public_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_page_public.py +8 -10
- opik/rest_api/types/trace_public.py +27 -13
- opik/rest_api/types/trace_public_visibility_mode.py +5 -0
- opik/rest_api/types/trace_thread.py +18 -9
- opik/rest_api/types/trace_thread_filter.py +23 -0
- opik/rest_api/types/trace_thread_filter_operator.py +21 -0
- opik/rest_api/types/trace_thread_filter_public.py +23 -0
- opik/rest_api/types/trace_thread_filter_public_operator.py +21 -0
- 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 +22 -0
- opik/rest_api/types/trace_thread_llm_as_judge_code.py +26 -0
- opik/rest_api/types/trace_thread_llm_as_judge_code_public.py +26 -0
- opik/rest_api/types/trace_thread_llm_as_judge_code_write.py +26 -0
- opik/rest_api/types/trace_thread_page.py +9 -6
- opik/rest_api/types/trace_thread_status.py +5 -0
- opik/rest_api/types/trace_thread_update.py +19 -0
- opik/rest_api/types/trace_thread_user_defined_metric_python_code.py +19 -0
- opik/rest_api/types/trace_thread_user_defined_metric_python_code_public.py +19 -0
- opik/rest_api/types/trace_thread_user_defined_metric_python_code_write.py +19 -0
- opik/rest_api/types/trace_update.py +39 -0
- opik/rest_api/types/trace_visibility_mode.py +5 -0
- opik/rest_api/types/trace_write.py +10 -11
- opik/rest_api/types/usage.py +6 -6
- opik/rest_api/types/user_defined_metric_python_code.py +3 -5
- opik/rest_api/types/user_defined_metric_python_code_public.py +3 -5
- opik/rest_api/types/user_defined_metric_python_code_write.py +3 -5
- opik/rest_api/types/value_entry.py +27 -0
- opik/rest_api/types/value_entry_compare.py +27 -0
- opik/rest_api/types/value_entry_compare_source.py +5 -0
- opik/rest_api/types/value_entry_experiment_item_bulk_write_view.py +27 -0
- opik/rest_api/types/value_entry_experiment_item_bulk_write_view_source.py +5 -0
- opik/rest_api/types/value_entry_public.py +27 -0
- opik/rest_api/types/value_entry_public_source.py +5 -0
- opik/rest_api/types/value_entry_source.py +5 -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 +27 -0
- opik/rest_api/types/workspace_metric_request.py +24 -0
- opik/rest_api/types/workspace_metric_response.py +20 -0
- opik/rest_api/types/workspace_metrics_summary_request.py +23 -0
- opik/rest_api/types/workspace_metrics_summary_response.py +20 -0
- opik/rest_api/types/workspace_name_holder.py +19 -0
- opik/rest_api/types/workspace_spans_count.py +20 -0
- opik/rest_api/types/workspace_trace_count.py +3 -5
- 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/__init__.py +2 -0
- opik/rest_api/workspaces/client.py +550 -77
- opik/rest_api/workspaces/raw_client.py +923 -0
- opik/rest_client_configurator/api.py +1 -0
- opik/rest_client_configurator/retry_decorator.py +1 -0
- opik/s3_httpx_client.py +67 -0
- opik/simulation/__init__.py +6 -0
- opik/simulation/simulated_user.py +99 -0
- opik/simulation/simulator.py +108 -0
- opik/synchronization.py +11 -24
- opik/tracing_runtime_config.py +48 -0
- opik/types.py +48 -2
- opik/url_helpers.py +13 -3
- opik/validation/chat_prompt_messages.py +241 -0
- opik/validation/feedback_score.py +4 -5
- opik/validation/parameter.py +122 -0
- opik/validation/parameters_validator.py +175 -0
- opik/validation/validator.py +30 -2
- opik/validation/validator_helpers.py +147 -0
- opik-1.9.71.dist-info/METADATA +370 -0
- opik-1.9.71.dist-info/RECORD +1110 -0
- {opik-1.6.4.dist-info → opik-1.9.71.dist-info}/WHEEL +1 -1
- opik-1.9.71.dist-info/licenses/LICENSE +203 -0
- opik/api_objects/prompt/prompt.py +0 -107
- opik/api_objects/prompt/prompt_template.py +0 -35
- opik/cli.py +0 -193
- opik/evaluation/metrics/models.py +0 -8
- opik/hooks.py +0 -13
- opik/integrations/bedrock/chunks_aggregator.py +0 -55
- opik/integrations/bedrock/helpers.py +0 -8
- opik/integrations/langchain/google_run_helpers.py +0 -75
- opik/integrations/langchain/openai_run_helpers.py +0 -122
- opik/message_processing/message_processors.py +0 -203
- opik/rest_api/types/delta_role.py +0 -7
- opik/rest_api/types/json_object_schema.py +0 -34
- opik-1.6.4.dist-info/METADATA +0 -270
- opik-1.6.4.dist-info/RECORD +0 -507
- /opik/integrations/bedrock/{stream_wrappers.py → converse/stream_wrappers.py} +0 -0
- {opik-1.6.4.dist-info → opik-1.9.71.dist-info}/entry_points.txt +0 -0
- {opik-1.6.4.dist-info → opik-1.9.71.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import mimetypes
|
|
5
|
+
import httpx
|
|
6
|
+
import json.decoder
|
|
7
|
+
from opik import s3_httpx_client
|
|
8
|
+
from typing import Iterator, List, Literal, Optional
|
|
9
|
+
from typing_extensions import TypeAlias
|
|
10
|
+
|
|
11
|
+
from opik.file_upload import file_uploader, upload_options
|
|
12
|
+
from opik.rest_api import client as rest_api_client
|
|
13
|
+
from opik.rest_api.types import attachment as rest_api_attachment
|
|
14
|
+
from opik.rest_api import core as rest_api_core
|
|
15
|
+
from opik import url_helpers
|
|
16
|
+
|
|
17
|
+
LOGGER = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
RESTAttachmentDetails: TypeAlias = rest_api_attachment.Attachment
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AttachmentClient:
|
|
23
|
+
"""
|
|
24
|
+
Client for interacting with attachment-related operations.
|
|
25
|
+
|
|
26
|
+
This client provides methods to retrieve attachment lists, download attachments,
|
|
27
|
+
and upload attachments for traces and spans.
|
|
28
|
+
|
|
29
|
+
The AttachmentClient supports:
|
|
30
|
+
- Listing attachments associated with traces or spans
|
|
31
|
+
- Downloading attachment content as a byte stream
|
|
32
|
+
- Uploading files as attachments to traces or spans
|
|
33
|
+
|
|
34
|
+
All operations are performed within the context of a specific project and require
|
|
35
|
+
the project name to be provided.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
rest_client: rest_api_client.OpikApi,
|
|
41
|
+
url_override: str,
|
|
42
|
+
workspace_name: str,
|
|
43
|
+
rest_httpx_client: httpx.Client,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Initialize the AttachmentClient.
|
|
47
|
+
It is typically created via ``Opik.get_attachment_client()`` rather
|
|
48
|
+
than being instantiated directly.
|
|
49
|
+
|
|
50
|
+
Parameters:
|
|
51
|
+
rest_client: The REST API client instance for making backend requests.
|
|
52
|
+
url_override: The base URL for the Opik server.
|
|
53
|
+
workspace_name: The workspace name used for download operations.
|
|
54
|
+
rest_httpx_client: The httpx client instance to use for making file uploads.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
None
|
|
58
|
+
"""
|
|
59
|
+
self._rest_client = rest_client
|
|
60
|
+
self._url_override = url_override
|
|
61
|
+
self._workspace_name = workspace_name
|
|
62
|
+
self._rest_httpx_client = rest_httpx_client
|
|
63
|
+
|
|
64
|
+
def get_attachment_list(
|
|
65
|
+
self,
|
|
66
|
+
project_name: str,
|
|
67
|
+
entity_id: str,
|
|
68
|
+
entity_type: Literal["span", "trace"],
|
|
69
|
+
) -> List[RESTAttachmentDetails]: # type: ignore
|
|
70
|
+
"""
|
|
71
|
+
Get a list of attachments for a specific entity (trace or span).
|
|
72
|
+
|
|
73
|
+
Parameters:
|
|
74
|
+
project_name: The name of the project containing the entity.
|
|
75
|
+
entity_id: The ID of the trace or span to retrieve attachments for.
|
|
76
|
+
entity_type: The type of entity ("trace" or "span").
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
List[RESTAttachmentDetails]: List of attachment detail objects containing metadata about each attachment.
|
|
80
|
+
"""
|
|
81
|
+
project_id = self._resolve_project_id(project_name)
|
|
82
|
+
url_override_path = base64.b64encode(self._url_override.encode("utf-8")).decode(
|
|
83
|
+
"utf-8"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
response = self._rest_client.attachments.attachment_list(
|
|
87
|
+
project_id=project_id,
|
|
88
|
+
entity_type=entity_type,
|
|
89
|
+
entity_id=entity_id,
|
|
90
|
+
path=url_override_path,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return response.content or []
|
|
94
|
+
|
|
95
|
+
def download_attachment(
|
|
96
|
+
self,
|
|
97
|
+
project_name: str,
|
|
98
|
+
entity_type: Literal["trace", "span"],
|
|
99
|
+
entity_id: str,
|
|
100
|
+
file_name: str,
|
|
101
|
+
mime_type: str,
|
|
102
|
+
) -> Iterator[bytes]:
|
|
103
|
+
"""
|
|
104
|
+
Download an attachment as a stream of bytes.
|
|
105
|
+
|
|
106
|
+
Parameters:
|
|
107
|
+
project_name: The name of the project containing the entity.
|
|
108
|
+
entity_type: The type of entity ("trace" or "span").
|
|
109
|
+
entity_id: The ID of the trace or span containing the attachment.
|
|
110
|
+
file_name: The name of the file to download.
|
|
111
|
+
mime_type: The MIME type of the file.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Iterator[bytes]: Iterator yielding bytes of the attachment content.
|
|
115
|
+
"""
|
|
116
|
+
attachments_details = self.get_attachment_list(
|
|
117
|
+
project_name=project_name,
|
|
118
|
+
entity_type=entity_type,
|
|
119
|
+
entity_id=entity_id,
|
|
120
|
+
)
|
|
121
|
+
attachment_to_download: Optional[RESTAttachmentDetails] = None
|
|
122
|
+
|
|
123
|
+
for attachment_details in attachments_details:
|
|
124
|
+
if (
|
|
125
|
+
attachment_details.file_name == file_name
|
|
126
|
+
and attachment_details.mime_type == mime_type
|
|
127
|
+
):
|
|
128
|
+
attachment_to_download = attachment_details
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
if attachment_to_download is None:
|
|
132
|
+
raise ValueError(f"Attachment not found: {file_name}")
|
|
133
|
+
|
|
134
|
+
if attachment_to_download.link is None:
|
|
135
|
+
raise ValueError(f"No download URL available for attachment: {file_name}")
|
|
136
|
+
|
|
137
|
+
httpx_client_upload = (
|
|
138
|
+
s3_httpx_client.get_cached()
|
|
139
|
+
if url_helpers.is_aws_presigned_url(attachment_to_download.link)
|
|
140
|
+
else self._rest_httpx_client
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
with httpx_client_upload.stream("GET", attachment_to_download.link) as response:
|
|
144
|
+
try:
|
|
145
|
+
if 200 <= response.status_code < 300:
|
|
146
|
+
for chunk in response.iter_bytes():
|
|
147
|
+
yield chunk
|
|
148
|
+
return
|
|
149
|
+
response.read()
|
|
150
|
+
response_json = response.json()
|
|
151
|
+
except json.decoder.JSONDecodeError:
|
|
152
|
+
raise rest_api_core.ApiError(
|
|
153
|
+
status_code=response.status_code,
|
|
154
|
+
headers=dict(response.headers),
|
|
155
|
+
body=response.text,
|
|
156
|
+
)
|
|
157
|
+
raise rest_api_core.ApiError(
|
|
158
|
+
status_code=response.status_code,
|
|
159
|
+
headers=dict(response.headers),
|
|
160
|
+
body=response_json,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def upload_attachment(
|
|
164
|
+
self,
|
|
165
|
+
project_name: str,
|
|
166
|
+
entity_type: Literal["trace", "span"],
|
|
167
|
+
entity_id: str,
|
|
168
|
+
file_path: str,
|
|
169
|
+
file_name: Optional[str] = None,
|
|
170
|
+
mime_type: Optional[str] = None,
|
|
171
|
+
) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Upload an attachment for a specific entity (trace or span).
|
|
174
|
+
|
|
175
|
+
Parameters:
|
|
176
|
+
project_name: The name of the project containing the entity.
|
|
177
|
+
entity_type: The type of entity ("trace" or "span").
|
|
178
|
+
entity_id: The ID of the trace or span to attach the file to.
|
|
179
|
+
file_path: The path to the file to upload on the local filesystem.
|
|
180
|
+
file_name: The name to assign to the uploaded file. If not provided, uses the basename of file_path.
|
|
181
|
+
mime_type: The MIME type of the file. If not provided, attempts to automatically detect based on the file extension.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
None
|
|
185
|
+
"""
|
|
186
|
+
if not os.path.exists(file_path):
|
|
187
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
188
|
+
|
|
189
|
+
if file_name is None:
|
|
190
|
+
file_name = os.path.basename(file_path)
|
|
191
|
+
|
|
192
|
+
if mime_type is None:
|
|
193
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
|
194
|
+
|
|
195
|
+
file_size = os.path.getsize(file_path)
|
|
196
|
+
encoded_url_override = base64.b64encode(
|
|
197
|
+
self._url_override.encode("utf-8")
|
|
198
|
+
).decode("utf-8")
|
|
199
|
+
|
|
200
|
+
upload_opts = upload_options.FileUploadOptions(
|
|
201
|
+
file_path=file_path,
|
|
202
|
+
file_name=file_name,
|
|
203
|
+
file_size=file_size,
|
|
204
|
+
mime_type=mime_type,
|
|
205
|
+
entity_type=entity_type,
|
|
206
|
+
entity_id=entity_id,
|
|
207
|
+
project_name=project_name,
|
|
208
|
+
encoded_url_override=encoded_url_override,
|
|
209
|
+
delete_after_upload=False,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
file_uploader.upload_attachment(
|
|
213
|
+
upload_options=upload_opts,
|
|
214
|
+
rest_client=self._rest_client,
|
|
215
|
+
upload_httpx_client=self._rest_httpx_client,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def _resolve_project_id(self, project_name: str) -> str:
|
|
219
|
+
project = self._rest_client.projects.retrieve_project(name=project_name)
|
|
220
|
+
return project.id
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import os
|
|
3
|
+
from typing import Literal, Optional
|
|
4
|
+
|
|
5
|
+
from ...file_upload import mime_type
|
|
6
|
+
from ...message_processing import messages
|
|
7
|
+
from . import attachment
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def attachment_to_message(
|
|
11
|
+
attachment_data: attachment.Attachment,
|
|
12
|
+
entity_type: Literal["trace", "span"],
|
|
13
|
+
entity_id: str,
|
|
14
|
+
project_name: str,
|
|
15
|
+
url_override: str,
|
|
16
|
+
delete_after_upload: bool = False,
|
|
17
|
+
) -> messages.CreateAttachmentMessage:
|
|
18
|
+
if attachment_data.data is None:
|
|
19
|
+
raise ValueError("Attachment data cannot be None")
|
|
20
|
+
|
|
21
|
+
mimetype = guess_attachment_type(attachment_data)
|
|
22
|
+
base_url_path = base64.b64encode(url_override.encode("utf-8")).decode("utf-8")
|
|
23
|
+
file_path = attachment_data.data
|
|
24
|
+
file_name = attachment_data.file_name
|
|
25
|
+
if file_name is None:
|
|
26
|
+
file_name = os.path.basename(file_path)
|
|
27
|
+
|
|
28
|
+
return messages.CreateAttachmentMessage(
|
|
29
|
+
file_path=file_path,
|
|
30
|
+
file_name=file_name,
|
|
31
|
+
mime_type=mimetype,
|
|
32
|
+
entity_type=entity_type,
|
|
33
|
+
entity_id=entity_id,
|
|
34
|
+
project_name=project_name,
|
|
35
|
+
encoded_url_override=base_url_path,
|
|
36
|
+
delete_after_upload=delete_after_upload,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def guess_attachment_type(attachment_data: attachment.Attachment) -> Optional[str]:
|
|
41
|
+
if attachment_data.content_type is not None:
|
|
42
|
+
return attachment_data.content_type
|
|
43
|
+
|
|
44
|
+
mimetype = None
|
|
45
|
+
if attachment_data.file_name is not None:
|
|
46
|
+
mimetype = mime_type.guess_mime_type(file=attachment_data.file_name)
|
|
47
|
+
|
|
48
|
+
if mimetype is None and isinstance(attachment_data.data, str):
|
|
49
|
+
mimetype = mime_type.guess_mime_type(file=attachment_data.data)
|
|
50
|
+
|
|
51
|
+
return mimetype
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
from . import attachment
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AttachmentDecoder(abc.ABC):
|
|
8
|
+
"""
|
|
9
|
+
Abstract base class for decoding file attachments.
|
|
10
|
+
|
|
11
|
+
This class serves as an interface for decoding raw attachment data into
|
|
12
|
+
an `Attachment` object. Implementing classes should define the specific
|
|
13
|
+
logic to handle various attachment decoding formats.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@abc.abstractmethod
|
|
17
|
+
def decode(self, raw_data: str, **kwargs: Any) -> Optional[attachment.Attachment]:
|
|
18
|
+
pass
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import binascii
|
|
3
|
+
import logging
|
|
4
|
+
import tempfile
|
|
5
|
+
from typing import Any, Optional, Literal
|
|
6
|
+
|
|
7
|
+
from . import attachment, decoder, decoder_helpers
|
|
8
|
+
|
|
9
|
+
LOGGER = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Base64AttachmentDecoder(decoder.AttachmentDecoder):
|
|
13
|
+
"""Decodes base64 encoded attachment data.
|
|
14
|
+
|
|
15
|
+
This decoder decodes base64 strings, detects MIME types from content, and creates Attachment objects.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def decode(
|
|
19
|
+
self,
|
|
20
|
+
raw_data: str,
|
|
21
|
+
context: Literal["input", "output", "metadata"] = "input",
|
|
22
|
+
**kwargs: Any,
|
|
23
|
+
) -> Optional[attachment.Attachment]:
|
|
24
|
+
"""Decode base64 encoded data into an Attachment object.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
raw_data: Base64 encoded string data
|
|
28
|
+
context: Context string for filename generation.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Attachment object with decoded data, or None if decoding fails or type is not recognizable
|
|
32
|
+
"""
|
|
33
|
+
if not isinstance(raw_data, str):
|
|
34
|
+
LOGGER.warning("Attachment data is not a string, skipping.")
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
# Decode base64 string to bytes
|
|
39
|
+
decoded_bytes = base64.b64decode(raw_data, validate=True)
|
|
40
|
+
|
|
41
|
+
# Detect MIME type from content
|
|
42
|
+
mime_type = decoder_helpers.detect_mime_type(decoded_bytes)
|
|
43
|
+
|
|
44
|
+
# Skip if not a recognizable file type
|
|
45
|
+
if not mime_type or mime_type in ("application/octet-stream", "text/plain"):
|
|
46
|
+
LOGGER.debug("Attachment type is not recognized, skipping.")
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
# Get file extension from the MIME type
|
|
50
|
+
extension = decoder_helpers.get_file_extension(mime_type)
|
|
51
|
+
|
|
52
|
+
# Generate filename
|
|
53
|
+
file_name = decoder_helpers.create_attachment_filename(
|
|
54
|
+
context, extension=extension
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Save decoded bytes to a temporary file
|
|
58
|
+
temp_file = tempfile.NamedTemporaryFile(
|
|
59
|
+
mode="wb", delete=False, suffix=extension
|
|
60
|
+
)
|
|
61
|
+
temp_file.write(decoded_bytes)
|
|
62
|
+
temp_file.flush()
|
|
63
|
+
temp_file.close()
|
|
64
|
+
|
|
65
|
+
# Return Attachment object with a file path
|
|
66
|
+
return attachment.Attachment(
|
|
67
|
+
data=temp_file.name, file_name=file_name, content_type=mime_type
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
except (ValueError, binascii.Error) as e:
|
|
71
|
+
LOGGER.debug(
|
|
72
|
+
"Failed to decode attachment data, reason: invalid base64. Reason: %s",
|
|
73
|
+
e,
|
|
74
|
+
exc_info=True,
|
|
75
|
+
)
|
|
76
|
+
# Not valid base64, return None
|
|
77
|
+
return None
|
|
78
|
+
except Exception as ex:
|
|
79
|
+
LOGGER.warning(
|
|
80
|
+
"Failed to decode attachment data, reason: %s", ex, exc_info=True
|
|
81
|
+
)
|
|
82
|
+
# Unexpected error, return None to avoid crashing the pipeline
|
|
83
|
+
return None
|
|
@@ -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}"
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import List, Callable
|
|
2
|
+
|
|
3
|
+
from opik.rest_api import TracePublic, JsonListStringPublic
|
|
4
|
+
from . import conversation_thread
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def create_conversation_from_traces(
|
|
8
|
+
traces: List[TracePublic],
|
|
9
|
+
input_transform: Callable[[JsonListStringPublic], str],
|
|
10
|
+
output_transform: Callable[[JsonListStringPublic], str],
|
|
11
|
+
) -> conversation_thread.ConversationThread:
|
|
12
|
+
"""
|
|
13
|
+
Creates a conversation object from given traces, transforming inputs and outputs using
|
|
14
|
+
provided transformation functions. The method processes each trace to compose a complete
|
|
15
|
+
conversation by consecutively adding user messages and assistant messages.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
traces: A list of TracePublic objects representing trace data for user
|
|
19
|
+
and assistant interaction flows.
|
|
20
|
+
input_transform: A callable function that transforms the input data
|
|
21
|
+
from a JsonListStringPublic format to a string.
|
|
22
|
+
output_transform: A callable function that transforms the output data
|
|
23
|
+
from a JsonListStringPublic format to a string.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
A Conversation object that contains user and assistant message
|
|
27
|
+
sequences derived from the provided traces.
|
|
28
|
+
"""
|
|
29
|
+
# Sort traces by start time to ensure they are processed in the correct order -
|
|
30
|
+
# the first user message should be first recorded
|
|
31
|
+
traces.sort(key=lambda trace_: trace_.start_time)
|
|
32
|
+
|
|
33
|
+
discussion = conversation_thread.ConversationThread()
|
|
34
|
+
for trace in traces:
|
|
35
|
+
trace_input = input_transform(trace.input)
|
|
36
|
+
if trace_input is not None:
|
|
37
|
+
discussion.add_user_message(trace_input)
|
|
38
|
+
|
|
39
|
+
trace_output = output_transform(trace.output)
|
|
40
|
+
if trace_output is not None:
|
|
41
|
+
discussion.add_assistant_message(trace_output)
|
|
42
|
+
|
|
43
|
+
return discussion
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
|
|
3
|
+
import pydantic
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConversationThreadItem(pydantic.BaseModel):
|
|
7
|
+
"""
|
|
8
|
+
Represents a single message within a conversation thread.
|
|
9
|
+
|
|
10
|
+
Each ConversationItem contains the role of the sender (e.g., 'user', 'assistant', 'system')
|
|
11
|
+
and the content of the message. This structured format allows for consistent representation
|
|
12
|
+
of messages across different conversation interfaces and evaluation systems.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
role: str
|
|
16
|
+
content: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ConversationThread(pydantic.BaseModel):
|
|
20
|
+
"""
|
|
21
|
+
Represents a conversation thread composed of multiple conversation items.
|
|
22
|
+
|
|
23
|
+
This class is built using Pydantic's BaseModel to ensure type validation and data
|
|
24
|
+
integrity. It maintains a list of conversation items, where each item is an
|
|
25
|
+
instance of the `ConversationThreadItem` class. The conversation thread allows
|
|
26
|
+
adding messages from various roles, such as assistant, user, and system, and
|
|
27
|
+
provides the ability to export the conversation data as a JSON-serializable list.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
discussion (List[ConversationThreadItem]): A list of conversation items
|
|
31
|
+
representing the dialogue between the roles.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
discussion: List[ConversationThreadItem] = pydantic.Field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
def add_item(self, item: ConversationThreadItem) -> None:
|
|
37
|
+
self.discussion.append(item)
|
|
38
|
+
|
|
39
|
+
def add_assistant_message(self, message: str) -> None:
|
|
40
|
+
self.add_item(ConversationThreadItem(role="assistant", content=message))
|
|
41
|
+
|
|
42
|
+
def add_user_message(self, message: str) -> None:
|
|
43
|
+
self.add_item(ConversationThreadItem(role="user", content=message))
|
|
44
|
+
|
|
45
|
+
def add_system_message(self, message: str) -> None:
|
|
46
|
+
self.add_item(ConversationThreadItem(role="system", content=message))
|
|
47
|
+
|
|
48
|
+
def as_json_list(self) -> List[Dict[str, str]]:
|
|
49
|
+
return [item.model_dump() for item in self.discussion]
|
|
@@ -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
|