opik 1.8.39__py3-none-any.whl → 1.9.71__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (592) hide show
  1. opik/__init__.py +19 -3
  2. opik/anonymizer/__init__.py +5 -0
  3. opik/anonymizer/anonymizer.py +12 -0
  4. opik/anonymizer/factory.py +80 -0
  5. opik/anonymizer/recursive_anonymizer.py +64 -0
  6. opik/anonymizer/rules.py +56 -0
  7. opik/anonymizer/rules_anonymizer.py +35 -0
  8. opik/api_objects/attachment/attachment_context.py +36 -0
  9. opik/api_objects/attachment/attachments_extractor.py +153 -0
  10. opik/api_objects/attachment/client.py +1 -0
  11. opik/api_objects/attachment/converters.py +2 -0
  12. opik/api_objects/attachment/decoder.py +18 -0
  13. opik/api_objects/attachment/decoder_base64.py +83 -0
  14. opik/api_objects/attachment/decoder_helpers.py +137 -0
  15. opik/api_objects/data_helpers.py +79 -0
  16. opik/api_objects/dataset/dataset.py +64 -4
  17. opik/api_objects/dataset/rest_operations.py +11 -2
  18. opik/api_objects/experiment/experiment.py +57 -57
  19. opik/api_objects/experiment/experiment_item.py +2 -1
  20. opik/api_objects/experiment/experiments_client.py +64 -0
  21. opik/api_objects/experiment/helpers.py +35 -11
  22. opik/api_objects/experiment/rest_operations.py +65 -5
  23. opik/api_objects/helpers.py +8 -5
  24. opik/api_objects/local_recording.py +81 -0
  25. opik/api_objects/opik_client.py +600 -108
  26. opik/api_objects/opik_query_language.py +39 -5
  27. opik/api_objects/prompt/__init__.py +12 -2
  28. opik/api_objects/prompt/base_prompt.py +69 -0
  29. opik/api_objects/prompt/base_prompt_template.py +29 -0
  30. opik/api_objects/prompt/chat/__init__.py +1 -0
  31. opik/api_objects/prompt/chat/chat_prompt.py +210 -0
  32. opik/api_objects/prompt/chat/chat_prompt_template.py +350 -0
  33. opik/api_objects/prompt/chat/content_renderer_registry.py +203 -0
  34. opik/api_objects/prompt/client.py +189 -47
  35. opik/api_objects/prompt/text/__init__.py +1 -0
  36. opik/api_objects/prompt/text/prompt.py +174 -0
  37. opik/api_objects/prompt/{prompt_template.py → text/prompt_template.py} +10 -6
  38. opik/api_objects/prompt/types.py +23 -0
  39. opik/api_objects/search_helpers.py +89 -0
  40. opik/api_objects/span/span_data.py +35 -25
  41. opik/api_objects/threads/threads_client.py +39 -5
  42. opik/api_objects/trace/trace_client.py +52 -2
  43. opik/api_objects/trace/trace_data.py +15 -24
  44. opik/api_objects/validation_helpers.py +3 -3
  45. opik/cli/__init__.py +5 -0
  46. opik/cli/__main__.py +6 -0
  47. opik/cli/configure.py +66 -0
  48. opik/cli/exports/__init__.py +131 -0
  49. opik/cli/exports/dataset.py +278 -0
  50. opik/cli/exports/experiment.py +784 -0
  51. opik/cli/exports/project.py +685 -0
  52. opik/cli/exports/prompt.py +578 -0
  53. opik/cli/exports/utils.py +406 -0
  54. opik/cli/harbor.py +39 -0
  55. opik/cli/healthcheck.py +21 -0
  56. opik/cli/imports/__init__.py +439 -0
  57. opik/cli/imports/dataset.py +143 -0
  58. opik/cli/imports/experiment.py +1192 -0
  59. opik/cli/imports/project.py +262 -0
  60. opik/cli/imports/prompt.py +177 -0
  61. opik/cli/imports/utils.py +280 -0
  62. opik/cli/main.py +49 -0
  63. opik/cli/proxy.py +93 -0
  64. opik/cli/usage_report/__init__.py +16 -0
  65. opik/cli/usage_report/charts.py +783 -0
  66. opik/cli/usage_report/cli.py +274 -0
  67. opik/cli/usage_report/constants.py +9 -0
  68. opik/cli/usage_report/extraction.py +749 -0
  69. opik/cli/usage_report/pdf.py +244 -0
  70. opik/cli/usage_report/statistics.py +78 -0
  71. opik/cli/usage_report/utils.py +235 -0
  72. opik/config.py +13 -7
  73. opik/configurator/configure.py +17 -0
  74. opik/datetime_helpers.py +12 -0
  75. opik/decorator/arguments_helpers.py +9 -1
  76. opik/decorator/base_track_decorator.py +205 -133
  77. opik/decorator/context_manager/span_context_manager.py +123 -0
  78. opik/decorator/context_manager/trace_context_manager.py +84 -0
  79. opik/decorator/opik_args/__init__.py +13 -0
  80. opik/decorator/opik_args/api_classes.py +71 -0
  81. opik/decorator/opik_args/helpers.py +120 -0
  82. opik/decorator/span_creation_handler.py +25 -6
  83. opik/dict_utils.py +3 -3
  84. opik/evaluation/__init__.py +13 -2
  85. opik/evaluation/engine/engine.py +272 -75
  86. opik/evaluation/engine/evaluation_tasks_executor.py +6 -3
  87. opik/evaluation/engine/helpers.py +31 -6
  88. opik/evaluation/engine/metrics_evaluator.py +237 -0
  89. opik/evaluation/evaluation_result.py +168 -2
  90. opik/evaluation/evaluator.py +533 -62
  91. opik/evaluation/metrics/__init__.py +103 -4
  92. opik/evaluation/metrics/aggregated_metric.py +35 -6
  93. opik/evaluation/metrics/base_metric.py +1 -1
  94. opik/evaluation/metrics/conversation/__init__.py +48 -0
  95. opik/evaluation/metrics/conversation/conversation_thread_metric.py +56 -2
  96. opik/evaluation/metrics/conversation/g_eval_wrappers.py +19 -0
  97. opik/evaluation/metrics/conversation/helpers.py +14 -15
  98. opik/evaluation/metrics/conversation/heuristics/__init__.py +14 -0
  99. opik/evaluation/metrics/conversation/heuristics/degeneration/__init__.py +3 -0
  100. opik/evaluation/metrics/conversation/heuristics/degeneration/metric.py +189 -0
  101. opik/evaluation/metrics/conversation/heuristics/degeneration/phrases.py +12 -0
  102. opik/evaluation/metrics/conversation/heuristics/knowledge_retention/__init__.py +3 -0
  103. opik/evaluation/metrics/conversation/heuristics/knowledge_retention/metric.py +172 -0
  104. opik/evaluation/metrics/conversation/llm_judges/__init__.py +32 -0
  105. opik/evaluation/metrics/conversation/{conversational_coherence → llm_judges/conversational_coherence}/metric.py +22 -17
  106. opik/evaluation/metrics/conversation/{conversational_coherence → llm_judges/conversational_coherence}/templates.py +1 -1
  107. opik/evaluation/metrics/conversation/llm_judges/g_eval_wrappers.py +442 -0
  108. opik/evaluation/metrics/conversation/{session_completeness → llm_judges/session_completeness}/metric.py +13 -7
  109. opik/evaluation/metrics/conversation/{session_completeness → llm_judges/session_completeness}/templates.py +1 -1
  110. opik/evaluation/metrics/conversation/llm_judges/user_frustration/__init__.py +0 -0
  111. opik/evaluation/metrics/conversation/{user_frustration → llm_judges/user_frustration}/metric.py +21 -14
  112. opik/evaluation/metrics/conversation/{user_frustration → llm_judges/user_frustration}/templates.py +1 -1
  113. opik/evaluation/metrics/conversation/types.py +4 -5
  114. opik/evaluation/metrics/conversation_types.py +9 -0
  115. opik/evaluation/metrics/heuristics/bertscore.py +107 -0
  116. opik/evaluation/metrics/heuristics/bleu.py +35 -15
  117. opik/evaluation/metrics/heuristics/chrf.py +127 -0
  118. opik/evaluation/metrics/heuristics/contains.py +47 -11
  119. opik/evaluation/metrics/heuristics/distribution_metrics.py +331 -0
  120. opik/evaluation/metrics/heuristics/gleu.py +113 -0
  121. opik/evaluation/metrics/heuristics/language_adherence.py +123 -0
  122. opik/evaluation/metrics/heuristics/meteor.py +119 -0
  123. opik/evaluation/metrics/heuristics/prompt_injection.py +150 -0
  124. opik/evaluation/metrics/heuristics/readability.py +129 -0
  125. opik/evaluation/metrics/heuristics/rouge.py +26 -9
  126. opik/evaluation/metrics/heuristics/spearman.py +88 -0
  127. opik/evaluation/metrics/heuristics/tone.py +155 -0
  128. opik/evaluation/metrics/heuristics/vader_sentiment.py +77 -0
  129. opik/evaluation/metrics/llm_judges/answer_relevance/metric.py +20 -5
  130. opik/evaluation/metrics/llm_judges/context_precision/metric.py +20 -6
  131. opik/evaluation/metrics/llm_judges/context_recall/metric.py +20 -6
  132. opik/evaluation/metrics/llm_judges/g_eval/__init__.py +5 -0
  133. opik/evaluation/metrics/llm_judges/g_eval/metric.py +219 -68
  134. opik/evaluation/metrics/llm_judges/g_eval/parser.py +102 -52
  135. opik/evaluation/metrics/llm_judges/g_eval/presets.py +209 -0
  136. opik/evaluation/metrics/llm_judges/g_eval_presets/__init__.py +36 -0
  137. opik/evaluation/metrics/llm_judges/g_eval_presets/agent_assessment.py +77 -0
  138. opik/evaluation/metrics/llm_judges/g_eval_presets/bias_classifier.py +181 -0
  139. opik/evaluation/metrics/llm_judges/g_eval_presets/compliance_risk.py +41 -0
  140. opik/evaluation/metrics/llm_judges/g_eval_presets/prompt_uncertainty.py +41 -0
  141. opik/evaluation/metrics/llm_judges/g_eval_presets/qa_suite.py +146 -0
  142. opik/evaluation/metrics/llm_judges/hallucination/metric.py +16 -3
  143. opik/evaluation/metrics/llm_judges/llm_juries/__init__.py +3 -0
  144. opik/evaluation/metrics/llm_judges/llm_juries/metric.py +76 -0
  145. opik/evaluation/metrics/llm_judges/moderation/metric.py +16 -4
  146. opik/evaluation/metrics/llm_judges/structure_output_compliance/__init__.py +0 -0
  147. opik/evaluation/metrics/llm_judges/structure_output_compliance/metric.py +144 -0
  148. opik/evaluation/metrics/llm_judges/structure_output_compliance/parser.py +79 -0
  149. opik/evaluation/metrics/llm_judges/structure_output_compliance/schema.py +15 -0
  150. opik/evaluation/metrics/llm_judges/structure_output_compliance/template.py +50 -0
  151. opik/evaluation/metrics/llm_judges/syc_eval/__init__.py +0 -0
  152. opik/evaluation/metrics/llm_judges/syc_eval/metric.py +252 -0
  153. opik/evaluation/metrics/llm_judges/syc_eval/parser.py +82 -0
  154. opik/evaluation/metrics/llm_judges/syc_eval/template.py +155 -0
  155. opik/evaluation/metrics/llm_judges/trajectory_accuracy/metric.py +20 -5
  156. opik/evaluation/metrics/llm_judges/usefulness/metric.py +16 -4
  157. opik/evaluation/metrics/ragas_metric.py +43 -23
  158. opik/evaluation/models/__init__.py +8 -0
  159. opik/evaluation/models/base_model.py +107 -1
  160. opik/evaluation/models/langchain/langchain_chat_model.py +15 -7
  161. opik/evaluation/models/langchain/message_converters.py +97 -15
  162. opik/evaluation/models/litellm/litellm_chat_model.py +156 -29
  163. opik/evaluation/models/litellm/util.py +125 -0
  164. opik/evaluation/models/litellm/warning_filters.py +16 -4
  165. opik/evaluation/models/model_capabilities.py +187 -0
  166. opik/evaluation/models/models_factory.py +25 -3
  167. opik/evaluation/preprocessing.py +92 -0
  168. opik/evaluation/report.py +70 -12
  169. opik/evaluation/rest_operations.py +49 -45
  170. opik/evaluation/samplers/__init__.py +4 -0
  171. opik/evaluation/samplers/base_dataset_sampler.py +40 -0
  172. opik/evaluation/samplers/random_dataset_sampler.py +48 -0
  173. opik/evaluation/score_statistics.py +66 -0
  174. opik/evaluation/scorers/__init__.py +4 -0
  175. opik/evaluation/scorers/scorer_function.py +55 -0
  176. opik/evaluation/scorers/scorer_wrapper_metric.py +130 -0
  177. opik/evaluation/test_case.py +3 -2
  178. opik/evaluation/test_result.py +1 -0
  179. opik/evaluation/threads/evaluator.py +31 -3
  180. opik/evaluation/threads/helpers.py +3 -2
  181. opik/evaluation/types.py +9 -1
  182. opik/exceptions.py +33 -0
  183. opik/file_upload/file_uploader.py +13 -0
  184. opik/file_upload/upload_options.py +2 -0
  185. opik/hooks/__init__.py +23 -0
  186. opik/hooks/anonymizer_hook.py +36 -0
  187. opik/hooks/httpx_client_hook.py +112 -0
  188. opik/httpx_client.py +12 -9
  189. opik/id_helpers.py +18 -0
  190. opik/integrations/adk/graph/subgraph_edges_builders.py +1 -2
  191. opik/integrations/adk/helpers.py +16 -7
  192. opik/integrations/adk/legacy_opik_tracer.py +7 -4
  193. opik/integrations/adk/opik_tracer.py +14 -1
  194. opik/integrations/adk/patchers/adk_otel_tracer/opik_adk_otel_tracer.py +7 -3
  195. opik/integrations/adk/recursive_callback_injector.py +4 -7
  196. opik/integrations/bedrock/converse/__init__.py +0 -0
  197. opik/integrations/bedrock/converse/chunks_aggregator.py +188 -0
  198. opik/integrations/bedrock/{converse_decorator.py → converse/converse_decorator.py} +4 -3
  199. opik/integrations/bedrock/invoke_agent_decorator.py +5 -4
  200. opik/integrations/bedrock/invoke_model/__init__.py +0 -0
  201. opik/integrations/bedrock/invoke_model/chunks_aggregator/__init__.py +78 -0
  202. opik/integrations/bedrock/invoke_model/chunks_aggregator/api.py +45 -0
  203. opik/integrations/bedrock/invoke_model/chunks_aggregator/base.py +23 -0
  204. opik/integrations/bedrock/invoke_model/chunks_aggregator/claude.py +121 -0
  205. opik/integrations/bedrock/invoke_model/chunks_aggregator/format_detector.py +107 -0
  206. opik/integrations/bedrock/invoke_model/chunks_aggregator/llama.py +108 -0
  207. opik/integrations/bedrock/invoke_model/chunks_aggregator/mistral.py +118 -0
  208. opik/integrations/bedrock/invoke_model/chunks_aggregator/nova.py +99 -0
  209. opik/integrations/bedrock/invoke_model/invoke_model_decorator.py +178 -0
  210. opik/integrations/bedrock/invoke_model/response_types.py +34 -0
  211. opik/integrations/bedrock/invoke_model/stream_wrappers.py +122 -0
  212. opik/integrations/bedrock/invoke_model/usage_converters.py +87 -0
  213. opik/integrations/bedrock/invoke_model/usage_extraction.py +108 -0
  214. opik/integrations/bedrock/opik_tracker.py +42 -4
  215. opik/integrations/bedrock/types.py +19 -0
  216. opik/integrations/crewai/crewai_decorator.py +8 -51
  217. opik/integrations/crewai/opik_tracker.py +31 -10
  218. opik/integrations/crewai/patchers/__init__.py +5 -0
  219. opik/integrations/crewai/patchers/flow.py +118 -0
  220. opik/integrations/crewai/patchers/litellm_completion.py +30 -0
  221. opik/integrations/crewai/patchers/llm_client.py +207 -0
  222. opik/integrations/dspy/callback.py +80 -17
  223. opik/integrations/dspy/parsers.py +168 -0
  224. opik/integrations/harbor/__init__.py +17 -0
  225. opik/integrations/harbor/experiment_service.py +269 -0
  226. opik/integrations/harbor/opik_tracker.py +528 -0
  227. opik/integrations/haystack/opik_connector.py +2 -2
  228. opik/integrations/haystack/opik_tracer.py +3 -7
  229. opik/integrations/langchain/__init__.py +3 -1
  230. opik/integrations/langchain/helpers.py +96 -0
  231. opik/integrations/langchain/langgraph_async_context_bridge.py +131 -0
  232. opik/integrations/langchain/langgraph_tracer_injector.py +88 -0
  233. opik/integrations/langchain/opik_encoder_extension.py +1 -1
  234. opik/integrations/langchain/opik_tracer.py +474 -229
  235. opik/integrations/litellm/__init__.py +5 -0
  236. opik/integrations/litellm/completion_chunks_aggregator.py +115 -0
  237. opik/integrations/litellm/litellm_completion_decorator.py +242 -0
  238. opik/integrations/litellm/opik_tracker.py +43 -0
  239. opik/integrations/litellm/stream_patchers.py +151 -0
  240. opik/integrations/llama_index/callback.py +146 -107
  241. opik/integrations/openai/agents/opik_tracing_processor.py +1 -2
  242. opik/integrations/openai/openai_chat_completions_decorator.py +2 -16
  243. opik/integrations/openai/opik_tracker.py +1 -1
  244. opik/integrations/sagemaker/auth.py +5 -1
  245. opik/llm_usage/google_usage.py +3 -1
  246. opik/llm_usage/opik_usage.py +7 -8
  247. opik/llm_usage/opik_usage_factory.py +4 -2
  248. opik/logging_messages.py +6 -0
  249. opik/message_processing/batching/base_batcher.py +14 -21
  250. opik/message_processing/batching/batch_manager.py +22 -10
  251. opik/message_processing/batching/batch_manager_constuctors.py +10 -0
  252. opik/message_processing/batching/batchers.py +59 -27
  253. opik/message_processing/batching/flushing_thread.py +0 -3
  254. opik/message_processing/emulation/__init__.py +0 -0
  255. opik/message_processing/emulation/emulator_message_processor.py +578 -0
  256. opik/message_processing/emulation/local_emulator_message_processor.py +140 -0
  257. opik/message_processing/emulation/models.py +162 -0
  258. opik/message_processing/encoder_helpers.py +79 -0
  259. opik/message_processing/messages.py +56 -1
  260. opik/message_processing/preprocessing/__init__.py +0 -0
  261. opik/message_processing/preprocessing/attachments_preprocessor.py +70 -0
  262. opik/message_processing/preprocessing/batching_preprocessor.py +53 -0
  263. opik/message_processing/preprocessing/constants.py +1 -0
  264. opik/message_processing/preprocessing/file_upload_preprocessor.py +38 -0
  265. opik/message_processing/preprocessing/preprocessor.py +36 -0
  266. opik/message_processing/processors/__init__.py +0 -0
  267. opik/message_processing/processors/attachments_extraction_processor.py +146 -0
  268. opik/message_processing/processors/message_processors.py +92 -0
  269. opik/message_processing/processors/message_processors_chain.py +96 -0
  270. opik/message_processing/{message_processors.py → processors/online_message_processor.py} +85 -29
  271. opik/message_processing/queue_consumer.py +9 -3
  272. opik/message_processing/streamer.py +71 -33
  273. opik/message_processing/streamer_constructors.py +43 -10
  274. opik/opik_context.py +16 -4
  275. opik/plugins/pytest/hooks.py +5 -3
  276. opik/rest_api/__init__.py +346 -15
  277. opik/rest_api/alerts/__init__.py +7 -0
  278. opik/rest_api/alerts/client.py +667 -0
  279. opik/rest_api/alerts/raw_client.py +1015 -0
  280. opik/rest_api/alerts/types/__init__.py +7 -0
  281. opik/rest_api/alerts/types/get_webhook_examples_request_alert_type.py +5 -0
  282. opik/rest_api/annotation_queues/__init__.py +4 -0
  283. opik/rest_api/annotation_queues/client.py +668 -0
  284. opik/rest_api/annotation_queues/raw_client.py +1019 -0
  285. opik/rest_api/automation_rule_evaluators/client.py +34 -2
  286. opik/rest_api/automation_rule_evaluators/raw_client.py +24 -0
  287. opik/rest_api/client.py +15 -0
  288. opik/rest_api/dashboards/__init__.py +4 -0
  289. opik/rest_api/dashboards/client.py +462 -0
  290. opik/rest_api/dashboards/raw_client.py +648 -0
  291. opik/rest_api/datasets/client.py +1310 -44
  292. opik/rest_api/datasets/raw_client.py +2269 -358
  293. opik/rest_api/experiments/__init__.py +2 -2
  294. opik/rest_api/experiments/client.py +191 -5
  295. opik/rest_api/experiments/raw_client.py +301 -7
  296. opik/rest_api/experiments/types/__init__.py +4 -1
  297. opik/rest_api/experiments/types/experiment_update_status.py +5 -0
  298. opik/rest_api/experiments/types/experiment_update_type.py +5 -0
  299. opik/rest_api/experiments/types/experiment_write_status.py +5 -0
  300. opik/rest_api/feedback_definitions/types/find_feedback_definitions_request_type.py +1 -1
  301. opik/rest_api/llm_provider_key/client.py +20 -0
  302. opik/rest_api/llm_provider_key/raw_client.py +20 -0
  303. opik/rest_api/llm_provider_key/types/provider_api_key_write_provider.py +1 -1
  304. opik/rest_api/manual_evaluation/__init__.py +4 -0
  305. opik/rest_api/manual_evaluation/client.py +347 -0
  306. opik/rest_api/manual_evaluation/raw_client.py +543 -0
  307. opik/rest_api/optimizations/client.py +145 -9
  308. opik/rest_api/optimizations/raw_client.py +237 -13
  309. opik/rest_api/optimizations/types/optimization_update_status.py +3 -1
  310. opik/rest_api/prompts/__init__.py +2 -2
  311. opik/rest_api/prompts/client.py +227 -6
  312. opik/rest_api/prompts/raw_client.py +331 -2
  313. opik/rest_api/prompts/types/__init__.py +3 -1
  314. opik/rest_api/prompts/types/create_prompt_version_detail_template_structure.py +5 -0
  315. opik/rest_api/prompts/types/prompt_write_template_structure.py +5 -0
  316. opik/rest_api/spans/__init__.py +0 -2
  317. opik/rest_api/spans/client.py +238 -76
  318. opik/rest_api/spans/raw_client.py +307 -95
  319. opik/rest_api/spans/types/__init__.py +0 -2
  320. opik/rest_api/traces/client.py +572 -161
  321. opik/rest_api/traces/raw_client.py +736 -229
  322. opik/rest_api/types/__init__.py +352 -17
  323. opik/rest_api/types/aggregation_data.py +1 -0
  324. opik/rest_api/types/alert.py +33 -0
  325. opik/rest_api/types/alert_alert_type.py +5 -0
  326. opik/rest_api/types/alert_page_public.py +24 -0
  327. opik/rest_api/types/alert_public.py +33 -0
  328. opik/rest_api/types/alert_public_alert_type.py +5 -0
  329. opik/rest_api/types/alert_trigger.py +27 -0
  330. opik/rest_api/types/alert_trigger_config.py +28 -0
  331. opik/rest_api/types/alert_trigger_config_public.py +28 -0
  332. opik/rest_api/types/alert_trigger_config_public_type.py +10 -0
  333. opik/rest_api/types/alert_trigger_config_type.py +10 -0
  334. opik/rest_api/types/alert_trigger_config_write.py +22 -0
  335. opik/rest_api/types/alert_trigger_config_write_type.py +10 -0
  336. opik/rest_api/types/alert_trigger_event_type.py +19 -0
  337. opik/rest_api/types/alert_trigger_public.py +27 -0
  338. opik/rest_api/types/alert_trigger_public_event_type.py +19 -0
  339. opik/rest_api/types/alert_trigger_write.py +23 -0
  340. opik/rest_api/types/alert_trigger_write_event_type.py +19 -0
  341. opik/rest_api/types/alert_write.py +28 -0
  342. opik/rest_api/types/alert_write_alert_type.py +5 -0
  343. opik/rest_api/types/annotation_queue.py +42 -0
  344. opik/rest_api/types/annotation_queue_batch.py +27 -0
  345. opik/rest_api/types/annotation_queue_item_ids.py +19 -0
  346. opik/rest_api/types/annotation_queue_page_public.py +28 -0
  347. opik/rest_api/types/annotation_queue_public.py +38 -0
  348. opik/rest_api/types/annotation_queue_public_scope.py +5 -0
  349. opik/rest_api/types/annotation_queue_reviewer.py +20 -0
  350. opik/rest_api/types/annotation_queue_reviewer_public.py +20 -0
  351. opik/rest_api/types/annotation_queue_scope.py +5 -0
  352. opik/rest_api/types/annotation_queue_write.py +31 -0
  353. opik/rest_api/types/annotation_queue_write_scope.py +5 -0
  354. opik/rest_api/types/audio_url.py +19 -0
  355. opik/rest_api/types/audio_url_public.py +19 -0
  356. opik/rest_api/types/audio_url_write.py +19 -0
  357. opik/rest_api/types/automation_rule_evaluator.py +62 -2
  358. opik/rest_api/types/automation_rule_evaluator_llm_as_judge.py +2 -0
  359. opik/rest_api/types/automation_rule_evaluator_llm_as_judge_public.py +2 -0
  360. opik/rest_api/types/automation_rule_evaluator_llm_as_judge_write.py +2 -0
  361. opik/rest_api/types/automation_rule_evaluator_object_object_public.py +155 -0
  362. opik/rest_api/types/automation_rule_evaluator_page_public.py +3 -2
  363. opik/rest_api/types/automation_rule_evaluator_public.py +57 -2
  364. opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge.py +22 -0
  365. opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_public.py +22 -0
  366. opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_write.py +22 -0
  367. opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python.py +22 -0
  368. opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python_public.py +22 -0
  369. opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python_write.py +22 -0
  370. opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge.py +2 -0
  371. opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_public.py +2 -0
  372. opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_write.py +2 -0
  373. opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python.py +2 -0
  374. opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_public.py +2 -0
  375. opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_write.py +2 -0
  376. opik/rest_api/types/automation_rule_evaluator_update.py +51 -1
  377. opik/rest_api/types/automation_rule_evaluator_update_llm_as_judge.py +2 -0
  378. opik/rest_api/types/automation_rule_evaluator_update_span_llm_as_judge.py +22 -0
  379. opik/rest_api/types/automation_rule_evaluator_update_span_user_defined_metric_python.py +22 -0
  380. opik/rest_api/types/automation_rule_evaluator_update_trace_thread_llm_as_judge.py +2 -0
  381. opik/rest_api/types/automation_rule_evaluator_update_trace_thread_user_defined_metric_python.py +2 -0
  382. opik/rest_api/types/automation_rule_evaluator_update_user_defined_metric_python.py +2 -0
  383. opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python.py +2 -0
  384. opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_public.py +2 -0
  385. opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_write.py +2 -0
  386. opik/rest_api/types/automation_rule_evaluator_write.py +51 -1
  387. opik/rest_api/types/boolean_feedback_definition.py +25 -0
  388. opik/rest_api/types/boolean_feedback_definition_create.py +20 -0
  389. opik/rest_api/types/boolean_feedback_definition_public.py +25 -0
  390. opik/rest_api/types/boolean_feedback_definition_update.py +20 -0
  391. opik/rest_api/types/boolean_feedback_detail.py +29 -0
  392. opik/rest_api/types/boolean_feedback_detail_create.py +29 -0
  393. opik/rest_api/types/boolean_feedback_detail_public.py +29 -0
  394. opik/rest_api/types/boolean_feedback_detail_update.py +29 -0
  395. opik/rest_api/types/dashboard_page_public.py +24 -0
  396. opik/rest_api/types/dashboard_public.py +30 -0
  397. opik/rest_api/types/dataset.py +4 -0
  398. opik/rest_api/types/dataset_expansion.py +42 -0
  399. opik/rest_api/types/dataset_expansion_response.py +39 -0
  400. opik/rest_api/types/dataset_item.py +2 -0
  401. opik/rest_api/types/dataset_item_changes_public.py +5 -0
  402. opik/rest_api/types/dataset_item_compare.py +2 -0
  403. opik/rest_api/types/dataset_item_filter.py +27 -0
  404. opik/rest_api/types/dataset_item_filter_operator.py +21 -0
  405. opik/rest_api/types/dataset_item_page_compare.py +5 -0
  406. opik/rest_api/types/dataset_item_page_public.py +5 -0
  407. opik/rest_api/types/dataset_item_public.py +2 -0
  408. opik/rest_api/types/dataset_item_update.py +39 -0
  409. opik/rest_api/types/dataset_item_write.py +1 -0
  410. opik/rest_api/types/dataset_public.py +4 -0
  411. opik/rest_api/types/dataset_public_status.py +5 -0
  412. opik/rest_api/types/dataset_status.py +5 -0
  413. opik/rest_api/types/dataset_version_diff.py +22 -0
  414. opik/rest_api/types/dataset_version_diff_stats.py +24 -0
  415. opik/rest_api/types/dataset_version_page_public.py +23 -0
  416. opik/rest_api/types/dataset_version_public.py +59 -0
  417. opik/rest_api/types/dataset_version_summary.py +46 -0
  418. opik/rest_api/types/dataset_version_summary_public.py +46 -0
  419. opik/rest_api/types/experiment.py +7 -2
  420. opik/rest_api/types/experiment_group_response.py +2 -0
  421. opik/rest_api/types/experiment_public.py +7 -2
  422. opik/rest_api/types/experiment_public_status.py +5 -0
  423. opik/rest_api/types/experiment_score.py +20 -0
  424. opik/rest_api/types/experiment_score_public.py +20 -0
  425. opik/rest_api/types/experiment_score_write.py +20 -0
  426. opik/rest_api/types/experiment_status.py +5 -0
  427. opik/rest_api/types/feedback.py +25 -1
  428. opik/rest_api/types/feedback_create.py +20 -1
  429. opik/rest_api/types/feedback_object_public.py +27 -1
  430. opik/rest_api/types/feedback_public.py +25 -1
  431. opik/rest_api/types/feedback_score_batch_item.py +2 -1
  432. opik/rest_api/types/feedback_score_batch_item_thread.py +2 -1
  433. opik/rest_api/types/feedback_score_public.py +4 -0
  434. opik/rest_api/types/feedback_update.py +20 -1
  435. opik/rest_api/types/group_content_with_aggregations.py +1 -0
  436. opik/rest_api/types/group_detail.py +19 -0
  437. opik/rest_api/types/group_details.py +20 -0
  438. opik/rest_api/types/guardrail.py +1 -0
  439. opik/rest_api/types/guardrail_write.py +1 -0
  440. opik/rest_api/types/ids_holder.py +19 -0
  441. opik/rest_api/types/image_url.py +20 -0
  442. opik/rest_api/types/image_url_public.py +20 -0
  443. opik/rest_api/types/image_url_write.py +20 -0
  444. opik/rest_api/types/llm_as_judge_message.py +5 -1
  445. opik/rest_api/types/llm_as_judge_message_content.py +26 -0
  446. opik/rest_api/types/llm_as_judge_message_content_public.py +26 -0
  447. opik/rest_api/types/llm_as_judge_message_content_write.py +26 -0
  448. opik/rest_api/types/llm_as_judge_message_public.py +5 -1
  449. opik/rest_api/types/llm_as_judge_message_write.py +5 -1
  450. opik/rest_api/types/llm_as_judge_model_parameters.py +3 -0
  451. opik/rest_api/types/llm_as_judge_model_parameters_public.py +3 -0
  452. opik/rest_api/types/llm_as_judge_model_parameters_write.py +3 -0
  453. opik/rest_api/types/manual_evaluation_request.py +38 -0
  454. opik/rest_api/types/manual_evaluation_request_entity_type.py +5 -0
  455. opik/rest_api/types/manual_evaluation_response.py +27 -0
  456. opik/rest_api/types/optimization.py +4 -2
  457. opik/rest_api/types/optimization_public.py +4 -2
  458. opik/rest_api/types/optimization_public_status.py +3 -1
  459. opik/rest_api/types/optimization_status.py +3 -1
  460. opik/rest_api/types/optimization_studio_config.py +27 -0
  461. opik/rest_api/types/optimization_studio_config_public.py +27 -0
  462. opik/rest_api/types/optimization_studio_config_write.py +27 -0
  463. opik/rest_api/types/optimization_studio_log.py +22 -0
  464. opik/rest_api/types/optimization_write.py +4 -2
  465. opik/rest_api/types/optimization_write_status.py +3 -1
  466. opik/rest_api/types/project.py +1 -0
  467. opik/rest_api/types/project_detailed.py +1 -0
  468. opik/rest_api/types/project_reference.py +31 -0
  469. opik/rest_api/types/project_reference_public.py +31 -0
  470. opik/rest_api/types/project_stats_summary_item.py +1 -0
  471. opik/rest_api/types/prompt.py +6 -0
  472. opik/rest_api/types/prompt_detail.py +6 -0
  473. opik/rest_api/types/prompt_detail_template_structure.py +5 -0
  474. opik/rest_api/types/prompt_public.py +6 -0
  475. opik/rest_api/types/prompt_public_template_structure.py +5 -0
  476. opik/rest_api/types/prompt_template_structure.py +5 -0
  477. opik/rest_api/types/prompt_version.py +3 -0
  478. opik/rest_api/types/prompt_version_detail.py +3 -0
  479. opik/rest_api/types/prompt_version_detail_template_structure.py +5 -0
  480. opik/rest_api/types/prompt_version_link.py +1 -0
  481. opik/rest_api/types/prompt_version_link_public.py +1 -0
  482. opik/rest_api/types/prompt_version_page_public.py +5 -0
  483. opik/rest_api/types/prompt_version_public.py +3 -0
  484. opik/rest_api/types/prompt_version_public_template_structure.py +5 -0
  485. opik/rest_api/types/prompt_version_template_structure.py +5 -0
  486. opik/rest_api/types/prompt_version_update.py +33 -0
  487. opik/rest_api/types/provider_api_key.py +9 -0
  488. opik/rest_api/types/provider_api_key_provider.py +1 -1
  489. opik/rest_api/types/provider_api_key_public.py +9 -0
  490. opik/rest_api/types/provider_api_key_public_provider.py +1 -1
  491. opik/rest_api/types/score_name.py +1 -0
  492. opik/rest_api/types/service_toggles_config.py +18 -0
  493. opik/rest_api/types/span.py +1 -2
  494. opik/rest_api/types/span_enrichment_options.py +31 -0
  495. opik/rest_api/types/span_experiment_item_bulk_write_view.py +1 -2
  496. opik/rest_api/types/span_filter.py +23 -0
  497. opik/rest_api/types/span_filter_operator.py +21 -0
  498. opik/rest_api/types/span_filter_write.py +23 -0
  499. opik/rest_api/types/span_filter_write_operator.py +21 -0
  500. opik/rest_api/types/span_llm_as_judge_code.py +27 -0
  501. opik/rest_api/types/span_llm_as_judge_code_public.py +27 -0
  502. opik/rest_api/types/span_llm_as_judge_code_write.py +27 -0
  503. opik/rest_api/types/span_public.py +1 -2
  504. opik/rest_api/types/span_update.py +46 -0
  505. opik/rest_api/types/span_user_defined_metric_python_code.py +20 -0
  506. opik/rest_api/types/span_user_defined_metric_python_code_public.py +20 -0
  507. opik/rest_api/types/span_user_defined_metric_python_code_write.py +20 -0
  508. opik/rest_api/types/span_write.py +1 -2
  509. opik/rest_api/types/studio_evaluation.py +20 -0
  510. opik/rest_api/types/studio_evaluation_public.py +20 -0
  511. opik/rest_api/types/studio_evaluation_write.py +20 -0
  512. opik/rest_api/types/studio_llm_model.py +21 -0
  513. opik/rest_api/types/studio_llm_model_public.py +21 -0
  514. opik/rest_api/types/studio_llm_model_write.py +21 -0
  515. opik/rest_api/types/studio_message.py +20 -0
  516. opik/rest_api/types/studio_message_public.py +20 -0
  517. opik/rest_api/types/studio_message_write.py +20 -0
  518. opik/rest_api/types/studio_metric.py +21 -0
  519. opik/rest_api/types/studio_metric_public.py +21 -0
  520. opik/rest_api/types/studio_metric_write.py +21 -0
  521. opik/rest_api/types/studio_optimizer.py +21 -0
  522. opik/rest_api/types/studio_optimizer_public.py +21 -0
  523. opik/rest_api/types/studio_optimizer_write.py +21 -0
  524. opik/rest_api/types/studio_prompt.py +20 -0
  525. opik/rest_api/types/studio_prompt_public.py +20 -0
  526. opik/rest_api/types/studio_prompt_write.py +20 -0
  527. opik/rest_api/types/trace.py +11 -2
  528. opik/rest_api/types/trace_enrichment_options.py +32 -0
  529. opik/rest_api/types/trace_experiment_item_bulk_write_view.py +1 -2
  530. opik/rest_api/types/trace_filter.py +23 -0
  531. opik/rest_api/types/trace_filter_operator.py +21 -0
  532. opik/rest_api/types/trace_filter_write.py +23 -0
  533. opik/rest_api/types/trace_filter_write_operator.py +21 -0
  534. opik/rest_api/types/trace_public.py +11 -2
  535. opik/rest_api/types/trace_thread_filter_write.py +23 -0
  536. opik/rest_api/types/trace_thread_filter_write_operator.py +21 -0
  537. opik/rest_api/types/trace_thread_identifier.py +1 -0
  538. opik/rest_api/types/trace_update.py +39 -0
  539. opik/rest_api/types/trace_write.py +1 -2
  540. opik/rest_api/types/value_entry.py +2 -0
  541. opik/rest_api/types/value_entry_compare.py +2 -0
  542. opik/rest_api/types/value_entry_experiment_item_bulk_write_view.py +2 -0
  543. opik/rest_api/types/value_entry_public.py +2 -0
  544. opik/rest_api/types/video_url.py +19 -0
  545. opik/rest_api/types/video_url_public.py +19 -0
  546. opik/rest_api/types/video_url_write.py +19 -0
  547. opik/rest_api/types/webhook.py +28 -0
  548. opik/rest_api/types/webhook_examples.py +19 -0
  549. opik/rest_api/types/webhook_public.py +28 -0
  550. opik/rest_api/types/webhook_test_result.py +23 -0
  551. opik/rest_api/types/webhook_test_result_status.py +5 -0
  552. opik/rest_api/types/webhook_write.py +23 -0
  553. opik/rest_api/types/welcome_wizard_tracking.py +22 -0
  554. opik/rest_api/types/workspace_configuration.py +5 -0
  555. opik/rest_api/welcome_wizard/__init__.py +4 -0
  556. opik/rest_api/welcome_wizard/client.py +195 -0
  557. opik/rest_api/welcome_wizard/raw_client.py +208 -0
  558. opik/rest_api/workspaces/client.py +14 -2
  559. opik/rest_api/workspaces/raw_client.py +10 -0
  560. opik/s3_httpx_client.py +14 -1
  561. opik/simulation/__init__.py +6 -0
  562. opik/simulation/simulated_user.py +99 -0
  563. opik/simulation/simulator.py +108 -0
  564. opik/synchronization.py +5 -6
  565. opik/{decorator/tracing_runtime_config.py → tracing_runtime_config.py} +6 -7
  566. opik/types.py +36 -0
  567. opik/validation/chat_prompt_messages.py +241 -0
  568. opik/validation/feedback_score.py +3 -3
  569. opik/validation/validator.py +28 -0
  570. opik-1.9.71.dist-info/METADATA +370 -0
  571. opik-1.9.71.dist-info/RECORD +1110 -0
  572. opik/api_objects/prompt/prompt.py +0 -112
  573. opik/cli.py +0 -193
  574. opik/hooks.py +0 -13
  575. opik/integrations/bedrock/chunks_aggregator.py +0 -55
  576. opik/integrations/bedrock/helpers.py +0 -8
  577. opik/rest_api/types/automation_rule_evaluator_object_public.py +0 -100
  578. opik/rest_api/types/json_node_experiment_item_bulk_write_view.py +0 -5
  579. opik-1.8.39.dist-info/METADATA +0 -339
  580. opik-1.8.39.dist-info/RECORD +0 -790
  581. /opik/{evaluation/metrics/conversation/conversational_coherence → decorator/context_manager}/__init__.py +0 -0
  582. /opik/evaluation/metrics/conversation/{session_completeness → llm_judges/conversational_coherence}/__init__.py +0 -0
  583. /opik/evaluation/metrics/conversation/{conversational_coherence → llm_judges/conversational_coherence}/schema.py +0 -0
  584. /opik/evaluation/metrics/conversation/{user_frustration → llm_judges/session_completeness}/__init__.py +0 -0
  585. /opik/evaluation/metrics/conversation/{session_completeness → llm_judges/session_completeness}/schema.py +0 -0
  586. /opik/evaluation/metrics/conversation/{user_frustration → llm_judges/user_frustration}/schema.py +0 -0
  587. /opik/integrations/bedrock/{stream_wrappers.py → converse/stream_wrappers.py} +0 -0
  588. /opik/rest_api/{spans/types → types}/span_update_type.py +0 -0
  589. {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/WHEEL +0 -0
  590. {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/entry_points.txt +0 -0
  591. {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/licenses/LICENSE +0 -0
  592. {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,578 @@
1
+ """Prompt export functionality."""
2
+
3
+ import json
4
+ import sys
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ from typing import Any, List, Optional, Union
8
+
9
+ import click
10
+ from rich.console import Console
11
+
12
+ import opik
13
+ from opik.api_objects.prompt import Prompt, ChatPrompt
14
+ from .utils import (
15
+ debug_print,
16
+ prompt_to_csv_rows,
17
+ should_skip_file,
18
+ write_csv_data,
19
+ write_json_data,
20
+ print_export_summary,
21
+ )
22
+
23
+ console = Console()
24
+
25
+
26
+ def _get_prompt_content(prompt: Any) -> Any:
27
+ """Extract prompt content based on prompt type.
28
+
29
+ Args:
30
+ prompt: A Prompt or ChatPrompt instance
31
+
32
+ Returns:
33
+ For Prompt: the prompt string
34
+ For ChatPrompt: the template (list of message dicts)
35
+ Otherwise: None
36
+ """
37
+ if isinstance(prompt, Prompt):
38
+ return prompt.prompt
39
+ elif isinstance(prompt, ChatPrompt):
40
+ return prompt.template
41
+ return None
42
+
43
+
44
+ def _get_template_structure(prompt: Any) -> str:
45
+ """Get template_structure based on prompt type.
46
+
47
+ Args:
48
+ prompt: A Prompt or ChatPrompt instance
49
+
50
+ Returns:
51
+ "text" for Prompt, "chat" for ChatPrompt, "text" as default
52
+ """
53
+ if isinstance(prompt, ChatPrompt):
54
+ return "chat"
55
+ elif isinstance(prompt, Prompt):
56
+ return "text"
57
+ return "text"
58
+
59
+
60
+ def _get_prompt_type_string(prompt: Any) -> Optional[str]:
61
+ """Get prompt type as uppercase string.
62
+
63
+ Args:
64
+ prompt: A Prompt or ChatPrompt instance
65
+
66
+ Returns:
67
+ Uppercase type string (e.g., "JINJA2", "MUSTACHE") or None
68
+ """
69
+ prompt_type = getattr(prompt, "type", None)
70
+ if prompt_type is None:
71
+ return None
72
+
73
+ # If it's an enum, get the value and convert to uppercase
74
+ if hasattr(prompt_type, "value"):
75
+ return prompt_type.value.upper()
76
+ # If it's already a string, convert to uppercase
77
+ if isinstance(prompt_type, str):
78
+ return prompt_type.upper()
79
+ # Otherwise, convert to string and uppercase
80
+ return str(prompt_type).upper()
81
+
82
+
83
+ def export_single_prompt(
84
+ client: opik.Opik,
85
+ prompt: Union[Prompt, ChatPrompt],
86
+ output_dir: Path,
87
+ max_results: Optional[int],
88
+ force: bool,
89
+ debug: bool,
90
+ format: str,
91
+ ) -> int:
92
+ """Export a single prompt."""
93
+ try:
94
+ # Check if already exists and force is not set
95
+ if format.lower() == "csv":
96
+ prompt_file = output_dir / f"prompts_{prompt.name.replace('/', '_')}.csv"
97
+ else:
98
+ prompt_file = output_dir / f"prompt_{prompt.name.replace('/', '_')}.json"
99
+
100
+ if should_skip_file(prompt_file, force):
101
+ if debug:
102
+ debug_print(f"Skipping {prompt.name} (already exists)", debug)
103
+ return 0
104
+
105
+ # Get prompt history - use appropriate method based on prompt type
106
+ prompt_history: List[Union[Prompt, ChatPrompt]]
107
+ if isinstance(prompt, ChatPrompt):
108
+ prompt_history = list(client.get_chat_prompt_history(prompt.name))
109
+ else:
110
+ prompt_history = list(client.get_prompt_history(prompt.name))
111
+
112
+ # Create prompt data structure
113
+ prompt_data = {
114
+ "name": prompt.name,
115
+ "current_version": {
116
+ "prompt": _get_prompt_content(prompt),
117
+ "metadata": getattr(prompt, "metadata", None),
118
+ "type": _get_prompt_type_string(prompt),
119
+ "commit": getattr(prompt, "commit", None),
120
+ "template_structure": _get_template_structure(prompt),
121
+ },
122
+ "history": [
123
+ {
124
+ "prompt": _get_prompt_content(version),
125
+ "metadata": getattr(version, "metadata", None),
126
+ "type": _get_prompt_type_string(version),
127
+ "commit": getattr(version, "commit", None),
128
+ "template_structure": _get_template_structure(version),
129
+ }
130
+ for version in prompt_history
131
+ ],
132
+ "downloaded_at": datetime.now().isoformat(),
133
+ }
134
+
135
+ # Save to file using the appropriate format
136
+ if format.lower() == "csv":
137
+ write_csv_data(prompt_data, prompt_file, prompt_to_csv_rows)
138
+ else:
139
+ write_json_data(prompt_data, prompt_file)
140
+
141
+ if debug:
142
+ debug_print(f"Exported prompt: {prompt.name}", debug)
143
+ return 1
144
+
145
+ except Exception as e:
146
+ console.print(f"[red]Error exporting prompt {prompt.name}: {e}[/red]")
147
+ return 0
148
+
149
+
150
+ def export_prompt_by_name(
151
+ name: str,
152
+ workspace: str,
153
+ output_path: str,
154
+ max_results: Optional[int],
155
+ force: bool,
156
+ debug: bool,
157
+ format: str,
158
+ api_key: Optional[str] = None,
159
+ ) -> None:
160
+ """Export a prompt by exact name."""
161
+ try:
162
+ if debug:
163
+ debug_print(f"Exporting prompt: {name}", debug)
164
+
165
+ # Initialize client
166
+ if api_key:
167
+ client = opik.Opik(api_key=api_key, workspace=workspace)
168
+ else:
169
+ client = opik.Opik(workspace=workspace)
170
+
171
+ # Create output directory
172
+ output_dir = Path(output_path) / workspace / "prompts"
173
+ output_dir.mkdir(parents=True, exist_ok=True)
174
+
175
+ if debug:
176
+ debug_print(f"Target directory: {output_dir}", debug)
177
+
178
+ # Try to get prompt by exact name
179
+ # Try ChatPrompt first, then regular Prompt
180
+ prompt: Optional[Union[Prompt, ChatPrompt]] = None
181
+ try:
182
+ prompt = client.get_chat_prompt(name)
183
+ if debug and prompt:
184
+ debug_print(f"Found ChatPrompt by direct lookup: {prompt.name}", debug)
185
+ except Exception:
186
+ # Not a ChatPrompt, try regular Prompt
187
+ try:
188
+ prompt = client.get_prompt(name)
189
+ if not prompt:
190
+ console.print(f"[red]Prompt '{name}' not found[/red]")
191
+ return
192
+ if debug:
193
+ debug_print(f"Found Prompt by direct lookup: {prompt.name}", debug)
194
+ except Exception as e:
195
+ console.print(f"[red]Prompt '{name}' not found: {e}[/red]")
196
+ return
197
+
198
+ if prompt is None:
199
+ console.print(f"[red]Prompt '{name}' not found[/red]")
200
+ return
201
+
202
+ # Export the prompt
203
+ exported_count = export_single_prompt(
204
+ client, prompt, output_dir, max_results, force, debug, format
205
+ )
206
+
207
+ # Collect statistics for summary
208
+ stats = {
209
+ "prompts": 1 if exported_count > 0 else 0,
210
+ "prompts_skipped": 0 if exported_count > 0 else 1,
211
+ }
212
+
213
+ # Show export summary
214
+ print_export_summary(stats, format)
215
+
216
+ if exported_count > 0:
217
+ console.print(
218
+ f"[green]Successfully exported prompt '{name}' to {output_dir}[/green]"
219
+ )
220
+ else:
221
+ console.print(
222
+ f"[yellow]Prompt '{name}' already exists (use --force to re-download)[/yellow]"
223
+ )
224
+
225
+ except Exception as e:
226
+ console.print(f"[red]Error exporting prompt: {e}[/red]")
227
+ sys.exit(1)
228
+
229
+
230
+ def export_prompts_by_ids(
231
+ client: opik.Opik,
232
+ prompt_ids: set[str],
233
+ prompts_dir: Path,
234
+ format: str,
235
+ debug: bool,
236
+ force: bool,
237
+ ) -> tuple[int, int]:
238
+ """Export prompts by their IDs.
239
+
240
+ Args:
241
+ client: Opik client instance
242
+ prompt_ids: Set of prompt IDs to export
243
+ prompts_dir: Directory to save prompts
244
+ format: Export format ('json' or 'csv')
245
+ debug: Enable debug output
246
+ force: Re-download prompts even if they already exist locally
247
+
248
+ Returns:
249
+ Tuple of (exported_count, skipped_count)
250
+ """
251
+ exported_count = 0
252
+ skipped_count = 0
253
+
254
+ for prompt_id in prompt_ids:
255
+ try:
256
+ # Get the prompt - try ChatPrompt first, then regular Prompt
257
+ prompt: Optional[Union[Prompt, ChatPrompt]] = None
258
+ try:
259
+ prompt = client.get_chat_prompt(prompt_id)
260
+ except Exception:
261
+ # Not a ChatPrompt, try regular Prompt
262
+ prompt = client.get_prompt(prompt_id)
263
+
264
+ if not prompt:
265
+ if debug:
266
+ console.print(
267
+ f"[yellow]Warning: Prompt {prompt_id} not found[/yellow]"
268
+ )
269
+ continue
270
+
271
+ # Determine file path
272
+ if format.lower() == "csv":
273
+ prompt_file = (
274
+ prompts_dir
275
+ / f"prompts_{prompt.name or getattr(prompt, 'id', 'unknown')}.csv"
276
+ )
277
+ else:
278
+ prompt_file = (
279
+ prompts_dir
280
+ / f"prompt_{prompt.name or getattr(prompt, 'id', 'unknown')}.json"
281
+ )
282
+
283
+ # Check if file already exists and should be skipped
284
+ if should_skip_file(prompt_file, force):
285
+ if debug:
286
+ debug_print(
287
+ f"Skipping prompt {prompt.name or prompt_id} (already exists)",
288
+ debug,
289
+ )
290
+ else:
291
+ console.print(
292
+ f"[yellow]Skipping prompt: {prompt.name or prompt_id} (already exists)[/yellow]"
293
+ )
294
+ skipped_count += 1
295
+ continue
296
+
297
+ # Get prompt history - use appropriate method based on prompt type
298
+ prompt_history: List[Union[Prompt, ChatPrompt]]
299
+ if isinstance(prompt, ChatPrompt):
300
+ prompt_history = list(client.get_chat_prompt_history(prompt.name))
301
+ else:
302
+ prompt_history = list(client.get_prompt_history(prompt_id))
303
+
304
+ # Create prompt data structure
305
+ prompt_data = {
306
+ "prompt": {
307
+ "id": getattr(prompt, "id", None),
308
+ "name": prompt.name,
309
+ "description": getattr(prompt, "description", None),
310
+ "created_at": (
311
+ created_at.isoformat()
312
+ if (created_at := getattr(prompt, "created_at", None))
313
+ else None
314
+ ),
315
+ "last_updated_at": (
316
+ last_updated_at.isoformat()
317
+ if (last_updated_at := getattr(prompt, "last_updated_at", None))
318
+ else None
319
+ ),
320
+ },
321
+ "current_version": {
322
+ "prompt": _get_prompt_content(prompt),
323
+ "metadata": getattr(prompt, "metadata", None),
324
+ "type": _get_prompt_type_string(prompt),
325
+ "commit": getattr(prompt, "commit", None),
326
+ "template_structure": _get_template_structure(prompt),
327
+ },
328
+ "history": [
329
+ {
330
+ "prompt": _get_prompt_content(version),
331
+ "metadata": getattr(version, "metadata", None),
332
+ "type": _get_prompt_type_string(version),
333
+ "commit": getattr(version, "commit", None),
334
+ "template_structure": _get_template_structure(version),
335
+ }
336
+ for version in prompt_history
337
+ ],
338
+ "downloaded_at": datetime.now().isoformat(),
339
+ }
340
+
341
+ # Save prompt data using the appropriate format
342
+ if format.lower() == "csv":
343
+ write_csv_data(prompt_data, prompt_file, prompt_to_csv_rows)
344
+ else:
345
+ write_json_data(prompt_data, prompt_file)
346
+
347
+ console.print(f"[green]Exported prompt: {prompt.name or prompt_id}[/green]")
348
+ exported_count += 1
349
+
350
+ except Exception as e:
351
+ if debug:
352
+ console.print(
353
+ f"[yellow]Warning: Could not export prompt {prompt_id}: {e}[/yellow]"
354
+ )
355
+ else:
356
+ console.print(f"[red]Error exporting prompt {prompt_id}: {e}[/red]")
357
+ continue
358
+
359
+ return exported_count, skipped_count
360
+
361
+
362
+ def export_related_prompts_by_name(
363
+ client: opik.Opik,
364
+ experiment: Any,
365
+ output_dir: Path,
366
+ force: bool,
367
+ debug: bool,
368
+ format: str = "json",
369
+ ) -> int:
370
+ """Export prompts explicitly related to the experiment from experiment metadata."""
371
+ try:
372
+ prompts_dir = output_dir.parent / "prompts"
373
+ prompts_dir.mkdir(parents=True, exist_ok=True)
374
+
375
+ # Get experiment data to access metadata
376
+ experiment_data = experiment.get_experiment_data()
377
+ if not experiment_data:
378
+ debug_print("Could not get experiment data", debug)
379
+ return 0
380
+
381
+ # Extract prompt names from experiment metadata
382
+ prompt_names = []
383
+ metadata = experiment_data.metadata
384
+
385
+ if metadata:
386
+ # Metadata can be a dict, list, or string (JsonListStringPublic)
387
+ # Parse if it's a string, otherwise use directly
388
+ if isinstance(metadata, str):
389
+ try:
390
+ metadata = json.loads(metadata)
391
+ except (json.JSONDecodeError, Exception) as e:
392
+ if debug:
393
+ debug_print(f"Could not parse metadata as JSON: {e}", debug)
394
+ metadata = None
395
+
396
+ # Check if metadata is a dict and has "prompts" key
397
+ if isinstance(metadata, dict) and "prompts" in metadata:
398
+ prompts_dict = metadata["prompts"]
399
+ if isinstance(prompts_dict, dict):
400
+ # Prompts are stored as a dict with prompt names as keys
401
+ prompt_names = list(prompts_dict.keys())
402
+ if debug:
403
+ debug_print(
404
+ f"Found {len(prompt_names)} prompt(s) in experiment metadata: {prompt_names}",
405
+ debug,
406
+ )
407
+ else:
408
+ if debug:
409
+ debug_print(
410
+ f"Metadata 'prompts' is not a dict, got: {type(prompts_dict)}",
411
+ debug,
412
+ )
413
+ else:
414
+ if debug:
415
+ debug_print("No 'prompts' key found in experiment metadata", debug)
416
+
417
+ if not prompt_names:
418
+ debug_print("No prompts found in experiment metadata", debug)
419
+ return 0
420
+
421
+ console.print(
422
+ f"[blue]Exporting {len(prompt_names)} prompt(s) from experiment metadata...[/blue]"
423
+ )
424
+
425
+ exported_count = 0
426
+ # Export each prompt by name from metadata
427
+ for prompt_name in prompt_names:
428
+ try:
429
+ debug_print(f"Exporting prompt: {prompt_name}", debug)
430
+
431
+ # Try to get the prompt - try ChatPrompt first, then regular Prompt
432
+ prompt: Optional[Union[Prompt, ChatPrompt]] = None
433
+ try:
434
+ prompt = client.get_chat_prompt(prompt_name)
435
+ except Exception:
436
+ # Not a ChatPrompt, try regular Prompt
437
+ try:
438
+ prompt = client.get_prompt(prompt_name)
439
+ except Exception as e:
440
+ if debug:
441
+ console.print(
442
+ f"[yellow]Warning: Could not get prompt '{prompt_name}': {e}[/yellow]"
443
+ )
444
+ continue
445
+
446
+ if not prompt:
447
+ if debug:
448
+ console.print(
449
+ f"[yellow]Warning: Prompt '{prompt_name}' not found[/yellow]"
450
+ )
451
+ continue
452
+
453
+ # Get prompt history - use appropriate method based on prompt type
454
+ prompt_history: List[Union[Prompt, ChatPrompt]]
455
+ if isinstance(prompt, ChatPrompt):
456
+ prompt_history = list(client.get_chat_prompt_history(prompt.name))
457
+ else:
458
+ prompt_history = list(client.get_prompt_history(prompt.name))
459
+
460
+ # Create prompt data structure
461
+ prompt_data = {
462
+ "prompt": {
463
+ "id": getattr(prompt, "__internal_api__prompt_id__", None),
464
+ "name": prompt.name,
465
+ "description": getattr(prompt, "description", None),
466
+ "created_at": getattr(prompt, "created_at", None),
467
+ "last_updated_at": getattr(prompt, "last_updated_at", None),
468
+ },
469
+ "current_version": {
470
+ "prompt": _get_prompt_content(prompt),
471
+ "metadata": getattr(prompt, "metadata", None),
472
+ "type": _get_prompt_type_string(prompt),
473
+ "commit": getattr(prompt, "commit", None),
474
+ "template_structure": _get_template_structure(prompt),
475
+ },
476
+ "history": [
477
+ {
478
+ "prompt": _get_prompt_content(version),
479
+ "metadata": getattr(version, "metadata", None),
480
+ "type": _get_prompt_type_string(version),
481
+ "commit": getattr(version, "commit", None),
482
+ "template_structure": _get_template_structure(version),
483
+ }
484
+ for version in prompt_history
485
+ ],
486
+ "downloaded_at": datetime.now().isoformat(),
487
+ "related_to_experiment": experiment.name or experiment.id,
488
+ }
489
+
490
+ # Save prompt data using the appropriate format
491
+ # Sanitize prompt name for filename (replace / with _)
492
+ sanitized_name = prompt.name.replace("/", "_")
493
+ if format.lower() == "csv":
494
+ prompt_file = prompts_dir / f"prompts_{sanitized_name}.csv"
495
+ else:
496
+ prompt_file = prompts_dir / f"prompt_{sanitized_name}.json"
497
+
498
+ # Check if file should be skipped using the standard utility
499
+ if should_skip_file(prompt_file, force):
500
+ debug_print(
501
+ f"Skipping prompt {prompt.name} (already exists)", debug
502
+ )
503
+ continue
504
+
505
+ # File doesn't exist or force is set, so export it
506
+ if format.lower() == "csv":
507
+ write_csv_data(prompt_data, prompt_file, prompt_to_csv_rows)
508
+ else:
509
+ write_json_data(prompt_data, prompt_file)
510
+
511
+ console.print(f"[green]Exported related prompt: {prompt.name}[/green]")
512
+ exported_count += 1
513
+
514
+ except Exception as e:
515
+ if debug:
516
+ prompt_display_name = prompt.name if prompt else prompt_name
517
+ console.print(
518
+ f"[yellow]Warning: Could not export related prompt {prompt_display_name}: {e}[/yellow]"
519
+ )
520
+ continue
521
+
522
+ return exported_count
523
+
524
+ except Exception as e:
525
+ if debug:
526
+ console.print(
527
+ f"[yellow]Warning: Could not export related prompts: {e}[/yellow]"
528
+ )
529
+ return 0
530
+
531
+
532
+ @click.command(name="prompt")
533
+ @click.argument("name", type=str)
534
+ @click.option(
535
+ "--max-results",
536
+ type=int,
537
+ help="Maximum number of prompts to export. Limits the total number of prompts downloaded.",
538
+ )
539
+ @click.option(
540
+ "--path",
541
+ "-p",
542
+ type=click.Path(file_okay=False, dir_okay=True, writable=True),
543
+ default="opik_exports",
544
+ help="Directory to save exported data. Defaults to opik_exports.",
545
+ )
546
+ @click.option(
547
+ "--force",
548
+ is_flag=True,
549
+ help="Re-download items even if they already exist locally.",
550
+ )
551
+ @click.option(
552
+ "--debug",
553
+ is_flag=True,
554
+ help="Enable debug output to show detailed information about the export process.",
555
+ )
556
+ @click.option(
557
+ "--format",
558
+ type=click.Choice(["json", "csv"], case_sensitive=False),
559
+ default="json",
560
+ help="Format for exporting data. Defaults to json.",
561
+ )
562
+ @click.pass_context
563
+ def export_prompt_command(
564
+ ctx: click.Context,
565
+ name: str,
566
+ max_results: Optional[int],
567
+ path: str,
568
+ force: bool,
569
+ debug: bool,
570
+ format: str,
571
+ ) -> None:
572
+ """Export a prompt by exact name to workspace/prompts."""
573
+ # Get workspace and API key from context
574
+ workspace = ctx.obj["workspace"]
575
+ api_key = ctx.obj.get("api_key") if ctx.obj else None
576
+ export_prompt_by_name(
577
+ name, workspace, path, max_results, force, debug, format, api_key
578
+ )