opik 1.9.39__py3-none-any.whl → 1.9.86__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/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/constants.py +2 -0
- opik/api_objects/dataset/dataset.py +133 -40
- opik/api_objects/dataset/rest_operations.py +2 -0
- opik/api_objects/experiment/experiment.py +6 -0
- opik/api_objects/helpers.py +8 -4
- opik/api_objects/local_recording.py +6 -5
- opik/api_objects/observation_data.py +101 -0
- opik/api_objects/opik_client.py +78 -45
- opik/api_objects/opik_query_language.py +9 -3
- opik/api_objects/prompt/chat/chat_prompt.py +18 -1
- opik/api_objects/prompt/client.py +8 -1
- opik/api_objects/span/span_data.py +3 -88
- opik/api_objects/threads/threads_client.py +7 -4
- opik/api_objects/trace/trace_data.py +3 -74
- opik/api_objects/validation_helpers.py +3 -3
- 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/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 +14 -12
- opik/config.py +12 -1
- opik/datetime_helpers.py +12 -0
- opik/decorator/arguments_helpers.py +4 -1
- opik/decorator/base_track_decorator.py +111 -37
- opik/decorator/context_manager/span_context_manager.py +5 -1
- opik/decorator/generator_wrappers.py +5 -4
- opik/decorator/span_creation_handler.py +13 -4
- opik/evaluation/engine/engine.py +111 -28
- opik/evaluation/engine/evaluation_tasks_executor.py +71 -19
- opik/evaluation/evaluator.py +12 -0
- opik/evaluation/metrics/conversation/llm_judges/conversational_coherence/metric.py +3 -1
- opik/evaluation/metrics/conversation/llm_judges/session_completeness/metric.py +3 -1
- opik/evaluation/metrics/conversation/llm_judges/user_frustration/metric.py +3 -1
- opik/evaluation/metrics/heuristics/equals.py +11 -7
- opik/evaluation/metrics/llm_judges/answer_relevance/metric.py +3 -1
- opik/evaluation/metrics/llm_judges/context_precision/metric.py +3 -1
- opik/evaluation/metrics/llm_judges/context_recall/metric.py +3 -1
- opik/evaluation/metrics/llm_judges/factuality/metric.py +1 -1
- opik/evaluation/metrics/llm_judges/g_eval/metric.py +3 -1
- opik/evaluation/metrics/llm_judges/hallucination/metric.py +3 -1
- opik/evaluation/metrics/llm_judges/moderation/metric.py +3 -1
- opik/evaluation/metrics/llm_judges/structure_output_compliance/metric.py +3 -1
- opik/evaluation/metrics/llm_judges/syc_eval/metric.py +4 -2
- opik/evaluation/metrics/llm_judges/trajectory_accuracy/metric.py +3 -1
- opik/evaluation/metrics/llm_judges/usefulness/metric.py +3 -1
- opik/evaluation/metrics/ragas_metric.py +43 -23
- opik/evaluation/models/litellm/litellm_chat_model.py +7 -2
- opik/evaluation/models/litellm/util.py +4 -20
- opik/evaluation/models/models_factory.py +19 -5
- opik/evaluation/rest_operations.py +3 -3
- opik/evaluation/threads/helpers.py +3 -2
- opik/file_upload/file_uploader.py +13 -0
- opik/file_upload/upload_options.py +2 -0
- opik/integrations/adk/legacy_opik_tracer.py +9 -11
- opik/integrations/adk/opik_tracer.py +2 -2
- opik/integrations/adk/patchers/adk_otel_tracer/opik_adk_otel_tracer.py +2 -2
- opik/integrations/dspy/callback.py +100 -14
- 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_tracer.py +2 -2
- opik/integrations/langchain/__init__.py +15 -2
- opik/integrations/langchain/langgraph_tracer_injector.py +88 -0
- opik/integrations/langchain/opik_tracer.py +258 -160
- opik/integrations/langchain/provider_usage_extractors/langchain_run_helpers/helpers.py +7 -4
- opik/integrations/llama_index/callback.py +43 -6
- opik/integrations/openai/agents/opik_tracing_processor.py +8 -10
- opik/integrations/openai/opik_tracker.py +99 -4
- opik/integrations/openai/videos/__init__.py +9 -0
- opik/integrations/openai/videos/binary_response_write_to_file_decorator.py +88 -0
- opik/integrations/openai/videos/videos_create_decorator.py +159 -0
- opik/integrations/openai/videos/videos_download_decorator.py +110 -0
- opik/message_processing/batching/base_batcher.py +14 -21
- opik/message_processing/batching/batch_manager.py +22 -10
- opik/message_processing/batching/batchers.py +32 -40
- opik/message_processing/batching/flushing_thread.py +0 -3
- opik/message_processing/emulation/emulator_message_processor.py +36 -1
- opik/message_processing/emulation/models.py +21 -0
- opik/message_processing/messages.py +9 -0
- 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/{message_processors.py → processors/message_processors.py} +15 -1
- opik/message_processing/{message_processors_chain.py → processors/message_processors_chain.py} +3 -2
- opik/message_processing/{online_message_processor.py → processors/online_message_processor.py} +11 -9
- opik/message_processing/queue_consumer.py +4 -2
- opik/message_processing/streamer.py +71 -33
- opik/message_processing/streamer_constructors.py +36 -8
- opik/plugins/pytest/experiment_runner.py +1 -1
- opik/plugins/pytest/hooks.py +5 -3
- opik/rest_api/__init__.py +42 -0
- opik/rest_api/datasets/client.py +321 -123
- opik/rest_api/datasets/raw_client.py +470 -145
- opik/rest_api/experiments/client.py +26 -0
- opik/rest_api/experiments/raw_client.py +26 -0
- opik/rest_api/llm_provider_key/client.py +4 -4
- opik/rest_api/llm_provider_key/raw_client.py +4 -4
- opik/rest_api/llm_provider_key/types/provider_api_key_write_provider.py +2 -1
- opik/rest_api/manual_evaluation/client.py +101 -0
- opik/rest_api/manual_evaluation/raw_client.py +172 -0
- opik/rest_api/optimizations/client.py +0 -166
- opik/rest_api/optimizations/raw_client.py +0 -248
- opik/rest_api/projects/client.py +9 -0
- opik/rest_api/projects/raw_client.py +13 -0
- opik/rest_api/projects/types/project_metric_request_public_metric_type.py +4 -0
- opik/rest_api/prompts/client.py +130 -2
- opik/rest_api/prompts/raw_client.py +175 -0
- opik/rest_api/traces/client.py +101 -0
- opik/rest_api/traces/raw_client.py +120 -0
- opik/rest_api/types/__init__.py +50 -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 +38 -2
- opik/rest_api/types/automation_rule_evaluator_object_object_public.py +33 -2
- opik/rest_api/types/automation_rule_evaluator_public.py +33 -2
- 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_update.py +27 -1
- opik/rest_api/types/automation_rule_evaluator_update_span_user_defined_metric_python.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_write.py +27 -1
- opik/rest_api/types/dataset.py +2 -0
- opik/rest_api/types/dataset_item.py +1 -1
- opik/rest_api/types/dataset_item_batch.py +4 -0
- opik/rest_api/types/dataset_item_changes_public.py +5 -0
- opik/rest_api/types/dataset_item_compare.py +1 -1
- opik/rest_api/types/dataset_item_filter.py +4 -0
- opik/rest_api/types/dataset_item_page_compare.py +0 -1
- opik/rest_api/types/dataset_item_page_public.py +0 -1
- opik/rest_api/types/dataset_item_public.py +1 -1
- opik/rest_api/types/dataset_public.py +2 -0
- opik/rest_api/types/dataset_version_public.py +10 -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 +9 -0
- opik/rest_api/types/experiment_public.py +9 -0
- opik/rest_api/types/group_content_with_aggregations.py +1 -0
- opik/rest_api/types/llm_as_judge_message_content.py +2 -0
- opik/rest_api/types/llm_as_judge_message_content_public.py +2 -0
- opik/rest_api/types/llm_as_judge_message_content_write.py +2 -0
- opik/rest_api/types/manual_evaluation_request_entity_type.py +1 -1
- opik/rest_api/types/project.py +1 -0
- opik/rest_api/types/project_detailed.py +1 -0
- opik/rest_api/types/project_metric_response_public_metric_type.py +4 -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_version.py +1 -0
- opik/rest_api/types/prompt_version_detail.py +1 -0
- opik/rest_api/types/prompt_version_page_public.py +5 -0
- opik/rest_api/types/prompt_version_public.py +1 -0
- opik/rest_api/types/prompt_version_update.py +33 -0
- opik/rest_api/types/provider_api_key.py +5 -1
- opik/rest_api/types/provider_api_key_provider.py +2 -1
- opik/rest_api/types/provider_api_key_public.py +5 -1
- opik/rest_api/types/provider_api_key_public_provider.py +2 -1
- opik/rest_api/types/service_toggles_config.py +11 -1
- 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/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.39.dist-info → opik-1.9.86.dist-info}/METADATA +7 -7
- {opik-1.9.39.dist-info → opik-1.9.86.dist-info}/RECORD +193 -142
- opik/cli/export.py +0 -791
- opik/cli/import_command.py +0 -575
- {opik-1.9.39.dist-info → opik-1.9.86.dist-info}/WHEEL +0 -0
- {opik-1.9.39.dist-info → opik-1.9.86.dist-info}/entry_points.txt +0 -0
- {opik-1.9.39.dist-info → opik-1.9.86.dist-info}/licenses/LICENSE +0 -0
- {opik-1.9.39.dist-info → opik-1.9.86.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"""Import command for Opik CLI."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Optional
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
import opik
|
|
11
|
+
|
|
12
|
+
from .dataset import import_datasets_from_directory
|
|
13
|
+
from .experiment import import_experiments_from_directory
|
|
14
|
+
from .project import import_projects_from_directory
|
|
15
|
+
from .prompt import import_prompts_from_directory
|
|
16
|
+
from .utils import print_import_summary, debug_print
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
IMPORT_CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _import_by_type(
|
|
24
|
+
import_type: str,
|
|
25
|
+
path: str,
|
|
26
|
+
workspace: str,
|
|
27
|
+
dry_run: bool,
|
|
28
|
+
name_pattern: Optional[str],
|
|
29
|
+
debug: bool,
|
|
30
|
+
recreate_experiments: bool = False,
|
|
31
|
+
api_key: Optional[str] = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Import data by type (dataset, project, experiment) with pattern matching.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
import_type: Type of data to import ("dataset", "project", "experiment")
|
|
38
|
+
path: Base directory containing the exported data
|
|
39
|
+
workspace: Target workspace name
|
|
40
|
+
dry_run: Whether to show what would be imported without importing
|
|
41
|
+
name_pattern: Optional string pattern to filter items by name (case-insensitive substring matching)
|
|
42
|
+
debug: Enable debug output
|
|
43
|
+
recreate_experiments: Whether to recreate experiments after importing
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
debug_print(f"DEBUG: Starting {import_type} import from {path}", debug)
|
|
47
|
+
|
|
48
|
+
# Initialize Opik client
|
|
49
|
+
if api_key:
|
|
50
|
+
client = opik.Opik(api_key=api_key, workspace=workspace)
|
|
51
|
+
else:
|
|
52
|
+
client = opik.Opik(workspace=workspace)
|
|
53
|
+
|
|
54
|
+
# Determine source directory based on import type
|
|
55
|
+
base_path = Path(path)
|
|
56
|
+
|
|
57
|
+
if import_type == "dataset":
|
|
58
|
+
source_dir = base_path / "datasets"
|
|
59
|
+
elif import_type == "project":
|
|
60
|
+
source_dir = base_path / "projects"
|
|
61
|
+
elif import_type == "experiment":
|
|
62
|
+
source_dir = base_path / "experiments"
|
|
63
|
+
elif import_type == "prompt":
|
|
64
|
+
source_dir = base_path / "prompts"
|
|
65
|
+
else:
|
|
66
|
+
console.print(f"[red]Unknown import type: {import_type}[/red]")
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
if not source_dir.exists():
|
|
70
|
+
console.print(f"[red]Source directory {source_dir} does not exist[/red]")
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
|
|
73
|
+
debug_print(f"Source directory: {source_dir}", debug)
|
|
74
|
+
|
|
75
|
+
stats: Dict[str, int] = {}
|
|
76
|
+
|
|
77
|
+
if import_type == "dataset":
|
|
78
|
+
stats = import_datasets_from_directory(
|
|
79
|
+
client, source_dir, dry_run, name_pattern, debug
|
|
80
|
+
)
|
|
81
|
+
elif import_type == "project":
|
|
82
|
+
stats = import_projects_from_directory(
|
|
83
|
+
client, source_dir, dry_run, name_pattern, debug, recreate_experiments
|
|
84
|
+
)
|
|
85
|
+
elif import_type == "experiment":
|
|
86
|
+
stats = import_experiments_from_directory(
|
|
87
|
+
client, source_dir, dry_run, name_pattern, debug
|
|
88
|
+
)
|
|
89
|
+
elif import_type == "prompt":
|
|
90
|
+
stats = import_prompts_from_directory(
|
|
91
|
+
client, source_dir, dry_run, name_pattern, debug
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Display summary
|
|
95
|
+
print_import_summary(stats)
|
|
96
|
+
|
|
97
|
+
# Also show a simple message for backward compatibility
|
|
98
|
+
# Map import_type to the key used in stats dictionary
|
|
99
|
+
type_key_map = {
|
|
100
|
+
"dataset": "datasets",
|
|
101
|
+
"prompt": "prompts",
|
|
102
|
+
"project": "projects",
|
|
103
|
+
"experiment": "experiments",
|
|
104
|
+
}
|
|
105
|
+
stats_key = type_key_map.get(import_type, import_type + "s")
|
|
106
|
+
imported_count = stats.get(stats_key, 0)
|
|
107
|
+
errors = stats.get(stats_key + "_errors", 0)
|
|
108
|
+
|
|
109
|
+
if dry_run:
|
|
110
|
+
console.print(
|
|
111
|
+
f"[blue]Dry run complete: Would import {imported_count} {import_type}s[/blue]"
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
if errors > 0:
|
|
115
|
+
console.print(
|
|
116
|
+
f"[yellow]Import completed with {errors} error(s) while importing {import_type}s[/yellow]"
|
|
117
|
+
)
|
|
118
|
+
elif imported_count == 0:
|
|
119
|
+
console.print(f"[yellow]No {import_type}s were imported[/yellow]")
|
|
120
|
+
else:
|
|
121
|
+
console.print(
|
|
122
|
+
f"[green]Successfully imported {imported_count} {import_type}s[/green]"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
console.print(f"[red]Error importing {import_type}s: {e}[/red]")
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@click.group(name="import", context_settings=IMPORT_CONTEXT_SETTINGS)
|
|
131
|
+
@click.argument("workspace", type=str)
|
|
132
|
+
@click.option(
|
|
133
|
+
"--api-key",
|
|
134
|
+
type=str,
|
|
135
|
+
help="Opik API key. If not provided, will use OPIK_API_KEY environment variable or configuration.",
|
|
136
|
+
)
|
|
137
|
+
@click.pass_context
|
|
138
|
+
def import_group(ctx: click.Context, workspace: str, api_key: Optional[str]) -> None:
|
|
139
|
+
"""Import data to Opik workspace.
|
|
140
|
+
|
|
141
|
+
This command allows you to import previously exported data back into an Opik workspace.
|
|
142
|
+
Supported data types include projects, datasets, experiments, and prompts.
|
|
143
|
+
|
|
144
|
+
\b
|
|
145
|
+
General Usage:
|
|
146
|
+
opik import WORKSPACE TYPE NAME [OPTIONS]
|
|
147
|
+
|
|
148
|
+
\b
|
|
149
|
+
Data Types:
|
|
150
|
+
project Import projects from path/projects/ (default: opik_exports)
|
|
151
|
+
dataset Import datasets from path/datasets/ (default: opik_exports)
|
|
152
|
+
experiment Import experiments from path/experiments/ (default: opik_exports)
|
|
153
|
+
prompt Import prompts from path/prompts/ (default: opik_exports)
|
|
154
|
+
|
|
155
|
+
\b
|
|
156
|
+
Common Options:
|
|
157
|
+
--path, -p Directory containing exported data (default: opik_exports)
|
|
158
|
+
--dry-run Preview what would be imported without actually importing
|
|
159
|
+
--debug Show detailed information about the import process
|
|
160
|
+
|
|
161
|
+
\b
|
|
162
|
+
Examples:
|
|
163
|
+
# Preview an experiment that would be imported
|
|
164
|
+
opik import my-workspace experiment "my-experiment" --dry-run
|
|
165
|
+
|
|
166
|
+
# Import a specific project
|
|
167
|
+
opik import my-workspace project "my-project"
|
|
168
|
+
|
|
169
|
+
# Import a specific dataset
|
|
170
|
+
opik import my-workspace dataset "my-dataset"
|
|
171
|
+
|
|
172
|
+
# Import from a custom path
|
|
173
|
+
opik import my-workspace project "my-project" --path ./custom-exports/
|
|
174
|
+
"""
|
|
175
|
+
ctx.ensure_object(dict)
|
|
176
|
+
ctx.obj["workspace"] = workspace
|
|
177
|
+
# Use API key from this command or from parent context
|
|
178
|
+
ctx.obj["api_key"] = api_key or (
|
|
179
|
+
ctx.parent.obj.get("api_key") if ctx.parent and ctx.parent.obj else None
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# Set subcommand metavar to ITEM instead of COMMAND
|
|
184
|
+
import_group.subcommand_metavar = "ITEM [ARGS]..."
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def format_commands(
|
|
188
|
+
self: click.Group, ctx: click.Context, formatter: click.HelpFormatter
|
|
189
|
+
) -> None:
|
|
190
|
+
"""Override to change 'Commands' heading to 'Items'."""
|
|
191
|
+
commands = []
|
|
192
|
+
for subcommand in self.list_commands(ctx):
|
|
193
|
+
cmd = self.get_command(ctx, subcommand)
|
|
194
|
+
if cmd is None or cmd.hidden:
|
|
195
|
+
continue
|
|
196
|
+
commands.append((subcommand, cmd))
|
|
197
|
+
|
|
198
|
+
if len(commands):
|
|
199
|
+
limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands)
|
|
200
|
+
rows = []
|
|
201
|
+
for subcommand, cmd in commands:
|
|
202
|
+
help = cmd.get_short_help_str(limit)
|
|
203
|
+
rows.append((subcommand, help))
|
|
204
|
+
|
|
205
|
+
if rows:
|
|
206
|
+
with formatter.section("Items"):
|
|
207
|
+
formatter.write_dl(rows)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# Override format_commands method
|
|
211
|
+
setattr(
|
|
212
|
+
import_group,
|
|
213
|
+
"format_commands",
|
|
214
|
+
format_commands.__get__(import_group, type(import_group)),
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@import_group.command(name="dataset")
|
|
219
|
+
@click.argument("name", type=str)
|
|
220
|
+
@click.option(
|
|
221
|
+
"--path",
|
|
222
|
+
"-p",
|
|
223
|
+
type=click.Path(file_okay=False, dir_okay=True, readable=True),
|
|
224
|
+
default="opik_exports",
|
|
225
|
+
help="Directory containing exported data. Defaults to opik_exports.",
|
|
226
|
+
)
|
|
227
|
+
@click.option(
|
|
228
|
+
"--dry-run",
|
|
229
|
+
is_flag=True,
|
|
230
|
+
help="Show what would be imported without actually importing. Use this to preview datasets before importing.",
|
|
231
|
+
)
|
|
232
|
+
@click.option(
|
|
233
|
+
"--debug",
|
|
234
|
+
is_flag=True,
|
|
235
|
+
help="Enable debug output to show detailed information about the import process.",
|
|
236
|
+
)
|
|
237
|
+
@click.pass_context
|
|
238
|
+
def import_dataset(
|
|
239
|
+
ctx: click.Context,
|
|
240
|
+
name: str,
|
|
241
|
+
path: str,
|
|
242
|
+
dry_run: bool,
|
|
243
|
+
debug: bool,
|
|
244
|
+
) -> None:
|
|
245
|
+
"""Import datasets from workspace/datasets directory.
|
|
246
|
+
|
|
247
|
+
This command imports datasets matching the specified name from the path/datasets/ directory.
|
|
248
|
+
The name is matched using case-insensitive substring matching.
|
|
249
|
+
|
|
250
|
+
\b
|
|
251
|
+
Examples:
|
|
252
|
+
\b
|
|
253
|
+
# Preview a dataset that would be imported
|
|
254
|
+
opik import my-workspace dataset "my-dataset" --dry-run
|
|
255
|
+
\b
|
|
256
|
+
# Import a specific dataset
|
|
257
|
+
opik import my-workspace dataset "my-dataset"
|
|
258
|
+
\b
|
|
259
|
+
# Import datasets containing "training" in the name
|
|
260
|
+
opik import my-workspace dataset "training"
|
|
261
|
+
\b
|
|
262
|
+
# Import from a custom path
|
|
263
|
+
opik import my-workspace dataset "my-dataset" --path ./custom-exports/
|
|
264
|
+
"""
|
|
265
|
+
workspace = ctx.obj["workspace"]
|
|
266
|
+
api_key = ctx.obj.get("api_key") if ctx.obj else None
|
|
267
|
+
_import_by_type("dataset", path, workspace, dry_run, name, debug, api_key=api_key)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@import_group.command(name="project")
|
|
271
|
+
@click.argument("name", type=str)
|
|
272
|
+
@click.option(
|
|
273
|
+
"--path",
|
|
274
|
+
"-p",
|
|
275
|
+
type=click.Path(file_okay=False, dir_okay=True, readable=True),
|
|
276
|
+
default="opik_exports",
|
|
277
|
+
help="Directory containing exported data. Defaults to opik_exports.",
|
|
278
|
+
)
|
|
279
|
+
@click.option(
|
|
280
|
+
"--dry-run",
|
|
281
|
+
is_flag=True,
|
|
282
|
+
help="Show what would be imported without actually importing. Use this to preview projects before importing.",
|
|
283
|
+
)
|
|
284
|
+
@click.option(
|
|
285
|
+
"--debug",
|
|
286
|
+
is_flag=True,
|
|
287
|
+
help="Enable debug output to show detailed information about the import process.",
|
|
288
|
+
)
|
|
289
|
+
@click.pass_context
|
|
290
|
+
def import_project(
|
|
291
|
+
ctx: click.Context,
|
|
292
|
+
name: str,
|
|
293
|
+
path: str,
|
|
294
|
+
dry_run: bool,
|
|
295
|
+
debug: bool,
|
|
296
|
+
) -> None:
|
|
297
|
+
"""Import projects from workspace/projects directory.
|
|
298
|
+
|
|
299
|
+
This command imports projects matching the specified name from the path/projects/ directory.
|
|
300
|
+
The name is matched using case-insensitive substring matching.
|
|
301
|
+
|
|
302
|
+
\b
|
|
303
|
+
Examples:
|
|
304
|
+
\b
|
|
305
|
+
# Preview a project that would be imported
|
|
306
|
+
opik import my-workspace project "my-project" --dry-run
|
|
307
|
+
\b
|
|
308
|
+
# Import a specific project
|
|
309
|
+
opik import my-workspace project "my-project"
|
|
310
|
+
\b
|
|
311
|
+
# Import projects containing "test" in the name
|
|
312
|
+
opik import my-workspace project "test"
|
|
313
|
+
\b
|
|
314
|
+
# Import projects with debug output
|
|
315
|
+
opik import my-workspace project "my-project" --debug
|
|
316
|
+
\b
|
|
317
|
+
# Import from a custom path
|
|
318
|
+
opik import my-workspace project "my-project" --path ./custom-exports/
|
|
319
|
+
"""
|
|
320
|
+
workspace = ctx.obj["workspace"]
|
|
321
|
+
api_key = ctx.obj.get("api_key") if ctx.obj else None
|
|
322
|
+
_import_by_type(
|
|
323
|
+
"project",
|
|
324
|
+
path,
|
|
325
|
+
workspace,
|
|
326
|
+
dry_run,
|
|
327
|
+
name,
|
|
328
|
+
debug,
|
|
329
|
+
True, # Always recreate experiments when importing projects
|
|
330
|
+
api_key=api_key,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@import_group.command(name="experiment")
|
|
335
|
+
@click.argument("name", type=str)
|
|
336
|
+
@click.option(
|
|
337
|
+
"--path",
|
|
338
|
+
"-p",
|
|
339
|
+
type=click.Path(file_okay=False, dir_okay=True, readable=True),
|
|
340
|
+
default="opik_exports",
|
|
341
|
+
help="Directory containing exported data. Defaults to opik_exports.",
|
|
342
|
+
)
|
|
343
|
+
@click.option(
|
|
344
|
+
"--dry-run",
|
|
345
|
+
is_flag=True,
|
|
346
|
+
help="Show what would be imported without actually importing.",
|
|
347
|
+
)
|
|
348
|
+
@click.option(
|
|
349
|
+
"--debug",
|
|
350
|
+
is_flag=True,
|
|
351
|
+
help="Enable debug output to show detailed information about the import process.",
|
|
352
|
+
)
|
|
353
|
+
@click.pass_context
|
|
354
|
+
def import_experiment(
|
|
355
|
+
ctx: click.Context,
|
|
356
|
+
name: str,
|
|
357
|
+
path: str,
|
|
358
|
+
dry_run: bool,
|
|
359
|
+
debug: bool,
|
|
360
|
+
) -> None:
|
|
361
|
+
"""Import experiments from workspace/experiments directory.
|
|
362
|
+
|
|
363
|
+
This command imports experiments matching the specified name from the path/experiments/ directory.
|
|
364
|
+
The name is matched using case-insensitive substring matching.
|
|
365
|
+
|
|
366
|
+
\b
|
|
367
|
+
Examples:
|
|
368
|
+
\b
|
|
369
|
+
# Preview an experiment that would be imported
|
|
370
|
+
opik import my-workspace experiment "my-experiment" --dry-run
|
|
371
|
+
\b
|
|
372
|
+
# Import a specific experiment
|
|
373
|
+
opik import my-workspace experiment "my-experiment"
|
|
374
|
+
\b
|
|
375
|
+
# Import from a custom path
|
|
376
|
+
opik import my-workspace experiment "my-experiment" --path ./custom-exports/
|
|
377
|
+
"""
|
|
378
|
+
workspace = ctx.obj["workspace"]
|
|
379
|
+
api_key = ctx.obj.get("api_key") if ctx.obj else None
|
|
380
|
+
# Always recreate experiments when importing
|
|
381
|
+
_import_by_type(
|
|
382
|
+
"experiment",
|
|
383
|
+
path,
|
|
384
|
+
workspace,
|
|
385
|
+
dry_run,
|
|
386
|
+
name,
|
|
387
|
+
debug,
|
|
388
|
+
True,
|
|
389
|
+
api_key=api_key,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@import_group.command(name="prompt")
|
|
394
|
+
@click.argument("name", type=str)
|
|
395
|
+
@click.option(
|
|
396
|
+
"--path",
|
|
397
|
+
"-p",
|
|
398
|
+
type=click.Path(file_okay=False, dir_okay=True, readable=True),
|
|
399
|
+
default="opik_exports",
|
|
400
|
+
help="Directory containing exported data. Defaults to opik_exports.",
|
|
401
|
+
)
|
|
402
|
+
@click.option(
|
|
403
|
+
"--dry-run",
|
|
404
|
+
is_flag=True,
|
|
405
|
+
help="Show what would be imported without actually importing.",
|
|
406
|
+
)
|
|
407
|
+
@click.option(
|
|
408
|
+
"--debug",
|
|
409
|
+
is_flag=True,
|
|
410
|
+
help="Enable debug output to show detailed information about the import process.",
|
|
411
|
+
)
|
|
412
|
+
@click.pass_context
|
|
413
|
+
def import_prompt(
|
|
414
|
+
ctx: click.Context,
|
|
415
|
+
name: str,
|
|
416
|
+
path: str,
|
|
417
|
+
dry_run: bool,
|
|
418
|
+
debug: bool,
|
|
419
|
+
) -> None:
|
|
420
|
+
"""Import prompts from workspace/prompts directory.
|
|
421
|
+
|
|
422
|
+
This command imports prompts matching the specified name from the path/prompts/ directory.
|
|
423
|
+
The name is matched using case-insensitive substring matching.
|
|
424
|
+
|
|
425
|
+
\b
|
|
426
|
+
Examples:
|
|
427
|
+
\b
|
|
428
|
+
# Preview a prompt that would be imported
|
|
429
|
+
opik import my-workspace prompt "my-prompt" --dry-run
|
|
430
|
+
\b
|
|
431
|
+
# Import a specific prompt
|
|
432
|
+
opik import my-workspace prompt "my-prompt"
|
|
433
|
+
\b
|
|
434
|
+
# Import from a custom path
|
|
435
|
+
opik import my-workspace prompt "my-prompt" --path ./custom-exports/
|
|
436
|
+
"""
|
|
437
|
+
workspace = ctx.obj["workspace"]
|
|
438
|
+
api_key = ctx.obj.get("api_key") if ctx.obj else None
|
|
439
|
+
_import_by_type("prompt", path, workspace, dry_run, name, debug, api_key=api_key)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Dataset import functionality."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Optional
|
|
6
|
+
|
|
7
|
+
import opik
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from .utils import matches_name_pattern
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def import_datasets_from_directory(
|
|
16
|
+
client: opik.Opik,
|
|
17
|
+
source_dir: Path,
|
|
18
|
+
dry_run: bool,
|
|
19
|
+
name_pattern: Optional[str],
|
|
20
|
+
debug: bool,
|
|
21
|
+
) -> Dict[str, int]:
|
|
22
|
+
"""Import datasets from a directory.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Dictionary with keys: 'datasets', 'datasets_skipped', 'datasets_errors'
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
dataset_files = list(source_dir.glob("dataset_*.json"))
|
|
29
|
+
|
|
30
|
+
if not dataset_files:
|
|
31
|
+
console.print("[yellow]No dataset files found in the directory[/yellow]")
|
|
32
|
+
return {"datasets": 0, "datasets_skipped": 0, "datasets_errors": 0}
|
|
33
|
+
|
|
34
|
+
imported_count = 0
|
|
35
|
+
skipped_count = 0
|
|
36
|
+
error_count = 0
|
|
37
|
+
for dataset_file in dataset_files:
|
|
38
|
+
try:
|
|
39
|
+
with open(dataset_file, "r", encoding="utf-8") as f:
|
|
40
|
+
dataset_data = json.load(f)
|
|
41
|
+
|
|
42
|
+
# Handle two export formats:
|
|
43
|
+
# 1. {"name": "...", "items": [...]} - from export_single_dataset
|
|
44
|
+
# 2. {"dataset": {"name": "...", "id": "..."}, "items": [...]} - from export_experiment_datasets
|
|
45
|
+
dataset_name = dataset_data.get("name") or (
|
|
46
|
+
dataset_data.get("dataset", {}).get("name")
|
|
47
|
+
if dataset_data.get("dataset")
|
|
48
|
+
else None
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Check if name is missing or empty
|
|
52
|
+
if not dataset_name or (
|
|
53
|
+
isinstance(dataset_name, str) and not dataset_name.strip()
|
|
54
|
+
):
|
|
55
|
+
console.print(
|
|
56
|
+
f"[red]Error: Dataset file {dataset_file.name} is missing or has an empty name field[/red]"
|
|
57
|
+
)
|
|
58
|
+
error_count += 1
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# Strip whitespace from name
|
|
62
|
+
dataset_name = dataset_name.strip()
|
|
63
|
+
|
|
64
|
+
# Filter by name pattern if specified
|
|
65
|
+
if name_pattern and not matches_name_pattern(
|
|
66
|
+
dataset_name, name_pattern
|
|
67
|
+
):
|
|
68
|
+
if debug:
|
|
69
|
+
console.print(
|
|
70
|
+
f"[blue]Skipping dataset {dataset_name} (doesn't match pattern)[/blue]"
|
|
71
|
+
)
|
|
72
|
+
skipped_count += 1
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
if dry_run:
|
|
76
|
+
console.print(f"[blue]Would import dataset: {dataset_name}[/blue]")
|
|
77
|
+
imported_count += 1
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
if debug:
|
|
81
|
+
console.print(f"[blue]Importing dataset: {dataset_name}[/blue]")
|
|
82
|
+
|
|
83
|
+
# Get or create dataset (handles case where dataset already exists)
|
|
84
|
+
try:
|
|
85
|
+
dataset = client.get_dataset(dataset_name)
|
|
86
|
+
if debug:
|
|
87
|
+
console.print(
|
|
88
|
+
f"[blue]Dataset '{dataset_name}' already exists, using existing dataset[/blue]"
|
|
89
|
+
)
|
|
90
|
+
except Exception:
|
|
91
|
+
# Dataset doesn't exist, create it
|
|
92
|
+
dataset = client.create_dataset(name=dataset_name)
|
|
93
|
+
if debug:
|
|
94
|
+
console.print(
|
|
95
|
+
f"[blue]Created new dataset: {dataset_name}[/blue]"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Import dataset items
|
|
99
|
+
items = dataset_data.get("items", [])
|
|
100
|
+
if items:
|
|
101
|
+
# Remove 'id' field from items before inserting (IDs are auto-generated)
|
|
102
|
+
items_to_insert = []
|
|
103
|
+
for item in items:
|
|
104
|
+
if isinstance(item, dict):
|
|
105
|
+
# Create a copy without the 'id' field
|
|
106
|
+
item_copy = {k: v for k, v in item.items() if k != "id"}
|
|
107
|
+
items_to_insert.append(item_copy)
|
|
108
|
+
else:
|
|
109
|
+
items_to_insert.append(item)
|
|
110
|
+
|
|
111
|
+
if items_to_insert:
|
|
112
|
+
dataset.insert(items_to_insert)
|
|
113
|
+
if debug:
|
|
114
|
+
console.print(
|
|
115
|
+
f"[blue]Inserted {len(items_to_insert)} items into dataset '{dataset_name}'[/blue]"
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
console.print(
|
|
119
|
+
f"[yellow]Warning: No items to insert for dataset '{dataset_name}' (all items were empty after removing 'id' field)[/yellow]"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
imported_count += 1
|
|
123
|
+
if debug:
|
|
124
|
+
console.print(
|
|
125
|
+
f"[green]Imported dataset: {dataset_name} with {len(items)} items[/green]"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
console.print(
|
|
130
|
+
f"[red]Error importing dataset from {dataset_file.name}: {e}[/red]"
|
|
131
|
+
)
|
|
132
|
+
error_count += 1
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
"datasets": imported_count,
|
|
137
|
+
"datasets_skipped": skipped_count,
|
|
138
|
+
"datasets_errors": error_count,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
except Exception as e:
|
|
142
|
+
console.print(f"[red]Error importing datasets: {e}[/red]")
|
|
143
|
+
return {"datasets": 0, "datasets_skipped": 0, "datasets_errors": 1}
|