opik 1.9.41__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 +38 -0
- opik/rest_api/datasets/client.py +249 -148
- opik/rest_api/datasets/raw_client.py +356 -217
- 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 +46 -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_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_version_public.py +5 -0
- opik/rest_api/types/dataset_version_summary.py +5 -0
- opik/rest_api/types/dataset_version_summary_public.py +5 -0
- opik/rest_api/types/experiment.py +9 -0
- opik/rest_api/types/experiment_public.py +9 -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.41.dist-info → opik-1.9.86.dist-info}/METADATA +5 -5
- {opik-1.9.41.dist-info → opik-1.9.86.dist-info}/RECORD +190 -141
- opik/cli/export.py +0 -791
- opik/cli/import_command.py +0 -575
- {opik-1.9.41.dist-info → opik-1.9.86.dist-info}/WHEEL +0 -0
- {opik-1.9.41.dist-info → opik-1.9.86.dist-info}/entry_points.txt +0 -0
- {opik-1.9.41.dist-info → opik-1.9.86.dist-info}/licenses/LICENSE +0 -0
- {opik-1.9.41.dist-info → opik-1.9.86.dist-info}/top_level.txt +0 -0
opik/cli/export.py
DELETED
|
@@ -1,791 +0,0 @@
|
|
|
1
|
-
"""Download command for Opik CLI."""
|
|
2
|
-
|
|
3
|
-
import csv
|
|
4
|
-
import json
|
|
5
|
-
import re
|
|
6
|
-
import sys
|
|
7
|
-
from datetime import datetime
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import Dict, List, Optional
|
|
10
|
-
|
|
11
|
-
import click
|
|
12
|
-
from rich.console import Console
|
|
13
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
14
|
-
|
|
15
|
-
import opik
|
|
16
|
-
|
|
17
|
-
console = Console()
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _matches_name_pattern(name: str, pattern: Optional[str]) -> bool:
|
|
21
|
-
"""Check if a name matches the given regex pattern."""
|
|
22
|
-
if pattern is None:
|
|
23
|
-
return True
|
|
24
|
-
try:
|
|
25
|
-
return bool(re.search(pattern, name))
|
|
26
|
-
except re.error as e:
|
|
27
|
-
console.print(f"[red]Invalid regex pattern '{pattern}': {e}[/red]")
|
|
28
|
-
return False
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _flatten_dict_with_prefix(data: Dict, prefix: str = "") -> Dict:
|
|
32
|
-
"""Flatten a nested dictionary with a prefix for CSV export."""
|
|
33
|
-
if not data:
|
|
34
|
-
return {}
|
|
35
|
-
|
|
36
|
-
flattened = {}
|
|
37
|
-
for key, value in data.items():
|
|
38
|
-
prefixed_key = f"{prefix}_{key}" if prefix else key
|
|
39
|
-
if isinstance(value, (dict, list)):
|
|
40
|
-
flattened[prefixed_key] = str(value)
|
|
41
|
-
else:
|
|
42
|
-
flattened[prefixed_key] = value if value is not None else ""
|
|
43
|
-
|
|
44
|
-
return flattened
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _dump_to_file(
|
|
48
|
-
data: dict,
|
|
49
|
-
file_path: Path,
|
|
50
|
-
file_format: str,
|
|
51
|
-
csv_writer: Optional[csv.DictWriter] = None,
|
|
52
|
-
csv_fieldnames: Optional[List[str]] = None,
|
|
53
|
-
) -> tuple:
|
|
54
|
-
"""
|
|
55
|
-
Helper function to dump data to file in the specified format.
|
|
56
|
-
|
|
57
|
-
Args:
|
|
58
|
-
data: The data to dump
|
|
59
|
-
file_path: Path where to save the file
|
|
60
|
-
file_format: Format to use ("json" or "csv")
|
|
61
|
-
csv_writer: Existing CSV writer (for CSV format)
|
|
62
|
-
csv_fieldnames: Existing CSV fieldnames (for CSV format)
|
|
63
|
-
|
|
64
|
-
Returns:
|
|
65
|
-
Tuple of (csv_writer, csv_fieldnames) for CSV format, or (None, None) for JSON
|
|
66
|
-
"""
|
|
67
|
-
if file_format.lower() == "csv":
|
|
68
|
-
# Convert to CSV rows
|
|
69
|
-
csv_rows = _trace_to_csv_rows(data)
|
|
70
|
-
|
|
71
|
-
# Initialize CSV writer if not already done
|
|
72
|
-
if csv_writer is None and csv_rows:
|
|
73
|
-
csv_file_handle = open(file_path, "w", newline="", encoding="utf-8")
|
|
74
|
-
csv_fieldnames = list(csv_rows[0].keys())
|
|
75
|
-
csv_writer = csv.DictWriter(csv_file_handle, fieldnames=csv_fieldnames)
|
|
76
|
-
csv_writer.writeheader()
|
|
77
|
-
|
|
78
|
-
# Write rows
|
|
79
|
-
if csv_writer and csv_rows:
|
|
80
|
-
csv_writer.writerows(csv_rows)
|
|
81
|
-
|
|
82
|
-
return csv_writer, csv_fieldnames
|
|
83
|
-
else:
|
|
84
|
-
# Save to JSON file
|
|
85
|
-
with open(file_path, "w", encoding="utf-8") as f:
|
|
86
|
-
json.dump(data, f, indent=2, default=str)
|
|
87
|
-
|
|
88
|
-
return None, None
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def _trace_to_csv_rows(trace_data: dict) -> List[Dict]:
|
|
92
|
-
"""Convert trace data to CSV rows format."""
|
|
93
|
-
trace = trace_data["trace"]
|
|
94
|
-
spans = trace_data.get("spans", [])
|
|
95
|
-
|
|
96
|
-
# Flatten trace data with "trace" prefix
|
|
97
|
-
trace_flat = _flatten_dict_with_prefix(trace, "trace")
|
|
98
|
-
|
|
99
|
-
# If no spans, create a single row for the trace
|
|
100
|
-
if not spans:
|
|
101
|
-
# Create empty span fields to maintain consistent structure
|
|
102
|
-
span_flat = {f"span_{key}": "" for key in trace.keys()}
|
|
103
|
-
span_flat["span_parent_span_id"] = "" # Special case for parent_span_id
|
|
104
|
-
|
|
105
|
-
# Combine trace and empty span data
|
|
106
|
-
row = {**trace_flat, **span_flat}
|
|
107
|
-
return [row]
|
|
108
|
-
|
|
109
|
-
# Create rows for each span
|
|
110
|
-
rows = []
|
|
111
|
-
for span in spans:
|
|
112
|
-
# Flatten span data with "span" prefix
|
|
113
|
-
span_flat = _flatten_dict_with_prefix(span, "span")
|
|
114
|
-
|
|
115
|
-
# Combine trace and span data
|
|
116
|
-
row = {**trace_flat, **span_flat}
|
|
117
|
-
rows.append(row)
|
|
118
|
-
|
|
119
|
-
return rows
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def _export_data_type(
|
|
123
|
-
data_type: str,
|
|
124
|
-
client: opik.Opik,
|
|
125
|
-
output_dir: Path,
|
|
126
|
-
max_results: int,
|
|
127
|
-
name_pattern: Optional[str] = None,
|
|
128
|
-
debug: bool = False,
|
|
129
|
-
) -> int:
|
|
130
|
-
"""
|
|
131
|
-
Helper function to export a specific data type.
|
|
132
|
-
|
|
133
|
-
Args:
|
|
134
|
-
data_type: Type of data to export ("traces", "datasets", "prompts")
|
|
135
|
-
client: Opik client instance
|
|
136
|
-
output_dir: Directory to save exported data
|
|
137
|
-
max_results: Maximum number of items to export
|
|
138
|
-
name_pattern: Optional name pattern filter
|
|
139
|
-
debug: Whether to enable debug output
|
|
140
|
-
|
|
141
|
-
Returns:
|
|
142
|
-
Number of items exported
|
|
143
|
-
"""
|
|
144
|
-
if data_type == "traces":
|
|
145
|
-
# This requires additional parameters, so we'll handle it separately
|
|
146
|
-
raise ValueError(
|
|
147
|
-
"Traces export requires additional parameters - use _export_traces directly"
|
|
148
|
-
)
|
|
149
|
-
elif data_type == "datasets":
|
|
150
|
-
if debug:
|
|
151
|
-
console.print("[blue]Downloading datasets...[/blue]")
|
|
152
|
-
return _export_datasets(client, output_dir, max_results, name_pattern, debug)
|
|
153
|
-
elif data_type == "prompts":
|
|
154
|
-
if debug:
|
|
155
|
-
console.print("[blue]Downloading prompts...[/blue]")
|
|
156
|
-
return _export_prompts(client, output_dir, max_results, name_pattern)
|
|
157
|
-
else:
|
|
158
|
-
console.print(f"[red]Unknown data type: {data_type}[/red]")
|
|
159
|
-
return 0
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def _export_traces(
|
|
163
|
-
client: opik.Opik,
|
|
164
|
-
project_name: str,
|
|
165
|
-
project_dir: Path,
|
|
166
|
-
max_results: int,
|
|
167
|
-
filter: Optional[str],
|
|
168
|
-
name_pattern: Optional[str] = None,
|
|
169
|
-
trace_format: str = "json",
|
|
170
|
-
) -> int:
|
|
171
|
-
"""Download traces and their spans with pagination support for large projects."""
|
|
172
|
-
console.print(
|
|
173
|
-
f"[blue]DEBUG: _export_traces called with project_name: {project_name}, project_dir: {project_dir}[/blue]"
|
|
174
|
-
)
|
|
175
|
-
exported_count = 0
|
|
176
|
-
page_size = min(100, max_results) # Process in smaller batches
|
|
177
|
-
last_trace_time = None
|
|
178
|
-
total_processed = 0
|
|
179
|
-
|
|
180
|
-
# For CSV format, set up streaming writer
|
|
181
|
-
csv_file = None
|
|
182
|
-
csv_file_handle = None
|
|
183
|
-
csv_writer = None
|
|
184
|
-
csv_fieldnames = None
|
|
185
|
-
|
|
186
|
-
try:
|
|
187
|
-
with Progress(
|
|
188
|
-
SpinnerColumn(),
|
|
189
|
-
TextColumn("[progress.description]{task.description}"),
|
|
190
|
-
console=console,
|
|
191
|
-
) as progress:
|
|
192
|
-
task = progress.add_task("Searching for traces...", total=None)
|
|
193
|
-
|
|
194
|
-
while total_processed < max_results:
|
|
195
|
-
# Calculate how many traces to fetch in this batch
|
|
196
|
-
remaining = max_results - total_processed
|
|
197
|
-
current_page_size = min(page_size, remaining)
|
|
198
|
-
|
|
199
|
-
# Build filter string with pagination
|
|
200
|
-
pagination_filter = filter or ""
|
|
201
|
-
if last_trace_time:
|
|
202
|
-
# Add timestamp filter to continue from where we left off
|
|
203
|
-
time_filter = f"start_time < '{last_trace_time.isoformat()}'"
|
|
204
|
-
if pagination_filter:
|
|
205
|
-
pagination_filter = f"({pagination_filter}) AND {time_filter}"
|
|
206
|
-
else:
|
|
207
|
-
pagination_filter = time_filter
|
|
208
|
-
|
|
209
|
-
try:
|
|
210
|
-
console.print(
|
|
211
|
-
f"[blue]DEBUG: Searching traces with project_name: {project_name}, filter: {pagination_filter}, max_results: {current_page_size}[/blue]"
|
|
212
|
-
)
|
|
213
|
-
traces = client.search_traces(
|
|
214
|
-
project_name=project_name,
|
|
215
|
-
filter_string=pagination_filter if pagination_filter else None,
|
|
216
|
-
max_results=current_page_size,
|
|
217
|
-
truncate=False, # Don't truncate data for download
|
|
218
|
-
)
|
|
219
|
-
console.print(
|
|
220
|
-
f"[blue]DEBUG: Found {len(traces) if traces else 0} traces[/blue]"
|
|
221
|
-
)
|
|
222
|
-
except Exception as e:
|
|
223
|
-
console.print(f"[red]Error searching traces: {e}[/red]")
|
|
224
|
-
break
|
|
225
|
-
|
|
226
|
-
if not traces:
|
|
227
|
-
# No more traces to process
|
|
228
|
-
break
|
|
229
|
-
|
|
230
|
-
# Update progress description
|
|
231
|
-
progress.update(
|
|
232
|
-
task, description=f"Found {len(traces)} traces in current batch"
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
# Store original traces for pagination before filtering
|
|
236
|
-
original_traces = traces
|
|
237
|
-
|
|
238
|
-
# Filter traces by name pattern if specified
|
|
239
|
-
if name_pattern:
|
|
240
|
-
original_count = len(traces)
|
|
241
|
-
traces = [
|
|
242
|
-
trace
|
|
243
|
-
for trace in traces
|
|
244
|
-
if _matches_name_pattern(trace.name or "", name_pattern)
|
|
245
|
-
]
|
|
246
|
-
if len(traces) < original_count:
|
|
247
|
-
console.print(
|
|
248
|
-
f"[blue]Filtered to {len(traces)} traces matching pattern '{name_pattern}' in current batch[/blue]"
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
if not traces:
|
|
252
|
-
# No traces match the name pattern, but we might have more to process
|
|
253
|
-
# Use original_traces for pagination, not the filtered empty list
|
|
254
|
-
last_trace_time = (
|
|
255
|
-
original_traces[0].start_time if original_traces else None
|
|
256
|
-
)
|
|
257
|
-
total_processed += current_page_size
|
|
258
|
-
continue
|
|
259
|
-
|
|
260
|
-
# Update progress for downloading
|
|
261
|
-
progress.update(
|
|
262
|
-
task,
|
|
263
|
-
description=f"Downloading traces... (batch {total_processed // page_size + 1})",
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
# Download each trace with its spans
|
|
267
|
-
for trace in traces:
|
|
268
|
-
try:
|
|
269
|
-
# Get spans for this trace
|
|
270
|
-
spans = client.search_spans(
|
|
271
|
-
project_name=project_name,
|
|
272
|
-
trace_id=trace.id,
|
|
273
|
-
max_results=1000, # Get all spans for the trace
|
|
274
|
-
truncate=False,
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
# Create trace data structure
|
|
278
|
-
trace_data = {
|
|
279
|
-
"trace": trace.model_dump(),
|
|
280
|
-
"spans": [span.model_dump() for span in spans],
|
|
281
|
-
"downloaded_at": datetime.now().isoformat(),
|
|
282
|
-
"project_name": project_name,
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
# Determine file path based on format
|
|
286
|
-
if trace_format.lower() == "csv":
|
|
287
|
-
file_path = project_dir / f"traces_{project_name}.csv"
|
|
288
|
-
else:
|
|
289
|
-
file_path = project_dir / f"trace_{trace.id}.json"
|
|
290
|
-
|
|
291
|
-
# Use helper function to dump data
|
|
292
|
-
csv_writer, csv_fieldnames = _dump_to_file(
|
|
293
|
-
trace_data,
|
|
294
|
-
file_path,
|
|
295
|
-
trace_format,
|
|
296
|
-
csv_writer,
|
|
297
|
-
csv_fieldnames,
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
exported_count += 1
|
|
301
|
-
total_processed += 1
|
|
302
|
-
|
|
303
|
-
except Exception as e:
|
|
304
|
-
console.print(
|
|
305
|
-
f"[red]Error exporting trace {trace.id}: {e}[/red]"
|
|
306
|
-
)
|
|
307
|
-
continue
|
|
308
|
-
|
|
309
|
-
# Update last trace time for pagination
|
|
310
|
-
if traces:
|
|
311
|
-
last_trace_time = traces[-1].start_time
|
|
312
|
-
|
|
313
|
-
# If we got fewer traces than requested, we've reached the end
|
|
314
|
-
if len(traces) < current_page_size:
|
|
315
|
-
break
|
|
316
|
-
|
|
317
|
-
# Final progress update
|
|
318
|
-
if exported_count == 0:
|
|
319
|
-
console.print("[yellow]No traces found in the project.[/yellow]")
|
|
320
|
-
else:
|
|
321
|
-
progress.update(
|
|
322
|
-
task, description=f"Exported {exported_count} traces total"
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
finally:
|
|
326
|
-
# Close CSV file if it was opened
|
|
327
|
-
if csv_file_handle:
|
|
328
|
-
csv_file_handle.close()
|
|
329
|
-
if csv_file and csv_file.exists():
|
|
330
|
-
console.print(f"[green]CSV file saved to {csv_file}[/green]")
|
|
331
|
-
|
|
332
|
-
return exported_count
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
def _export_datasets(
|
|
336
|
-
client: opik.Opik,
|
|
337
|
-
project_dir: Path,
|
|
338
|
-
max_results: int,
|
|
339
|
-
name_pattern: Optional[str] = None,
|
|
340
|
-
debug: bool = False,
|
|
341
|
-
) -> int:
|
|
342
|
-
"""Export datasets."""
|
|
343
|
-
try:
|
|
344
|
-
datasets = client.get_datasets(max_results=max_results, sync_items=True)
|
|
345
|
-
|
|
346
|
-
if not datasets:
|
|
347
|
-
console.print("[yellow]No datasets found in the project.[/yellow]")
|
|
348
|
-
return 0
|
|
349
|
-
|
|
350
|
-
# Filter datasets by name pattern if specified
|
|
351
|
-
if name_pattern:
|
|
352
|
-
original_count = len(datasets)
|
|
353
|
-
datasets = [
|
|
354
|
-
dataset
|
|
355
|
-
for dataset in datasets
|
|
356
|
-
if _matches_name_pattern(dataset.name, name_pattern)
|
|
357
|
-
]
|
|
358
|
-
if len(datasets) < original_count:
|
|
359
|
-
console.print(
|
|
360
|
-
f"[blue]Filtered to {len(datasets)} datasets matching pattern '{name_pattern}'[/blue]"
|
|
361
|
-
)
|
|
362
|
-
|
|
363
|
-
if not datasets:
|
|
364
|
-
console.print(
|
|
365
|
-
"[yellow]No datasets found matching the name pattern.[/yellow]"
|
|
366
|
-
)
|
|
367
|
-
return 0
|
|
368
|
-
|
|
369
|
-
exported_count = 0
|
|
370
|
-
for dataset in datasets:
|
|
371
|
-
try:
|
|
372
|
-
# Get dataset items using the get_items method
|
|
373
|
-
if debug:
|
|
374
|
-
console.print(
|
|
375
|
-
f"[blue]Getting items for dataset: {dataset.name}[/blue]"
|
|
376
|
-
)
|
|
377
|
-
dataset_items = dataset.get_items()
|
|
378
|
-
|
|
379
|
-
# Convert dataset items to the expected format for import
|
|
380
|
-
formatted_items = []
|
|
381
|
-
for item in dataset_items:
|
|
382
|
-
formatted_item = {
|
|
383
|
-
"input": item.get("input"),
|
|
384
|
-
"expected_output": item.get("expected_output"),
|
|
385
|
-
"metadata": item.get("metadata"),
|
|
386
|
-
}
|
|
387
|
-
formatted_items.append(formatted_item)
|
|
388
|
-
|
|
389
|
-
# Create dataset data structure
|
|
390
|
-
dataset_data = {
|
|
391
|
-
"name": dataset.name,
|
|
392
|
-
"description": dataset.description,
|
|
393
|
-
"items": formatted_items,
|
|
394
|
-
"downloaded_at": datetime.now().isoformat(),
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
# Save to file
|
|
398
|
-
dataset_file = project_dir / f"dataset_{dataset.name}.json"
|
|
399
|
-
with open(dataset_file, "w", encoding="utf-8") as f:
|
|
400
|
-
json.dump(dataset_data, f, indent=2, default=str)
|
|
401
|
-
|
|
402
|
-
exported_count += 1
|
|
403
|
-
|
|
404
|
-
except Exception as e:
|
|
405
|
-
console.print(
|
|
406
|
-
f"[red]Error downloading dataset {dataset.name}: {e}[/red]"
|
|
407
|
-
)
|
|
408
|
-
continue
|
|
409
|
-
|
|
410
|
-
return exported_count
|
|
411
|
-
|
|
412
|
-
except Exception as e:
|
|
413
|
-
console.print(f"[red]Error exporting datasets: {e}[/red]")
|
|
414
|
-
return 0
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
def _export_prompts(
|
|
418
|
-
client: opik.Opik,
|
|
419
|
-
project_dir: Path,
|
|
420
|
-
max_results: int,
|
|
421
|
-
name_pattern: Optional[str] = None,
|
|
422
|
-
) -> int:
|
|
423
|
-
"""Export prompts."""
|
|
424
|
-
try:
|
|
425
|
-
prompts = client.search_prompts()
|
|
426
|
-
|
|
427
|
-
if not prompts:
|
|
428
|
-
console.print("[yellow]No prompts found in the project.[/yellow]")
|
|
429
|
-
return 0
|
|
430
|
-
|
|
431
|
-
# Filter prompts by name pattern if specified
|
|
432
|
-
if name_pattern:
|
|
433
|
-
original_count = len(prompts)
|
|
434
|
-
prompts = [
|
|
435
|
-
prompt
|
|
436
|
-
for prompt in prompts
|
|
437
|
-
if _matches_name_pattern(prompt.name, name_pattern)
|
|
438
|
-
]
|
|
439
|
-
if len(prompts) < original_count:
|
|
440
|
-
console.print(
|
|
441
|
-
f"[blue]Filtered to {len(prompts)} prompts matching pattern '{name_pattern}'[/blue]"
|
|
442
|
-
)
|
|
443
|
-
|
|
444
|
-
if not prompts:
|
|
445
|
-
console.print(
|
|
446
|
-
"[yellow]No prompts found matching the name pattern.[/yellow]"
|
|
447
|
-
)
|
|
448
|
-
return 0
|
|
449
|
-
|
|
450
|
-
exported_count = 0
|
|
451
|
-
for prompt in prompts:
|
|
452
|
-
try:
|
|
453
|
-
# Get prompt history
|
|
454
|
-
prompt_history = client.get_prompt_history(prompt.name)
|
|
455
|
-
|
|
456
|
-
# Create prompt data structure
|
|
457
|
-
prompt_data = {
|
|
458
|
-
"name": prompt.name,
|
|
459
|
-
"current_version": {
|
|
460
|
-
"prompt": prompt.prompt
|
|
461
|
-
if isinstance(prompt, opik.Prompt)
|
|
462
|
-
else None, # TODO: add support for chat prompts
|
|
463
|
-
"metadata": prompt.metadata,
|
|
464
|
-
"type": prompt.type if prompt.type else None,
|
|
465
|
-
"commit": prompt.commit,
|
|
466
|
-
},
|
|
467
|
-
"history": [
|
|
468
|
-
{
|
|
469
|
-
"prompt": version.prompt
|
|
470
|
-
if isinstance(version, opik.Prompt)
|
|
471
|
-
else None, # TODO: add support for chat prompts
|
|
472
|
-
"metadata": version.metadata,
|
|
473
|
-
"type": version.type if version.type else None,
|
|
474
|
-
"commit": version.commit,
|
|
475
|
-
}
|
|
476
|
-
for version in prompt_history
|
|
477
|
-
],
|
|
478
|
-
"downloaded_at": datetime.now().isoformat(),
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
# Save to file
|
|
482
|
-
prompt_file = (
|
|
483
|
-
project_dir / f"prompt_{prompt.name.replace('/', '_')}.json"
|
|
484
|
-
)
|
|
485
|
-
with open(prompt_file, "w", encoding="utf-8") as f:
|
|
486
|
-
json.dump(prompt_data, f, indent=2, default=str)
|
|
487
|
-
|
|
488
|
-
exported_count += 1
|
|
489
|
-
|
|
490
|
-
except Exception as e:
|
|
491
|
-
console.print(f"[red]Error downloading prompt {prompt.name}: {e}[/red]")
|
|
492
|
-
continue
|
|
493
|
-
|
|
494
|
-
return exported_count
|
|
495
|
-
|
|
496
|
-
except Exception as e:
|
|
497
|
-
console.print(f"[red]Error exporting prompts: {e}[/red]")
|
|
498
|
-
return 0
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
@click.command(name="export")
|
|
502
|
-
@click.argument("workspace_or_project", type=str)
|
|
503
|
-
@click.option(
|
|
504
|
-
"--path",
|
|
505
|
-
"-p",
|
|
506
|
-
type=click.Path(file_okay=False, dir_okay=True, writable=True),
|
|
507
|
-
default="./",
|
|
508
|
-
help="Directory to save exported data. Defaults to current directory.",
|
|
509
|
-
)
|
|
510
|
-
@click.option(
|
|
511
|
-
"--max-results",
|
|
512
|
-
type=int,
|
|
513
|
-
default=1000,
|
|
514
|
-
help="Maximum number of items to download per data type. Defaults to 1000.",
|
|
515
|
-
)
|
|
516
|
-
@click.option(
|
|
517
|
-
"--filter",
|
|
518
|
-
type=str,
|
|
519
|
-
help="Filter string to narrow down the search using Opik Query Language (OQL).",
|
|
520
|
-
)
|
|
521
|
-
@click.option(
|
|
522
|
-
"--all",
|
|
523
|
-
is_flag=True,
|
|
524
|
-
help="Include all data types (traces, datasets, prompts).",
|
|
525
|
-
)
|
|
526
|
-
@click.option(
|
|
527
|
-
"--include",
|
|
528
|
-
type=click.Choice(["traces", "datasets", "prompts"], case_sensitive=False),
|
|
529
|
-
multiple=True,
|
|
530
|
-
default=["traces"],
|
|
531
|
-
help="Data types to include in download. Can be specified multiple times. Defaults to traces only.",
|
|
532
|
-
)
|
|
533
|
-
@click.option(
|
|
534
|
-
"--exclude",
|
|
535
|
-
type=click.Choice(["traces", "datasets", "prompts"], case_sensitive=False),
|
|
536
|
-
multiple=True,
|
|
537
|
-
help="Data types to exclude from download. Can be specified multiple times.",
|
|
538
|
-
)
|
|
539
|
-
@click.option(
|
|
540
|
-
"--name",
|
|
541
|
-
type=str,
|
|
542
|
-
help="Filter items by name using Python regex patterns. Matches against trace names, dataset names, or prompt names.",
|
|
543
|
-
)
|
|
544
|
-
@click.option(
|
|
545
|
-
"--debug",
|
|
546
|
-
is_flag=True,
|
|
547
|
-
help="Enable debug output to show detailed information about the export process.",
|
|
548
|
-
)
|
|
549
|
-
@click.option(
|
|
550
|
-
"--trace-format",
|
|
551
|
-
type=click.Choice(["json", "csv"], case_sensitive=False),
|
|
552
|
-
default="json",
|
|
553
|
-
help="Format for exporting traces. Defaults to json.",
|
|
554
|
-
)
|
|
555
|
-
def export(
|
|
556
|
-
workspace_or_project: str,
|
|
557
|
-
path: str,
|
|
558
|
-
max_results: int,
|
|
559
|
-
filter: Optional[str],
|
|
560
|
-
all: bool,
|
|
561
|
-
include: tuple,
|
|
562
|
-
exclude: tuple,
|
|
563
|
-
name: Optional[str],
|
|
564
|
-
debug: bool,
|
|
565
|
-
trace_format: str,
|
|
566
|
-
) -> None:
|
|
567
|
-
"""
|
|
568
|
-
Download data from a workspace or workspace/project to local files.
|
|
569
|
-
|
|
570
|
-
This command fetches traces, datasets, and prompts from the specified workspace or project
|
|
571
|
-
and saves them to local JSON files in the output directory.
|
|
572
|
-
|
|
573
|
-
Note: Thread metadata is automatically derived from traces with the same thread_id,
|
|
574
|
-
so threads don't need to be exported separately.
|
|
575
|
-
|
|
576
|
-
WORKSPACE_OR_PROJECT: Either a workspace name (e.g., "my-workspace") to export all projects,
|
|
577
|
-
or workspace/project (e.g., "my-workspace/my-project") to export a specific project.
|
|
578
|
-
"""
|
|
579
|
-
try:
|
|
580
|
-
if debug:
|
|
581
|
-
console.print("[blue]DEBUG: Starting export with parameters:[/blue]")
|
|
582
|
-
console.print(
|
|
583
|
-
f"[blue] workspace_or_project: {workspace_or_project}[/blue]"
|
|
584
|
-
)
|
|
585
|
-
console.print(f"[blue] path: {path}[/blue]")
|
|
586
|
-
console.print(f"[blue] max_results: {max_results}[/blue]")
|
|
587
|
-
console.print(f"[blue] include: {include}[/blue]")
|
|
588
|
-
console.print(f"[blue] debug: {debug}[/blue]")
|
|
589
|
-
|
|
590
|
-
# Parse workspace/project from the argument
|
|
591
|
-
if "/" in workspace_or_project:
|
|
592
|
-
workspace, project_name = workspace_or_project.split("/", 1)
|
|
593
|
-
export_specific_project = True
|
|
594
|
-
if debug:
|
|
595
|
-
console.print(
|
|
596
|
-
f"[blue]DEBUG: Parsed workspace: {workspace}, project: {project_name}[/blue]"
|
|
597
|
-
)
|
|
598
|
-
else:
|
|
599
|
-
# Only workspace specified - download all projects
|
|
600
|
-
workspace = workspace_or_project
|
|
601
|
-
project_name = None
|
|
602
|
-
export_specific_project = False
|
|
603
|
-
if debug:
|
|
604
|
-
console.print(f"[blue]DEBUG: Workspace only: {workspace}[/blue]")
|
|
605
|
-
|
|
606
|
-
# Initialize Opik client with workspace
|
|
607
|
-
if debug:
|
|
608
|
-
console.print(
|
|
609
|
-
f"[blue]DEBUG: Initializing Opik client with workspace: {workspace}[/blue]"
|
|
610
|
-
)
|
|
611
|
-
client = opik.Opik(workspace=workspace)
|
|
612
|
-
|
|
613
|
-
# Create output directory
|
|
614
|
-
output_path = Path(path)
|
|
615
|
-
output_path.mkdir(parents=True, exist_ok=True)
|
|
616
|
-
|
|
617
|
-
# Determine which data types to download
|
|
618
|
-
if all:
|
|
619
|
-
# If --all is specified, include all data types
|
|
620
|
-
include_set = {"traces", "datasets", "prompts"}
|
|
621
|
-
else:
|
|
622
|
-
include_set = set(item.lower() for item in include)
|
|
623
|
-
|
|
624
|
-
exclude_set = set(item.lower() for item in exclude)
|
|
625
|
-
|
|
626
|
-
# Apply exclusions
|
|
627
|
-
data_types = include_set - exclude_set
|
|
628
|
-
|
|
629
|
-
if export_specific_project:
|
|
630
|
-
# Download from specific project
|
|
631
|
-
console.print(
|
|
632
|
-
f"[green]Downloading data from workspace: {workspace}, project: {project_name}[/green]"
|
|
633
|
-
)
|
|
634
|
-
|
|
635
|
-
# Create workspace/project directory structure
|
|
636
|
-
workspace_dir = output_path / workspace
|
|
637
|
-
assert project_name is not None # Type narrowing for mypy
|
|
638
|
-
project_dir = workspace_dir / project_name
|
|
639
|
-
project_dir.mkdir(parents=True, exist_ok=True)
|
|
640
|
-
|
|
641
|
-
if debug:
|
|
642
|
-
console.print(
|
|
643
|
-
f"[blue]Output directory: {workspace}/{project_name}[/blue]"
|
|
644
|
-
)
|
|
645
|
-
console.print(
|
|
646
|
-
f"[blue]Data types: {', '.join(sorted(data_types))}[/blue]"
|
|
647
|
-
)
|
|
648
|
-
|
|
649
|
-
# Note about workspace vs project-specific data
|
|
650
|
-
project_specific = [dt for dt in data_types if dt in ["traces"]]
|
|
651
|
-
workspace_data = [dt for dt in data_types if dt in ["datasets", "prompts"]]
|
|
652
|
-
|
|
653
|
-
if project_specific and workspace_data:
|
|
654
|
-
console.print(
|
|
655
|
-
f"[yellow]Note: {', '.join(project_specific)} are project-specific, {', '.join(workspace_data)} belong to workspace '{workspace}'[/yellow]"
|
|
656
|
-
)
|
|
657
|
-
elif workspace_data:
|
|
658
|
-
console.print(
|
|
659
|
-
f"[yellow]Note: {', '.join(workspace_data)} belong to workspace '{workspace}'[/yellow]"
|
|
660
|
-
)
|
|
661
|
-
|
|
662
|
-
# Download each data type
|
|
663
|
-
total_exported = 0
|
|
664
|
-
|
|
665
|
-
# Download traces
|
|
666
|
-
if "traces" in data_types:
|
|
667
|
-
if debug:
|
|
668
|
-
console.print("[blue]Downloading traces...[/blue]")
|
|
669
|
-
if debug:
|
|
670
|
-
console.print(
|
|
671
|
-
f"[blue]DEBUG: Calling _export_traces with project_name: {project_name}, project_dir: {project_dir}[/blue]"
|
|
672
|
-
)
|
|
673
|
-
traces_exported = _export_traces(
|
|
674
|
-
client,
|
|
675
|
-
project_name,
|
|
676
|
-
project_dir,
|
|
677
|
-
max_results,
|
|
678
|
-
filter,
|
|
679
|
-
name,
|
|
680
|
-
trace_format,
|
|
681
|
-
)
|
|
682
|
-
if debug:
|
|
683
|
-
console.print(
|
|
684
|
-
f"[blue]DEBUG: _export_traces returned: {traces_exported}[/blue]"
|
|
685
|
-
)
|
|
686
|
-
total_exported += traces_exported
|
|
687
|
-
|
|
688
|
-
# Download datasets and prompts (workspace-level data, but export to project directory for consistency)
|
|
689
|
-
for data_type in ["datasets", "prompts"]:
|
|
690
|
-
if data_type in data_types:
|
|
691
|
-
exported_count = _export_data_type(
|
|
692
|
-
data_type, client, project_dir, max_results, name, debug
|
|
693
|
-
)
|
|
694
|
-
total_exported += exported_count
|
|
695
|
-
|
|
696
|
-
console.print(
|
|
697
|
-
f"[green]Successfully exported {total_exported} items to {project_dir}[/green]"
|
|
698
|
-
)
|
|
699
|
-
else:
|
|
700
|
-
# Export from all projects in workspace
|
|
701
|
-
console.print(
|
|
702
|
-
f"[green]Exporting data from workspace: {workspace} (all projects)[/green]"
|
|
703
|
-
)
|
|
704
|
-
|
|
705
|
-
# Get all projects in the workspace
|
|
706
|
-
try:
|
|
707
|
-
projects_response = client.rest_client.projects.find_projects()
|
|
708
|
-
projects = projects_response.content or []
|
|
709
|
-
|
|
710
|
-
if not projects:
|
|
711
|
-
console.print(
|
|
712
|
-
f"[yellow]No projects found in workspace '{workspace}'[/yellow]"
|
|
713
|
-
)
|
|
714
|
-
return
|
|
715
|
-
|
|
716
|
-
console.print(
|
|
717
|
-
f"[blue]Found {len(projects)} projects in workspace[/blue]"
|
|
718
|
-
)
|
|
719
|
-
console.print(
|
|
720
|
-
f"[blue]Data types: {', '.join(sorted(data_types))}[/blue]"
|
|
721
|
-
)
|
|
722
|
-
|
|
723
|
-
# Note about workspace vs project-specific data
|
|
724
|
-
project_specific = [dt for dt in data_types if dt in ["traces"]]
|
|
725
|
-
workspace_data = [
|
|
726
|
-
dt for dt in data_types if dt in ["datasets", "prompts"]
|
|
727
|
-
]
|
|
728
|
-
|
|
729
|
-
if project_specific and workspace_data:
|
|
730
|
-
console.print(
|
|
731
|
-
f"[yellow]Note: {', '.join(project_specific)} are project-specific, {', '.join(workspace_data)} belong to workspace '{workspace}'[/yellow]"
|
|
732
|
-
)
|
|
733
|
-
elif workspace_data:
|
|
734
|
-
console.print(
|
|
735
|
-
f"[yellow]Note: {', '.join(workspace_data)} belong to workspace '{workspace}'[/yellow]"
|
|
736
|
-
)
|
|
737
|
-
|
|
738
|
-
total_exported = 0
|
|
739
|
-
|
|
740
|
-
# Download workspace-level data once (datasets, experiments, prompts)
|
|
741
|
-
workspace_dir = output_path / workspace
|
|
742
|
-
workspace_dir.mkdir(parents=True, exist_ok=True)
|
|
743
|
-
|
|
744
|
-
# Download datasets and prompts (workspace-level data)
|
|
745
|
-
for data_type in ["datasets", "prompts"]:
|
|
746
|
-
if data_type in data_types:
|
|
747
|
-
exported_count = _export_data_type(
|
|
748
|
-
data_type, client, workspace_dir, max_results, name, debug
|
|
749
|
-
)
|
|
750
|
-
total_exported += exported_count
|
|
751
|
-
|
|
752
|
-
# Download traces from each project
|
|
753
|
-
if "traces" in data_types:
|
|
754
|
-
for project in projects:
|
|
755
|
-
project_name = project.name
|
|
756
|
-
console.print(
|
|
757
|
-
f"[blue]Downloading traces from project: {project_name}...[/blue]"
|
|
758
|
-
)
|
|
759
|
-
|
|
760
|
-
# Create project directory
|
|
761
|
-
assert project_name is not None # Type narrowing for mypy
|
|
762
|
-
project_dir = workspace_dir / project_name
|
|
763
|
-
project_dir.mkdir(parents=True, exist_ok=True)
|
|
764
|
-
|
|
765
|
-
traces_exported = _export_traces(
|
|
766
|
-
client,
|
|
767
|
-
project_name,
|
|
768
|
-
project_dir,
|
|
769
|
-
max_results,
|
|
770
|
-
filter,
|
|
771
|
-
name,
|
|
772
|
-
trace_format,
|
|
773
|
-
)
|
|
774
|
-
total_exported += traces_exported
|
|
775
|
-
|
|
776
|
-
if traces_exported > 0:
|
|
777
|
-
console.print(
|
|
778
|
-
f"[green]Exported {traces_exported} traces from {project_name}[/green]"
|
|
779
|
-
)
|
|
780
|
-
|
|
781
|
-
console.print(
|
|
782
|
-
f"[green]Successfully exported {total_exported} items from workspace '{workspace}'[/green]"
|
|
783
|
-
)
|
|
784
|
-
|
|
785
|
-
except Exception as e:
|
|
786
|
-
console.print(f"[red]Error getting projects from workspace: {e}[/red]")
|
|
787
|
-
sys.exit(1)
|
|
788
|
-
|
|
789
|
-
except Exception as e:
|
|
790
|
-
console.print(f"[red]Error: {e}[/red]")
|
|
791
|
-
sys.exit(1)
|