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.
Files changed (195) hide show
  1. opik/api_objects/attachment/attachment_context.py +36 -0
  2. opik/api_objects/attachment/attachments_extractor.py +153 -0
  3. opik/api_objects/attachment/client.py +1 -0
  4. opik/api_objects/attachment/converters.py +2 -0
  5. opik/api_objects/attachment/decoder.py +18 -0
  6. opik/api_objects/attachment/decoder_base64.py +83 -0
  7. opik/api_objects/attachment/decoder_helpers.py +137 -0
  8. opik/api_objects/constants.py +2 -0
  9. opik/api_objects/dataset/dataset.py +133 -40
  10. opik/api_objects/dataset/rest_operations.py +2 -0
  11. opik/api_objects/experiment/experiment.py +6 -0
  12. opik/api_objects/helpers.py +8 -4
  13. opik/api_objects/local_recording.py +6 -5
  14. opik/api_objects/observation_data.py +101 -0
  15. opik/api_objects/opik_client.py +78 -45
  16. opik/api_objects/opik_query_language.py +9 -3
  17. opik/api_objects/prompt/chat/chat_prompt.py +18 -1
  18. opik/api_objects/prompt/client.py +8 -1
  19. opik/api_objects/span/span_data.py +3 -88
  20. opik/api_objects/threads/threads_client.py +7 -4
  21. opik/api_objects/trace/trace_data.py +3 -74
  22. opik/api_objects/validation_helpers.py +3 -3
  23. opik/cli/exports/__init__.py +131 -0
  24. opik/cli/exports/dataset.py +278 -0
  25. opik/cli/exports/experiment.py +784 -0
  26. opik/cli/exports/project.py +685 -0
  27. opik/cli/exports/prompt.py +578 -0
  28. opik/cli/exports/utils.py +406 -0
  29. opik/cli/harbor.py +39 -0
  30. opik/cli/imports/__init__.py +439 -0
  31. opik/cli/imports/dataset.py +143 -0
  32. opik/cli/imports/experiment.py +1192 -0
  33. opik/cli/imports/project.py +262 -0
  34. opik/cli/imports/prompt.py +177 -0
  35. opik/cli/imports/utils.py +280 -0
  36. opik/cli/main.py +14 -12
  37. opik/config.py +12 -1
  38. opik/datetime_helpers.py +12 -0
  39. opik/decorator/arguments_helpers.py +4 -1
  40. opik/decorator/base_track_decorator.py +111 -37
  41. opik/decorator/context_manager/span_context_manager.py +5 -1
  42. opik/decorator/generator_wrappers.py +5 -4
  43. opik/decorator/span_creation_handler.py +13 -4
  44. opik/evaluation/engine/engine.py +111 -28
  45. opik/evaluation/engine/evaluation_tasks_executor.py +71 -19
  46. opik/evaluation/evaluator.py +12 -0
  47. opik/evaluation/metrics/conversation/llm_judges/conversational_coherence/metric.py +3 -1
  48. opik/evaluation/metrics/conversation/llm_judges/session_completeness/metric.py +3 -1
  49. opik/evaluation/metrics/conversation/llm_judges/user_frustration/metric.py +3 -1
  50. opik/evaluation/metrics/heuristics/equals.py +11 -7
  51. opik/evaluation/metrics/llm_judges/answer_relevance/metric.py +3 -1
  52. opik/evaluation/metrics/llm_judges/context_precision/metric.py +3 -1
  53. opik/evaluation/metrics/llm_judges/context_recall/metric.py +3 -1
  54. opik/evaluation/metrics/llm_judges/factuality/metric.py +1 -1
  55. opik/evaluation/metrics/llm_judges/g_eval/metric.py +3 -1
  56. opik/evaluation/metrics/llm_judges/hallucination/metric.py +3 -1
  57. opik/evaluation/metrics/llm_judges/moderation/metric.py +3 -1
  58. opik/evaluation/metrics/llm_judges/structure_output_compliance/metric.py +3 -1
  59. opik/evaluation/metrics/llm_judges/syc_eval/metric.py +4 -2
  60. opik/evaluation/metrics/llm_judges/trajectory_accuracy/metric.py +3 -1
  61. opik/evaluation/metrics/llm_judges/usefulness/metric.py +3 -1
  62. opik/evaluation/metrics/ragas_metric.py +43 -23
  63. opik/evaluation/models/litellm/litellm_chat_model.py +7 -2
  64. opik/evaluation/models/litellm/util.py +4 -20
  65. opik/evaluation/models/models_factory.py +19 -5
  66. opik/evaluation/rest_operations.py +3 -3
  67. opik/evaluation/threads/helpers.py +3 -2
  68. opik/file_upload/file_uploader.py +13 -0
  69. opik/file_upload/upload_options.py +2 -0
  70. opik/integrations/adk/legacy_opik_tracer.py +9 -11
  71. opik/integrations/adk/opik_tracer.py +2 -2
  72. opik/integrations/adk/patchers/adk_otel_tracer/opik_adk_otel_tracer.py +2 -2
  73. opik/integrations/dspy/callback.py +100 -14
  74. opik/integrations/dspy/parsers.py +168 -0
  75. opik/integrations/harbor/__init__.py +17 -0
  76. opik/integrations/harbor/experiment_service.py +269 -0
  77. opik/integrations/harbor/opik_tracker.py +528 -0
  78. opik/integrations/haystack/opik_tracer.py +2 -2
  79. opik/integrations/langchain/__init__.py +15 -2
  80. opik/integrations/langchain/langgraph_tracer_injector.py +88 -0
  81. opik/integrations/langchain/opik_tracer.py +258 -160
  82. opik/integrations/langchain/provider_usage_extractors/langchain_run_helpers/helpers.py +7 -4
  83. opik/integrations/llama_index/callback.py +43 -6
  84. opik/integrations/openai/agents/opik_tracing_processor.py +8 -10
  85. opik/integrations/openai/opik_tracker.py +99 -4
  86. opik/integrations/openai/videos/__init__.py +9 -0
  87. opik/integrations/openai/videos/binary_response_write_to_file_decorator.py +88 -0
  88. opik/integrations/openai/videos/videos_create_decorator.py +159 -0
  89. opik/integrations/openai/videos/videos_download_decorator.py +110 -0
  90. opik/message_processing/batching/base_batcher.py +14 -21
  91. opik/message_processing/batching/batch_manager.py +22 -10
  92. opik/message_processing/batching/batchers.py +32 -40
  93. opik/message_processing/batching/flushing_thread.py +0 -3
  94. opik/message_processing/emulation/emulator_message_processor.py +36 -1
  95. opik/message_processing/emulation/models.py +21 -0
  96. opik/message_processing/messages.py +9 -0
  97. opik/message_processing/preprocessing/__init__.py +0 -0
  98. opik/message_processing/preprocessing/attachments_preprocessor.py +70 -0
  99. opik/message_processing/preprocessing/batching_preprocessor.py +53 -0
  100. opik/message_processing/preprocessing/constants.py +1 -0
  101. opik/message_processing/preprocessing/file_upload_preprocessor.py +38 -0
  102. opik/message_processing/preprocessing/preprocessor.py +36 -0
  103. opik/message_processing/processors/__init__.py +0 -0
  104. opik/message_processing/processors/attachments_extraction_processor.py +146 -0
  105. opik/message_processing/{message_processors.py → processors/message_processors.py} +15 -1
  106. opik/message_processing/{message_processors_chain.py → processors/message_processors_chain.py} +3 -2
  107. opik/message_processing/{online_message_processor.py → processors/online_message_processor.py} +11 -9
  108. opik/message_processing/queue_consumer.py +4 -2
  109. opik/message_processing/streamer.py +71 -33
  110. opik/message_processing/streamer_constructors.py +36 -8
  111. opik/plugins/pytest/experiment_runner.py +1 -1
  112. opik/plugins/pytest/hooks.py +5 -3
  113. opik/rest_api/__init__.py +42 -0
  114. opik/rest_api/datasets/client.py +321 -123
  115. opik/rest_api/datasets/raw_client.py +470 -145
  116. opik/rest_api/experiments/client.py +26 -0
  117. opik/rest_api/experiments/raw_client.py +26 -0
  118. opik/rest_api/llm_provider_key/client.py +4 -4
  119. opik/rest_api/llm_provider_key/raw_client.py +4 -4
  120. opik/rest_api/llm_provider_key/types/provider_api_key_write_provider.py +2 -1
  121. opik/rest_api/manual_evaluation/client.py +101 -0
  122. opik/rest_api/manual_evaluation/raw_client.py +172 -0
  123. opik/rest_api/optimizations/client.py +0 -166
  124. opik/rest_api/optimizations/raw_client.py +0 -248
  125. opik/rest_api/projects/client.py +9 -0
  126. opik/rest_api/projects/raw_client.py +13 -0
  127. opik/rest_api/projects/types/project_metric_request_public_metric_type.py +4 -0
  128. opik/rest_api/prompts/client.py +130 -2
  129. opik/rest_api/prompts/raw_client.py +175 -0
  130. opik/rest_api/traces/client.py +101 -0
  131. opik/rest_api/traces/raw_client.py +120 -0
  132. opik/rest_api/types/__init__.py +50 -0
  133. opik/rest_api/types/audio_url.py +19 -0
  134. opik/rest_api/types/audio_url_public.py +19 -0
  135. opik/rest_api/types/audio_url_write.py +19 -0
  136. opik/rest_api/types/automation_rule_evaluator.py +38 -2
  137. opik/rest_api/types/automation_rule_evaluator_object_object_public.py +33 -2
  138. opik/rest_api/types/automation_rule_evaluator_public.py +33 -2
  139. opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python.py +22 -0
  140. opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python_public.py +22 -0
  141. opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python_write.py +22 -0
  142. opik/rest_api/types/automation_rule_evaluator_update.py +27 -1
  143. opik/rest_api/types/automation_rule_evaluator_update_span_user_defined_metric_python.py +22 -0
  144. opik/rest_api/types/automation_rule_evaluator_write.py +27 -1
  145. opik/rest_api/types/dataset.py +2 -0
  146. opik/rest_api/types/dataset_item.py +1 -1
  147. opik/rest_api/types/dataset_item_batch.py +4 -0
  148. opik/rest_api/types/dataset_item_changes_public.py +5 -0
  149. opik/rest_api/types/dataset_item_compare.py +1 -1
  150. opik/rest_api/types/dataset_item_filter.py +4 -0
  151. opik/rest_api/types/dataset_item_page_compare.py +0 -1
  152. opik/rest_api/types/dataset_item_page_public.py +0 -1
  153. opik/rest_api/types/dataset_item_public.py +1 -1
  154. opik/rest_api/types/dataset_public.py +2 -0
  155. opik/rest_api/types/dataset_version_public.py +10 -0
  156. opik/rest_api/types/dataset_version_summary.py +46 -0
  157. opik/rest_api/types/dataset_version_summary_public.py +46 -0
  158. opik/rest_api/types/experiment.py +9 -0
  159. opik/rest_api/types/experiment_public.py +9 -0
  160. opik/rest_api/types/group_content_with_aggregations.py +1 -0
  161. opik/rest_api/types/llm_as_judge_message_content.py +2 -0
  162. opik/rest_api/types/llm_as_judge_message_content_public.py +2 -0
  163. opik/rest_api/types/llm_as_judge_message_content_write.py +2 -0
  164. opik/rest_api/types/manual_evaluation_request_entity_type.py +1 -1
  165. opik/rest_api/types/project.py +1 -0
  166. opik/rest_api/types/project_detailed.py +1 -0
  167. opik/rest_api/types/project_metric_response_public_metric_type.py +4 -0
  168. opik/rest_api/types/project_reference.py +31 -0
  169. opik/rest_api/types/project_reference_public.py +31 -0
  170. opik/rest_api/types/project_stats_summary_item.py +1 -0
  171. opik/rest_api/types/prompt_version.py +1 -0
  172. opik/rest_api/types/prompt_version_detail.py +1 -0
  173. opik/rest_api/types/prompt_version_page_public.py +5 -0
  174. opik/rest_api/types/prompt_version_public.py +1 -0
  175. opik/rest_api/types/prompt_version_update.py +33 -0
  176. opik/rest_api/types/provider_api_key.py +5 -1
  177. opik/rest_api/types/provider_api_key_provider.py +2 -1
  178. opik/rest_api/types/provider_api_key_public.py +5 -1
  179. opik/rest_api/types/provider_api_key_public_provider.py +2 -1
  180. opik/rest_api/types/service_toggles_config.py +11 -1
  181. opik/rest_api/types/span_user_defined_metric_python_code.py +20 -0
  182. opik/rest_api/types/span_user_defined_metric_python_code_public.py +20 -0
  183. opik/rest_api/types/span_user_defined_metric_python_code_write.py +20 -0
  184. opik/types.py +36 -0
  185. opik/validation/chat_prompt_messages.py +241 -0
  186. opik/validation/feedback_score.py +3 -3
  187. opik/validation/validator.py +28 -0
  188. {opik-1.9.39.dist-info → opik-1.9.86.dist-info}/METADATA +7 -7
  189. {opik-1.9.39.dist-info → opik-1.9.86.dist-info}/RECORD +193 -142
  190. opik/cli/export.py +0 -791
  191. opik/cli/import_command.py +0 -575
  192. {opik-1.9.39.dist-info → opik-1.9.86.dist-info}/WHEEL +0 -0
  193. {opik-1.9.39.dist-info → opik-1.9.86.dist-info}/entry_points.txt +0 -0
  194. {opik-1.9.39.dist-info → opik-1.9.86.dist-info}/licenses/LICENSE +0 -0
  195. {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}