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,262 @@
|
|
|
1
|
+
"""Project import functionality."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, Optional
|
|
7
|
+
|
|
8
|
+
import opik
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from .experiment import recreate_experiments
|
|
12
|
+
from .utils import matches_name_pattern, clean_feedback_scores
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def import_projects_from_directory(
|
|
18
|
+
client: opik.Opik,
|
|
19
|
+
source_dir: Path,
|
|
20
|
+
dry_run: bool,
|
|
21
|
+
name_pattern: Optional[str],
|
|
22
|
+
debug: bool,
|
|
23
|
+
recreate_experiments_flag: bool = False,
|
|
24
|
+
) -> Dict[str, int]:
|
|
25
|
+
"""Import projects from a directory.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Dictionary with keys: 'projects', 'projects_skipped', 'projects_errors', 'traces', 'traces_errors'
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
project_dirs = [d for d in source_dir.iterdir() if d.is_dir()]
|
|
32
|
+
|
|
33
|
+
if not project_dirs:
|
|
34
|
+
console.print("[yellow]No project directories found[/yellow]")
|
|
35
|
+
return {
|
|
36
|
+
"projects": 0,
|
|
37
|
+
"projects_skipped": 0,
|
|
38
|
+
"projects_errors": 0,
|
|
39
|
+
"traces": 0,
|
|
40
|
+
"traces_errors": 0,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
imported_count = 0
|
|
44
|
+
skipped_count = 0
|
|
45
|
+
error_count = 0
|
|
46
|
+
total_traces_imported = 0
|
|
47
|
+
total_traces_errors = 0
|
|
48
|
+
for project_dir in project_dirs:
|
|
49
|
+
try:
|
|
50
|
+
project_name = project_dir.name
|
|
51
|
+
# Maintain a per-project mapping from original -> new trace ids
|
|
52
|
+
trace_id_map: Dict[str, str] = {}
|
|
53
|
+
|
|
54
|
+
# Filter by name pattern if specified
|
|
55
|
+
if name_pattern and not matches_name_pattern(
|
|
56
|
+
project_name, name_pattern
|
|
57
|
+
):
|
|
58
|
+
if debug:
|
|
59
|
+
console.print(
|
|
60
|
+
f"[blue]Skipping project {project_name} (doesn't match pattern)[/blue]"
|
|
61
|
+
)
|
|
62
|
+
skipped_count += 1
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
if dry_run:
|
|
66
|
+
console.print(f"[blue]Would import project: {project_name}[/blue]")
|
|
67
|
+
imported_count += 1
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
if debug:
|
|
71
|
+
console.print(f"[blue]Importing project: {project_name}[/blue]")
|
|
72
|
+
|
|
73
|
+
# Import traces from the project directory
|
|
74
|
+
trace_files = list(project_dir.glob("trace_*.json"))
|
|
75
|
+
traces_imported = 0
|
|
76
|
+
traces_errors = 0
|
|
77
|
+
|
|
78
|
+
for trace_file in trace_files:
|
|
79
|
+
try:
|
|
80
|
+
with open(trace_file, "r", encoding="utf-8") as f:
|
|
81
|
+
trace_data = json.load(f)
|
|
82
|
+
|
|
83
|
+
# Import trace and spans
|
|
84
|
+
trace_info = trace_data.get("trace", {})
|
|
85
|
+
spans_info = trace_data.get("spans", [])
|
|
86
|
+
original_trace_id = trace_info.get("id")
|
|
87
|
+
|
|
88
|
+
# Create trace with full data
|
|
89
|
+
# Clean feedback scores to remove read-only fields
|
|
90
|
+
feedback_scores = clean_feedback_scores(
|
|
91
|
+
trace_info.get("feedback_scores")
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
trace = client.trace(
|
|
95
|
+
name=trace_info.get("name", "imported_trace"),
|
|
96
|
+
start_time=(
|
|
97
|
+
datetime.fromisoformat(
|
|
98
|
+
trace_info["start_time"].replace("Z", "+00:00")
|
|
99
|
+
)
|
|
100
|
+
if trace_info.get("start_time")
|
|
101
|
+
else None
|
|
102
|
+
),
|
|
103
|
+
end_time=(
|
|
104
|
+
datetime.fromisoformat(
|
|
105
|
+
trace_info["end_time"].replace("Z", "+00:00")
|
|
106
|
+
)
|
|
107
|
+
if trace_info.get("end_time")
|
|
108
|
+
else None
|
|
109
|
+
),
|
|
110
|
+
input=trace_info.get("input", {}),
|
|
111
|
+
output=trace_info.get("output", {}),
|
|
112
|
+
metadata=trace_info.get("metadata"),
|
|
113
|
+
tags=trace_info.get("tags"),
|
|
114
|
+
feedback_scores=feedback_scores,
|
|
115
|
+
error_info=trace_info.get("error_info"),
|
|
116
|
+
thread_id=trace_info.get("thread_id"),
|
|
117
|
+
project_name=project_name,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if original_trace_id:
|
|
121
|
+
trace_id_map[original_trace_id] = trace.id
|
|
122
|
+
|
|
123
|
+
# Create spans with full data, preserving parent-child relationships
|
|
124
|
+
# Build span_id_map to translate parent_span_id references
|
|
125
|
+
span_id_map: Dict[
|
|
126
|
+
str, str
|
|
127
|
+
] = {} # Maps original span ID to new span ID
|
|
128
|
+
|
|
129
|
+
# Sort spans to process root spans (no parent) first, then children
|
|
130
|
+
root_spans = [
|
|
131
|
+
s for s in spans_info if not s.get("parent_span_id")
|
|
132
|
+
]
|
|
133
|
+
child_spans = [s for s in spans_info if s.get("parent_span_id")]
|
|
134
|
+
sorted_spans = root_spans + child_spans
|
|
135
|
+
|
|
136
|
+
for span_info in sorted_spans:
|
|
137
|
+
# Clean feedback scores to remove read-only fields
|
|
138
|
+
span_feedback_scores = clean_feedback_scores(
|
|
139
|
+
span_info.get("feedback_scores")
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
original_span_id = span_info.get("id")
|
|
143
|
+
original_parent_span_id = span_info.get("parent_span_id")
|
|
144
|
+
|
|
145
|
+
# Translate parent_span_id if it exists
|
|
146
|
+
new_parent_span_id = None
|
|
147
|
+
if (
|
|
148
|
+
original_parent_span_id
|
|
149
|
+
and original_parent_span_id in span_id_map
|
|
150
|
+
):
|
|
151
|
+
new_parent_span_id = span_id_map[
|
|
152
|
+
original_parent_span_id
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
# Create span with parent_span_id if available
|
|
156
|
+
span = client.span(
|
|
157
|
+
name=span_info.get("name", "imported_span"),
|
|
158
|
+
start_time=(
|
|
159
|
+
datetime.fromisoformat(
|
|
160
|
+
span_info["start_time"].replace("Z", "+00:00")
|
|
161
|
+
)
|
|
162
|
+
if span_info.get("start_time")
|
|
163
|
+
else None
|
|
164
|
+
),
|
|
165
|
+
end_time=(
|
|
166
|
+
datetime.fromisoformat(
|
|
167
|
+
span_info["end_time"].replace("Z", "+00:00")
|
|
168
|
+
)
|
|
169
|
+
if span_info.get("end_time")
|
|
170
|
+
else None
|
|
171
|
+
),
|
|
172
|
+
input=span_info.get("input", {}),
|
|
173
|
+
output=span_info.get("output", {}),
|
|
174
|
+
metadata=span_info.get("metadata"),
|
|
175
|
+
tags=span_info.get("tags"),
|
|
176
|
+
usage=span_info.get("usage"),
|
|
177
|
+
feedback_scores=span_feedback_scores,
|
|
178
|
+
model=span_info.get("model"),
|
|
179
|
+
provider=span_info.get("provider"),
|
|
180
|
+
error_info=span_info.get("error_info"),
|
|
181
|
+
total_cost=span_info.get("total_cost"),
|
|
182
|
+
trace_id=trace.id,
|
|
183
|
+
parent_span_id=new_parent_span_id,
|
|
184
|
+
project_name=project_name,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Map original span ID to new span ID for parent relationship mapping
|
|
188
|
+
if original_span_id and span.id:
|
|
189
|
+
span_id_map[original_span_id] = span.id
|
|
190
|
+
|
|
191
|
+
traces_imported += 1
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
console.print(
|
|
195
|
+
f"[red]Error importing trace from {trace_file}: {e}[/red]"
|
|
196
|
+
)
|
|
197
|
+
traces_errors += 1
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
# Handle experiment recreation if requested
|
|
201
|
+
if recreate_experiments_flag:
|
|
202
|
+
# Flush client before recreating experiments
|
|
203
|
+
client.flush()
|
|
204
|
+
|
|
205
|
+
experiment_files = list(project_dir.glob("experiment_*.json"))
|
|
206
|
+
if experiment_files:
|
|
207
|
+
if debug:
|
|
208
|
+
console.print(
|
|
209
|
+
f"[blue]Found {len(experiment_files)} experiment files in project {project_name}[/blue]"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Recreate experiments
|
|
213
|
+
experiments_recreated = recreate_experiments(
|
|
214
|
+
client,
|
|
215
|
+
project_dir,
|
|
216
|
+
project_name,
|
|
217
|
+
dry_run,
|
|
218
|
+
name_pattern,
|
|
219
|
+
trace_id_map,
|
|
220
|
+
None, # dataset_item_id_map - not available in project import context
|
|
221
|
+
debug,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
if debug and experiments_recreated > 0:
|
|
225
|
+
console.print(
|
|
226
|
+
f"[green]Recreated {experiments_recreated} experiments for project {project_name}[/green]"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
total_traces_imported += traces_imported
|
|
230
|
+
total_traces_errors += traces_errors
|
|
231
|
+
|
|
232
|
+
if traces_imported > 0:
|
|
233
|
+
imported_count += 1
|
|
234
|
+
if debug:
|
|
235
|
+
console.print(
|
|
236
|
+
f"[green]Imported project: {project_name} with {traces_imported} traces[/green]"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
console.print(
|
|
241
|
+
f"[red]Error importing project {project_dir.name}: {e}[/red]"
|
|
242
|
+
)
|
|
243
|
+
error_count += 1
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
"projects": imported_count,
|
|
248
|
+
"projects_skipped": skipped_count,
|
|
249
|
+
"projects_errors": error_count,
|
|
250
|
+
"traces": total_traces_imported,
|
|
251
|
+
"traces_errors": total_traces_errors,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
except Exception as e:
|
|
255
|
+
console.print(f"[red]Error importing projects: {e}[/red]")
|
|
256
|
+
return {
|
|
257
|
+
"projects": 0,
|
|
258
|
+
"projects_skipped": 0,
|
|
259
|
+
"projects_errors": 1,
|
|
260
|
+
"traces": 0,
|
|
261
|
+
"traces_errors": 0,
|
|
262
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Prompt import functionality."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Optional
|
|
6
|
+
|
|
7
|
+
import opik
|
|
8
|
+
from opik.api_objects.prompt import Prompt, ChatPrompt
|
|
9
|
+
from opik.api_objects.prompt.types import PromptType
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
from .utils import matches_name_pattern
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def import_prompts_from_directory(
|
|
18
|
+
client: opik.Opik,
|
|
19
|
+
source_dir: Path,
|
|
20
|
+
dry_run: bool,
|
|
21
|
+
name_pattern: Optional[str],
|
|
22
|
+
debug: bool,
|
|
23
|
+
) -> Dict[str, int]:
|
|
24
|
+
"""Import prompts from a directory.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Dictionary with keys: 'prompts', 'prompts_skipped', 'prompts_errors'
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
prompt_files = list(source_dir.glob("prompt_*.json"))
|
|
31
|
+
|
|
32
|
+
if not prompt_files:
|
|
33
|
+
console.print("[yellow]No prompt files found in the directory[/yellow]")
|
|
34
|
+
return {"prompts": 0, "prompts_skipped": 0, "prompts_errors": 0}
|
|
35
|
+
|
|
36
|
+
imported_count = 0
|
|
37
|
+
skipped_count = 0
|
|
38
|
+
error_count = 0
|
|
39
|
+
for prompt_file in prompt_files:
|
|
40
|
+
try:
|
|
41
|
+
with open(prompt_file, "r", encoding="utf-8") as f:
|
|
42
|
+
prompt_data = json.load(f)
|
|
43
|
+
|
|
44
|
+
# Handle two export formats:
|
|
45
|
+
# 1. {"name": "...", "current_version": {...}, "history": [...]} - from export_single_prompt
|
|
46
|
+
# 2. {"prompt": {"name": "...", ...}, "current_version": {...}, "history": [...]} - from export_experiment_prompts
|
|
47
|
+
prompt_name = prompt_data.get("name") or (
|
|
48
|
+
prompt_data.get("prompt", {}).get("name")
|
|
49
|
+
if prompt_data.get("prompt")
|
|
50
|
+
else None
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Check if name is missing or empty
|
|
54
|
+
if not prompt_name or (
|
|
55
|
+
isinstance(prompt_name, str) and not prompt_name.strip()
|
|
56
|
+
):
|
|
57
|
+
console.print(
|
|
58
|
+
f"[yellow]Skipping {prompt_file.name} (no name found)[/yellow]"
|
|
59
|
+
)
|
|
60
|
+
skipped_count += 1
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
# Strip whitespace from name
|
|
64
|
+
prompt_name = prompt_name.strip()
|
|
65
|
+
|
|
66
|
+
# Filter by name pattern if specified
|
|
67
|
+
if name_pattern and not matches_name_pattern(prompt_name, name_pattern):
|
|
68
|
+
if debug:
|
|
69
|
+
console.print(
|
|
70
|
+
f"[blue]Skipping prompt {prompt_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 prompt: {prompt_name}[/blue]")
|
|
77
|
+
imported_count += 1
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
if debug:
|
|
81
|
+
console.print(f"[blue]Importing prompt: {prompt_name}[/blue]")
|
|
82
|
+
|
|
83
|
+
# Get current version data
|
|
84
|
+
current_version = prompt_data.get("current_version", {})
|
|
85
|
+
prompt_content = current_version.get("prompt")
|
|
86
|
+
metadata = current_version.get("metadata")
|
|
87
|
+
prompt_type = current_version.get("type")
|
|
88
|
+
template_structure = current_version.get("template_structure", "text")
|
|
89
|
+
|
|
90
|
+
# Validate prompt content exists
|
|
91
|
+
if prompt_content is None:
|
|
92
|
+
console.print(
|
|
93
|
+
f"[yellow]Skipping {prompt_name} (no prompt content found)[/yellow]"
|
|
94
|
+
)
|
|
95
|
+
skipped_count += 1
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
# Convert string type to PromptType enum if needed
|
|
99
|
+
if prompt_type and isinstance(prompt_type, str):
|
|
100
|
+
try:
|
|
101
|
+
prompt_type_enum = PromptType(prompt_type)
|
|
102
|
+
except ValueError:
|
|
103
|
+
console.print(
|
|
104
|
+
f"[yellow]Unknown prompt type '{prompt_type}', using MUSTACHE[/yellow]"
|
|
105
|
+
)
|
|
106
|
+
prompt_type_enum = PromptType.MUSTACHE
|
|
107
|
+
else:
|
|
108
|
+
prompt_type_enum = PromptType.MUSTACHE
|
|
109
|
+
|
|
110
|
+
# Create the prompt based on template_structure
|
|
111
|
+
try:
|
|
112
|
+
if template_structure == "chat":
|
|
113
|
+
# ChatPrompt expects a list of message dictionaries
|
|
114
|
+
if not isinstance(prompt_content, list):
|
|
115
|
+
console.print(
|
|
116
|
+
f"[yellow]Skipping {prompt_name} (chat prompt content must be a list of messages)[/yellow]"
|
|
117
|
+
)
|
|
118
|
+
skipped_count += 1
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
# Create ChatPrompt
|
|
122
|
+
ChatPrompt(
|
|
123
|
+
name=prompt_name,
|
|
124
|
+
messages=prompt_content,
|
|
125
|
+
metadata=metadata,
|
|
126
|
+
type=prompt_type_enum,
|
|
127
|
+
)
|
|
128
|
+
if debug:
|
|
129
|
+
console.print(
|
|
130
|
+
f"[green]Imported chat prompt: {prompt_name}[/green]"
|
|
131
|
+
)
|
|
132
|
+
else:
|
|
133
|
+
# Text Prompt expects a string
|
|
134
|
+
if not isinstance(prompt_content, str):
|
|
135
|
+
console.print(
|
|
136
|
+
f"[yellow]Skipping {prompt_name} (text prompt content must be a string)[/yellow]"
|
|
137
|
+
)
|
|
138
|
+
skipped_count += 1
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
# Create Prompt
|
|
142
|
+
Prompt(
|
|
143
|
+
name=prompt_name,
|
|
144
|
+
prompt=prompt_content,
|
|
145
|
+
metadata=metadata,
|
|
146
|
+
type=prompt_type_enum,
|
|
147
|
+
)
|
|
148
|
+
if debug:
|
|
149
|
+
console.print(
|
|
150
|
+
f"[green]Imported text prompt: {prompt_name}[/green]"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
imported_count += 1
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
console.print(
|
|
157
|
+
f"[red]Error creating prompt {prompt_name}: {e}[/red]"
|
|
158
|
+
)
|
|
159
|
+
error_count += 1
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
console.print(
|
|
164
|
+
f"[red]Error importing prompt from {prompt_file}: {e}[/red]"
|
|
165
|
+
)
|
|
166
|
+
error_count += 1
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
"prompts": imported_count,
|
|
171
|
+
"prompts_skipped": skipped_count,
|
|
172
|
+
"prompts_errors": error_count,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
console.print(f"[red]Error importing prompts: {e}[/red]")
|
|
177
|
+
return {"prompts": 0, "prompts_skipped": 0, "prompts_errors": 1}
|