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
@@ -2,11 +2,32 @@ import atexit
2
2
  import datetime
3
3
  import functools
4
4
  import logging
5
- from typing import Any, Dict, List, Optional, TypeVar, Union, Literal
5
+ from typing import Any, Dict, List, Optional, TypeVar, Union, Literal, cast
6
6
 
7
7
  import httpx
8
8
 
9
+ from . import (
10
+ constants,
11
+ dataset,
12
+ experiment,
13
+ optimization,
14
+ helpers,
15
+ opik_query_language,
16
+ search_helpers,
17
+ span,
18
+ trace,
19
+ )
20
+ from .attachment import Attachment
21
+ from .attachment import client as attachment_client
22
+ from .attachment import converters as attachment_converters
23
+ from .dataset import rest_operations as dataset_rest_operations
24
+ from .experiment import experiments_client
25
+ from .experiment import helpers as experiment_helpers
26
+ from .experiment import rest_operations as experiment_rest_operations
27
+ from . import prompt as prompt_module
28
+ from .prompt import client as prompt_client
9
29
  from .threads import threads_client
30
+ from .trace import migration as trace_migration, trace_client
10
31
  from .. import (
11
32
  config,
12
33
  datetime_helpers,
@@ -17,8 +38,13 @@ from .. import (
17
38
  rest_client_configurator,
18
39
  url_helpers,
19
40
  )
20
- from ..message_processing import messages, streamer_constructors, message_queue
41
+ from ..message_processing import (
42
+ messages,
43
+ streamer_constructors,
44
+ message_queue,
45
+ )
21
46
  from ..message_processing.batching import sequence_splitter
47
+ from ..message_processing.processors import message_processors_chain
22
48
  from ..rest_api import client as rest_api_client
23
49
  from ..rest_api.core.api_error import ApiError
24
50
  from ..rest_api.types import (
@@ -29,26 +55,13 @@ from ..rest_api.types import (
29
55
  span_filter_public,
30
56
  trace_filter_public,
31
57
  )
32
- from ..types import ErrorInfoDict, FeedbackScoreDict, LLMProvider, SpanType
33
- from . import (
34
- constants,
35
- dataset,
36
- experiment,
37
- optimization,
38
- helpers,
39
- span,
40
- trace,
58
+ from ..types import (
59
+ BatchFeedbackScoreDict,
60
+ ErrorInfoDict,
61
+ FeedbackScoreDict,
62
+ LLMProvider,
63
+ SpanType,
41
64
  )
42
- from .attachment import converters as attachment_converters
43
- from .attachment import Attachment
44
- from .attachment import client as attachment_client
45
- from . import rest_stream_parser
46
- from .dataset import rest_operations as dataset_rest_operations
47
- from .experiment import helpers as experiment_helpers
48
- from .experiment import rest_operations as experiment_rest_operations
49
- from .prompt import Prompt, PromptType
50
- from .prompt.client import PromptClient
51
- from .trace import migration as trace_migration
52
65
 
53
66
  LOGGER = logging.getLogger(__name__)
54
67
 
@@ -100,13 +113,7 @@ class Opik:
100
113
  self._use_batching = _use_batching
101
114
 
102
115
  self._initialize_streamer(
103
- url_override=config_.url_override,
104
- workers=config_.background_workers,
105
- file_upload_worker_count=config_.file_upload_background_workers,
106
- api_key=config_.api_key,
107
- check_tls_certificate=config_.check_tls_certificate,
108
116
  use_batching=_use_batching,
109
- enable_json_request_compression=config_.enable_json_request_compression,
110
117
  )
111
118
  atexit.register(self.end, timeout=self._flush_timeout)
112
119
 
@@ -145,24 +152,17 @@ class Opik:
145
152
 
146
153
  def _initialize_streamer(
147
154
  self,
148
- url_override: str,
149
- workers: int,
150
- file_upload_worker_count: int,
151
- api_key: Optional[str],
152
- check_tls_certificate: bool,
153
155
  use_batching: bool,
154
- enable_json_request_compression: bool,
155
156
  ) -> None:
156
- httpx_client_ = httpx_client.get(
157
+ self._httpx_client = httpx_client.get(
157
158
  workspace=self._workspace,
158
- api_key=api_key,
159
- check_tls_certificate=check_tls_certificate,
160
- compress_json_requests=enable_json_request_compression,
159
+ api_key=self._config.api_key,
160
+ check_tls_certificate=self._config.check_tls_certificate,
161
+ compress_json_requests=self._config.enable_json_request_compression,
161
162
  )
162
- self._httpx_client = httpx_client_
163
163
  self._rest_client = rest_api_client.OpikApi(
164
- base_url=url_override,
165
- httpx_client=httpx_client_,
164
+ base_url=self._config.url_override,
165
+ httpx_client=self._httpx_client,
166
166
  )
167
167
  self._rest_client._client_wrapper._timeout = (
168
168
  httpx.USE_CLIENT_DEFAULT
@@ -174,13 +174,22 @@ class Opik:
174
174
  batch_factor=self._config.maximal_queue_size_batch_factor,
175
175
  )
176
176
 
177
+ self.__internal_api__message_processor__ = (
178
+ message_processors_chain.create_message_processors_chain(
179
+ rest_client=self._rest_client
180
+ )
181
+ )
177
182
  self._streamer = streamer_constructors.construct_online_streamer(
178
- n_consumers=workers,
183
+ n_consumers=self._config.background_workers,
179
184
  rest_client=self._rest_client,
180
- httpx_client=httpx_client_,
185
+ httpx_client=self._httpx_client,
181
186
  use_batching=use_batching,
182
- file_upload_worker_count=file_upload_worker_count,
187
+ use_attachment_extraction=self._config.is_attachment_extraction_active,
188
+ min_base64_embedded_attachment_size=self._config.min_base64_embedded_attachment_size,
189
+ file_upload_worker_count=self._config.file_upload_background_workers,
183
190
  max_queue_size=max_queue_size,
191
+ message_processor=self.__internal_api__message_processor__,
192
+ url_override=self._config.url_override,
184
193
  )
185
194
 
186
195
  def _display_trace_url(self, trace_id: str, project_name: str) -> None:
@@ -282,7 +291,9 @@ class Opik:
282
291
  for feedback_score in feedback_scores:
283
292
  feedback_score["id"] = id
284
293
 
285
- self.log_traces_feedback_scores(feedback_scores, project_name)
294
+ self.log_traces_feedback_scores(
295
+ cast(List[BatchFeedbackScoreDict], feedback_scores), project_name
296
+ )
286
297
 
287
298
  if attachments is not None:
288
299
  for attachment_data in attachments:
@@ -457,7 +468,9 @@ class Opik:
457
468
  for feedback_score in feedback_scores:
458
469
  feedback_score["id"] = id
459
470
 
460
- self.log_spans_feedback_scores(feedback_scores, project_name)
471
+ self.log_spans_feedback_scores(
472
+ cast(List[BatchFeedbackScoreDict], feedback_scores), project_name
473
+ )
461
474
 
462
475
  return span.span_client.create_span(
463
476
  trace_id=trace_id,
@@ -563,24 +576,97 @@ class Opik:
563
576
  attachments=attachments,
564
577
  )
565
578
 
579
+ def update_trace(
580
+ self,
581
+ trace_id: str,
582
+ project_name: str,
583
+ end_time: Optional[datetime.datetime] = None,
584
+ metadata: Optional[Dict[str, Any]] = None,
585
+ input: Optional[Dict[str, Any]] = None,
586
+ output: Optional[Dict[str, Any]] = None,
587
+ tags: Optional[List[Any]] = None,
588
+ error_info: Optional[ErrorInfoDict] = None,
589
+ thread_id: Optional[str] = None,
590
+ ) -> None:
591
+ """
592
+ Update the trace attributes.
593
+
594
+ This method should only be used after the trace has been fully created and stored.
595
+ If called before or immediately after trace creation, the update may silently fail or result in incorrect data.
596
+
597
+ This method uses two parameters to identify the trace:
598
+ - `trace_id`
599
+ - `project_name`
600
+
601
+ These parameters **must match exactly** the values used when the trace was created.
602
+ If any of them are incorrect, the update may not apply and no error will be raised.
603
+
604
+ All other parameters are optional and will update the corresponding fields in the trace.
605
+ If a parameter is not provided, the existing value will remain unchanged.
606
+
607
+ Args:
608
+ trace_id: The unique identifier for the trace.
609
+ project_name: The project name to which the trace belongs.
610
+ end_time: The end time of the trace.
611
+ metadata: Additional metadata to be associated with the trace.
612
+ input: The input data for the trace.
613
+ output: The output data for the trace.
614
+ tags: A list of tags to be associated with the trace.
615
+ error_info: The dictionary with error information (typically used when the trace function has failed).
616
+ thread_id: Used to group multiple traces into a thread.
617
+ The identifier is user-defined and has to be unique per project.
618
+
619
+ Returns:
620
+ None
621
+ """
622
+ if not trace_id or not project_name:
623
+ raise ValueError(
624
+ "trace_id and project_name must be provided and can not be None or empty, "
625
+ f"trace_id: {trace_id}, project_name: {project_name}"
626
+ )
627
+
628
+ trace_client.update_trace(
629
+ trace_id=trace_id,
630
+ project_name=project_name,
631
+ message_streamer=self._streamer,
632
+ end_time=end_time,
633
+ metadata=metadata,
634
+ input=input,
635
+ output=output,
636
+ tags=tags,
637
+ error_info=error_info,
638
+ thread_id=thread_id,
639
+ )
640
+
566
641
  def log_spans_feedback_scores(
567
- self, scores: List[FeedbackScoreDict], project_name: Optional[str] = None
642
+ self, scores: List[BatchFeedbackScoreDict], project_name: Optional[str] = None
568
643
  ) -> None:
569
644
  """
570
645
  Log feedback scores for spans.
571
646
 
572
647
  Args:
573
- scores (List[FeedbackScoreDict]): A list of feedback score dictionaries.
648
+ scores (List[BatchFeedbackScoreDict]): A list of feedback score dictionaries.
574
649
  Specifying a span id via `id` key for each score is mandatory.
575
650
  project_name: The name of the project in which the spans are logged. If not set, the project name
576
651
  which was configured when the Opik instance was created will be used.
652
+ Deprecated: use `project_name` in the feedback score dictionary that's listed in the `scores` parameter.
577
653
 
578
654
  Returns:
579
655
  None
656
+
657
+ Example:
658
+ >>> from opik import Opik
659
+ >>> client = Opik()
660
+ >>> # Batch logging across multiple projects
661
+ >>> scores = [
662
+ >>> {"id": span1_id, "name": "accuracy", "value": 0.95, "project_name": "project-A"},
663
+ >>> {"id": span2_id, "name": "accuracy", "value": 0.88, "project_name": "project-B"},
664
+ >>> ]
665
+ >>> client.log_spans_feedback_scores(scores=scores)
580
666
  """
581
667
  score_messages = helpers.parse_feedback_score_messages(
582
668
  scores=scores,
583
- project_name=project_name or self._project_name,
669
+ project_name=project_name or self.project_name,
584
670
  parsed_item_class=messages.FeedbackScoreMessage,
585
671
  logger=LOGGER,
586
672
  )
@@ -602,23 +688,34 @@ class Opik:
602
688
  self._streamer.put(add_span_feedback_scores_batch_message)
603
689
 
604
690
  def log_traces_feedback_scores(
605
- self, scores: List[FeedbackScoreDict], project_name: Optional[str] = None
691
+ self, scores: List[BatchFeedbackScoreDict], project_name: Optional[str] = None
606
692
  ) -> None:
607
693
  """
608
694
  Log feedback scores for traces.
609
695
 
610
696
  Args:
611
- scores (List[FeedbackScoreDict]): A list of feedback score dictionaries.
697
+ scores (List[BatchFeedbackScoreDict]): A list of feedback score dictionaries.
612
698
  Specifying a trace id via `id` key for each score is mandatory.
613
699
  project_name: The name of the project in which the traces are logged. If not set, the project name
614
700
  which was configured when the Opik instance was created will be used.
701
+ Deprecated: use `project_name` in the feedback score dictionary that's listed in the `scores` parameter.
615
702
 
616
703
  Returns:
617
704
  None
705
+
706
+ Example:
707
+ >>> from opik import Opik
708
+ >>> client = Opik()
709
+ >>> # Batch logging across multiple projects
710
+ >>> scores = [
711
+ >>> {"id": trace1_id, "name": "accuracy", "value": 0.95, "project_name": "project-A"},
712
+ >>> {"id": trace2_id, "name": "accuracy", "value": 0.88, "project_name": "project-B"},
713
+ >>> ]
714
+ >>> client.log_traces_feedback_scores(scores=scores)
618
715
  """
619
716
  score_messages = helpers.parse_feedback_score_messages(
620
717
  scores=scores,
621
- project_name=project_name or self._project_name,
718
+ project_name=project_name or self.project_name,
622
719
  parsed_item_class=messages.FeedbackScoreMessage,
623
720
  logger=LOGGER,
624
721
  )
@@ -640,6 +737,36 @@ class Opik:
640
737
 
641
738
  self._streamer.put(add_trace_feedback_scores_batch_message)
642
739
 
740
+ def log_threads_feedback_scores(
741
+ self, scores: List[BatchFeedbackScoreDict], project_name: Optional[str] = None
742
+ ) -> None:
743
+ """
744
+ Log feedback scores for threads.
745
+
746
+ Args:
747
+ scores (List[BatchFeedbackScoreDict]): A list of feedback score dictionaries.
748
+ Specifying a thread id via `id` key for each score is mandatory.
749
+ project_name: The name of the project in which the threads are logged. If not set, the project name
750
+ which was configured when the Opik instance was created will be used.
751
+ Deprecated: use `project_name` in the feedback score dictionary that's listed in the `scores` parameter.
752
+
753
+ Returns:
754
+ None
755
+
756
+ Example:
757
+ >>> from opik import Opik
758
+ >>> client = Opik()
759
+ >>> # Batch logging across multiple projects
760
+ >>> scores = [
761
+ >>> {"id": "thread_123", "name": "user_satisfaction", "value": 0.85, "project_name": "project-A"},
762
+ >>> {"id": "thread_456", "name": "user_satisfaction", "value": 0.92, "project_name": "project-B"},
763
+ >>> ]
764
+ >>> client.log_threads_feedback_scores(scores=scores)
765
+ """
766
+ self.get_threads_client().log_threads_feedback_scores(
767
+ scores=scores, project_name=project_name
768
+ )
769
+
643
770
  def delete_trace_feedback_score(self, trace_id: str, name: str) -> None:
644
771
  """
645
772
  Deletes a feedback score associated with a specific trace.
@@ -740,8 +867,13 @@ class Opik:
740
867
  self._rest_client, dataset_name
741
868
  )
742
869
 
870
+ experiments_client = self.get_experiments_client()
743
871
  experiments = dataset_rest_operations.get_dataset_experiments(
744
- self._rest_client, dataset_id, max_results
872
+ rest_client=self._rest_client,
873
+ dataset_id=dataset_id,
874
+ max_results=max_results,
875
+ streamer=self._streamer,
876
+ experiments_client=experiments_client,
745
877
  )
746
878
 
747
879
  return experiments
@@ -805,8 +937,8 @@ class Opik:
805
937
  dataset_name: str,
806
938
  name: Optional[str] = None,
807
939
  experiment_config: Optional[Dict[str, Any]] = None,
808
- prompt: Optional[Prompt] = None,
809
- prompts: Optional[List[Prompt]] = None,
940
+ prompt: Optional[prompt_module.base_prompt.BasePrompt] = None,
941
+ prompts: Optional[List[prompt_module.base_prompt.BasePrompt]] = None,
810
942
  type: Literal["regular", "trial", "mini-batch"] = "regular",
811
943
  optimization_id: Optional[str] = None,
812
944
  ) -> experiment.Experiment:
@@ -853,11 +985,49 @@ class Opik:
853
985
  name=name,
854
986
  dataset_name=dataset_name,
855
987
  rest_client=self._rest_client,
988
+ streamer=self._streamer,
989
+ experiments_client=self.get_experiments_client(),
856
990
  prompts=checked_prompts,
857
991
  )
858
992
 
859
993
  return experiment_
860
994
 
995
+ def update_experiment(
996
+ self,
997
+ id: str,
998
+ name: Optional[str] = None,
999
+ experiment_config: Optional[Dict[str, Any]] = None,
1000
+ ) -> None:
1001
+ """
1002
+ Update an experiment's name and/or configuration.
1003
+
1004
+ Args:
1005
+ id: The experiment ID.
1006
+ name: The new name for the experiment. If None, the name will not be updated.
1007
+ experiment_config: The new configuration for the experiment. If None, the configuration will not be updated.
1008
+
1009
+ Raises:
1010
+ ValueError: if id is None or empty, or if both name and experiment_config are None
1011
+ """
1012
+ if not id:
1013
+ raise ValueError(
1014
+ f"id must be provided and can not be None or empty, id: {id}"
1015
+ )
1016
+
1017
+ if name is None and experiment_config is None:
1018
+ raise ValueError(
1019
+ "At least one of 'name' or 'experiment_config' must be provided"
1020
+ )
1021
+
1022
+ # Only include parameters that are provided to avoid clearing fields
1023
+ request_params: Dict[str, Any] = {}
1024
+ if name is not None:
1025
+ request_params["name"] = name
1026
+ if experiment_config is not None:
1027
+ request_params["metadata"] = experiment_config
1028
+
1029
+ self._rest_client.experiments.update_experiment(id, **request_params)
1030
+
861
1031
  def get_experiment_by_name(self, name: str) -> experiment.Experiment:
862
1032
  """
863
1033
  Returns an existing experiment by its name.
@@ -877,18 +1047,20 @@ class Opik:
877
1047
 
878
1048
  return experiment.Experiment(
879
1049
  id=experiment_public.id,
880
- name=name,
1050
+ name=experiment_public.name,
881
1051
  dataset_name=experiment_public.dataset_name,
882
1052
  rest_client=self._rest_client,
883
- # TODO: add prompt if exists
1053
+ streamer=self._streamer,
1054
+ experiments_client=self.get_experiments_client(),
884
1055
  )
885
1056
 
886
1057
  def get_experiments_by_name(self, name: str) -> List[experiment.Experiment]:
887
1058
  """
888
- Returns a list of existing experiments by its name.
1059
+ Returns a list of existing experiments containing the given string in their name.
1060
+ Search is case-insensitive.
889
1061
 
890
1062
  Args:
891
- name: The name of the experiment(s).
1063
+ name: The string to search for in the experiment names.
892
1064
 
893
1065
  Returns:
894
1066
  List[experiment.Experiment]: List of existing experiments.
@@ -901,9 +1073,11 @@ class Opik:
901
1073
  for public_experiment in experiments_public:
902
1074
  experiment_ = experiment.Experiment(
903
1075
  id=public_experiment.id,
1076
+ name=public_experiment.name,
904
1077
  dataset_name=public_experiment.dataset_name,
905
- name=name,
906
1078
  rest_client=self._rest_client,
1079
+ streamer=self._streamer,
1080
+ experiments_client=self.get_experiments_client(),
907
1081
  )
908
1082
  result.append(experiment_)
909
1083
 
@@ -935,7 +1109,8 @@ class Opik:
935
1109
  name=experiment_public.name,
936
1110
  dataset_name=experiment_public.dataset_name,
937
1111
  rest_client=self._rest_client,
938
- # TODO: add prompt if exists
1112
+ streamer=self._streamer,
1113
+ experiments_client=self.get_experiments_client(),
939
1114
  )
940
1115
 
941
1116
  def end(self, timeout: Optional[int] = None) -> None:
@@ -970,34 +1145,90 @@ class Opik:
970
1145
  filter_string: Optional[str] = None,
971
1146
  max_results: int = 1000,
972
1147
  truncate: bool = True,
1148
+ wait_for_at_least: Optional[int] = None,
1149
+ wait_for_timeout: int = httpx_client.READ_TIMEOUT_SECONDS,
973
1150
  ) -> List[trace_public.TracePublic]:
974
1151
  """
975
- Search for traces in the given project.
1152
+ Search for traces in the given project. Optionally, you can wait for at least a certain number of traces
1153
+ to be found before returning within the specified timeout. If wait_for_at_least number of traces are not found
1154
+ within the specified timeout, an exception will be raised.
976
1155
 
977
1156
  Args:
978
1157
  project_name: The name of the project to search traces in. If not provided, will search across the project name configured when the Client was created which defaults to the `Default Project`.
979
- filter_string: A filter string to narrow down the search. If not provided, all traces in the project will be returned up to the limit.
1158
+ filter_string: A filter string to narrow down the search using Opik Query Language (OQL).
1159
+ The format is: "<COLUMN> <OPERATOR> <VALUE> [AND <COLUMN> <OPERATOR> <VALUE>]*"
1160
+
1161
+ Supported columns include:
1162
+ - `id`, `name`, `created_by`, `thread_id`, `type`, `model`, `provider`: String fields with full operator support
1163
+ - `status`: String field (=, contains, not_contains only)
1164
+ - `start_time`, `end_time`: DateTime fields (use ISO 8601 format, e.g., "2024-01-01T00:00:00Z")
1165
+ - `input`, `output`: String fields for content (=, contains, not_contains only)
1166
+ - `metadata`: Dictionary field (use dot notation, e.g., "metadata.model")
1167
+ - `feedback_scores`: Numeric field (use dot notation, e.g., "feedback_scores.accuracy")
1168
+ - `tags`: List field (use "contains" operator only)
1169
+ - `usage.total_tokens`, `usage.prompt_tokens`, `usage.completion_tokens`: Numeric usage fields
1170
+ - `duration`, `number_of_messages`, `total_estimated_cost`: Numeric fields
1171
+
1172
+ Supported operators by column:
1173
+ - `id`, `name`, `created_by`, `thread_id`, `type`, `model`, `provider`: =, !=, contains, not_contains, starts_with, ends_with, >, <
1174
+ - `status`: =, contains, not_contains
1175
+ - `start_time`, `end_time`: =, >, <, >=, <=
1176
+ - `input`, `output`: =, contains, not_contains
1177
+ - `metadata`: =, contains, >, <
1178
+ - `feedback_scores`: =, >, <, >=, <=, is_empty, is_not_empty
1179
+ - `tags`: contains (only)
1180
+ - `usage.total_tokens`, `usage.prompt_tokens`, `usage.completion_tokens`, `duration`, `number_of_messages`, `total_estimated_cost`: =, !=, >, <, >=, <=
1181
+
1182
+ Examples:
1183
+ - `start_time >= "2024-01-01T00:00:00Z"` - Filter by start date
1184
+ - `start_time > "2024-01-01T00:00:00Z" AND start_time < "2024-02-01T00:00:00Z"` - Date range
1185
+ - `input contains "question"` - Filter by input content
1186
+ - `usage.total_tokens > 1000` - Filter by token usage
1187
+ - `feedback_scores.accuracy > 0.8` - Filter by feedback score
1188
+ - `feedback_scores.my_metric is_empty` - Filter traces with empty feedback score
1189
+ - `feedback_scores.my_metric is_not_empty` - Filter traces with non-empty feedback score
1190
+ - `tags contains "production"` - Filter by tag
1191
+ - `metadata.model = "gpt-4"` - Filter by metadata field
1192
+ - `thread_id = "thread_123"` - Filter by thread ID
1193
+
1194
+ If not provided, all traces in the project will be returned up to the limit.
980
1195
  max_results: The maximum number of traces to return.
981
1196
  truncate: Whether to truncate image data stored in input, output, or metadata
1197
+ wait_for_at_least: The minimum number of traces to wait for before returning.
1198
+ wait_for_timeout: The timeout for waiting for traces.
1199
+
1200
+ Raises:
1201
+ exceptions.SearchTimeoutError if wait_for_at_least traces are not found within the specified timeout.
982
1202
  """
983
1203
  filters_ = helpers.parse_filter_expressions(
984
1204
  filter_string, parsed_item_class=trace_filter_public.TraceFilterPublic
985
1205
  )
986
1206
 
987
- traces = rest_stream_parser.read_and_parse_full_stream(
988
- read_source=lambda current_batch_size,
989
- last_retrieved_id: self._rest_client.traces.search_traces(
990
- project_name=project_name or self._project_name,
991
- filters=filters_,
992
- limit=current_batch_size,
993
- truncate=truncate,
994
- last_retrieved_id=last_retrieved_id,
995
- ),
1207
+ search_functor = functools.partial(
1208
+ search_helpers.search_traces_with_filters,
1209
+ rest_client=self._rest_client,
1210
+ project_name=project_name or self._project_name,
1211
+ filters=filters_,
996
1212
  max_results=max_results,
997
- parsed_item_class=trace_public.TracePublic,
1213
+ truncate=truncate,
1214
+ )
1215
+
1216
+ if wait_for_at_least is None:
1217
+ return search_functor()
1218
+
1219
+ # do synchronization with backend if wait_for_at_least is provided until a specific number of traces are found
1220
+ result = search_helpers.search_and_wait_for_done(
1221
+ search_functor=search_functor,
1222
+ wait_for_at_least=wait_for_at_least,
1223
+ wait_for_timeout=wait_for_timeout,
1224
+ sleep_time=5,
998
1225
  )
1226
+ if len(result) < wait_for_at_least:
1227
+ raise exceptions.SearchTimeoutError(
1228
+ f"Timeout after {wait_for_timeout} seconds: expected {wait_for_at_least} traces, but only {len(result)} were found."
1229
+ )
999
1230
 
1000
- return traces
1231
+ return result
1001
1232
 
1002
1233
  def search_spans(
1003
1234
  self,
@@ -1006,37 +1237,93 @@ class Opik:
1006
1237
  filter_string: Optional[str] = None,
1007
1238
  max_results: int = 1000,
1008
1239
  truncate: bool = True,
1240
+ wait_for_at_least: Optional[int] = None,
1241
+ wait_for_timeout: int = httpx_client.READ_TIMEOUT_SECONDS,
1009
1242
  ) -> List[span_public.SpanPublic]:
1010
1243
  """
1011
1244
  Search for spans in the given trace. This allows you to search spans based on the span input, output,
1012
- metadata, tags, etc. or based on the trace ID.
1245
+ metadata, tags, etc. or based on the trace ID. Also, you can wait for at least a certain number of spans
1246
+ to be found before returning within the specified timeout. If wait_for_at_least number of spans are not found
1247
+ within the specified timeout, an exception will be raised.
1013
1248
 
1014
1249
  Args:
1015
1250
  project_name: The name of the project to search spans in. If not provided, will search across the project name configured when the Client was created which defaults to the `Default Project`.
1016
1251
  trace_id: The ID of the trace to search spans in. If provided, the search will be limited to the spans in the given trace.
1017
- filter_string: A filter string to narrow down the search.
1252
+ filter_string: A filter string to narrow down the search using Opik Query Language (OQL).
1253
+ The format is: "<COLUMN> <OPERATOR> <VALUE> [AND <COLUMN> <OPERATOR> <VALUE>]*"
1254
+
1255
+ Supported columns include:
1256
+ - `id`, `name`, `created_by`, `thread_id`, `type`, `model`, `provider`: String fields with full operator support
1257
+ - `status`: String field (=, contains, not_contains only)
1258
+ - `start_time`, `end_time`: DateTime fields (use ISO 8601 format, e.g., "2024-01-01T00:00:00Z")
1259
+ - `input`, `output`: String fields for content (=, contains, not_contains only)
1260
+ - `metadata`: Dictionary field (use dot notation, e.g., "metadata.model")
1261
+ - `feedback_scores`: Numeric field (use dot notation, e.g., "feedback_scores.accuracy")
1262
+ - `tags`: List field (use "contains" operator only)
1263
+ - `usage.total_tokens`, `usage.prompt_tokens`, `usage.completion_tokens`: Numeric usage fields
1264
+ - `duration`, `number_of_messages`, `total_estimated_cost`: Numeric fields
1265
+
1266
+ Supported operators by column:
1267
+ - `id`, `name`, `created_by`, `thread_id`, `type`, `model`, `provider`: =, !=, contains, not_contains, starts_with, ends_with, >, <
1268
+ - `status`: =, contains, not_contains
1269
+ - `start_time`, `end_time`: =, >, <, >=, <=
1270
+ - `input`, `output`: =, contains, not_contains
1271
+ - `metadata`: =, contains, >, <
1272
+ - `feedback_scores`: =, >, <, >=, <=, is_empty, is_not_empty
1273
+ - `tags`: contains (only)
1274
+ - `usage.total_tokens`, `usage.prompt_tokens`, `usage.completion_tokens`, `duration`, `number_of_messages`, `total_estimated_cost`: =, !=, >, <, >=, <=
1275
+
1276
+ Examples:
1277
+ - `start_time >= "2024-01-01T00:00:00Z"` - Filter by start date
1278
+ - `start_time > "2024-01-01T00:00:00Z" AND start_time < "2024-02-01T00:00:00Z"` - Date range
1279
+ - `input contains "question"` - Filter by input content
1280
+ - `usage.total_tokens > 1000` - Filter by token usage
1281
+ - `feedback_scores.accuracy > 0.8` - Filter by feedback score
1282
+ - `feedback_scores.my_metric is_empty` - Filter spans with empty feedback score
1283
+ - `feedback_scores.my_metric is_not_empty` - Filter spans with non-empty feedback score
1284
+ - `tags contains "production"` - Filter by tag
1285
+ - `metadata.model = "gpt-4"` - Filter by metadata field
1286
+ - `thread_id = "thread_123"` - Filter by thread ID
1287
+
1288
+ If not provided, all spans in the project/trace will be returned up to the limit.
1018
1289
  max_results: The maximum number of spans to return.
1019
1290
  truncate: Whether to truncate image data stored in input, output, or metadata
1291
+ wait_for_at_least: The minimum number of spans to wait for before returning.
1292
+ wait_for_timeout: The timeout for waiting for spans.
1293
+
1294
+ Raises:
1295
+ exceptions.SearchTimeoutError if wait_for_at_least spans are not found within the specified timeout.
1020
1296
  """
1021
1297
  filters = helpers.parse_filter_expressions(
1022
1298
  filter_string, parsed_item_class=span_filter_public.SpanFilterPublic
1023
1299
  )
1024
1300
 
1025
- spans = rest_stream_parser.read_and_parse_full_stream(
1026
- read_source=lambda current_batch_size,
1027
- last_retrieved_id: self._rest_client.spans.search_spans(
1028
- trace_id=trace_id,
1029
- project_name=project_name or self._project_name,
1030
- filters=filters,
1031
- limit=current_batch_size,
1032
- truncate=truncate,
1033
- last_retrieved_id=last_retrieved_id,
1034
- ),
1301
+ search_functor = functools.partial(
1302
+ search_helpers.search_spans_with_filters,
1303
+ rest_client=self._rest_client,
1304
+ project_name=project_name or self._project_name,
1305
+ trace_id=trace_id,
1306
+ filters=filters,
1035
1307
  max_results=max_results,
1036
- parsed_item_class=span_public.SpanPublic,
1308
+ truncate=truncate,
1037
1309
  )
1038
1310
 
1039
- return spans
1311
+ if wait_for_at_least is None:
1312
+ return search_functor()
1313
+
1314
+ # do synchronization with backend if wait_for_at_least is provided until a specific number of spans are found
1315
+ result = search_helpers.search_and_wait_for_done(
1316
+ search_functor=search_functor,
1317
+ wait_for_at_least=wait_for_at_least,
1318
+ wait_for_timeout=wait_for_timeout,
1319
+ sleep_time=5,
1320
+ )
1321
+ if len(result) < wait_for_at_least:
1322
+ raise exceptions.SearchTimeoutError(
1323
+ f"Timeout after {wait_for_timeout} seconds: expected {wait_for_at_least} spans, but only {len(result)} were found."
1324
+ )
1325
+
1326
+ return result
1040
1327
 
1041
1328
  def get_trace_content(self, id: str) -> trace_public.TracePublic:
1042
1329
  """
@@ -1131,77 +1418,273 @@ class Opik:
1131
1418
  name: str,
1132
1419
  prompt: str,
1133
1420
  metadata: Optional[Dict[str, Any]] = None,
1134
- type: PromptType = PromptType.MUSTACHE,
1135
- ) -> Prompt:
1421
+ type: prompt_module.PromptType = prompt_module.PromptType.MUSTACHE,
1422
+ ) -> prompt_module.Prompt:
1136
1423
  """
1137
- Creates a new prompt with the given name and template.
1138
- If a prompt with the same name already exists, it will create a new version of the existing prompt if the templates differ.
1424
+ Creates a new text prompt with the given name and template.
1425
+ If a text prompt with the same name already exists, it will create a new version of the existing prompt if the templates differ.
1139
1426
 
1140
1427
  Parameters:
1141
1428
  name: The name of the prompt.
1142
1429
  prompt: The template content of the prompt.
1143
1430
  metadata: Optional metadata to be included in the prompt.
1431
+ type: The template type (MUSTACHE or JINJA2).
1144
1432
 
1145
1433
  Returns:
1146
1434
  A Prompt object containing details of the created or retrieved prompt.
1147
1435
 
1148
1436
  Raises:
1149
- ApiError: If there is an error during the creation of the prompt and the status code is not 409.
1437
+ PromptTemplateStructureMismatch: If a chat prompt with the same name already exists (template structure is immutable).
1438
+ ApiError: If there is an error during the creation of the prompt.
1150
1439
  """
1151
- prompt_client = PromptClient(self._rest_client)
1152
- prompt_version = prompt_client.create_prompt(
1440
+ prompt_client_ = prompt_client.PromptClient(self._rest_client)
1441
+ prompt_version = prompt_client_.create_prompt(
1153
1442
  name=name, prompt=prompt, metadata=metadata, type=type
1154
1443
  )
1155
- return Prompt.from_fern_prompt_version(name, prompt_version)
1444
+ return prompt_module.Prompt.from_fern_prompt_version(name, prompt_version)
1445
+
1446
+ def create_chat_prompt(
1447
+ self,
1448
+ name: str,
1449
+ messages: List[Dict[str, Any]],
1450
+ metadata: Optional[Dict[str, Any]] = None,
1451
+ type: prompt_module.PromptType = prompt_module.PromptType.MUSTACHE,
1452
+ ) -> prompt_module.ChatPrompt:
1453
+ """
1454
+ Creates a new chat prompt with the given name and message templates.
1455
+ If a chat prompt with the same name already exists, it will create a new version if the messages differ.
1456
+
1457
+ Parameters:
1458
+ name: The name of the chat prompt.
1459
+ messages: List of message dictionaries with 'role' and 'content' fields.
1460
+ metadata: Optional metadata to be included in the prompt.
1461
+ type: The template type (MUSTACHE or JINJA2).
1462
+
1463
+ Returns:
1464
+ A ChatPrompt object containing details of the created or retrieved chat prompt.
1465
+
1466
+ Raises:
1467
+ PromptTemplateStructureMismatch: If a text prompt with the same name already exists (template structure is immutable).
1468
+ ApiError: If there is an error during the creation of the prompt.
1469
+ """
1470
+ return prompt_module.ChatPrompt(
1471
+ name=name, messages=messages, metadata=metadata, type=type
1472
+ )
1156
1473
 
1157
1474
  def get_prompt(
1158
1475
  self,
1159
1476
  name: str,
1160
1477
  commit: Optional[str] = None,
1161
- ) -> Optional[Prompt]:
1478
+ ) -> Optional[prompt_module.Prompt]:
1162
1479
  """
1163
- Retrieve the prompt detail for a given prompt name and commit version.
1480
+ Retrieve a text prompt by name and optional commit version.
1481
+
1482
+ This method only returns text prompts.
1164
1483
 
1165
1484
  Parameters:
1166
1485
  name: The name of the prompt.
1167
1486
  commit: An optional commit version of the prompt. If not provided, the latest version is retrieved.
1168
1487
 
1169
1488
  Returns:
1170
- Prompt: The details of the specified prompt.
1489
+ Prompt: The details of the specified text prompt, or None if not found.
1490
+
1491
+ Raises:
1492
+ PromptTemplateStructureMismatch: If the prompt exists but is a chat prompt (template structure mismatch).
1171
1493
  """
1172
- prompt_client = PromptClient(self._rest_client)
1173
- fern_prompt_version = prompt_client.get_prompt(name=name, commit=commit)
1494
+ prompt_client_ = prompt_client.PromptClient(self._rest_client)
1495
+ fern_prompt_version = prompt_client_.get_prompt(
1496
+ name=name, commit=commit, raise_if_not_template_structure="text"
1497
+ )
1498
+
1499
+ if fern_prompt_version is None:
1500
+ return None
1501
+
1502
+ return prompt_module.Prompt.from_fern_prompt_version(name, fern_prompt_version)
1503
+
1504
+ def get_chat_prompt(
1505
+ self,
1506
+ name: str,
1507
+ commit: Optional[str] = None,
1508
+ ) -> Optional[prompt_module.ChatPrompt]:
1509
+ """
1510
+ Retrieve a chat prompt by name and optional commit version.
1511
+
1512
+ This method only returns chat prompts.
1513
+
1514
+ Parameters:
1515
+ name: The name of the prompt.
1516
+ commit: An optional commit version of the prompt. If not provided, the latest version is retrieved.
1517
+
1518
+ Returns:
1519
+ ChatPrompt: The details of the specified chat prompt, or None if not found.
1520
+
1521
+ Raises:
1522
+ PromptTemplateStructureMismatch: If the prompt exists but is a text prompt (template structure mismatch).
1523
+ """
1524
+ prompt_client_ = prompt_client.PromptClient(self._rest_client)
1525
+ fern_prompt_version = prompt_client_.get_prompt(
1526
+ name=name, commit=commit, raise_if_not_template_structure="chat"
1527
+ )
1528
+
1174
1529
  if fern_prompt_version is None:
1175
1530
  return None
1176
1531
 
1177
- return Prompt.from_fern_prompt_version(name, fern_prompt_version)
1532
+ return prompt_module.ChatPrompt.from_fern_prompt_version(
1533
+ name, fern_prompt_version
1534
+ )
1178
1535
 
1179
- def get_all_prompts(self, name: str) -> List[Prompt]:
1536
+ def get_prompt_history(self, name: str) -> List[prompt_module.Prompt]:
1180
1537
  """
1181
- Retrieve all the prompt versions for a given prompt name.
1538
+ Retrieve all text prompt versions history for a given prompt name.
1182
1539
 
1183
1540
  Parameters:
1184
1541
  name: The name of the prompt.
1185
1542
 
1186
1543
  Returns:
1187
- List[Prompt]: A list of prompts for the given name.
1544
+ List[Prompt]: A list of text Prompt instances for the given name, or an empty list if not found.
1545
+
1546
+ Raises:
1547
+ PromptTemplateStructureMismatch: If the prompt exists but is a chat prompt (template structure mismatch).
1188
1548
  """
1189
- prompt_client = PromptClient(self._rest_client)
1190
- fern_prompt_versions = prompt_client.get_all_prompts(name=name)
1549
+ prompt_client_ = prompt_client.PromptClient(self._rest_client)
1550
+
1551
+ # First, validate that this is a text prompt by trying to get the latest version
1552
+ # Let PromptTemplateStructureMismatch exception propagate - this is a hard error
1553
+ latest_version = prompt_client_.get_prompt(
1554
+ name=name, raise_if_not_template_structure="text"
1555
+ )
1556
+
1557
+ if latest_version is None:
1558
+ return []
1559
+
1560
+ # Now get all versions (we know it's a text prompt)
1561
+ fern_prompt_versions = prompt_client_.get_all_prompt_versions(name=name)
1562
+
1191
1563
  result = [
1192
- Prompt.from_fern_prompt_version(name, version)
1564
+ prompt_module.Prompt.from_fern_prompt_version(name, version)
1193
1565
  for version in fern_prompt_versions
1194
1566
  ]
1195
1567
  return result
1196
1568
 
1569
+ def get_chat_prompt_history(self, name: str) -> List[prompt_module.ChatPrompt]:
1570
+ """
1571
+ Retrieve all chat prompt versions history for a given prompt name.
1572
+
1573
+ Parameters:
1574
+ name: The name of the prompt.
1575
+
1576
+ Returns:
1577
+ List[ChatPrompt]: A list of ChatPrompt instances for the given name, or an empty list if not found.
1578
+
1579
+ Raises:
1580
+ PromptTemplateStructureMismatch: If the prompt exists but is a text prompt (template structure mismatch).
1581
+ """
1582
+ prompt_client_ = prompt_client.PromptClient(self._rest_client)
1583
+
1584
+ # First, validate that this is a chat prompt by trying to get the latest version
1585
+ # Let PromptTemplateStructureMismatch exception propagate - this is a hard error
1586
+ latest_version = prompt_client_.get_prompt(
1587
+ name=name, raise_if_not_template_structure="chat"
1588
+ )
1589
+
1590
+ if latest_version is None:
1591
+ return []
1592
+
1593
+ # Now get all versions (we know it's a chat prompt)
1594
+ fern_prompt_versions = prompt_client_.get_all_prompt_versions(name=name)
1595
+
1596
+ result = [
1597
+ prompt_module.ChatPrompt.from_fern_prompt_version(name, version)
1598
+ for version in fern_prompt_versions
1599
+ ]
1600
+ return result
1601
+
1602
+ def get_all_prompts(self, name: str) -> List[prompt_module.Prompt]:
1603
+ """
1604
+ DEPRECATED: Please use Opik.get_prompt_history() instead.
1605
+ Retrieve all the prompt versions history for a given prompt name.
1606
+
1607
+ Parameters:
1608
+ name: The name of the prompt.
1609
+
1610
+ Returns:
1611
+ List[prompt_module.Prompt]: A list of Prompt instances for the given name.
1612
+ """
1613
+ LOGGER.warning(
1614
+ "Opik.get_all_prompts() is deprecated. Please use Opik.get_prompt_history() instead."
1615
+ )
1616
+ return self.get_prompt_history(name)
1617
+
1618
+ def search_prompts(
1619
+ self, filter_string: Optional[str] = None
1620
+ ) -> List[Union[prompt_module.Prompt, prompt_module.ChatPrompt]]:
1621
+ """
1622
+ Retrieve the latest prompt versions (both string and chat prompts) for the given search parameters.
1623
+
1624
+ Parameters:
1625
+ filter_string: A filter string to narrow down the search using Opik Query Language (OQL).
1626
+ The format is: "<COLUMN> <OPERATOR> <VALUE> [AND <COLUMN> <OPERATOR> <VALUE>]*"
1627
+
1628
+ Supported columns include:
1629
+ - `id`, `name`: String fields
1630
+ - `tags`: List field (use "contains" operator only)
1631
+ - `created_by`: String field
1632
+ - `template_structure`: String field ("string" or "chat")
1633
+
1634
+ Supported operators by column:
1635
+ - `id`: =, !=, contains, not_contains, starts_with, ends_with, >, <
1636
+ - `name`: =, !=, contains, not_contains, starts_with, ends_with, >, <
1637
+ - `created_by`: =, !=, contains, not_contains, starts_with, ends_with, >, <
1638
+ - `template_structure`: =, !=
1639
+ - `tags`: contains (only)
1640
+
1641
+ Examples:
1642
+ - `tags contains "alpha"` - Filter by tag
1643
+ - `tags contains "alpha" AND tags contains "beta"` - Filter by multiple tags
1644
+ - `name contains "summary"` - Filter by name substring
1645
+ - `created_by = "user@example.com"` - Filter by creator
1646
+ - `id starts_with "prompt_"` - Filter by ID prefix
1647
+ - `template_structure = "text"` - Only text prompts
1648
+ - `template_structure = "chat"` - Only chat prompts
1649
+
1650
+ If not provided, all prompts (both text and chat) will be returned.
1651
+
1652
+ Returns:
1653
+ List[Union[Prompt, ChatPrompt]]: A list of Prompt and/or ChatPrompt instances found.
1654
+ """
1655
+ oql = opik_query_language.OpikQueryLanguage(filter_string or "")
1656
+ parsed_filters = oql.get_filter_expressions()
1657
+
1658
+ prompt_client_ = prompt_client.PromptClient(self._rest_client)
1659
+ search_results = prompt_client_.search_prompts(parsed_filters=parsed_filters)
1660
+
1661
+ # Convert to Prompt or ChatPrompt objects based on template_structure
1662
+ prompts: List[Union[prompt_module.Prompt, prompt_module.ChatPrompt]] = []
1663
+ for result in search_results:
1664
+ if result.template_structure == "chat":
1665
+ prompts.append(
1666
+ prompt_module.ChatPrompt.from_fern_prompt_version(
1667
+ result.name, result.prompt_version_detail
1668
+ )
1669
+ )
1670
+ else:
1671
+ prompts.append(
1672
+ prompt_module.Prompt.from_fern_prompt_version(
1673
+ result.name, result.prompt_version_detail
1674
+ )
1675
+ )
1676
+
1677
+ return prompts
1678
+
1197
1679
  def create_optimization(
1198
1680
  self,
1199
1681
  dataset_name: str,
1200
1682
  objective_name: str,
1201
1683
  name: Optional[str] = None,
1202
1684
  metadata: Optional[Dict[str, Any]] = None,
1685
+ optimization_id: Optional[str] = None,
1203
1686
  ) -> optimization.Optimization:
1204
- id = id_helpers.generate_id()
1687
+ id = optimization_id or id_helpers.generate_id()
1205
1688
 
1206
1689
  self._rest_client.optimizations.create_optimization(
1207
1690
  id=id,
@@ -1224,6 +1707,15 @@ class Opik:
1224
1707
  _ = self._rest_client.optimizations.get_optimization_by_id(id)
1225
1708
  return optimization.Optimization(id=id, rest_client=self._rest_client)
1226
1709
 
1710
+ def get_experiments_client(self) -> experiments_client.ExperimentsClient:
1711
+ """
1712
+ Retrieves an instance of `ExperimentsClient`.
1713
+
1714
+ Returns:
1715
+ An instance of the ExperimentsClient initialized with a cached REST client.
1716
+ """
1717
+ return experiments_client.ExperimentsClient(self._rest_client)
1718
+
1227
1719
 
1228
1720
  @functools.lru_cache()
1229
1721
  def get_client_cached() -> Opik: