opik 1.6.4__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 (1021) hide show
  1. opik/__init__.py +33 -2
  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/__init__.py +5 -0
  9. opik/api_objects/attachment/attachment.py +20 -0
  10. opik/api_objects/attachment/attachment_context.py +36 -0
  11. opik/api_objects/attachment/attachments_extractor.py +153 -0
  12. opik/api_objects/attachment/client.py +220 -0
  13. opik/api_objects/attachment/converters.py +51 -0
  14. opik/api_objects/attachment/decoder.py +18 -0
  15. opik/api_objects/attachment/decoder_base64.py +83 -0
  16. opik/api_objects/attachment/decoder_helpers.py +137 -0
  17. opik/api_objects/conversation/__init__.py +0 -0
  18. opik/api_objects/conversation/conversation_factory.py +43 -0
  19. opik/api_objects/conversation/conversation_thread.py +49 -0
  20. opik/api_objects/data_helpers.py +79 -0
  21. opik/api_objects/dataset/dataset.py +107 -45
  22. opik/api_objects/dataset/rest_operations.py +12 -3
  23. opik/api_objects/experiment/experiment.py +81 -45
  24. opik/api_objects/experiment/experiment_item.py +2 -1
  25. opik/api_objects/experiment/experiments_client.py +64 -0
  26. opik/api_objects/experiment/helpers.py +35 -11
  27. opik/api_objects/experiment/rest_operations.py +88 -19
  28. opik/api_objects/helpers.py +104 -7
  29. opik/api_objects/local_recording.py +81 -0
  30. opik/api_objects/opik_client.py +872 -174
  31. opik/api_objects/opik_query_language.py +136 -18
  32. opik/api_objects/optimization/__init__.py +3 -0
  33. opik/api_objects/optimization/optimization.py +39 -0
  34. opik/api_objects/prompt/__init__.py +13 -1
  35. opik/api_objects/prompt/base_prompt.py +69 -0
  36. opik/api_objects/prompt/base_prompt_template.py +29 -0
  37. opik/api_objects/prompt/chat/__init__.py +1 -0
  38. opik/api_objects/prompt/chat/chat_prompt.py +210 -0
  39. opik/api_objects/prompt/chat/chat_prompt_template.py +350 -0
  40. opik/api_objects/prompt/chat/content_renderer_registry.py +203 -0
  41. opik/api_objects/prompt/client.py +193 -41
  42. opik/api_objects/prompt/text/__init__.py +1 -0
  43. opik/api_objects/prompt/text/prompt.py +174 -0
  44. opik/api_objects/prompt/text/prompt_template.py +55 -0
  45. opik/api_objects/prompt/types.py +29 -0
  46. opik/api_objects/rest_stream_parser.py +98 -0
  47. opik/api_objects/search_helpers.py +89 -0
  48. opik/api_objects/span/span_client.py +165 -45
  49. opik/api_objects/span/span_data.py +136 -25
  50. opik/api_objects/threads/__init__.py +0 -0
  51. opik/api_objects/threads/threads_client.py +185 -0
  52. opik/api_objects/trace/trace_client.py +72 -36
  53. opik/api_objects/trace/trace_data.py +112 -26
  54. opik/api_objects/validation_helpers.py +3 -3
  55. opik/cli/__init__.py +5 -0
  56. opik/cli/__main__.py +6 -0
  57. opik/cli/configure.py +66 -0
  58. opik/cli/exports/__init__.py +131 -0
  59. opik/cli/exports/dataset.py +278 -0
  60. opik/cli/exports/experiment.py +784 -0
  61. opik/cli/exports/project.py +685 -0
  62. opik/cli/exports/prompt.py +578 -0
  63. opik/cli/exports/utils.py +406 -0
  64. opik/cli/harbor.py +39 -0
  65. opik/cli/healthcheck.py +21 -0
  66. opik/cli/imports/__init__.py +439 -0
  67. opik/cli/imports/dataset.py +143 -0
  68. opik/cli/imports/experiment.py +1192 -0
  69. opik/cli/imports/project.py +262 -0
  70. opik/cli/imports/prompt.py +177 -0
  71. opik/cli/imports/utils.py +280 -0
  72. opik/cli/main.py +49 -0
  73. opik/cli/proxy.py +93 -0
  74. opik/cli/usage_report/__init__.py +16 -0
  75. opik/cli/usage_report/charts.py +783 -0
  76. opik/cli/usage_report/cli.py +274 -0
  77. opik/cli/usage_report/constants.py +9 -0
  78. opik/cli/usage_report/extraction.py +749 -0
  79. opik/cli/usage_report/pdf.py +244 -0
  80. opik/cli/usage_report/statistics.py +78 -0
  81. opik/cli/usage_report/utils.py +235 -0
  82. opik/config.py +62 -4
  83. opik/configurator/configure.py +45 -6
  84. opik/configurator/opik_rest_helpers.py +4 -1
  85. opik/context_storage.py +164 -65
  86. opik/datetime_helpers.py +12 -0
  87. opik/decorator/arguments_helpers.py +9 -1
  88. opik/decorator/base_track_decorator.py +298 -146
  89. opik/decorator/context_manager/__init__.py +0 -0
  90. opik/decorator/context_manager/span_context_manager.py +123 -0
  91. opik/decorator/context_manager/trace_context_manager.py +84 -0
  92. opik/decorator/generator_wrappers.py +3 -2
  93. opik/decorator/inspect_helpers.py +11 -0
  94. opik/decorator/opik_args/__init__.py +13 -0
  95. opik/decorator/opik_args/api_classes.py +71 -0
  96. opik/decorator/opik_args/helpers.py +120 -0
  97. opik/decorator/span_creation_handler.py +49 -21
  98. opik/decorator/tracker.py +9 -1
  99. opik/dict_utils.py +3 -3
  100. opik/environment.py +13 -1
  101. opik/error_tracking/api.py +1 -1
  102. opik/error_tracking/before_send.py +6 -5
  103. opik/error_tracking/environment_details.py +29 -7
  104. opik/error_tracking/error_filtering/filter_by_response_status_code.py +42 -0
  105. opik/error_tracking/error_filtering/filter_chain_builder.py +14 -3
  106. opik/evaluation/__init__.py +14 -2
  107. opik/evaluation/engine/engine.py +280 -82
  108. opik/evaluation/engine/evaluation_tasks_executor.py +15 -10
  109. opik/evaluation/engine/helpers.py +34 -9
  110. opik/evaluation/engine/metrics_evaluator.py +237 -0
  111. opik/evaluation/engine/types.py +5 -4
  112. opik/evaluation/evaluation_result.py +169 -2
  113. opik/evaluation/evaluator.py +659 -58
  114. opik/evaluation/metrics/__init__.py +121 -6
  115. opik/evaluation/metrics/aggregated_metric.py +92 -0
  116. opik/evaluation/metrics/arguments_helpers.py +15 -21
  117. opik/evaluation/metrics/arguments_validator.py +38 -0
  118. opik/evaluation/metrics/base_metric.py +20 -10
  119. opik/evaluation/metrics/conversation/__init__.py +48 -0
  120. opik/evaluation/metrics/conversation/conversation_thread_metric.py +79 -0
  121. opik/evaluation/metrics/conversation/conversation_turns_factory.py +39 -0
  122. opik/evaluation/metrics/conversation/g_eval_wrappers.py +19 -0
  123. opik/evaluation/metrics/conversation/helpers.py +84 -0
  124. opik/evaluation/metrics/conversation/heuristics/__init__.py +14 -0
  125. opik/evaluation/metrics/conversation/heuristics/degeneration/__init__.py +3 -0
  126. opik/evaluation/metrics/conversation/heuristics/degeneration/metric.py +189 -0
  127. opik/evaluation/metrics/conversation/heuristics/degeneration/phrases.py +12 -0
  128. opik/evaluation/metrics/conversation/heuristics/knowledge_retention/__init__.py +3 -0
  129. opik/evaluation/metrics/conversation/heuristics/knowledge_retention/metric.py +172 -0
  130. opik/evaluation/metrics/conversation/llm_judges/__init__.py +32 -0
  131. opik/evaluation/metrics/conversation/llm_judges/conversational_coherence/__init__.py +0 -0
  132. opik/evaluation/metrics/conversation/llm_judges/conversational_coherence/metric.py +274 -0
  133. opik/evaluation/metrics/conversation/llm_judges/conversational_coherence/schema.py +16 -0
  134. opik/evaluation/metrics/conversation/llm_judges/conversational_coherence/templates.py +95 -0
  135. opik/evaluation/metrics/conversation/llm_judges/g_eval_wrappers.py +442 -0
  136. opik/evaluation/metrics/conversation/llm_judges/session_completeness/__init__.py +0 -0
  137. opik/evaluation/metrics/conversation/llm_judges/session_completeness/metric.py +295 -0
  138. opik/evaluation/metrics/conversation/llm_judges/session_completeness/schema.py +22 -0
  139. opik/evaluation/metrics/conversation/llm_judges/session_completeness/templates.py +139 -0
  140. opik/evaluation/metrics/conversation/llm_judges/user_frustration/__init__.py +0 -0
  141. opik/evaluation/metrics/conversation/llm_judges/user_frustration/metric.py +277 -0
  142. opik/evaluation/metrics/conversation/llm_judges/user_frustration/schema.py +16 -0
  143. opik/evaluation/metrics/conversation/llm_judges/user_frustration/templates.py +135 -0
  144. opik/evaluation/metrics/conversation/types.py +34 -0
  145. opik/evaluation/metrics/conversation_types.py +9 -0
  146. opik/evaluation/metrics/heuristics/bertscore.py +107 -0
  147. opik/evaluation/metrics/heuristics/bleu.py +43 -16
  148. opik/evaluation/metrics/heuristics/chrf.py +127 -0
  149. opik/evaluation/metrics/heuristics/contains.py +50 -11
  150. opik/evaluation/metrics/heuristics/distribution_metrics.py +331 -0
  151. opik/evaluation/metrics/heuristics/equals.py +4 -1
  152. opik/evaluation/metrics/heuristics/gleu.py +113 -0
  153. opik/evaluation/metrics/heuristics/is_json.py +9 -3
  154. opik/evaluation/metrics/heuristics/language_adherence.py +123 -0
  155. opik/evaluation/metrics/heuristics/levenshtein_ratio.py +6 -5
  156. opik/evaluation/metrics/heuristics/meteor.py +119 -0
  157. opik/evaluation/metrics/heuristics/prompt_injection.py +150 -0
  158. opik/evaluation/metrics/heuristics/readability.py +129 -0
  159. opik/evaluation/metrics/heuristics/regex_match.py +4 -1
  160. opik/evaluation/metrics/heuristics/rouge.py +148 -0
  161. opik/evaluation/metrics/heuristics/sentiment.py +98 -0
  162. opik/evaluation/metrics/heuristics/spearman.py +88 -0
  163. opik/evaluation/metrics/heuristics/tone.py +155 -0
  164. opik/evaluation/metrics/heuristics/vader_sentiment.py +77 -0
  165. opik/evaluation/metrics/llm_judges/answer_relevance/metric.py +27 -30
  166. opik/evaluation/metrics/llm_judges/answer_relevance/parser.py +27 -0
  167. opik/evaluation/metrics/llm_judges/answer_relevance/templates.py +10 -10
  168. opik/evaluation/metrics/llm_judges/context_precision/metric.py +28 -31
  169. opik/evaluation/metrics/llm_judges/context_precision/parser.py +27 -0
  170. opik/evaluation/metrics/llm_judges/context_precision/template.py +7 -7
  171. opik/evaluation/metrics/llm_judges/context_recall/metric.py +27 -31
  172. opik/evaluation/metrics/llm_judges/context_recall/parser.py +27 -0
  173. opik/evaluation/metrics/llm_judges/context_recall/template.py +7 -7
  174. opik/evaluation/metrics/llm_judges/factuality/metric.py +7 -26
  175. opik/evaluation/metrics/llm_judges/factuality/parser.py +35 -0
  176. opik/evaluation/metrics/llm_judges/factuality/template.py +1 -1
  177. opik/evaluation/metrics/llm_judges/g_eval/__init__.py +5 -0
  178. opik/evaluation/metrics/llm_judges/g_eval/metric.py +244 -113
  179. opik/evaluation/metrics/llm_judges/g_eval/parser.py +161 -0
  180. opik/evaluation/metrics/llm_judges/g_eval/presets.py +209 -0
  181. opik/evaluation/metrics/llm_judges/g_eval_presets/__init__.py +36 -0
  182. opik/evaluation/metrics/llm_judges/g_eval_presets/agent_assessment.py +77 -0
  183. opik/evaluation/metrics/llm_judges/g_eval_presets/bias_classifier.py +181 -0
  184. opik/evaluation/metrics/llm_judges/g_eval_presets/compliance_risk.py +41 -0
  185. opik/evaluation/metrics/llm_judges/g_eval_presets/prompt_uncertainty.py +41 -0
  186. opik/evaluation/metrics/llm_judges/g_eval_presets/qa_suite.py +146 -0
  187. opik/evaluation/metrics/llm_judges/hallucination/metric.py +23 -27
  188. opik/evaluation/metrics/llm_judges/hallucination/parser.py +29 -0
  189. opik/evaluation/metrics/llm_judges/hallucination/template.py +2 -4
  190. opik/evaluation/metrics/llm_judges/llm_juries/__init__.py +3 -0
  191. opik/evaluation/metrics/llm_judges/llm_juries/metric.py +76 -0
  192. opik/evaluation/metrics/llm_judges/moderation/metric.py +23 -28
  193. opik/evaluation/metrics/llm_judges/moderation/parser.py +27 -0
  194. opik/evaluation/metrics/llm_judges/moderation/template.py +2 -2
  195. opik/evaluation/metrics/llm_judges/parsing_helpers.py +26 -0
  196. opik/evaluation/metrics/llm_judges/structure_output_compliance/__init__.py +0 -0
  197. opik/evaluation/metrics/llm_judges/structure_output_compliance/metric.py +144 -0
  198. opik/evaluation/metrics/llm_judges/structure_output_compliance/parser.py +79 -0
  199. opik/evaluation/metrics/llm_judges/structure_output_compliance/schema.py +15 -0
  200. opik/evaluation/metrics/llm_judges/structure_output_compliance/template.py +50 -0
  201. opik/evaluation/metrics/llm_judges/syc_eval/__init__.py +0 -0
  202. opik/evaluation/metrics/llm_judges/syc_eval/metric.py +252 -0
  203. opik/evaluation/metrics/llm_judges/syc_eval/parser.py +82 -0
  204. opik/evaluation/metrics/llm_judges/syc_eval/template.py +155 -0
  205. opik/evaluation/metrics/llm_judges/trajectory_accuracy/__init__.py +3 -0
  206. opik/evaluation/metrics/llm_judges/trajectory_accuracy/metric.py +171 -0
  207. opik/evaluation/metrics/llm_judges/trajectory_accuracy/parser.py +38 -0
  208. opik/evaluation/metrics/llm_judges/trajectory_accuracy/templates.py +65 -0
  209. opik/evaluation/metrics/llm_judges/usefulness/metric.py +23 -32
  210. opik/evaluation/metrics/llm_judges/usefulness/parser.py +28 -0
  211. opik/evaluation/metrics/ragas_metric.py +112 -0
  212. opik/evaluation/models/__init__.py +10 -0
  213. opik/evaluation/models/base_model.py +140 -18
  214. opik/evaluation/models/langchain/__init__.py +3 -0
  215. opik/evaluation/models/langchain/langchain_chat_model.py +166 -0
  216. opik/evaluation/models/langchain/message_converters.py +106 -0
  217. opik/evaluation/models/langchain/opik_monitoring.py +23 -0
  218. opik/evaluation/models/litellm/litellm_chat_model.py +186 -40
  219. opik/evaluation/models/litellm/opik_monitor.py +24 -21
  220. opik/evaluation/models/litellm/util.py +125 -0
  221. opik/evaluation/models/litellm/warning_filters.py +16 -4
  222. opik/evaluation/models/model_capabilities.py +187 -0
  223. opik/evaluation/models/models_factory.py +25 -3
  224. opik/evaluation/preprocessing.py +92 -0
  225. opik/evaluation/report.py +70 -12
  226. opik/evaluation/rest_operations.py +49 -45
  227. opik/evaluation/samplers/__init__.py +4 -0
  228. opik/evaluation/samplers/base_dataset_sampler.py +40 -0
  229. opik/evaluation/samplers/random_dataset_sampler.py +48 -0
  230. opik/evaluation/score_statistics.py +66 -0
  231. opik/evaluation/scorers/__init__.py +4 -0
  232. opik/evaluation/scorers/scorer_function.py +55 -0
  233. opik/evaluation/scorers/scorer_wrapper_metric.py +130 -0
  234. opik/evaluation/test_case.py +3 -2
  235. opik/evaluation/test_result.py +1 -0
  236. opik/evaluation/threads/__init__.py +0 -0
  237. opik/evaluation/threads/context_helper.py +32 -0
  238. opik/evaluation/threads/evaluation_engine.py +181 -0
  239. opik/evaluation/threads/evaluation_result.py +18 -0
  240. opik/evaluation/threads/evaluator.py +120 -0
  241. opik/evaluation/threads/helpers.py +51 -0
  242. opik/evaluation/types.py +9 -1
  243. opik/exceptions.py +116 -3
  244. opik/file_upload/__init__.py +0 -0
  245. opik/file_upload/base_upload_manager.py +39 -0
  246. opik/file_upload/file_upload_monitor.py +14 -0
  247. opik/file_upload/file_uploader.py +141 -0
  248. opik/file_upload/mime_type.py +9 -0
  249. opik/file_upload/s3_multipart_upload/__init__.py +0 -0
  250. opik/file_upload/s3_multipart_upload/file_parts_strategy.py +89 -0
  251. opik/file_upload/s3_multipart_upload/s3_file_uploader.py +86 -0
  252. opik/file_upload/s3_multipart_upload/s3_upload_error.py +29 -0
  253. opik/file_upload/thread_pool.py +17 -0
  254. opik/file_upload/upload_client.py +114 -0
  255. opik/file_upload/upload_manager.py +255 -0
  256. opik/file_upload/upload_options.py +37 -0
  257. opik/format_helpers.py +17 -0
  258. opik/guardrails/__init__.py +4 -0
  259. opik/guardrails/guardrail.py +157 -0
  260. opik/guardrails/guards/__init__.py +5 -0
  261. opik/guardrails/guards/guard.py +17 -0
  262. opik/guardrails/guards/pii.py +47 -0
  263. opik/guardrails/guards/topic.py +76 -0
  264. opik/guardrails/rest_api_client.py +34 -0
  265. opik/guardrails/schemas.py +24 -0
  266. opik/guardrails/tracing.py +61 -0
  267. opik/healthcheck/__init__.py +2 -1
  268. opik/healthcheck/checks.py +2 -2
  269. opik/healthcheck/rich_representation.py +1 -1
  270. opik/hooks/__init__.py +23 -0
  271. opik/hooks/anonymizer_hook.py +36 -0
  272. opik/hooks/httpx_client_hook.py +112 -0
  273. opik/httpx_client.py +75 -4
  274. opik/id_helpers.py +18 -0
  275. opik/integrations/adk/__init__.py +14 -0
  276. opik/integrations/adk/callback_context_info_extractors.py +32 -0
  277. opik/integrations/adk/graph/__init__.py +0 -0
  278. opik/integrations/adk/graph/mermaid_graph_builder.py +128 -0
  279. opik/integrations/adk/graph/nodes.py +101 -0
  280. opik/integrations/adk/graph/subgraph_edges_builders.py +41 -0
  281. opik/integrations/adk/helpers.py +48 -0
  282. opik/integrations/adk/legacy_opik_tracer.py +381 -0
  283. opik/integrations/adk/opik_tracer.py +370 -0
  284. opik/integrations/adk/patchers/__init__.py +4 -0
  285. opik/integrations/adk/patchers/adk_otel_tracer/__init__.py +0 -0
  286. opik/integrations/adk/patchers/adk_otel_tracer/llm_span_helpers.py +30 -0
  287. opik/integrations/adk/patchers/adk_otel_tracer/opik_adk_otel_tracer.py +201 -0
  288. opik/integrations/adk/patchers/litellm_wrappers.py +91 -0
  289. opik/integrations/adk/patchers/llm_response_wrapper.py +105 -0
  290. opik/integrations/adk/patchers/patchers.py +64 -0
  291. opik/integrations/adk/recursive_callback_injector.py +126 -0
  292. opik/integrations/aisuite/aisuite_decorator.py +8 -3
  293. opik/integrations/aisuite/opik_tracker.py +1 -0
  294. opik/integrations/anthropic/messages_create_decorator.py +8 -3
  295. opik/integrations/anthropic/opik_tracker.py +0 -1
  296. opik/integrations/bedrock/converse/__init__.py +0 -0
  297. opik/integrations/bedrock/converse/chunks_aggregator.py +188 -0
  298. opik/integrations/bedrock/{converse_decorator.py → converse/converse_decorator.py} +18 -8
  299. opik/integrations/bedrock/invoke_agent_decorator.py +12 -7
  300. opik/integrations/bedrock/invoke_model/__init__.py +0 -0
  301. opik/integrations/bedrock/invoke_model/chunks_aggregator/__init__.py +78 -0
  302. opik/integrations/bedrock/invoke_model/chunks_aggregator/api.py +45 -0
  303. opik/integrations/bedrock/invoke_model/chunks_aggregator/base.py +23 -0
  304. opik/integrations/bedrock/invoke_model/chunks_aggregator/claude.py +121 -0
  305. opik/integrations/bedrock/invoke_model/chunks_aggregator/format_detector.py +107 -0
  306. opik/integrations/bedrock/invoke_model/chunks_aggregator/llama.py +108 -0
  307. opik/integrations/bedrock/invoke_model/chunks_aggregator/mistral.py +118 -0
  308. opik/integrations/bedrock/invoke_model/chunks_aggregator/nova.py +99 -0
  309. opik/integrations/bedrock/invoke_model/invoke_model_decorator.py +178 -0
  310. opik/integrations/bedrock/invoke_model/response_types.py +34 -0
  311. opik/integrations/bedrock/invoke_model/stream_wrappers.py +122 -0
  312. opik/integrations/bedrock/invoke_model/usage_converters.py +87 -0
  313. opik/integrations/bedrock/invoke_model/usage_extraction.py +108 -0
  314. opik/integrations/bedrock/opik_tracker.py +43 -4
  315. opik/integrations/bedrock/types.py +19 -0
  316. opik/integrations/crewai/crewai_decorator.py +34 -56
  317. opik/integrations/crewai/opik_tracker.py +31 -10
  318. opik/integrations/crewai/patchers/__init__.py +5 -0
  319. opik/integrations/crewai/patchers/flow.py +118 -0
  320. opik/integrations/crewai/patchers/litellm_completion.py +30 -0
  321. opik/integrations/crewai/patchers/llm_client.py +207 -0
  322. opik/integrations/dspy/callback.py +246 -84
  323. opik/integrations/dspy/graph.py +88 -0
  324. opik/integrations/dspy/parsers.py +168 -0
  325. opik/integrations/genai/encoder_extension.py +2 -6
  326. opik/integrations/genai/generate_content_decorator.py +20 -13
  327. opik/integrations/guardrails/guardrails_decorator.py +4 -0
  328. opik/integrations/harbor/__init__.py +17 -0
  329. opik/integrations/harbor/experiment_service.py +269 -0
  330. opik/integrations/harbor/opik_tracker.py +528 -0
  331. opik/integrations/haystack/constants.py +35 -0
  332. opik/integrations/haystack/converters.py +1 -2
  333. opik/integrations/haystack/opik_connector.py +28 -6
  334. opik/integrations/haystack/opik_span_bridge.py +284 -0
  335. opik/integrations/haystack/opik_tracer.py +124 -222
  336. opik/integrations/langchain/__init__.py +3 -1
  337. opik/integrations/langchain/helpers.py +96 -0
  338. opik/integrations/langchain/langgraph_async_context_bridge.py +131 -0
  339. opik/integrations/langchain/langgraph_tracer_injector.py +88 -0
  340. opik/integrations/langchain/opik_encoder_extension.py +2 -2
  341. opik/integrations/langchain/opik_tracer.py +641 -206
  342. opik/integrations/langchain/provider_usage_extractors/__init__.py +5 -0
  343. opik/integrations/langchain/provider_usage_extractors/anthropic_usage_extractor.py +101 -0
  344. opik/integrations/langchain/provider_usage_extractors/anthropic_vertexai_usage_extractor.py +67 -0
  345. opik/integrations/langchain/provider_usage_extractors/bedrock_usage_extractor.py +94 -0
  346. opik/integrations/langchain/provider_usage_extractors/google_generative_ai_usage_extractor.py +109 -0
  347. opik/integrations/langchain/provider_usage_extractors/groq_usage_extractor.py +92 -0
  348. opik/integrations/langchain/provider_usage_extractors/langchain_run_helpers/__init__.py +15 -0
  349. opik/integrations/langchain/provider_usage_extractors/langchain_run_helpers/helpers.py +134 -0
  350. opik/integrations/langchain/provider_usage_extractors/langchain_run_helpers/langchain_usage.py +163 -0
  351. opik/integrations/langchain/provider_usage_extractors/openai_usage_extractor.py +124 -0
  352. opik/integrations/langchain/provider_usage_extractors/provider_usage_extractor_protocol.py +29 -0
  353. opik/integrations/langchain/provider_usage_extractors/usage_extractor.py +48 -0
  354. opik/integrations/langchain/provider_usage_extractors/vertexai_usage_extractor.py +109 -0
  355. opik/integrations/litellm/__init__.py +5 -0
  356. opik/integrations/litellm/completion_chunks_aggregator.py +115 -0
  357. opik/integrations/litellm/litellm_completion_decorator.py +242 -0
  358. opik/integrations/litellm/opik_tracker.py +43 -0
  359. opik/integrations/litellm/stream_patchers.py +151 -0
  360. opik/integrations/llama_index/callback.py +179 -78
  361. opik/integrations/llama_index/event_parsing_utils.py +29 -9
  362. opik/integrations/openai/agents/opik_tracing_processor.py +204 -32
  363. opik/integrations/openai/agents/span_data_parsers.py +15 -6
  364. opik/integrations/openai/chat_completion_chunks_aggregator.py +1 -1
  365. opik/integrations/openai/{openai_decorator.py → openai_chat_completions_decorator.py} +45 -35
  366. opik/integrations/openai/openai_responses_decorator.py +158 -0
  367. opik/integrations/openai/opik_tracker.py +94 -13
  368. opik/integrations/openai/response_events_aggregator.py +36 -0
  369. opik/integrations/openai/stream_patchers.py +125 -15
  370. opik/integrations/sagemaker/auth.py +5 -1
  371. opik/jsonable_encoder.py +29 -1
  372. opik/llm_usage/base_original_provider_usage.py +15 -8
  373. opik/llm_usage/bedrock_usage.py +8 -2
  374. opik/llm_usage/google_usage.py +6 -1
  375. opik/llm_usage/llm_usage_info.py +6 -0
  376. opik/llm_usage/{openai_usage.py → openai_chat_completions_usage.py} +2 -12
  377. opik/llm_usage/{openai_agent_usage.py → openai_responses_usage.py} +7 -15
  378. opik/llm_usage/opik_usage.py +36 -10
  379. opik/llm_usage/opik_usage_factory.py +35 -19
  380. opik/logging_messages.py +19 -7
  381. opik/message_processing/arguments_utils.py +22 -0
  382. opik/message_processing/batching/base_batcher.py +45 -17
  383. opik/message_processing/batching/batch_manager.py +22 -10
  384. opik/message_processing/batching/batch_manager_constuctors.py +36 -11
  385. opik/message_processing/batching/batchers.py +167 -44
  386. opik/message_processing/batching/flushing_thread.py +0 -3
  387. opik/message_processing/batching/sequence_splitter.py +50 -5
  388. opik/message_processing/emulation/__init__.py +0 -0
  389. opik/message_processing/emulation/emulator_message_processor.py +578 -0
  390. opik/message_processing/emulation/local_emulator_message_processor.py +140 -0
  391. opik/message_processing/emulation/models.py +162 -0
  392. opik/message_processing/encoder_helpers.py +79 -0
  393. opik/message_processing/message_queue.py +79 -0
  394. opik/message_processing/messages.py +154 -12
  395. opik/message_processing/preprocessing/__init__.py +0 -0
  396. opik/message_processing/preprocessing/attachments_preprocessor.py +70 -0
  397. opik/message_processing/preprocessing/batching_preprocessor.py +53 -0
  398. opik/message_processing/preprocessing/constants.py +1 -0
  399. opik/message_processing/preprocessing/file_upload_preprocessor.py +38 -0
  400. opik/message_processing/preprocessing/preprocessor.py +36 -0
  401. opik/message_processing/processors/__init__.py +0 -0
  402. opik/message_processing/processors/attachments_extraction_processor.py +146 -0
  403. opik/message_processing/processors/message_processors.py +92 -0
  404. opik/message_processing/processors/message_processors_chain.py +96 -0
  405. opik/message_processing/processors/online_message_processor.py +324 -0
  406. opik/message_processing/queue_consumer.py +61 -13
  407. opik/message_processing/streamer.py +102 -31
  408. opik/message_processing/streamer_constructors.py +67 -12
  409. opik/opik_context.py +103 -11
  410. opik/plugins/pytest/decorator.py +2 -2
  411. opik/plugins/pytest/experiment_runner.py +3 -2
  412. opik/plugins/pytest/hooks.py +6 -4
  413. opik/rate_limit/__init__.py +0 -0
  414. opik/rate_limit/rate_limit.py +25 -0
  415. opik/rest_api/__init__.py +643 -11
  416. opik/rest_api/alerts/__init__.py +7 -0
  417. opik/rest_api/alerts/client.py +667 -0
  418. opik/rest_api/alerts/raw_client.py +1015 -0
  419. opik/rest_api/alerts/types/__init__.py +7 -0
  420. opik/rest_api/alerts/types/get_webhook_examples_request_alert_type.py +5 -0
  421. opik/rest_api/annotation_queues/__init__.py +4 -0
  422. opik/rest_api/annotation_queues/client.py +668 -0
  423. opik/rest_api/annotation_queues/raw_client.py +1019 -0
  424. opik/rest_api/attachments/__init__.py +17 -0
  425. opik/rest_api/attachments/client.py +752 -0
  426. opik/rest_api/attachments/raw_client.py +1125 -0
  427. opik/rest_api/attachments/types/__init__.py +15 -0
  428. opik/rest_api/attachments/types/attachment_list_request_entity_type.py +5 -0
  429. opik/rest_api/attachments/types/download_attachment_request_entity_type.py +5 -0
  430. opik/rest_api/attachments/types/start_multipart_upload_request_entity_type.py +5 -0
  431. opik/rest_api/attachments/types/upload_attachment_request_entity_type.py +5 -0
  432. opik/rest_api/automation_rule_evaluators/__init__.py +2 -0
  433. opik/rest_api/automation_rule_evaluators/client.py +182 -1162
  434. opik/rest_api/automation_rule_evaluators/raw_client.py +598 -0
  435. opik/rest_api/chat_completions/__init__.py +2 -0
  436. opik/rest_api/chat_completions/client.py +115 -149
  437. opik/rest_api/chat_completions/raw_client.py +339 -0
  438. opik/rest_api/check/__init__.py +2 -0
  439. opik/rest_api/check/client.py +88 -106
  440. opik/rest_api/check/raw_client.py +258 -0
  441. opik/rest_api/client.py +112 -212
  442. opik/rest_api/core/__init__.py +5 -0
  443. opik/rest_api/core/api_error.py +12 -6
  444. opik/rest_api/core/client_wrapper.py +4 -14
  445. opik/rest_api/core/datetime_utils.py +1 -3
  446. opik/rest_api/core/file.py +2 -5
  447. opik/rest_api/core/http_client.py +42 -120
  448. opik/rest_api/core/http_response.py +55 -0
  449. opik/rest_api/core/jsonable_encoder.py +1 -4
  450. opik/rest_api/core/pydantic_utilities.py +79 -147
  451. opik/rest_api/core/query_encoder.py +1 -3
  452. opik/rest_api/core/serialization.py +10 -10
  453. opik/rest_api/dashboards/__init__.py +4 -0
  454. opik/rest_api/dashboards/client.py +462 -0
  455. opik/rest_api/dashboards/raw_client.py +648 -0
  456. opik/rest_api/datasets/__init__.py +5 -0
  457. opik/rest_api/datasets/client.py +1638 -1091
  458. opik/rest_api/datasets/raw_client.py +3389 -0
  459. opik/rest_api/datasets/types/__init__.py +8 -0
  460. opik/rest_api/datasets/types/dataset_update_visibility.py +5 -0
  461. opik/rest_api/datasets/types/dataset_write_visibility.py +5 -0
  462. opik/rest_api/errors/__init__.py +2 -0
  463. opik/rest_api/errors/bad_request_error.py +4 -3
  464. opik/rest_api/errors/conflict_error.py +4 -3
  465. opik/rest_api/errors/forbidden_error.py +4 -2
  466. opik/rest_api/errors/not_found_error.py +4 -3
  467. opik/rest_api/errors/not_implemented_error.py +4 -3
  468. opik/rest_api/errors/unauthorized_error.py +4 -3
  469. opik/rest_api/errors/unprocessable_entity_error.py +4 -3
  470. opik/rest_api/experiments/__init__.py +5 -0
  471. opik/rest_api/experiments/client.py +676 -752
  472. opik/rest_api/experiments/raw_client.py +1872 -0
  473. opik/rest_api/experiments/types/__init__.py +10 -0
  474. opik/rest_api/experiments/types/experiment_update_status.py +5 -0
  475. opik/rest_api/experiments/types/experiment_update_type.py +5 -0
  476. opik/rest_api/experiments/types/experiment_write_status.py +5 -0
  477. opik/rest_api/experiments/types/experiment_write_type.py +5 -0
  478. opik/rest_api/feedback_definitions/__init__.py +2 -0
  479. opik/rest_api/feedback_definitions/client.py +96 -370
  480. opik/rest_api/feedback_definitions/raw_client.py +541 -0
  481. opik/rest_api/feedback_definitions/types/__init__.py +2 -0
  482. opik/rest_api/feedback_definitions/types/find_feedback_definitions_request_type.py +1 -3
  483. opik/rest_api/guardrails/__init__.py +4 -0
  484. opik/rest_api/guardrails/client.py +104 -0
  485. opik/rest_api/guardrails/raw_client.py +102 -0
  486. opik/rest_api/llm_provider_key/__init__.py +2 -0
  487. opik/rest_api/llm_provider_key/client.py +166 -440
  488. opik/rest_api/llm_provider_key/raw_client.py +643 -0
  489. opik/rest_api/llm_provider_key/types/__init__.py +2 -0
  490. opik/rest_api/llm_provider_key/types/provider_api_key_write_provider.py +1 -1
  491. opik/rest_api/manual_evaluation/__init__.py +4 -0
  492. opik/rest_api/manual_evaluation/client.py +347 -0
  493. opik/rest_api/manual_evaluation/raw_client.py +543 -0
  494. opik/rest_api/open_telemetry_ingestion/__init__.py +2 -0
  495. opik/rest_api/open_telemetry_ingestion/client.py +38 -63
  496. opik/rest_api/open_telemetry_ingestion/raw_client.py +88 -0
  497. opik/rest_api/optimizations/__init__.py +7 -0
  498. opik/rest_api/optimizations/client.py +704 -0
  499. opik/rest_api/optimizations/raw_client.py +920 -0
  500. opik/rest_api/optimizations/types/__init__.py +7 -0
  501. opik/rest_api/optimizations/types/optimization_update_status.py +7 -0
  502. opik/rest_api/projects/__init__.py +10 -1
  503. opik/rest_api/projects/client.py +180 -855
  504. opik/rest_api/projects/raw_client.py +1216 -0
  505. opik/rest_api/projects/types/__init__.py +11 -4
  506. opik/rest_api/projects/types/project_metric_request_public_interval.py +1 -3
  507. opik/rest_api/projects/types/project_metric_request_public_metric_type.py +11 -1
  508. opik/rest_api/projects/types/project_update_visibility.py +5 -0
  509. opik/rest_api/projects/types/project_write_visibility.py +5 -0
  510. opik/rest_api/prompts/__init__.py +4 -2
  511. opik/rest_api/prompts/client.py +381 -970
  512. opik/rest_api/prompts/raw_client.py +1634 -0
  513. opik/rest_api/prompts/types/__init__.py +5 -1
  514. opik/rest_api/prompts/types/create_prompt_version_detail_template_structure.py +5 -0
  515. opik/rest_api/prompts/types/prompt_write_template_structure.py +5 -0
  516. opik/rest_api/raw_client.py +156 -0
  517. opik/rest_api/redirect/__init__.py +4 -0
  518. opik/rest_api/redirect/client.py +375 -0
  519. opik/rest_api/redirect/raw_client.py +566 -0
  520. opik/rest_api/service_toggles/__init__.py +4 -0
  521. opik/rest_api/service_toggles/client.py +91 -0
  522. opik/rest_api/service_toggles/raw_client.py +93 -0
  523. opik/rest_api/spans/__init__.py +2 -0
  524. opik/rest_api/spans/client.py +659 -1354
  525. opik/rest_api/spans/raw_client.py +2383 -0
  526. opik/rest_api/spans/types/__init__.py +2 -0
  527. opik/rest_api/spans/types/find_feedback_score_names_1_request_type.py +1 -3
  528. opik/rest_api/spans/types/get_span_stats_request_type.py +1 -3
  529. opik/rest_api/spans/types/get_spans_by_project_request_type.py +1 -3
  530. opik/rest_api/spans/types/span_search_stream_request_public_type.py +1 -3
  531. opik/rest_api/system_usage/__init__.py +2 -0
  532. opik/rest_api/system_usage/client.py +157 -216
  533. opik/rest_api/system_usage/raw_client.py +455 -0
  534. opik/rest_api/traces/__init__.py +2 -0
  535. opik/rest_api/traces/client.py +2102 -1625
  536. opik/rest_api/traces/raw_client.py +4144 -0
  537. opik/rest_api/types/__init__.py +629 -24
  538. opik/rest_api/types/aggregation_data.py +27 -0
  539. opik/rest_api/types/alert.py +33 -0
  540. opik/rest_api/types/alert_alert_type.py +5 -0
  541. opik/rest_api/types/alert_page_public.py +24 -0
  542. opik/rest_api/types/alert_public.py +33 -0
  543. opik/rest_api/types/alert_public_alert_type.py +5 -0
  544. opik/rest_api/types/alert_trigger.py +27 -0
  545. opik/rest_api/types/alert_trigger_config.py +28 -0
  546. opik/rest_api/types/alert_trigger_config_public.py +28 -0
  547. opik/rest_api/types/alert_trigger_config_public_type.py +10 -0
  548. opik/rest_api/types/alert_trigger_config_type.py +10 -0
  549. opik/rest_api/types/alert_trigger_config_write.py +22 -0
  550. opik/rest_api/types/alert_trigger_config_write_type.py +10 -0
  551. opik/rest_api/types/alert_trigger_event_type.py +19 -0
  552. opik/rest_api/types/alert_trigger_public.py +27 -0
  553. opik/rest_api/types/alert_trigger_public_event_type.py +19 -0
  554. opik/rest_api/types/alert_trigger_write.py +23 -0
  555. opik/rest_api/types/alert_trigger_write_event_type.py +19 -0
  556. opik/rest_api/types/alert_write.py +28 -0
  557. opik/rest_api/types/alert_write_alert_type.py +5 -0
  558. opik/rest_api/types/annotation_queue.py +42 -0
  559. opik/rest_api/types/annotation_queue_batch.py +27 -0
  560. opik/rest_api/types/{json_schema_element.py → annotation_queue_item_ids.py} +5 -7
  561. opik/rest_api/types/annotation_queue_page_public.py +28 -0
  562. opik/rest_api/types/annotation_queue_public.py +38 -0
  563. opik/rest_api/types/annotation_queue_public_scope.py +5 -0
  564. opik/rest_api/types/{workspace_metadata.py → annotation_queue_reviewer.py} +6 -7
  565. opik/rest_api/types/annotation_queue_reviewer_public.py +20 -0
  566. opik/rest_api/types/annotation_queue_scope.py +5 -0
  567. opik/rest_api/types/annotation_queue_write.py +31 -0
  568. opik/rest_api/types/annotation_queue_write_scope.py +5 -0
  569. opik/rest_api/types/assistant_message.py +7 -8
  570. opik/rest_api/types/assistant_message_role.py +1 -3
  571. opik/rest_api/types/attachment.py +22 -0
  572. opik/rest_api/types/attachment_page.py +28 -0
  573. opik/rest_api/types/audio_url.py +19 -0
  574. opik/rest_api/types/audio_url_public.py +19 -0
  575. opik/rest_api/types/audio_url_write.py +19 -0
  576. opik/rest_api/types/automation_rule_evaluator.py +160 -0
  577. opik/rest_api/types/automation_rule_evaluator_llm_as_judge.py +6 -6
  578. opik/rest_api/types/automation_rule_evaluator_llm_as_judge_public.py +6 -6
  579. opik/rest_api/types/automation_rule_evaluator_llm_as_judge_write.py +6 -6
  580. opik/rest_api/types/automation_rule_evaluator_object_object_public.py +155 -0
  581. opik/rest_api/types/automation_rule_evaluator_page_public.py +6 -6
  582. opik/rest_api/types/automation_rule_evaluator_public.py +155 -0
  583. opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge.py +22 -0
  584. opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_public.py +22 -0
  585. opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_write.py +22 -0
  586. opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python.py +22 -0
  587. opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python_public.py +22 -0
  588. opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python_write.py +22 -0
  589. opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge.py +22 -0
  590. opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_public.py +22 -0
  591. opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_write.py +22 -0
  592. opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python.py +22 -0
  593. opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_public.py +22 -0
  594. opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_write.py +22 -0
  595. opik/rest_api/types/automation_rule_evaluator_update.py +143 -0
  596. opik/rest_api/types/automation_rule_evaluator_update_llm_as_judge.py +6 -6
  597. opik/rest_api/types/automation_rule_evaluator_update_span_llm_as_judge.py +22 -0
  598. opik/rest_api/types/automation_rule_evaluator_update_span_user_defined_metric_python.py +22 -0
  599. opik/rest_api/types/automation_rule_evaluator_update_trace_thread_llm_as_judge.py +22 -0
  600. opik/rest_api/types/automation_rule_evaluator_update_trace_thread_user_defined_metric_python.py +22 -0
  601. opik/rest_api/types/automation_rule_evaluator_update_user_defined_metric_python.py +6 -6
  602. opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python.py +6 -6
  603. opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_public.py +6 -6
  604. opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_write.py +6 -6
  605. opik/rest_api/types/automation_rule_evaluator_write.py +143 -0
  606. opik/rest_api/types/avg_value_stat_public.py +3 -5
  607. opik/rest_api/types/batch_delete.py +3 -5
  608. opik/rest_api/types/batch_delete_by_project.py +20 -0
  609. opik/rest_api/types/bi_information.py +3 -5
  610. opik/rest_api/types/bi_information_response.py +4 -6
  611. opik/rest_api/types/boolean_feedback_definition.py +25 -0
  612. opik/rest_api/types/boolean_feedback_definition_create.py +20 -0
  613. opik/rest_api/types/boolean_feedback_definition_public.py +25 -0
  614. opik/rest_api/types/boolean_feedback_definition_update.py +20 -0
  615. opik/rest_api/types/boolean_feedback_detail.py +29 -0
  616. opik/rest_api/types/boolean_feedback_detail_create.py +29 -0
  617. opik/rest_api/types/boolean_feedback_detail_public.py +29 -0
  618. opik/rest_api/types/boolean_feedback_detail_update.py +29 -0
  619. opik/rest_api/types/categorical_feedback_definition.py +5 -7
  620. opik/rest_api/types/categorical_feedback_definition_create.py +4 -6
  621. opik/rest_api/types/categorical_feedback_definition_public.py +5 -7
  622. opik/rest_api/types/categorical_feedback_definition_update.py +4 -6
  623. opik/rest_api/types/categorical_feedback_detail.py +3 -5
  624. opik/rest_api/types/categorical_feedback_detail_create.py +3 -5
  625. opik/rest_api/types/categorical_feedback_detail_public.py +3 -5
  626. opik/rest_api/types/categorical_feedback_detail_update.py +3 -5
  627. opik/rest_api/types/chat_completion_choice.py +4 -6
  628. opik/rest_api/types/chat_completion_response.py +5 -6
  629. opik/rest_api/types/check.py +22 -0
  630. opik/rest_api/types/{json_node_compare.py → check_name.py} +1 -1
  631. opik/rest_api/types/check_public.py +22 -0
  632. opik/rest_api/types/check_public_name.py +5 -0
  633. opik/rest_api/types/check_public_result.py +5 -0
  634. opik/rest_api/types/check_result.py +5 -0
  635. opik/rest_api/types/chunked_output_json_node.py +4 -6
  636. opik/rest_api/types/chunked_output_json_node_public.py +4 -6
  637. opik/rest_api/types/chunked_output_json_node_public_type.py +6 -10
  638. opik/rest_api/types/chunked_output_json_node_type.py +6 -10
  639. opik/rest_api/types/column.py +8 -10
  640. opik/rest_api/types/column_compare.py +8 -10
  641. opik/rest_api/types/column_public.py +8 -10
  642. opik/rest_api/types/column_types_item.py +1 -3
  643. opik/rest_api/types/comment.py +4 -6
  644. opik/rest_api/types/comment_compare.py +4 -6
  645. opik/rest_api/types/comment_public.py +4 -6
  646. opik/rest_api/types/complete_multipart_upload_request.py +33 -0
  647. opik/rest_api/types/complete_multipart_upload_request_entity_type.py +5 -0
  648. opik/rest_api/types/completion_tokens_details.py +3 -5
  649. opik/rest_api/types/count_value_stat_public.py +3 -5
  650. opik/rest_api/types/dashboard_page_public.py +24 -0
  651. opik/rest_api/types/dashboard_public.py +30 -0
  652. opik/rest_api/types/data_point_double.py +21 -0
  653. opik/rest_api/types/data_point_number_public.py +3 -5
  654. opik/rest_api/types/dataset.py +14 -6
  655. opik/rest_api/types/dataset_expansion.py +42 -0
  656. opik/rest_api/types/dataset_expansion_response.py +39 -0
  657. opik/rest_api/types/dataset_item.py +9 -8
  658. opik/rest_api/types/dataset_item_batch.py +3 -5
  659. opik/rest_api/types/dataset_item_changes_public.py +5 -0
  660. opik/rest_api/types/dataset_item_compare.py +9 -8
  661. opik/rest_api/types/dataset_item_compare_source.py +1 -3
  662. opik/rest_api/types/dataset_item_filter.py +27 -0
  663. opik/rest_api/types/dataset_item_filter_operator.py +21 -0
  664. opik/rest_api/types/dataset_item_page_compare.py +10 -7
  665. opik/rest_api/types/dataset_item_page_public.py +10 -7
  666. opik/rest_api/types/dataset_item_public.py +9 -8
  667. opik/rest_api/types/dataset_item_public_source.py +1 -3
  668. opik/rest_api/types/dataset_item_source.py +1 -3
  669. opik/rest_api/types/dataset_item_update.py +39 -0
  670. opik/rest_api/types/dataset_item_write.py +5 -6
  671. opik/rest_api/types/dataset_item_write_source.py +1 -3
  672. opik/rest_api/types/dataset_page_public.py +9 -6
  673. opik/rest_api/types/dataset_public.py +14 -6
  674. opik/rest_api/types/dataset_public_status.py +5 -0
  675. opik/rest_api/types/dataset_public_visibility.py +5 -0
  676. opik/rest_api/types/dataset_status.py +5 -0
  677. opik/rest_api/types/dataset_version_diff.py +22 -0
  678. opik/rest_api/types/dataset_version_diff_stats.py +24 -0
  679. opik/rest_api/types/dataset_version_page_public.py +23 -0
  680. opik/rest_api/types/dataset_version_public.py +59 -0
  681. opik/rest_api/types/dataset_version_summary.py +46 -0
  682. opik/rest_api/types/dataset_version_summary_public.py +46 -0
  683. opik/rest_api/types/dataset_visibility.py +5 -0
  684. opik/rest_api/types/delete_attachments_request.py +23 -0
  685. opik/rest_api/types/delete_attachments_request_entity_type.py +5 -0
  686. opik/rest_api/types/delete_feedback_score.py +4 -5
  687. opik/rest_api/types/delete_ids_holder.py +19 -0
  688. opik/rest_api/types/delta.py +7 -9
  689. opik/rest_api/types/error_count_with_deviation.py +21 -0
  690. opik/rest_api/types/error_count_with_deviation_detailed.py +21 -0
  691. opik/rest_api/types/error_info.py +3 -5
  692. opik/rest_api/types/error_info_experiment_item_bulk_write_view.py +21 -0
  693. opik/rest_api/types/error_info_public.py +3 -5
  694. opik/rest_api/types/error_info_write.py +3 -5
  695. opik/rest_api/types/error_message.py +3 -5
  696. opik/rest_api/types/error_message_detail.py +3 -5
  697. opik/rest_api/types/error_message_detailed.py +3 -5
  698. opik/rest_api/types/error_message_public.py +3 -5
  699. opik/rest_api/types/experiment.py +21 -10
  700. opik/rest_api/types/experiment_group_aggregations_response.py +20 -0
  701. opik/rest_api/types/experiment_group_response.py +22 -0
  702. opik/rest_api/types/experiment_item.py +14 -11
  703. opik/rest_api/types/experiment_item_bulk_record.py +27 -0
  704. opik/rest_api/types/experiment_item_bulk_record_experiment_item_bulk_write_view.py +27 -0
  705. opik/rest_api/types/experiment_item_bulk_upload.py +27 -0
  706. opik/rest_api/types/experiment_item_compare.py +14 -11
  707. opik/rest_api/types/experiment_item_compare_trace_visibility_mode.py +5 -0
  708. opik/rest_api/types/experiment_item_public.py +6 -6
  709. opik/rest_api/types/experiment_item_public_trace_visibility_mode.py +5 -0
  710. opik/rest_api/types/experiment_item_trace_visibility_mode.py +5 -0
  711. opik/rest_api/types/experiment_page_public.py +9 -6
  712. opik/rest_api/types/experiment_public.py +21 -10
  713. opik/rest_api/types/experiment_public_status.py +5 -0
  714. opik/rest_api/types/experiment_public_type.py +5 -0
  715. opik/rest_api/types/experiment_score.py +20 -0
  716. opik/rest_api/types/experiment_score_public.py +20 -0
  717. opik/rest_api/types/experiment_score_write.py +20 -0
  718. opik/rest_api/types/experiment_status.py +5 -0
  719. opik/rest_api/types/experiment_type.py +5 -0
  720. opik/rest_api/types/export_trace_service_request.py +5 -0
  721. opik/rest_api/types/feedback.py +40 -27
  722. opik/rest_api/types/feedback_create.py +27 -13
  723. opik/rest_api/types/feedback_definition_page_public.py +4 -6
  724. opik/rest_api/types/feedback_object_public.py +40 -27
  725. opik/rest_api/types/feedback_public.py +40 -27
  726. opik/rest_api/types/feedback_score.py +7 -7
  727. opik/rest_api/types/feedback_score_average.py +3 -5
  728. opik/rest_api/types/feedback_score_average_detailed.py +3 -5
  729. opik/rest_api/types/feedback_score_average_public.py +3 -5
  730. opik/rest_api/types/feedback_score_batch.py +4 -6
  731. opik/rest_api/types/feedback_score_batch_item.py +6 -6
  732. opik/rest_api/types/feedback_score_batch_item_source.py +1 -3
  733. opik/rest_api/types/feedback_score_batch_item_thread.py +32 -0
  734. opik/rest_api/types/feedback_score_batch_item_thread_source.py +5 -0
  735. opik/rest_api/types/feedback_score_compare.py +7 -7
  736. opik/rest_api/types/feedback_score_compare_source.py +1 -3
  737. opik/rest_api/types/feedback_score_experiment_item_bulk_write_view.py +31 -0
  738. opik/rest_api/types/feedback_score_experiment_item_bulk_write_view_source.py +5 -0
  739. opik/rest_api/types/feedback_score_names.py +4 -6
  740. opik/rest_api/types/feedback_score_public.py +11 -7
  741. opik/rest_api/types/feedback_score_public_source.py +1 -3
  742. opik/rest_api/types/feedback_score_source.py +1 -3
  743. opik/rest_api/types/feedback_update.py +27 -13
  744. opik/rest_api/types/function.py +4 -7
  745. opik/rest_api/types/function_call.py +3 -5
  746. opik/rest_api/types/group_content.py +19 -0
  747. opik/rest_api/types/group_content_with_aggregations.py +21 -0
  748. opik/rest_api/types/group_detail.py +19 -0
  749. opik/rest_api/types/group_details.py +20 -0
  750. opik/rest_api/types/guardrail.py +34 -0
  751. opik/rest_api/types/guardrail_batch.py +20 -0
  752. opik/rest_api/types/guardrail_name.py +5 -0
  753. opik/rest_api/types/guardrail_result.py +5 -0
  754. opik/rest_api/types/guardrail_write.py +33 -0
  755. opik/rest_api/types/guardrail_write_name.py +5 -0
  756. opik/rest_api/types/guardrail_write_result.py +5 -0
  757. opik/rest_api/types/guardrails_validation.py +21 -0
  758. opik/rest_api/types/guardrails_validation_public.py +21 -0
  759. opik/rest_api/types/ids_holder.py +19 -0
  760. opik/rest_api/types/image_url.py +20 -0
  761. opik/rest_api/types/image_url_public.py +20 -0
  762. opik/rest_api/types/image_url_write.py +20 -0
  763. opik/rest_api/types/json_list_string.py +7 -0
  764. opik/rest_api/types/json_list_string_compare.py +7 -0
  765. opik/rest_api/types/json_list_string_experiment_item_bulk_write_view.py +7 -0
  766. opik/rest_api/types/json_list_string_public.py +7 -0
  767. opik/rest_api/types/json_list_string_write.py +7 -0
  768. opik/rest_api/types/json_schema.py +5 -8
  769. opik/rest_api/types/llm_as_judge_code.py +8 -12
  770. opik/rest_api/types/llm_as_judge_code_public.py +8 -12
  771. opik/rest_api/types/llm_as_judge_code_write.py +8 -12
  772. opik/rest_api/types/llm_as_judge_message.py +9 -7
  773. opik/rest_api/types/llm_as_judge_message_content.py +26 -0
  774. opik/rest_api/types/llm_as_judge_message_content_public.py +26 -0
  775. opik/rest_api/types/llm_as_judge_message_content_write.py +26 -0
  776. opik/rest_api/types/llm_as_judge_message_public.py +9 -7
  777. opik/rest_api/types/llm_as_judge_message_public_role.py +1 -1
  778. opik/rest_api/types/llm_as_judge_message_role.py +1 -1
  779. opik/rest_api/types/llm_as_judge_message_write.py +9 -7
  780. opik/rest_api/types/llm_as_judge_message_write_role.py +1 -1
  781. opik/rest_api/types/llm_as_judge_model_parameters.py +6 -5
  782. opik/rest_api/types/llm_as_judge_model_parameters_public.py +6 -5
  783. opik/rest_api/types/llm_as_judge_model_parameters_write.py +6 -5
  784. opik/rest_api/types/llm_as_judge_output_schema.py +4 -6
  785. opik/rest_api/types/llm_as_judge_output_schema_public.py +4 -6
  786. opik/rest_api/types/llm_as_judge_output_schema_public_type.py +1 -3
  787. opik/rest_api/types/llm_as_judge_output_schema_type.py +1 -3
  788. opik/rest_api/types/llm_as_judge_output_schema_write.py +4 -6
  789. opik/rest_api/types/llm_as_judge_output_schema_write_type.py +1 -3
  790. opik/rest_api/types/log_item.py +5 -7
  791. opik/rest_api/types/log_item_level.py +1 -3
  792. opik/rest_api/types/log_page.py +4 -6
  793. opik/rest_api/types/manual_evaluation_request.py +38 -0
  794. opik/rest_api/types/manual_evaluation_request_entity_type.py +5 -0
  795. opik/rest_api/types/manual_evaluation_response.py +27 -0
  796. opik/rest_api/types/multipart_upload_part.py +20 -0
  797. opik/rest_api/types/numerical_feedback_definition.py +5 -7
  798. opik/rest_api/types/numerical_feedback_definition_create.py +4 -6
  799. opik/rest_api/types/numerical_feedback_definition_public.py +5 -7
  800. opik/rest_api/types/numerical_feedback_definition_update.py +4 -6
  801. opik/rest_api/types/numerical_feedback_detail.py +3 -5
  802. opik/rest_api/types/numerical_feedback_detail_create.py +3 -5
  803. opik/rest_api/types/numerical_feedback_detail_public.py +3 -5
  804. opik/rest_api/types/numerical_feedback_detail_update.py +3 -5
  805. opik/rest_api/types/optimization.py +37 -0
  806. opik/rest_api/types/optimization_page_public.py +28 -0
  807. opik/rest_api/types/optimization_public.py +37 -0
  808. opik/rest_api/types/optimization_public_status.py +7 -0
  809. opik/rest_api/types/optimization_status.py +7 -0
  810. opik/rest_api/types/optimization_studio_config.py +27 -0
  811. opik/rest_api/types/optimization_studio_config_public.py +27 -0
  812. opik/rest_api/types/optimization_studio_config_write.py +27 -0
  813. opik/rest_api/types/optimization_studio_log.py +22 -0
  814. opik/rest_api/types/optimization_write.py +30 -0
  815. opik/rest_api/types/optimization_write_status.py +7 -0
  816. opik/rest_api/types/page_columns.py +4 -6
  817. opik/rest_api/types/percentage_value_stat_public.py +4 -6
  818. opik/rest_api/types/percentage_values.py +8 -16
  819. opik/rest_api/types/percentage_values_detailed.py +8 -16
  820. opik/rest_api/types/percentage_values_public.py +8 -16
  821. opik/rest_api/types/project.py +12 -7
  822. opik/rest_api/types/project_detailed.py +12 -7
  823. opik/rest_api/types/project_detailed_visibility.py +5 -0
  824. opik/rest_api/types/project_metric_response_public.py +5 -9
  825. opik/rest_api/types/project_metric_response_public_interval.py +1 -3
  826. opik/rest_api/types/project_metric_response_public_metric_type.py +11 -1
  827. opik/rest_api/types/project_page_public.py +8 -10
  828. opik/rest_api/types/project_public.py +6 -6
  829. opik/rest_api/types/project_public_visibility.py +5 -0
  830. opik/rest_api/types/project_reference.py +31 -0
  831. opik/rest_api/types/project_reference_public.py +31 -0
  832. opik/rest_api/types/project_stat_item_object_public.py +8 -17
  833. opik/rest_api/types/project_stats_public.py +4 -6
  834. opik/rest_api/types/project_stats_summary.py +4 -6
  835. opik/rest_api/types/project_stats_summary_item.py +9 -6
  836. opik/rest_api/types/project_visibility.py +5 -0
  837. opik/rest_api/types/prompt.py +12 -7
  838. opik/rest_api/types/prompt_detail.py +12 -7
  839. opik/rest_api/types/prompt_detail_template_structure.py +5 -0
  840. opik/rest_api/types/prompt_page_public.py +9 -6
  841. opik/rest_api/types/prompt_public.py +11 -6
  842. opik/rest_api/types/prompt_public_template_structure.py +5 -0
  843. opik/rest_api/types/prompt_template_structure.py +5 -0
  844. opik/rest_api/types/prompt_tokens_details.py +19 -0
  845. opik/rest_api/types/prompt_version.py +7 -6
  846. opik/rest_api/types/prompt_version_detail.py +7 -6
  847. opik/rest_api/types/prompt_version_detail_template_structure.py +5 -0
  848. opik/rest_api/types/prompt_version_link.py +4 -5
  849. opik/rest_api/types/prompt_version_link_public.py +4 -5
  850. opik/rest_api/types/prompt_version_link_write.py +3 -5
  851. opik/rest_api/types/prompt_version_page_public.py +9 -6
  852. opik/rest_api/types/prompt_version_public.py +7 -6
  853. opik/rest_api/types/prompt_version_public_template_structure.py +5 -0
  854. opik/rest_api/types/prompt_version_template_structure.py +5 -0
  855. opik/rest_api/types/prompt_version_update.py +33 -0
  856. opik/rest_api/types/provider_api_key.py +18 -8
  857. opik/rest_api/types/provider_api_key_page_public.py +27 -0
  858. opik/rest_api/types/provider_api_key_provider.py +1 -1
  859. opik/rest_api/types/provider_api_key_public.py +18 -8
  860. opik/rest_api/types/provider_api_key_public_provider.py +1 -1
  861. opik/rest_api/types/response_format.py +5 -7
  862. opik/rest_api/types/response_format_type.py +1 -3
  863. opik/rest_api/types/result.py +21 -0
  864. opik/rest_api/types/results_number_public.py +4 -6
  865. opik/rest_api/types/score_name.py +4 -5
  866. opik/rest_api/types/service_toggles_config.py +44 -0
  867. opik/rest_api/types/span.py +13 -15
  868. opik/rest_api/types/span_batch.py +4 -6
  869. opik/rest_api/types/span_enrichment_options.py +31 -0
  870. opik/rest_api/types/span_experiment_item_bulk_write_view.py +39 -0
  871. opik/rest_api/types/span_experiment_item_bulk_write_view_type.py +5 -0
  872. opik/rest_api/types/span_filter.py +23 -0
  873. opik/rest_api/types/span_filter_operator.py +21 -0
  874. opik/rest_api/types/span_filter_public.py +4 -6
  875. opik/rest_api/types/span_filter_public_operator.py +2 -0
  876. opik/rest_api/types/span_filter_write.py +23 -0
  877. opik/rest_api/types/span_filter_write_operator.py +21 -0
  878. opik/rest_api/types/span_llm_as_judge_code.py +27 -0
  879. opik/rest_api/types/span_llm_as_judge_code_public.py +27 -0
  880. opik/rest_api/types/span_llm_as_judge_code_write.py +27 -0
  881. opik/rest_api/types/span_page_public.py +9 -6
  882. opik/rest_api/types/span_public.py +19 -16
  883. opik/rest_api/types/span_public_type.py +1 -1
  884. opik/rest_api/types/span_type.py +1 -1
  885. opik/rest_api/types/span_update.py +46 -0
  886. opik/rest_api/types/span_update_type.py +5 -0
  887. opik/rest_api/types/span_user_defined_metric_python_code.py +20 -0
  888. opik/rest_api/types/span_user_defined_metric_python_code_public.py +20 -0
  889. opik/rest_api/types/span_user_defined_metric_python_code_write.py +20 -0
  890. opik/rest_api/types/span_write.py +13 -14
  891. opik/rest_api/types/span_write_type.py +1 -1
  892. opik/rest_api/types/spans_count_response.py +20 -0
  893. opik/rest_api/types/start_multipart_upload_response.py +20 -0
  894. opik/rest_api/types/stream_options.py +3 -5
  895. opik/rest_api/types/studio_evaluation.py +20 -0
  896. opik/rest_api/types/studio_evaluation_public.py +20 -0
  897. opik/rest_api/types/studio_evaluation_write.py +20 -0
  898. opik/rest_api/types/studio_llm_model.py +21 -0
  899. opik/rest_api/types/studio_llm_model_public.py +21 -0
  900. opik/rest_api/types/studio_llm_model_write.py +21 -0
  901. opik/rest_api/types/studio_message.py +20 -0
  902. opik/rest_api/types/studio_message_public.py +20 -0
  903. opik/rest_api/types/studio_message_write.py +20 -0
  904. opik/rest_api/types/studio_metric.py +21 -0
  905. opik/rest_api/types/studio_metric_public.py +21 -0
  906. opik/rest_api/types/studio_metric_write.py +21 -0
  907. opik/rest_api/types/studio_optimizer.py +21 -0
  908. opik/rest_api/types/studio_optimizer_public.py +21 -0
  909. opik/rest_api/types/studio_optimizer_write.py +21 -0
  910. opik/rest_api/types/studio_prompt.py +20 -0
  911. opik/rest_api/types/studio_prompt_public.py +20 -0
  912. opik/rest_api/types/studio_prompt_write.py +20 -0
  913. opik/rest_api/types/tool.py +4 -6
  914. opik/rest_api/types/tool_call.py +4 -6
  915. opik/rest_api/types/trace.py +26 -12
  916. opik/rest_api/types/trace_batch.py +4 -6
  917. opik/rest_api/types/trace_count_response.py +4 -6
  918. opik/rest_api/types/trace_enrichment_options.py +32 -0
  919. opik/rest_api/types/trace_experiment_item_bulk_write_view.py +41 -0
  920. opik/rest_api/types/trace_filter.py +23 -0
  921. opik/rest_api/types/trace_filter_operator.py +21 -0
  922. opik/rest_api/types/trace_filter_public.py +23 -0
  923. opik/rest_api/types/trace_filter_public_operator.py +21 -0
  924. opik/rest_api/types/trace_filter_write.py +23 -0
  925. opik/rest_api/types/trace_filter_write_operator.py +21 -0
  926. opik/rest_api/types/trace_page_public.py +8 -10
  927. opik/rest_api/types/trace_public.py +27 -13
  928. opik/rest_api/types/trace_public_visibility_mode.py +5 -0
  929. opik/rest_api/types/trace_thread.py +18 -9
  930. opik/rest_api/types/trace_thread_filter.py +23 -0
  931. opik/rest_api/types/trace_thread_filter_operator.py +21 -0
  932. opik/rest_api/types/trace_thread_filter_public.py +23 -0
  933. opik/rest_api/types/trace_thread_filter_public_operator.py +21 -0
  934. opik/rest_api/types/trace_thread_filter_write.py +23 -0
  935. opik/rest_api/types/trace_thread_filter_write_operator.py +21 -0
  936. opik/rest_api/types/trace_thread_identifier.py +22 -0
  937. opik/rest_api/types/trace_thread_llm_as_judge_code.py +26 -0
  938. opik/rest_api/types/trace_thread_llm_as_judge_code_public.py +26 -0
  939. opik/rest_api/types/trace_thread_llm_as_judge_code_write.py +26 -0
  940. opik/rest_api/types/trace_thread_page.py +9 -6
  941. opik/rest_api/types/trace_thread_status.py +5 -0
  942. opik/rest_api/types/trace_thread_update.py +19 -0
  943. opik/rest_api/types/trace_thread_user_defined_metric_python_code.py +19 -0
  944. opik/rest_api/types/trace_thread_user_defined_metric_python_code_public.py +19 -0
  945. opik/rest_api/types/trace_thread_user_defined_metric_python_code_write.py +19 -0
  946. opik/rest_api/types/trace_update.py +39 -0
  947. opik/rest_api/types/trace_visibility_mode.py +5 -0
  948. opik/rest_api/types/trace_write.py +10 -11
  949. opik/rest_api/types/usage.py +6 -6
  950. opik/rest_api/types/user_defined_metric_python_code.py +3 -5
  951. opik/rest_api/types/user_defined_metric_python_code_public.py +3 -5
  952. opik/rest_api/types/user_defined_metric_python_code_write.py +3 -5
  953. opik/rest_api/types/value_entry.py +27 -0
  954. opik/rest_api/types/value_entry_compare.py +27 -0
  955. opik/rest_api/types/value_entry_compare_source.py +5 -0
  956. opik/rest_api/types/value_entry_experiment_item_bulk_write_view.py +27 -0
  957. opik/rest_api/types/value_entry_experiment_item_bulk_write_view_source.py +5 -0
  958. opik/rest_api/types/value_entry_public.py +27 -0
  959. opik/rest_api/types/value_entry_public_source.py +5 -0
  960. opik/rest_api/types/value_entry_source.py +5 -0
  961. opik/rest_api/types/video_url.py +19 -0
  962. opik/rest_api/types/video_url_public.py +19 -0
  963. opik/rest_api/types/video_url_write.py +19 -0
  964. opik/rest_api/types/webhook.py +28 -0
  965. opik/rest_api/types/webhook_examples.py +19 -0
  966. opik/rest_api/types/webhook_public.py +28 -0
  967. opik/rest_api/types/webhook_test_result.py +23 -0
  968. opik/rest_api/types/webhook_test_result_status.py +5 -0
  969. opik/rest_api/types/webhook_write.py +23 -0
  970. opik/rest_api/types/welcome_wizard_tracking.py +22 -0
  971. opik/rest_api/types/workspace_configuration.py +27 -0
  972. opik/rest_api/types/workspace_metric_request.py +24 -0
  973. opik/rest_api/types/workspace_metric_response.py +20 -0
  974. opik/rest_api/types/workspace_metrics_summary_request.py +23 -0
  975. opik/rest_api/types/workspace_metrics_summary_response.py +20 -0
  976. opik/rest_api/types/workspace_name_holder.py +19 -0
  977. opik/rest_api/types/workspace_spans_count.py +20 -0
  978. opik/rest_api/types/workspace_trace_count.py +3 -5
  979. opik/rest_api/welcome_wizard/__init__.py +4 -0
  980. opik/rest_api/welcome_wizard/client.py +195 -0
  981. opik/rest_api/welcome_wizard/raw_client.py +208 -0
  982. opik/rest_api/workspaces/__init__.py +2 -0
  983. opik/rest_api/workspaces/client.py +550 -77
  984. opik/rest_api/workspaces/raw_client.py +923 -0
  985. opik/rest_client_configurator/api.py +1 -0
  986. opik/rest_client_configurator/retry_decorator.py +1 -0
  987. opik/s3_httpx_client.py +67 -0
  988. opik/simulation/__init__.py +6 -0
  989. opik/simulation/simulated_user.py +99 -0
  990. opik/simulation/simulator.py +108 -0
  991. opik/synchronization.py +11 -24
  992. opik/tracing_runtime_config.py +48 -0
  993. opik/types.py +48 -2
  994. opik/url_helpers.py +13 -3
  995. opik/validation/chat_prompt_messages.py +241 -0
  996. opik/validation/feedback_score.py +4 -5
  997. opik/validation/parameter.py +122 -0
  998. opik/validation/parameters_validator.py +175 -0
  999. opik/validation/validator.py +30 -2
  1000. opik/validation/validator_helpers.py +147 -0
  1001. opik-1.9.71.dist-info/METADATA +370 -0
  1002. opik-1.9.71.dist-info/RECORD +1110 -0
  1003. {opik-1.6.4.dist-info → opik-1.9.71.dist-info}/WHEEL +1 -1
  1004. opik-1.9.71.dist-info/licenses/LICENSE +203 -0
  1005. opik/api_objects/prompt/prompt.py +0 -107
  1006. opik/api_objects/prompt/prompt_template.py +0 -35
  1007. opik/cli.py +0 -193
  1008. opik/evaluation/metrics/models.py +0 -8
  1009. opik/hooks.py +0 -13
  1010. opik/integrations/bedrock/chunks_aggregator.py +0 -55
  1011. opik/integrations/bedrock/helpers.py +0 -8
  1012. opik/integrations/langchain/google_run_helpers.py +0 -75
  1013. opik/integrations/langchain/openai_run_helpers.py +0 -122
  1014. opik/message_processing/message_processors.py +0 -203
  1015. opik/rest_api/types/delta_role.py +0 -7
  1016. opik/rest_api/types/json_object_schema.py +0 -34
  1017. opik-1.6.4.dist-info/METADATA +0 -270
  1018. opik-1.6.4.dist-info/RECORD +0 -507
  1019. /opik/integrations/bedrock/{stream_wrappers.py → converse/stream_wrappers.py} +0 -0
  1020. {opik-1.6.4.dist-info → opik-1.9.71.dist-info}/entry_points.txt +0 -0
  1021. {opik-1.6.4.dist-info → opik-1.9.71.dist-info}/top_level.txt +0 -0
@@ -2,10 +2,32 @@ import atexit
2
2
  import datetime
3
3
  import functools
4
4
  import logging
5
- from typing import Any, Dict, List, Optional, Union
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
29
+ from .threads import threads_client
30
+ from .trace import migration as trace_migration, trace_client
9
31
  from .. import (
10
32
  config,
11
33
  datetime_helpers,
@@ -16,31 +38,35 @@ from .. import (
16
38
  rest_client_configurator,
17
39
  url_helpers,
18
40
  )
19
- from ..message_processing import messages, streamer_constructors
41
+ from ..message_processing import (
42
+ messages,
43
+ streamer_constructors,
44
+ message_queue,
45
+ )
20
46
  from ..message_processing.batching import sequence_splitter
47
+ from ..message_processing.processors import message_processors_chain
21
48
  from ..rest_api import client as rest_api_client
22
49
  from ..rest_api.core.api_error import ApiError
23
- from ..rest_api.types import dataset_public, project_public, span_public, trace_public
24
- from ..types import ErrorInfoDict, FeedbackScoreDict, LLMProvider, SpanType
25
- from . import (
26
- constants,
27
- dataset,
28
- experiment,
29
- helpers,
30
- opik_query_language,
31
- span,
32
- trace,
33
- validation_helpers,
50
+ from ..rest_api.types import (
51
+ dataset_public,
52
+ project_public,
53
+ span_public,
54
+ trace_public,
55
+ span_filter_public,
56
+ trace_filter_public,
57
+ )
58
+ from ..types import (
59
+ BatchFeedbackScoreDict,
60
+ ErrorInfoDict,
61
+ FeedbackScoreDict,
62
+ LLMProvider,
63
+ SpanType,
34
64
  )
35
- from .dataset import rest_operations as dataset_rest_operations
36
- from .experiment import helpers as experiment_helpers
37
- from .experiment import rest_operations as experiment_rest_operations
38
- from .prompt import Prompt
39
- from .prompt.client import PromptClient
40
- from .trace import migration as trace_migration
41
65
 
42
66
  LOGGER = logging.getLogger(__name__)
43
67
 
68
+ T = TypeVar("T")
69
+
44
70
 
45
71
  class Opik:
46
72
  def __init__(
@@ -87,10 +113,6 @@ class Opik:
87
113
  self._use_batching = _use_batching
88
114
 
89
115
  self._initialize_streamer(
90
- base_url=config_.url_override,
91
- workers=config_.background_workers,
92
- api_key=config_.api_key,
93
- check_tls_certificate=config_.check_tls_certificate,
94
116
  use_batching=_use_batching,
95
117
  )
96
118
  atexit.register(self.end, timeout=self._flush_timeout)
@@ -103,31 +125,71 @@ class Opik:
103
125
  """
104
126
  return self._config.model_copy()
105
127
 
128
+ @property
129
+ def rest_client(self) -> rest_api_client.OpikApi:
130
+ """
131
+ Provides direct access to the underlying REST API client.
132
+
133
+ WARNING: This client is not guaranteed to be backward compatible with future SDK versions.
134
+ While it provides a convenient way to use the current REST API of Opik.
135
+ However, it's not considered safe to heavily rely on its API as Opik's REST API contracts may change.
136
+
137
+ Returns:
138
+ OpikApi: The REST client used by the Opik client.
139
+ """
140
+ return self._rest_client
141
+
142
+ @property
143
+ def project_name(self) -> str:
144
+ """
145
+ This property retrieves the name of the project associated with the instance.
146
+ It is a read-only property.
147
+
148
+ Returns:
149
+ str: The name of the project.
150
+ """
151
+ return self._project_name
152
+
106
153
  def _initialize_streamer(
107
154
  self,
108
- base_url: str,
109
- workers: int,
110
- api_key: Optional[str],
111
- check_tls_certificate: bool,
112
155
  use_batching: bool,
113
156
  ) -> None:
114
- httpx_client_ = httpx_client.get(
157
+ self._httpx_client = httpx_client.get(
115
158
  workspace=self._workspace,
116
- api_key=api_key,
117
- check_tls_certificate=check_tls_certificate,
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,
118
162
  )
119
163
  self._rest_client = rest_api_client.OpikApi(
120
- base_url=base_url,
121
- httpx_client=httpx_client_,
164
+ base_url=self._config.url_override,
165
+ httpx_client=self._httpx_client,
122
166
  )
123
167
  self._rest_client._client_wrapper._timeout = (
124
168
  httpx.USE_CLIENT_DEFAULT
125
169
  ) # See https://github.com/fern-api/fern/issues/5321
126
170
  rest_client_configurator.configure(self._rest_client)
171
+
172
+ max_queue_size = message_queue.calculate_max_queue_size(
173
+ maximal_queue_size=self._config.maximal_queue_size,
174
+ batch_factor=self._config.maximal_queue_size_batch_factor,
175
+ )
176
+
177
+ self.__internal_api__message_processor__ = (
178
+ message_processors_chain.create_message_processors_chain(
179
+ rest_client=self._rest_client
180
+ )
181
+ )
127
182
  self._streamer = streamer_constructors.construct_online_streamer(
128
- n_consumers=workers,
183
+ n_consumers=self._config.background_workers,
129
184
  rest_client=self._rest_client,
185
+ httpx_client=self._httpx_client,
130
186
  use_batching=use_batching,
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,
190
+ max_queue_size=max_queue_size,
191
+ message_processor=self.__internal_api__message_processor__,
192
+ url_override=self._config.url_override,
131
193
  )
132
194
 
133
195
  def _display_trace_url(self, trace_id: str, project_name: str) -> None:
@@ -173,13 +235,14 @@ class Opik:
173
235
  project_name: Optional[str] = None,
174
236
  error_info: Optional[ErrorInfoDict] = None,
175
237
  thread_id: Optional[str] = None,
238
+ attachments: Optional[List[Attachment]] = None,
176
239
  **ignored_kwargs: Any,
177
240
  ) -> trace.Trace:
178
241
  """
179
242
  Create and log a new trace.
180
243
 
181
244
  Args:
182
- id: The unique identifier for the trace, if not provided a new ID will be generated. Must be a valid [UUIDv7](https://uuid7.com/) ID.
245
+ id: The unique identifier for the trace, if not provided, a new ID will be generated. Must be a valid [UUIDv7](https://uuid7.com/) ID.
183
246
  name: The name of the trace.
184
247
  start_time: The start time of the trace. If not provided, the current local time will be used.
185
248
  end_time: The end time of the trace.
@@ -193,6 +256,7 @@ class Opik:
193
256
  error_info: The dictionary with error information (typically used when the trace function has failed).
194
257
  thread_id: Used to group multiple traces into a thread.
195
258
  The identifier is user-defined and has to be unique per project.
259
+ attachments: The list of attachments to be uploaded to the trace.
196
260
 
197
261
  Returns:
198
262
  trace.Trace: The created trace object.
@@ -201,6 +265,7 @@ class Opik:
201
265
  start_time = (
202
266
  start_time if start_time is not None else datetime_helpers.local_timestamp()
203
267
  )
268
+ last_updated_at = datetime_helpers.local_timestamp()
204
269
 
205
270
  if project_name is None:
206
271
  project_name = self._project_name
@@ -217,6 +282,7 @@ class Opik:
217
282
  tags=tags,
218
283
  error_info=error_info,
219
284
  thread_id=thread_id,
285
+ last_updated_at=last_updated_at,
220
286
  )
221
287
  self._streamer.put(create_trace_message)
222
288
  self._display_trace_url(trace_id=id, project_name=project_name)
@@ -225,12 +291,27 @@ class Opik:
225
291
  for feedback_score in feedback_scores:
226
292
  feedback_score["id"] = id
227
293
 
228
- self.log_traces_feedback_scores(feedback_scores, project_name)
294
+ self.log_traces_feedback_scores(
295
+ cast(List[BatchFeedbackScoreDict], feedback_scores), project_name
296
+ )
297
+
298
+ if attachments is not None:
299
+ for attachment_data in attachments:
300
+ self._streamer.put(
301
+ attachment_converters.attachment_to_message(
302
+ attachment_data=attachment_data,
303
+ entity_type="trace",
304
+ entity_id=id,
305
+ project_name=project_name,
306
+ url_override=self._config.url_override,
307
+ )
308
+ )
229
309
 
230
310
  return trace.Trace(
231
311
  id=id,
232
312
  message_streamer=self._streamer,
233
313
  project_name=project_name,
314
+ url_override=self._config.url_override,
234
315
  )
235
316
 
236
317
  def copy_traces(
@@ -248,7 +329,7 @@ class Opik:
248
329
 
249
330
  Note: This method is not optimized for large projects, if you run into any issues please raise
250
331
  an issue on GitHub. In addition, be aware that deleting traces that are linked to experiments
251
- will lead to inconsistancies in the UI.
332
+ will lead to inconsistencies in the UI.
252
333
 
253
334
  Args:
254
335
  project_name: The name of the project to copy traces from.
@@ -287,10 +368,10 @@ class Opik:
287
368
  )
288
369
 
289
370
  for trace_data_ in new_trace_data:
290
- self.trace(**trace_data_.__dict__)
371
+ self.trace(**trace_data_.as_parameters)
291
372
 
292
373
  for span_data_ in new_span_data:
293
- self.span(**span_data_.__dict__)
374
+ self.span(**span_data_.as_parameters)
294
375
 
295
376
  if delete_original_project:
296
377
  trace_ids = [trace_.id for trace_ in trace_data]
@@ -320,6 +401,7 @@ class Opik:
320
401
  provider: Optional[Union[str, LLMProvider]] = None,
321
402
  error_info: Optional[ErrorInfoDict] = None,
322
403
  total_cost: Optional[float] = None,
404
+ attachments: Optional[List[Attachment]] = None,
323
405
  ) -> span.Span:
324
406
  """
325
407
  Create and log a new span.
@@ -336,19 +418,20 @@ class Opik:
336
418
  input: The input data for the span. This can be any valid JSON serializable object.
337
419
  output: The output data for the span. This can be any valid JSON serializable object.
338
420
  tags: Tags associated with the span.
339
- feedback_scores: The list of feedback score dicts associated with the span. Dicts don't require to have an `id` value.
340
- project_name: The name of the project. If not set, the project name which was configured when Opik instance
421
+ feedback_scores: The list of feedback score dicts associated with the span. Dicts don't require having an `id` value.
422
+ project_name: The name of the project. If not set, the project name which was configured when the Opik instance
341
423
  was created will be used.
342
- usage: Usage data for the span. In order for input, output and total tokens to be visible in the UI,
343
- the usage must contain OpenAI-formatted keys (they can be passed additionaly to original usage on the top level of the dict): prompt_tokens, completion_tokens and total_tokens.
424
+ usage: Usage data for the span. In order for input, output, and total tokens to be visible in the UI,
425
+ the usage must contain OpenAI-formatted keys (they can be passed additionally to the original usage on the top level of the dict): prompt_tokens, completion_tokens, and total_tokens.
344
426
  If OpenAI-formatted keys were not found, Opik will try to calculate them automatically if the usage
345
427
  format is recognized (you can see which provider's formats are recognized in opik.LLMProvider enum), but it is not guaranteed.
346
428
  model: The name of LLM (in this case `type` parameter should be == `llm`)
347
429
  provider: The provider of LLM. You can find providers officially supported by Opik for cost tracking
348
- in `opik.LLMProvider` enum. If your provider is not here, please open an issue in our github - https://github.com/comet-ml/opik.
349
- If your provider not in the list, you can still specify it but the cost tracking will not be available
430
+ in `opik.LLMProvider` enum. If your provider is not here, please open an issue in our GitHub - https://github.com/comet-ml/opik.
431
+ If your provider is not in the list, you can still specify it, but the cost tracking will not be available
350
432
  error_info: The dictionary with error information (typically used when the span function has failed).
351
433
  total_cost: The cost of the span in USD. This value takes priority over the cost calculated by Opik from the usage.
434
+ attachments: The list of attachments to be uploaded to the span.
352
435
 
353
436
  Returns:
354
437
  span.Span: The created span object.
@@ -358,15 +441,6 @@ class Opik:
358
441
  start_time if start_time is not None else datetime_helpers.local_timestamp()
359
442
  )
360
443
 
361
- backend_compatible_usage = validation_helpers.validate_and_parse_usage(
362
- usage=usage,
363
- logger=LOGGER,
364
- provider=provider,
365
- )
366
-
367
- if backend_compatible_usage is not None:
368
- metadata = helpers.add_usage_to_metadata(usage=usage, metadata=metadata)
369
-
370
444
  if project_name is None:
371
445
  project_name = self._project_name
372
446
 
@@ -386,13 +460,24 @@ class Opik:
386
460
  tags=tags,
387
461
  error_info=error_info,
388
462
  thread_id=None,
463
+ last_updated_at=datetime_helpers.local_timestamp(),
389
464
  )
390
465
  self._streamer.put(create_trace_message)
391
466
 
392
- create_span_message = messages.CreateSpanMessage(
393
- span_id=id,
467
+ if feedback_scores is not None:
468
+ for feedback_score in feedback_scores:
469
+ feedback_score["id"] = id
470
+
471
+ self.log_spans_feedback_scores(
472
+ cast(List[BatchFeedbackScoreDict], feedback_scores), project_name
473
+ )
474
+
475
+ return span.span_client.create_span(
394
476
  trace_id=trace_id,
395
477
  project_name=project_name,
478
+ url_override=self._config.url_override,
479
+ message_streamer=self._streamer,
480
+ span_id=id,
396
481
  parent_span_id=parent_span_id,
397
482
  name=name,
398
483
  type=type,
@@ -402,60 +487,194 @@ class Opik:
402
487
  output=output,
403
488
  metadata=metadata,
404
489
  tags=tags,
405
- usage=backend_compatible_usage,
490
+ usage=usage,
406
491
  model=model,
407
492
  provider=provider,
408
493
  error_info=error_info,
409
494
  total_cost=total_cost,
495
+ attachments=attachments,
410
496
  )
411
- self._streamer.put(create_span_message)
412
497
 
413
- if feedback_scores is not None:
414
- for feedback_score in feedback_scores:
415
- feedback_score["id"] = id
498
+ def update_span(
499
+ self,
500
+ id: str,
501
+ trace_id: str,
502
+ parent_span_id: Optional[str],
503
+ project_name: str,
504
+ end_time: Optional[datetime.datetime] = None,
505
+ metadata: Optional[Dict[str, Any]] = None,
506
+ input: Optional[Dict[str, Any]] = None,
507
+ output: Optional[Dict[str, Any]] = None,
508
+ tags: Optional[List[str]] = None,
509
+ usage: Optional[Union[Dict[str, Any], llm_usage.OpikUsage]] = None,
510
+ model: Optional[str] = None,
511
+ provider: Optional[Union[LLMProvider, str]] = None,
512
+ error_info: Optional[ErrorInfoDict] = None,
513
+ total_cost: Optional[float] = None,
514
+ attachments: Optional[List[Attachment]] = None,
515
+ ) -> None:
516
+ """
517
+ Update the attributes of an existing span.
416
518
 
417
- self.log_spans_feedback_scores(feedback_scores, project_name)
519
+ This method should only be used after the span has been fully created and stored.
520
+ If called before or immediately after span creation, the update may silently fail or result in incorrect data.
418
521
 
419
- return span.Span(
522
+ This method uses four parameters to identify the span:
523
+ - `id`
524
+ - `trace_id`
525
+ - `parent_span_id`
526
+ - `project_name`
527
+
528
+ These parameters **must match exactly** the values used when the span was created.
529
+ If any of them are incorrect, the update may not apply and no error will be raised.
530
+
531
+ All other parameters are optional and will update the corresponding fields in the span.
532
+ If a parameter is not provided, the existing value will remain unchanged.
533
+
534
+ Args:
535
+ id: The unique identifier for the span to update.
536
+ trace_id: The unique identifier for the trace to which the span belongs.
537
+ parent_span_id: The unique identifier for the parent span.
538
+ project_name: The project name to which the span belongs.
539
+ end_time: The new end time of the span.
540
+ metadata: The new metadata to be associated with the span.
541
+ input: The new input data for the span.
542
+ output: The new output data for the span.
543
+ tags: A new list of tags to be associated with the span.
544
+ usage: The new usage data for the span. In order for input, output and total tokens to be visible in the UI,
545
+ the usage must contain OpenAI-formatted keys (they can be passed additionaly to original usage on the top level of the dict): prompt_tokens, completion_tokens and total_tokens.
546
+ If OpenAI-formatted keys were not found, Opik will try to calculate them automatically if the usage
547
+ format is recognized (you can see which provider's formats are recognized in opik.LLMProvider enum), but it is not guaranteed.
548
+ model: The new name of LLM.
549
+ provider: The new provider of LLM. You can find providers officially supported by Opik for cost tracking
550
+ in `opik.LLMProvider` enum. If your provider is not here, please open an issue in our github - https://github.com/comet-ml/opik.
551
+ If your provider not in the list, you can still specify it but the cost tracking will not be available
552
+ error_info: The new dictionary with error information (typically used when the span function has failed).
553
+ total_cost: The new cost of the span in USD. This value takes priority over the cost calculated by Opik from the usage.
554
+ attachments: The new list of attachments to be uploaded to the span.
555
+
556
+ Returns:
557
+ None
558
+ """
559
+ span.span_client.update_span(
420
560
  id=id,
561
+ trace_id=trace_id,
421
562
  parent_span_id=parent_span_id,
563
+ url_override=self._config.url_override,
564
+ message_streamer=self._streamer,
565
+ project_name=project_name,
566
+ end_time=end_time,
567
+ metadata=metadata,
568
+ input=input,
569
+ output=output,
570
+ tags=tags,
571
+ usage=usage,
572
+ model=model,
573
+ provider=provider,
574
+ error_info=error_info,
575
+ total_cost=total_cost,
576
+ attachments=attachments,
577
+ )
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(
422
629
  trace_id=trace_id,
423
630
  project_name=project_name,
424
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,
425
639
  )
426
640
 
427
641
  def log_spans_feedback_scores(
428
- self, scores: List[FeedbackScoreDict], project_name: Optional[str] = None
642
+ self, scores: List[BatchFeedbackScoreDict], project_name: Optional[str] = None
429
643
  ) -> None:
430
644
  """
431
645
  Log feedback scores for spans.
432
646
 
433
647
  Args:
434
- scores (List[FeedbackScoreDict]): A list of feedback score dictionaries.
648
+ scores (List[BatchFeedbackScoreDict]): A list of feedback score dictionaries.
435
649
  Specifying a span id via `id` key for each score is mandatory.
436
650
  project_name: The name of the project in which the spans are logged. If not set, the project name
437
- which was configured when Opik instance was created will be used.
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.
438
653
 
439
654
  Returns:
440
655
  None
441
- """
442
- valid_scores = [
443
- score
444
- for score in scores
445
- if validation_helpers.validate_feedback_score(score, LOGGER) is not None
446
- ]
447
656
 
448
- if len(valid_scores) == 0:
449
- return None
450
-
451
- score_messages = [
452
- messages.FeedbackScoreMessage(
453
- source=constants.FEEDBACK_SCORE_SOURCE_SDK,
454
- project_name=project_name or self._project_name,
455
- **score_dict,
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)
666
+ """
667
+ score_messages = helpers.parse_feedback_score_messages(
668
+ scores=scores,
669
+ project_name=project_name or self.project_name,
670
+ parsed_item_class=messages.FeedbackScoreMessage,
671
+ logger=LOGGER,
672
+ )
673
+ if score_messages is None:
674
+ LOGGER.error(
675
+ f"No valid spans feedback scores to log from provided ones: {scores}"
456
676
  )
457
- for score_dict in valid_scores
458
- ]
677
+ return
459
678
 
460
679
  for batch in sequence_splitter.split_into_batches(
461
680
  score_messages,
@@ -469,47 +688,84 @@ class Opik:
469
688
  self._streamer.put(add_span_feedback_scores_batch_message)
470
689
 
471
690
  def log_traces_feedback_scores(
472
- self, scores: List[FeedbackScoreDict], project_name: Optional[str] = None
691
+ self, scores: List[BatchFeedbackScoreDict], project_name: Optional[str] = None
473
692
  ) -> None:
474
693
  """
475
694
  Log feedback scores for traces.
476
695
 
477
696
  Args:
478
- scores (List[FeedbackScoreDict]): A list of feedback score dictionaries.
697
+ scores (List[BatchFeedbackScoreDict]): A list of feedback score dictionaries.
479
698
  Specifying a trace id via `id` key for each score is mandatory.
480
699
  project_name: The name of the project in which the traces are logged. If not set, the project name
481
- which was configured when Opik instance was created will be used.
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.
482
702
 
483
703
  Returns:
484
704
  None
485
- """
486
- valid_scores = [
487
- score
488
- for score in scores
489
- if validation_helpers.validate_feedback_score(score, LOGGER) is not None
490
- ]
491
705
 
492
- if len(valid_scores) == 0:
493
- return None
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)
715
+ """
716
+ score_messages = helpers.parse_feedback_score_messages(
717
+ scores=scores,
718
+ project_name=project_name or self.project_name,
719
+ parsed_item_class=messages.FeedbackScoreMessage,
720
+ logger=LOGGER,
721
+ )
494
722
 
495
- score_messages = [
496
- messages.FeedbackScoreMessage(
497
- source=constants.FEEDBACK_SCORE_SOURCE_SDK,
498
- project_name=project_name or self._project_name,
499
- **score_dict,
723
+ if score_messages is None:
724
+ LOGGER.error(
725
+ f"No valid traces feedback scores to log from provided ones: {scores}"
500
726
  )
501
- for score_dict in valid_scores
502
- ]
727
+ return
728
+
503
729
  for batch in sequence_splitter.split_into_batches(
504
730
  score_messages,
505
731
  max_payload_size_MB=config.MAX_BATCH_SIZE_MB,
506
732
  max_length=constants.FEEDBACK_SCORES_MAX_BATCH_SIZE,
507
733
  ):
508
- add_span_feedback_scores_batch_message = (
734
+ add_trace_feedback_scores_batch_message = (
509
735
  messages.AddTraceFeedbackScoresBatchMessage(batch=batch)
510
736
  )
511
737
 
512
- self._streamer.put(add_span_feedback_scores_batch_message)
738
+ self._streamer.put(add_trace_feedback_scores_batch_message)
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
+ )
513
769
 
514
770
  def delete_trace_feedback_score(self, trace_id: str, name: str) -> None:
515
771
  """
@@ -611,8 +867,13 @@ class Opik:
611
867
  self._rest_client, dataset_name
612
868
  )
613
869
 
870
+ experiments_client = self.get_experiments_client()
614
871
  experiments = dataset_rest_operations.get_dataset_experiments(
615
- 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,
616
877
  )
617
878
 
618
879
  return experiments
@@ -676,8 +937,10 @@ class Opik:
676
937
  dataset_name: str,
677
938
  name: Optional[str] = None,
678
939
  experiment_config: Optional[Dict[str, Any]] = None,
679
- prompt: Optional[Prompt] = None,
680
- prompts: Optional[List[Prompt]] = None,
940
+ prompt: Optional[prompt_module.base_prompt.BasePrompt] = None,
941
+ prompts: Optional[List[prompt_module.base_prompt.BasePrompt]] = None,
942
+ type: Literal["regular", "trial", "mini-batch"] = "regular",
943
+ optimization_id: Optional[str] = None,
681
944
  ) -> experiment.Experiment:
682
945
  """
683
946
  Creates a new experiment using the given dataset name and optional parameters.
@@ -688,6 +951,9 @@ class Opik:
688
951
  experiment_config: Optional experiment configuration parameters. Must be a dictionary if provided.
689
952
  prompt: Prompt object to associate with the experiment. Deprecated, use `prompts` argument instead.
690
953
  prompts: List of Prompt objects to associate with the experiment.
954
+ type: The type of the experiment. Can be "regular", "trial", or "mini-batch".
955
+ Defaults to "regular". "trial" and "mini-batch" are only relevant for prompt optimization experiments.
956
+ optimization_id: Optional ID of the optimization associated with the experiment.
691
957
 
692
958
  Returns:
693
959
  experiment.Experiment: The newly created experiment object.
@@ -710,6 +976,8 @@ class Opik:
710
976
  id=id,
711
977
  metadata=metadata,
712
978
  prompt_versions=prompt_versions,
979
+ type=type,
980
+ optimization_id=optimization_id,
713
981
  )
714
982
 
715
983
  experiment_ = experiment.Experiment(
@@ -717,11 +985,49 @@ class Opik:
717
985
  name=name,
718
986
  dataset_name=dataset_name,
719
987
  rest_client=self._rest_client,
988
+ streamer=self._streamer,
989
+ experiments_client=self.get_experiments_client(),
720
990
  prompts=checked_prompts,
721
991
  )
722
992
 
723
993
  return experiment_
724
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
+
725
1031
  def get_experiment_by_name(self, name: str) -> experiment.Experiment:
726
1032
  """
727
1033
  Returns an existing experiment by its name.
@@ -741,18 +1047,20 @@ class Opik:
741
1047
 
742
1048
  return experiment.Experiment(
743
1049
  id=experiment_public.id,
744
- name=name,
1050
+ name=experiment_public.name,
745
1051
  dataset_name=experiment_public.dataset_name,
746
1052
  rest_client=self._rest_client,
747
- # TODO: add prompt if exists
1053
+ streamer=self._streamer,
1054
+ experiments_client=self.get_experiments_client(),
748
1055
  )
749
1056
 
750
1057
  def get_experiments_by_name(self, name: str) -> List[experiment.Experiment]:
751
1058
  """
752
- Returns an existing experiments by its name.
1059
+ Returns a list of existing experiments containing the given string in their name.
1060
+ Search is case-insensitive.
753
1061
 
754
1062
  Args:
755
- name: The name of the experiment(s).
1063
+ name: The string to search for in the experiment names.
756
1064
 
757
1065
  Returns:
758
1066
  List[experiment.Experiment]: List of existing experiments.
@@ -765,9 +1073,11 @@ class Opik:
765
1073
  for public_experiment in experiments_public:
766
1074
  experiment_ = experiment.Experiment(
767
1075
  id=public_experiment.id,
1076
+ name=public_experiment.name,
768
1077
  dataset_name=public_experiment.dataset_name,
769
- name=name,
770
1078
  rest_client=self._rest_client,
1079
+ streamer=self._streamer,
1080
+ experiments_client=self.get_experiments_client(),
771
1081
  )
772
1082
  result.append(experiment_)
773
1083
 
@@ -799,7 +1109,8 @@ class Opik:
799
1109
  name=experiment_public.name,
800
1110
  dataset_name=experiment_public.dataset_name,
801
1111
  rest_client=self._rest_client,
802
- # TODO: add prompt if exists
1112
+ streamer=self._streamer,
1113
+ experiments_client=self.get_experiments_client(),
803
1114
  )
804
1115
 
805
1116
  def end(self, timeout: Optional[int] = None) -> None:
@@ -815,7 +1126,7 @@ class Opik:
815
1126
  timeout = timeout if timeout is not None else self._flush_timeout
816
1127
  self._streamer.close(timeout)
817
1128
 
818
- def flush(self, timeout: Optional[int] = None) -> None:
1129
+ def flush(self, timeout: Optional[int] = None) -> bool:
819
1130
  """
820
1131
  Flush the streamer to ensure all messages are sent.
821
1132
 
@@ -823,10 +1134,10 @@ class Opik:
823
1134
  timeout (Optional[int]): The timeout for flushing the streamer. Once the timeout is reached, the flush method will return regardless of whether all messages have been sent.
824
1135
 
825
1136
  Returns:
826
- None
1137
+ True if all messages have been sent within specified timeout, False otherwise.
827
1138
  """
828
1139
  timeout = timeout if timeout is not None else self._flush_timeout
829
- self._streamer.flush(timeout)
1140
+ return self._streamer.flush(timeout)
830
1141
 
831
1142
  def search_traces(
832
1143
  self,
@@ -834,39 +1145,90 @@ class Opik:
834
1145
  filter_string: Optional[str] = None,
835
1146
  max_results: int = 1000,
836
1147
  truncate: bool = True,
1148
+ wait_for_at_least: Optional[int] = None,
1149
+ wait_for_timeout: int = httpx_client.READ_TIMEOUT_SECONDS,
837
1150
  ) -> List[trace_public.TracePublic]:
838
1151
  """
839
- 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.
840
1155
 
841
1156
  Args:
842
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`.
843
- 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.
844
1195
  max_results: The maximum number of traces to return.
845
- truncate: Whether to truncate image data stored in input, output or metadata
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.
846
1202
  """
1203
+ filters_ = helpers.parse_filter_expressions(
1204
+ filter_string, parsed_item_class=trace_filter_public.TraceFilterPublic
1205
+ )
847
1206
 
848
- page_size = 100
849
- traces: List[trace_public.TracePublic] = []
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_,
1212
+ max_results=max_results,
1213
+ truncate=truncate,
1214
+ )
850
1215
 
851
- filters = opik_query_language.OpikQueryLanguage(filter_string).parsed_filters
1216
+ if wait_for_at_least is None:
1217
+ return search_functor()
852
1218
 
853
- page = 1
854
- while len(traces) < max_results:
855
- page_traces = self._rest_client.traces.get_traces_by_project(
856
- project_name=project_name or self._project_name,
857
- filters=filters,
858
- page=page,
859
- size=page_size,
860
- truncate=truncate,
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,
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."
861
1229
  )
862
1230
 
863
- if len(page_traces.content) == 0:
864
- break
865
-
866
- traces.extend(page_traces.content)
867
- page += 1
868
-
869
- return traces[:max_results]
1231
+ return result
870
1232
 
871
1233
  def search_spans(
872
1234
  self,
@@ -875,41 +1237,93 @@ class Opik:
875
1237
  filter_string: Optional[str] = None,
876
1238
  max_results: int = 1000,
877
1239
  truncate: bool = True,
1240
+ wait_for_at_least: Optional[int] = None,
1241
+ wait_for_timeout: int = httpx_client.READ_TIMEOUT_SECONDS,
878
1242
  ) -> List[span_public.SpanPublic]:
879
1243
  """
880
1244
  Search for spans in the given trace. This allows you to search spans based on the span input, output,
881
- 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.
882
1248
 
883
1249
  Args:
884
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`.
885
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.
886
- 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.
887
1289
  max_results: The maximum number of spans to return.
888
- truncate: Whether to truncate image data stored in input, output or metadata
889
- """
890
- page_size = 100
891
- spans: List[span_public.SpanPublic] = []
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.
892
1293
 
893
- filters = opik_query_language.OpikQueryLanguage(filter_string).parsed_filters
1294
+ Raises:
1295
+ exceptions.SearchTimeoutError if wait_for_at_least spans are not found within the specified timeout.
1296
+ """
1297
+ filters = helpers.parse_filter_expressions(
1298
+ filter_string, parsed_item_class=span_filter_public.SpanFilterPublic
1299
+ )
894
1300
 
895
- page = 1
896
- while len(spans) < max_results:
897
- page_spans = self._rest_client.spans.get_spans_by_project(
898
- project_name=project_name or self._project_name,
899
- trace_id=trace_id,
900
- filters=filters,
901
- page=page,
902
- size=page_size,
903
- truncate=truncate,
904
- )
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,
1307
+ max_results=max_results,
1308
+ truncate=truncate,
1309
+ )
905
1310
 
906
- if len(page_spans.content) == 0:
907
- break
1311
+ if wait_for_at_least is None:
1312
+ return search_functor()
908
1313
 
909
- spans.extend(page_spans.content)
910
- page += 1
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
+ )
911
1325
 
912
- return spans[:max_results]
1326
+ return result
913
1327
 
914
1328
  def get_trace_content(self, id: str) -> trace_public.TracePublic:
915
1329
  """
@@ -936,7 +1350,7 @@ class Opik:
936
1350
  Fetches a project by its unique identifier.
937
1351
 
938
1352
  Parameters:
939
- id (str): project if (uuid).
1353
+ id (str): project id (uuid).
940
1354
 
941
1355
  Returns:
942
1356
  project_public.ProjectPublic: pydantic model object with all the data associated with the project found.
@@ -958,9 +1372,45 @@ class Opik:
958
1372
  str: URL
959
1373
  """
960
1374
 
1375
+ dereferenced_workspace = self._workspace
1376
+ if dereferenced_workspace == config.OPIK_WORKSPACE_DEFAULT_NAME:
1377
+ dereferenced_workspace = (
1378
+ self._rest_client.check.get_workspace_name().workspace_name
1379
+ )
1380
+
961
1381
  project_name = project_name or self._project_name
1382
+
962
1383
  return url_helpers.get_project_url_by_workspace(
963
- workspace=self._workspace, project_name=project_name
1384
+ workspace=dereferenced_workspace, project_name=project_name
1385
+ )
1386
+
1387
+ def get_threads_client(self) -> threads_client.ThreadsClient:
1388
+ """
1389
+ Creates and provides an instance of the ``ThreadsClient`` tied to the current context.
1390
+
1391
+ The ``ThreadsClient`` can be used to interact with the threads API to manage and interact with conversational threads.
1392
+
1393
+ Returns:
1394
+ ThreadsClient: An instance of ``threads_client.ThreadsClient`` initialized
1395
+ with the current context.
1396
+ """
1397
+ return threads_client.ThreadsClient(self)
1398
+
1399
+ def get_attachment_client(self) -> attachment_client.AttachmentClient:
1400
+ """
1401
+ Creates and provides an instance of the ``AttachmentClient`` tied to the current context.
1402
+
1403
+ The ``AttachmentClient`` can be used to interact with the attachments API to retrieve
1404
+ attachment lists, download attachments, and upload attachments for traces and spans.
1405
+
1406
+ Returns:
1407
+ AttachmentClient: An instance of ``attachment.client.AttachmentClient``
1408
+ """
1409
+ return attachment_client.AttachmentClient(
1410
+ rest_client=self._rest_client,
1411
+ url_override=self._config.url_override,
1412
+ workspace_name=self._workspace,
1413
+ rest_httpx_client=self._httpx_client,
964
1414
  )
965
1415
 
966
1416
  def create_prompt(
@@ -968,55 +1418,303 @@ class Opik:
968
1418
  name: str,
969
1419
  prompt: str,
970
1420
  metadata: Optional[Dict[str, Any]] = None,
971
- ) -> Prompt:
1421
+ type: prompt_module.PromptType = prompt_module.PromptType.MUSTACHE,
1422
+ ) -> prompt_module.Prompt:
972
1423
  """
973
- Creates a new prompt with the given name and template.
974
- 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.
975
1426
 
976
1427
  Parameters:
977
1428
  name: The name of the prompt.
978
1429
  prompt: The template content of the prompt.
979
1430
  metadata: Optional metadata to be included in the prompt.
1431
+ type: The template type (MUSTACHE or JINJA2).
980
1432
 
981
1433
  Returns:
982
1434
  A Prompt object containing details of the created or retrieved prompt.
983
1435
 
984
1436
  Raises:
985
- 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.
986
1439
  """
987
- prompt_client = PromptClient(self._rest_client)
988
- return prompt_client.create_prompt(name=name, prompt=prompt, metadata=metadata)
1440
+ prompt_client_ = prompt_client.PromptClient(self._rest_client)
1441
+ prompt_version = prompt_client_.create_prompt(
1442
+ name=name, prompt=prompt, metadata=metadata, type=type
1443
+ )
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
+ )
989
1473
 
990
1474
  def get_prompt(
991
1475
  self,
992
1476
  name: str,
993
1477
  commit: Optional[str] = None,
994
- ) -> Optional[Prompt]:
1478
+ ) -> Optional[prompt_module.Prompt]:
995
1479
  """
996
- 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.
997
1483
 
998
1484
  Parameters:
999
1485
  name: The name of the prompt.
1000
1486
  commit: An optional commit version of the prompt. If not provided, the latest version is retrieved.
1001
1487
 
1002
1488
  Returns:
1003
- 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).
1004
1493
  """
1005
- prompt_client = PromptClient(self._rest_client)
1006
- return 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
+ )
1007
1498
 
1008
- def get_all_prompts(self, name: str) -> List[Prompt]:
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]:
1009
1509
  """
1010
- Retrieve all the prompt versions for a given prompt name.
1510
+ Retrieve a chat prompt by name and optional commit version.
1511
+
1512
+ This method only returns chat prompts.
1011
1513
 
1012
1514
  Parameters:
1013
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
+
1529
+ if fern_prompt_version is None:
1530
+ return None
1531
+
1532
+ return prompt_module.ChatPrompt.from_fern_prompt_version(
1533
+ name, fern_prompt_version
1534
+ )
1535
+
1536
+ def get_prompt_history(self, name: str) -> List[prompt_module.Prompt]:
1537
+ """
1538
+ Retrieve all text prompt versions history for a given prompt name.
1539
+
1540
+ Parameters:
1541
+ name: The name of the prompt.
1542
+
1543
+ Returns:
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).
1548
+ """
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
+
1563
+ result = [
1564
+ prompt_module.Prompt.from_fern_prompt_version(name, version)
1565
+ for version in fern_prompt_versions
1566
+ ]
1567
+ return result
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
+
1679
+ def create_optimization(
1680
+ self,
1681
+ dataset_name: str,
1682
+ objective_name: str,
1683
+ name: Optional[str] = None,
1684
+ metadata: Optional[Dict[str, Any]] = None,
1685
+ optimization_id: Optional[str] = None,
1686
+ ) -> optimization.Optimization:
1687
+ id = optimization_id or id_helpers.generate_id()
1688
+
1689
+ self._rest_client.optimizations.create_optimization(
1690
+ id=id,
1691
+ name=name,
1692
+ dataset_name=dataset_name,
1693
+ objective_name=objective_name,
1694
+ status="running",
1695
+ metadata=metadata,
1696
+ )
1697
+
1698
+ optimization_client = optimization.Optimization(
1699
+ id=id, rest_client=self._rest_client
1700
+ )
1701
+ return optimization_client
1702
+
1703
+ def delete_optimizations(self, ids: List[str]) -> None:
1704
+ self._rest_client.optimizations.delete_optimizations_by_id(ids=ids)
1705
+
1706
+ def get_optimization_by_id(self, id: str) -> optimization.Optimization:
1707
+ _ = self._rest_client.optimizations.get_optimization_by_id(id)
1708
+ return optimization.Optimization(id=id, rest_client=self._rest_client)
1709
+
1710
+ def get_experiments_client(self) -> experiments_client.ExperimentsClient:
1711
+ """
1712
+ Retrieves an instance of `ExperimentsClient`.
1014
1713
 
1015
1714
  Returns:
1016
- List[Prompt]: A list of prompts for the given name.
1715
+ An instance of the ExperimentsClient initialized with a cached REST client.
1017
1716
  """
1018
- prompt_client = PromptClient(self._rest_client)
1019
- return prompt_client.get_all_prompts(name=name)
1717
+ return experiments_client.ExperimentsClient(self._rest_client)
1020
1718
 
1021
1719
 
1022
1720
  @functools.lru_cache()