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
@@ -1,10 +1,21 @@
1
- from typing import Any, Dict, List, Optional
1
+ from typing import Any, Dict, List, Optional, Tuple
2
+ import json
3
+ import dataclasses
2
4
 
5
+ import opik.exceptions
3
6
  from opik.rest_api import client as rest_client
4
7
  from opik.rest_api import core as rest_api_core
5
- from opik.rest_api.types import prompt_version_detail, PromptVersionDetailType
8
+ from opik.rest_api.types import prompt_version_detail
9
+ from . import types as prompt_types
6
10
 
7
- from .prompt import PromptType
11
+
12
+ @dataclasses.dataclass
13
+ class PromptSearchResult:
14
+ """Result from searching prompts, containing name, template structure, and latest version details."""
15
+
16
+ name: str
17
+ template_structure: str
18
+ prompt_version_detail: prompt_version_detail.PromptVersionDetail
8
19
 
9
20
 
10
21
  class PromptClient:
@@ -16,7 +27,8 @@ class PromptClient:
16
27
  name: str,
17
28
  prompt: str,
18
29
  metadata: Optional[Dict[str, Any]],
19
- type: PromptType = PromptType.MUSTACHE,
30
+ type: prompt_types.PromptType = prompt_types.PromptType.MUSTACHE,
31
+ template_structure: str = "text",
20
32
  ) -> prompt_version_detail.PromptVersionDetail:
21
33
  """
22
34
  Creates the prompt detail for the given prompt name and template.
@@ -24,20 +36,59 @@ class PromptClient:
24
36
  Parameters:
25
37
  - name: The name of the prompt.
26
38
  - prompt: The template content for the prompt.
39
+ - metadata: Optional metadata for the prompt.
40
+ - type: The template type (MUSTACHE or JINJA2).
41
+ - template_structure: Either "text" (default) or "chat".
27
42
 
28
43
  Returns:
29
44
  - A Prompt object for the provided prompt name and template.
45
+
46
+ Raises:
47
+ - PromptTemplateStructureMismatch: If a prompt with the same name already exists but has a different
48
+ template_structure (e.g., trying to create a text prompt when a chat prompt exists, or vice versa).
49
+ Template structure is immutable after prompt creation.
30
50
  """
31
51
  prompt_version = self._get_latest_version(name)
32
52
 
53
+ # For chat prompts, compare parsed JSON to avoid formatting differences
54
+ templates_equal = False
55
+
56
+ if prompt_version is not None:
57
+ if prompt_version.template_structure != template_structure:
58
+ raise opik.exceptions.PromptTemplateStructureMismatch(
59
+ prompt_name=name,
60
+ existing_structure=prompt_version.template_structure,
61
+ attempted_structure=template_structure,
62
+ )
63
+
64
+ if template_structure == "chat":
65
+ try:
66
+ existing_messages = json.loads(prompt_version.template)
67
+ new_messages = json.loads(prompt)
68
+ templates_equal = existing_messages == new_messages
69
+ except (json.JSONDecodeError, TypeError):
70
+ templates_equal = prompt_version.template == prompt
71
+ else:
72
+ templates_equal = prompt_version.template == prompt
73
+
74
+ # Create a new version if:
75
+ # - No version exists yet (new prompt)
76
+ # - Template content has changed
77
+ # - Metadata has changed
78
+ # - Type has changed
79
+ # Note: template_structure is immutable and used by the backend only if it is the first prompt version.
33
80
  if (
34
81
  prompt_version is None
35
- or prompt_version.template != prompt
82
+ or not templates_equal
36
83
  or prompt_version.metadata != metadata
37
84
  or prompt_version.type != type.value
38
85
  ):
39
86
  prompt_version = self._create_new_version(
40
- name=name, prompt=prompt, type=type, metadata=metadata
87
+ name=name,
88
+ prompt=prompt,
89
+ type=type,
90
+ metadata=metadata,
91
+ template_structure=template_structure,
41
92
  )
42
93
 
43
94
  return prompt_version
@@ -46,8 +97,9 @@ class PromptClient:
46
97
  self,
47
98
  name: str,
48
99
  prompt: str,
49
- type: PromptVersionDetailType,
100
+ type: prompt_version_detail.PromptVersionDetailType,
50
101
  metadata: Optional[Dict[str, Any]],
102
+ template_structure: str = "text",
51
103
  ) -> prompt_version_detail.PromptVersionDetail:
52
104
  new_prompt_version_detail_data = prompt_version_detail.PromptVersionDetail(
53
105
  template=prompt,
@@ -58,6 +110,7 @@ class PromptClient:
58
110
  self._rest_client.prompts.create_prompt_version(
59
111
  name=name,
60
112
  version=new_prompt_version_detail_data,
113
+ template_structure=template_structure,
61
114
  )
62
115
  )
63
116
  return new_prompt_version_detail
@@ -65,20 +118,13 @@ class PromptClient:
65
118
  def _get_latest_version(
66
119
  self, name: str
67
120
  ) -> Optional[prompt_version_detail.PromptVersionDetail]:
68
- try:
69
- prompt_latest_version = self._rest_client.prompts.retrieve_prompt_version(
70
- name=name
71
- )
72
- return prompt_latest_version
73
- except rest_api_core.ApiError as e:
74
- if e.status_code != 404:
75
- raise e
76
- return None
121
+ return self.get_prompt(name=name, commit=None)
77
122
 
78
123
  def get_prompt(
79
124
  self,
80
125
  name: str,
81
126
  commit: Optional[str] = None,
127
+ raise_if_not_template_structure: Optional[str] = None,
82
128
  ) -> Optional[prompt_version_detail.PromptVersionDetail]:
83
129
  """
84
130
  Retrieve the prompt detail for a given prompt name and commit version.
@@ -86,6 +132,7 @@ class PromptClient:
86
132
  Parameters:
87
133
  name: The name of the prompt.
88
134
  commit: An optional commit version of the prompt. If not provided, the latest version is retrieved.
135
+ raise_if_not_template_structure: Optional template structure validation. If provided and doesn't match, raises PromptTemplateStructureMismatch.
89
136
 
90
137
  Returns:
91
138
  Prompt: The details of the specified prompt.
@@ -95,17 +142,35 @@ class PromptClient:
95
142
  name=name,
96
143
  commit=commit,
97
144
  )
98
- return prompt_version
99
145
 
146
+ should_skip_validation = (
147
+ prompt_version.template_structure is None
148
+ and raise_if_not_template_structure == "text"
149
+ )
150
+ if should_skip_validation:
151
+ return prompt_version
152
+
153
+ # Client-side validation for template_structure if requested and not skipped
154
+ if (
155
+ raise_if_not_template_structure is not None
156
+ and prompt_version.template_structure != raise_if_not_template_structure
157
+ ):
158
+ raise opik.exceptions.PromptTemplateStructureMismatch(
159
+ prompt_name=name,
160
+ existing_structure=prompt_version.template_structure,
161
+ attempted_structure=raise_if_not_template_structure,
162
+ )
163
+
164
+ return prompt_version
100
165
  except rest_api_core.ApiError as e:
101
166
  if e.status_code != 404:
102
167
  raise e
103
-
168
+ # 400, 404 - not found
104
169
  return None
105
170
 
106
171
  # TODO: Need to add support for prompt name in the BE so we don't
107
172
  # need to retrieve the prompt id
108
- def get_all_prompts(
173
+ def get_all_prompt_versions(
109
174
  self, name: str
110
175
  ) -> List[prompt_version_detail.PromptVersionDetail]:
111
176
  """
@@ -134,42 +199,119 @@ class PromptClient:
134
199
  raise ValueError("No prompts found for name: " + name)
135
200
 
136
201
  prompt_id = filtered_prompt_list[0]
202
+ return self._get_prompt_versions_by_id_paginated(prompt_id)
137
203
 
138
- page = 1
139
- size = 100
204
+ except rest_api_core.ApiError as e:
205
+ if e.status_code != 404:
206
+ raise e
207
+
208
+ return []
209
+
210
+ def _get_prompt_versions_by_id_paginated(
211
+ self, prompt_id: str
212
+ ) -> List[prompt_version_detail.PromptVersionDetail]:
213
+ page = 1
214
+ size = 100
215
+ prompts: List[prompt_version_detail.PromptVersionDetail] = []
216
+ while True:
217
+ prompt_versions_page = self._rest_client.prompts.get_prompt_versions(
218
+ id=prompt_id, page=page, size=size
219
+ ).content
220
+
221
+ versions = prompt_versions_page or []
222
+ prompts.extend(
223
+ [
224
+ # Converting to PromptVersionDetail for consistency with other methods.
225
+ # TODO: backend should implement non-frontend endpoint which will return PromptVersionDetail objects
226
+ prompt_version_detail.PromptVersionDetail(
227
+ id=version.id,
228
+ prompt_id=version.prompt_id,
229
+ template=version.template,
230
+ type=version.type,
231
+ metadata=version.metadata,
232
+ commit=version.commit,
233
+ created_at=version.created_at,
234
+ created_by=version.created_by,
235
+ )
236
+ for version in versions
237
+ ]
238
+ )
239
+
240
+ if len(versions) < size:
241
+ break
242
+ page += 1
243
+
244
+ return prompts
245
+
246
+ def search_prompts(
247
+ self,
248
+ *,
249
+ name: Optional[str] = None,
250
+ parsed_filters: Optional[List[Dict[str, Any]]] = None,
251
+ ) -> List[PromptSearchResult]:
252
+ """
253
+ Search prompt containers by optional name substring and filters, then
254
+ return the latest version detail for each matched prompt container.
255
+
256
+ Parameters:
257
+ name: Optional substring of the prompt name to search for.
258
+ parsed_filters: List of parsed filters (OQL) that will be stringified for the backend.
259
+
260
+ Returns:
261
+ List[PromptSearchResult]: Each result contains name, template_structure, and prompt_version_detail.
262
+ """
263
+ try:
264
+ filters_str = (
265
+ json.dumps(parsed_filters) if parsed_filters is not None else None
266
+ )
140
267
 
141
- prompts: List[prompt_version_detail.PromptVersionDetail] = []
268
+ # Page through all prompt containers and collect name + template_structure
269
+ page = 1
270
+ size = 1000
271
+ prompt_info: List[Tuple[str, str]] = [] # (name, template_structure)
142
272
  while True:
143
- prompt_versions = self._rest_client.prompts.get_prompt_versions(
144
- id=prompt_id, page=page, size=size
145
- ).content
146
-
147
- prompts.extend(
148
- [
149
- # Converting to PromptVersionDetail for consistency with other methods.
150
- # TODO: backend should implement non-frontend endpoint which will return PromptVersionDetail objects
151
- prompt_version_detail.PromptVersionDetail(
152
- id=version.id,
153
- prompt_id=version.prompt_id,
154
- template=version.template,
155
- type=version.type,
156
- metadata=version.metadata,
157
- commit=version.commit,
158
- created_at=version.created_at,
159
- created_by=version.created_by,
160
- )
161
- for version in prompt_versions
162
- ]
273
+ prompts_page = self._rest_client.prompts.get_prompts(
274
+ page=page,
275
+ size=size,
276
+ name=name,
277
+ filters=filters_str,
163
278
  )
164
-
165
- if len(prompt_versions) < size:
279
+ content = prompts_page.content or []
280
+ if len(content) == 0:
281
+ break
282
+ prompt_info.extend(
283
+ [(p.name, p.template_structure or "text") for p in content]
284
+ )
285
+ if len(content) < size:
166
286
  break
167
287
  page += 1
168
288
 
169
- return prompts
289
+ if len(prompt_info) == 0:
290
+ return []
291
+
292
+ # Retrieve latest version for each container name
293
+ results: List[PromptSearchResult] = []
294
+ for prompt_name, template_structure in prompt_info:
295
+ try:
296
+ latest_version = self._rest_client.prompts.retrieve_prompt_version(
297
+ name=prompt_name,
298
+ )
299
+ results.append(
300
+ PromptSearchResult(
301
+ name=prompt_name,
302
+ template_structure=template_structure,
303
+ prompt_version_detail=latest_version,
304
+ )
305
+ )
306
+ except rest_api_core.ApiError as e:
307
+ # Skip prompts that can't be retrieved (e.g., deleted between search and retrieval)
308
+ if e.status_code == 404:
309
+ continue
310
+ raise e
311
+
312
+ return results
170
313
 
171
314
  except rest_api_core.ApiError as e:
172
315
  if e.status_code != 404:
173
316
  raise e
174
-
175
- return []
317
+ return []
@@ -0,0 +1 @@
1
+ # Empty - all exports handled by parent __init__.py
@@ -0,0 +1,174 @@
1
+ import copy
2
+ import json
3
+ import logging
4
+ from typing import Any, Dict, Optional, Union, List
5
+ from typing_extensions import override
6
+ from opik.rest_api import types as rest_api_types
7
+ from . import prompt_template
8
+ from .. import types as prompt_types
9
+ from .. import client as prompt_client
10
+ from .. import base_prompt
11
+
12
+ LOGGER = logging.getLogger(__name__)
13
+
14
+
15
+ class Prompt(base_prompt.BasePrompt):
16
+ """
17
+ Prompt class represents a prompt with a name, prompt text/template and commit hash.
18
+ """
19
+
20
+ def __init__(
21
+ self,
22
+ name: str,
23
+ prompt: str,
24
+ metadata: Optional[Dict[str, Any]] = None,
25
+ type: prompt_types.PromptType = prompt_types.PromptType.MUSTACHE,
26
+ validate_placeholders: bool = True,
27
+ ) -> None:
28
+ """
29
+ Initializes a new instance of the class with the given parameters.
30
+ Creates a new text prompt using the opik client and sets the initial state of the instance attributes based on the created prompt.
31
+
32
+ Parameters:
33
+ name: The name for the prompt.
34
+ prompt: The template for the prompt.
35
+ metadata: Optional metadata for the prompt.
36
+ type: The template type (MUSTACHE or JINJA2).
37
+ validate_placeholders: Whether to validate template placeholders.
38
+
39
+ Raises:
40
+ PromptTemplateStructureMismatch: If a chat prompt with the same name already exists (template structure is immutable).
41
+ """
42
+
43
+ self._template = prompt_template.PromptTemplate(
44
+ template=prompt, type=type, validate_placeholders=validate_placeholders
45
+ )
46
+ self._name = name
47
+ self._metadata = metadata
48
+ self._type = type
49
+
50
+ self._sync_with_backend()
51
+
52
+ def _sync_with_backend(self) -> None:
53
+ from opik.api_objects import opik_client
54
+
55
+ opik_client_ = opik_client.get_client_cached()
56
+ prompt_client_ = prompt_client.PromptClient(opik_client_.rest_client)
57
+ prompt_version = prompt_client_.create_prompt(
58
+ name=self._name,
59
+ prompt=self._template.text,
60
+ metadata=self._metadata,
61
+ type=self._type,
62
+ )
63
+
64
+ self._commit = prompt_version.commit
65
+ self.__internal_api__prompt_id__ = prompt_version.prompt_id
66
+ self.__internal_api__version_id__ = prompt_version.id
67
+
68
+ @property
69
+ @override
70
+ def name(self) -> str:
71
+ """The name of the prompt."""
72
+ return self._name
73
+
74
+ @property
75
+ def prompt(self) -> str:
76
+ """The latest template of the prompt."""
77
+ return str(self._template)
78
+
79
+ @property
80
+ @override
81
+ def commit(self) -> Optional[str]:
82
+ """The commit hash of the prompt."""
83
+ return self._commit
84
+
85
+ @property
86
+ @override
87
+ def metadata(self) -> Optional[Dict[str, Any]]:
88
+ """The metadata dictionary associated with the prompt"""
89
+ return copy.deepcopy(self._metadata)
90
+
91
+ @property
92
+ @override
93
+ def type(self) -> prompt_types.PromptType:
94
+ """The prompt type of the prompt."""
95
+ return self._type
96
+
97
+ @override
98
+ def format(self, **kwargs: Any) -> Union[str, List[Dict[str, Any]]]:
99
+ """
100
+ Replaces placeholders in the template with provided keyword arguments.
101
+
102
+ Args:
103
+ **kwargs: Arbitrary keyword arguments where the key represents the placeholder
104
+ in the template and the value is the value to replace the placeholder with.
105
+
106
+ Returns:
107
+ A string with all placeholders replaced by their corresponding values from kwargs.
108
+ """
109
+ is_playground_chat_prompt = (
110
+ self._metadata is not None
111
+ and self._metadata.get("created_from") == "opik_ui"
112
+ and self._metadata.get("type") == "messages_json"
113
+ )
114
+ formatted_string = self._template.format(**kwargs)
115
+
116
+ if is_playground_chat_prompt:
117
+ try:
118
+ return json.loads(formatted_string)
119
+ except json.JSONDecodeError:
120
+ LOGGER.error(
121
+ f"Failed to parse JSON string: {formatted_string}. Make sure chat prompt is valid JSON. Returning the raw string."
122
+ )
123
+ return formatted_string
124
+
125
+ return formatted_string
126
+
127
+ @override
128
+ def __internal_api__to_info_dict__(self) -> Dict[str, Any]:
129
+ """
130
+ Convert the prompt to an info dictionary for serialization.
131
+
132
+ Returns:
133
+ Dictionary containing prompt metadata and version information.
134
+ """
135
+ info_dict: Dict[str, Any] = {
136
+ "name": self.name,
137
+ "version": {
138
+ "template": self.prompt,
139
+ },
140
+ }
141
+
142
+ if self.__internal_api__prompt_id__ is not None:
143
+ info_dict["id"] = self.__internal_api__prompt_id__
144
+
145
+ if self.commit is not None:
146
+ info_dict["version"]["commit"] = self.commit
147
+
148
+ if self.__internal_api__version_id__ is not None:
149
+ info_dict["version"]["id"] = self.__internal_api__version_id__
150
+
151
+ return info_dict
152
+
153
+ @classmethod
154
+ def from_fern_prompt_version(
155
+ cls,
156
+ name: str,
157
+ prompt_version: rest_api_types.PromptVersionDetail,
158
+ ) -> "Prompt":
159
+ # will not call __init__ to avoid API calls, create new instance with __new__
160
+ prompt = cls.__new__(cls)
161
+
162
+ prompt.__internal_api__version_id__ = prompt_version.id
163
+ prompt.__internal_api__prompt_id__ = prompt_version.prompt_id
164
+
165
+ prompt._name = name
166
+ prompt._template = prompt_template.PromptTemplate(
167
+ template=prompt_version.template,
168
+ type=prompt_types.PromptType(prompt_version.type)
169
+ or prompt_types.PromptType.MUSTACHE,
170
+ )
171
+ prompt._commit = prompt_version.commit
172
+ prompt._metadata = prompt_version.metadata
173
+ prompt._type = prompt_version.type
174
+ return prompt
@@ -1,17 +1,19 @@
1
1
  import re
2
2
  from typing import Any, Set
3
+ from typing_extensions import override
3
4
  import jinja2
4
5
 
5
6
  import opik.exceptions as exceptions
6
- from .types import PromptType
7
+ from .. import types as prompt_types
8
+ from .. import base_prompt_template
7
9
 
8
10
 
9
- class PromptTemplate:
11
+ class PromptTemplate(base_prompt_template.BasePromptTemplate):
10
12
  def __init__(
11
13
  self,
12
14
  template: str,
13
15
  validate_placeholders: bool = True,
14
- type: PromptType = PromptType.MUSTACHE,
16
+ type: prompt_types.PromptType = prompt_types.PromptType.MUSTACHE,
15
17
  ) -> None:
16
18
  self._template = template
17
19
  self._type = type
@@ -21,8 +23,9 @@ class PromptTemplate:
21
23
  def text(self) -> str:
22
24
  return self._template
23
25
 
26
+ @override
24
27
  def format(self, **kwargs: Any) -> str:
25
- if self._type == PromptType.MUSTACHE:
28
+ if self._type == prompt_types.PromptType.MUSTACHE:
26
29
  template = self._template
27
30
  placeholders = _extract_mustache_placeholder_keys(self._template)
28
31
  kwargs_keys: Set[str] = set(kwargs.keys())
@@ -33,9 +36,10 @@ class PromptTemplate:
33
36
  )
34
37
 
35
38
  for key, value in kwargs.items():
36
- template = template.replace(f"{{{{{key}}}}}", str(value))
39
+ replacement = "" if value is None else str(value)
40
+ template = template.replace(f"{{{{{key}}}}}", replacement)
37
41
 
38
- elif self._type == PromptType.JINJA2:
42
+ elif self._type == prompt_types.PromptType.JINJA2:
39
43
  template = jinja2.Template(self._template).render(**kwargs)
40
44
  else:
41
45
  template = self._template
@@ -1,6 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Dict, List, Literal, Mapping, Optional, Union, Set
4
+
1
5
  import enum
2
6
 
3
7
 
4
8
  class PromptType(str, enum.Enum):
5
9
  MUSTACHE = "mustache"
6
10
  JINJA2 = "jinja2"
11
+
12
+
13
+ # Core multimodal/chat prompt related types
14
+ MessageContent = Union[str, List[Dict[str, Any]]]
15
+ ContentPart = Dict[str, Any]
16
+ RendererFn = Callable[[ContentPart, Dict[str, Any], PromptType], Optional[ContentPart]]
17
+ ModalityName = Literal["vision", "video"]
18
+ SupportedModalities = Mapping[ModalityName, bool]
19
+ ModalitySet = Set[ModalityName]
20
+
21
+ __all__ = [
22
+ "PromptType",
23
+ "MessageContent",
24
+ "ContentPart",
25
+ "RendererFn",
26
+ "ModalityName",
27
+ "SupportedModalities",
28
+ "ModalitySet",
29
+ ]
@@ -0,0 +1,89 @@
1
+ from typing import Optional, List, Callable, Any
2
+
3
+ from opik import synchronization
4
+ from opik.api_objects import rest_stream_parser
5
+ from opik.api_objects.helpers import OptionalFilterParsedItemList
6
+ from opik.rest_api import client as rest_api_client
7
+ from opik.rest_api.types import span_public, trace_public
8
+
9
+
10
+ def search_spans_with_filters(
11
+ rest_client: rest_api_client.OpikApi,
12
+ trace_id: Optional[str],
13
+ project_name: str,
14
+ filters: Optional[OptionalFilterParsedItemList],
15
+ max_results: int,
16
+ truncate: bool,
17
+ ) -> List[span_public.SpanPublic]:
18
+ spans = rest_stream_parser.read_and_parse_full_stream(
19
+ read_source=lambda current_batch_size,
20
+ last_retrieved_id: rest_client.spans.search_spans(
21
+ trace_id=trace_id,
22
+ project_name=project_name,
23
+ filters=filters,
24
+ limit=current_batch_size,
25
+ truncate=truncate,
26
+ last_retrieved_id=last_retrieved_id,
27
+ ),
28
+ max_results=max_results,
29
+ parsed_item_class=span_public.SpanPublic,
30
+ )
31
+
32
+ return spans
33
+
34
+
35
+ def search_traces_with_filters(
36
+ rest_client: rest_api_client.OpikApi,
37
+ project_name: Optional[str],
38
+ filters: Optional[OptionalFilterParsedItemList],
39
+ max_results: int,
40
+ truncate: bool,
41
+ ) -> List[trace_public.TracePublic]:
42
+ traces = rest_stream_parser.read_and_parse_full_stream(
43
+ read_source=lambda current_batch_size,
44
+ last_retrieved_id: rest_client.traces.search_traces(
45
+ project_name=project_name,
46
+ filters=filters,
47
+ limit=current_batch_size,
48
+ truncate=truncate,
49
+ last_retrieved_id=last_retrieved_id,
50
+ ),
51
+ max_results=max_results,
52
+ parsed_item_class=trace_public.TracePublic,
53
+ )
54
+ return traces
55
+
56
+
57
+ def search_and_wait_for_done(
58
+ search_functor: Callable[[], List[Any]],
59
+ wait_for_at_least: int,
60
+ wait_for_timeout: int,
61
+ sleep_time: float,
62
+ ) -> List[Any]:
63
+ """
64
+ The expected behavior is to keep making repeated calls until either the specified number of
65
+ results is found or the timeout is reached. The function will then return the best possible
66
+ attempt results to meet these conditions.
67
+ Args:
68
+ search_functor: The function to call to retrieve the results.
69
+ wait_for_at_least: The minimum number of results to return.
70
+ wait_for_timeout: The timeout for waiting for results.
71
+ sleep_time: The time to sleep between calls to search_functor.
72
+
73
+ Returns:
74
+ The function returns the results of the best possible attempt to meet both waiting conditions.
75
+ """
76
+ result: List[Any] = []
77
+
78
+ def search() -> List[Any]:
79
+ nonlocal result
80
+ result = search_functor()
81
+ return result
82
+
83
+ synchronization.wait_for_done(
84
+ check_function=lambda: len(search()) >= wait_for_at_least,
85
+ timeout=wait_for_timeout,
86
+ sleep_time=sleep_time,
87
+ )
88
+
89
+ return result