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
@@ -1,25 +1,41 @@
1
1
  import logging
2
- from typing import Any, Dict, List, Literal, Optional, Set, TYPE_CHECKING, cast
2
+ import datetime
3
+ from typing import (
4
+ Any,
5
+ Dict,
6
+ List,
7
+ Literal,
8
+ Optional,
9
+ Set,
10
+ TYPE_CHECKING,
11
+ cast,
12
+ Callable,
13
+ NamedTuple,
14
+ )
15
+ import contextvars
16
+ from uuid import UUID
3
17
 
4
18
  from langchain_core import language_models
5
19
  from langchain_core.tracers import BaseTracer
20
+ from langchain_core.tracers.schemas import Run
6
21
 
7
- from opik import dict_utils, opik_context, llm_usage
22
+ from opik import context_storage, dict_utils, llm_usage, tracing_runtime_config
8
23
  from opik.api_objects import span, trace
24
+ from opik.decorator import arguments_helpers, span_creation_handler
9
25
  from opik.types import DistributedTraceHeadersDict, ErrorInfoDict
26
+ from opik.validation import parameters_validator
10
27
  from . import (
11
28
  base_llm_patcher,
12
- google_run_helpers,
13
- openai_run_helpers,
29
+ helpers as langchain_helpers,
14
30
  opik_encoder_extension,
31
+ provider_usage_extractors,
15
32
  )
33
+
16
34
  from ...api_objects import helpers, opik_client
17
35
 
18
36
  if TYPE_CHECKING:
19
- from uuid import UUID
20
-
21
- from langchain_core.tracers.schemas import Run
22
37
  from langchain_core.runnables.graph import Graph
38
+ from langchain_core.messages import BaseMessage
23
39
 
24
40
  LOGGER = logging.getLogger(__name__)
25
41
 
@@ -29,6 +45,20 @@ language_models.BaseLLM.dict = base_llm_patcher.base_llm_dict_patched()
29
45
 
30
46
  SpanType = Literal["llm", "tool", "general"]
31
47
 
48
+ # A callable that receives an error string and returns True if the error should be skipped,
49
+ # or False otherwise.
50
+ SkipErrorCallback = Callable[[str], bool]
51
+
52
+ # Placeholder output dictionary used when errors are intentionally skipped
53
+ # via the skip_error_callback. This signals that the output was not produced
54
+ # due to a handled/ignored error during execution.
55
+ ERROR_SKIPPED_OUTPUTS = {"warning": "Error output skipped by skip_error_callback."}
56
+
57
+
58
+ class TrackRootRunResult(NamedTuple):
59
+ new_trace_data: Optional[trace.TraceData]
60
+ new_span_data: Optional[span.SpanData]
61
+
32
62
 
33
63
  def _get_span_type(run: Dict[str, Any]) -> SpanType:
34
64
  if run.get("run_type") in ["llm", "tool"]:
@@ -40,15 +70,16 @@ def _get_span_type(run: Dict[str, Any]) -> SpanType:
40
70
  return cast(SpanType, "general")
41
71
 
42
72
 
43
- class OpikTracer(BaseTracer):
44
- """Langchain Opik Tracer.
73
+ def _is_root_run(run_dict: Dict[str, Any]) -> bool:
74
+ return run_dict.get("parent_run_id") is None
75
+
76
+
77
+ def _get_run_metadata(run_dict: Dict[str, Any]) -> Dict[str, Any]:
78
+ return run_dict["extra"].get("metadata", {})
45
79
 
46
- Args:
47
- tags: List of tags to be applied to each trace logged by the tracer.
48
- metadata: Additional metadata for each trace logged by the tracer.
49
- graph: A LangGraph Graph object to track the Graph Definition in Opik.
50
- project_name: The name of the project to log data.
51
- """
80
+
81
+ class OpikTracer(BaseTracer):
82
+ """Langchain Opik Tracer."""
52
83
 
53
84
  def __init__(
54
85
  self,
@@ -57,260 +88,602 @@ class OpikTracer(BaseTracer):
57
88
  graph: Optional["Graph"] = None,
58
89
  project_name: Optional[str] = None,
59
90
  distributed_headers: Optional[DistributedTraceHeadersDict] = None,
91
+ thread_id: Optional[str] = None,
92
+ skip_error_callback: Optional[SkipErrorCallback] = None,
93
+ opik_context_read_only_mode: bool = False,
60
94
  **kwargs: Any,
61
95
  ) -> None:
96
+ """
97
+ Initializes an instance of the class with various parameters for traces, metadata, and project configuration.
98
+
99
+ Args:
100
+ tags: List of tags associated with logged traces.
101
+ metadata: Dictionary containing metadata information for logged traces.
102
+ graph: A LangGraph Graph object for representing dependencies or flow
103
+ to track the Graph Definition in Opik.
104
+ project_name: Name of the project associated with the traces.
105
+ distributed_headers: Headers for distributed tracing context.
106
+ thread_id: Unique identifier for the conversational thread
107
+ to be associated with traces.
108
+ skip_error_callback : Callback function to handle skip errors logic.
109
+ Allows defining custom logic for handling errors that are intentionally skipped.
110
+ Please note that in traces/spans where errors are intentionally skipped,
111
+ the output will be replaced with `ERROR_SKIPPED_OUTPUTS`. You can provide
112
+ the output manually using `opik_context.get_current_span_data().update(output=...)`.
113
+ opik_context_read_only_mode: Whether to adding/popping spans/traces to/from the context storage.
114
+ * If False (default), OpikTracer will add created spans/traces to the opik context, so if there is a @track-decorated
115
+ function called inside the LangChain runnable, it will be attached to it's parent span from LangChain automatically.
116
+ * If True, OpikTracer will not modify the context storage and only create spans/traces from LangChain's Run objects.
117
+ This might be useful when the environment doesn't support proper context isolation for concurrent operations and you
118
+ want to avoid modifying the Opik context stack due to unsafety.
119
+ **kwargs: Additional arguments passed to the parent class constructor.
120
+ """
121
+ validator = parameters_validator.create_validator(
122
+ method_name="__init__", class_name=self.__class__.__name__
123
+ )
124
+ validator.add_str_parameter(thread_id, name="thread_id")
125
+ validator.add_str_parameter(project_name, name="project_name")
126
+ validator.add_dict_parameter(metadata, name="metadata")
127
+ validator.add_list_parameter(tags, name="tags")
128
+ if not validator.validate():
129
+ validator.raise_validation_error()
130
+
62
131
  super().__init__(**kwargs)
63
132
  self._trace_default_metadata = metadata if metadata is not None else {}
64
133
  self._trace_default_metadata["created_from"] = "langchain"
65
134
 
66
135
  if graph:
67
- self._trace_default_metadata["_opik_graph_definition"] = {
68
- "format": "mermaid",
69
- "data": graph.draw_mermaid(),
70
- }
136
+ self.set_graph(graph)
71
137
 
72
138
  self._trace_default_tags = tags
73
139
 
74
- self._span_data_map: Dict["UUID", span.SpanData] = {}
140
+ self._span_data_map: Dict[UUID, span.SpanData] = {}
75
141
  """Map from run id to span data."""
76
142
 
77
- self._created_traces_data_map: Dict["UUID", trace.TraceData] = {}
143
+ self._created_traces_data_map: Dict[UUID, trace.TraceData] = {}
78
144
  """Map from run id to trace data."""
79
145
 
80
146
  self._created_traces: List[trace.Trace] = []
81
147
 
82
148
  self._externally_created_traces_ids: Set[str] = set()
83
149
 
150
+ self._skipped_langgraph_root_run_ids: Set[UUID] = set()
151
+ """Set of run IDs for LangGraph root runs where we skip creating the span."""
152
+
153
+ self._langgraph_parent_span_ids: Dict[UUID, Optional[str]] = {}
154
+ """Map from LangGraph root run ID to parent span ID (None if attached to trace)."""
155
+
84
156
  self._project_name = project_name
85
157
 
86
158
  self._distributed_headers = distributed_headers
87
159
 
88
160
  self._opik_client = opik_client.get_client_cached()
89
161
 
90
- def _persist_run(self, run: "Run") -> None:
91
- run_dict: Dict[str, Any] = run.dict()
92
-
93
- error_info: Optional[ErrorInfoDict]
94
- if run_dict["error"] is not None:
95
- output = None
96
- error_info = {
97
- "exception_type": "Exception",
98
- "traceback": run_dict["error"],
99
- }
100
- else:
101
- output = run_dict["outputs"]
102
- error_info = None
162
+ self._thread_id = thread_id
103
163
 
104
- span_data = self._span_data_map[run.id]
164
+ self._opik_context_storage = context_storage.get_current_context_instance()
105
165
 
106
- if span_data.trace_id not in self._externally_created_traces_ids:
107
- trace_data = self._created_traces_data_map[run.id]
166
+ self._root_run_external_parent_span_id: contextvars.ContextVar[
167
+ Optional[str]
168
+ ] = contextvars.ContextVar("root_run_external_parent_span_id", default=None)
108
169
 
109
- # workaround for `.astream()` method usage
110
- if trace_data.input == {"input": ""}:
111
- trace_data.input = run_dict["inputs"]
170
+ self._skip_error_callback = skip_error_callback
112
171
 
113
- trace_data.init_end_time().update(output=output, error_info=error_info)
114
- trace_ = self._opik_client.trace(**trace_data.__dict__)
115
- self._created_traces.append(trace_)
172
+ self._opik_context_read_only_mode = opik_context_read_only_mode
116
173
 
117
- def _process_start_span(self, run: "Run") -> None:
118
- run_dict: Dict[str, Any] = run.dict()
119
- if not run.parent_run_id:
120
- # This is the first run for the chain.
121
- self._track_root_run(run_dict)
122
- else:
123
- parent_span_data = self._span_data_map[run.parent_run_id]
174
+ def set_graph(self, graph: "Graph") -> None:
175
+ """
176
+ Set the LangGraph graph structure for visualization in Opik traces.
124
177
 
125
- project_name = helpers.resolve_child_span_project_name(
126
- parent_span_data.project_name,
127
- self._project_name,
128
- )
178
+ This method extracts the graph structure and stores it in trace metadata,
179
+ allowing the graph to be visualized in the Opik UI.
129
180
 
130
- span_data = span.SpanData(
131
- trace_id=parent_span_data.trace_id,
132
- parent_span_id=parent_span_data.id,
133
- input=run_dict["inputs"],
134
- metadata=run_dict["extra"],
135
- name=run.name,
136
- type=_get_span_type(run_dict),
137
- project_name=project_name,
138
- )
139
- span_data.update(metadata={"created_from": "langchain"})
181
+ Args:
182
+ graph: A LangGraph Graph object (typically obtained via graph.get_graph(xray=True)).
183
+ """
184
+ self._trace_default_metadata["_opik_graph_definition"] = {
185
+ "format": "mermaid",
186
+ "data": graph.draw_mermaid(),
187
+ }
140
188
 
141
- self._span_data_map[run.id] = span_data
189
+ def _is_opik_span_created_by_this_tracer(self, span_id: str) -> bool:
190
+ return any(span_.id == span_id for span_ in self._span_data_map.values())
142
191
 
143
- if span_data.trace_id not in self._externally_created_traces_ids:
144
- self._created_traces_data_map[run.id] = self._created_traces_data_map[
145
- run.parent_run_id
146
- ]
192
+ def _is_opik_trace_created_by_this_tracer(self, trace_id: str) -> bool:
193
+ return any(
194
+ trace_.id == trace_id for trace_ in self._created_traces_data_map.values()
195
+ )
147
196
 
148
- def _track_root_run(self, run_dict: Dict[str, Any]) -> None:
149
- run_metadata = run_dict["extra"].get("metadata", {})
150
- root_metadata = dict_utils.deepmerge(self._trace_default_metadata, run_metadata)
197
+ def _persist_run(self, run: Run) -> None:
198
+ run_dict: Dict[str, Any] = run.dict()
151
199
 
152
- if self._distributed_headers:
153
- self._attach_span_to_distributed_headers(
154
- run_dict=run_dict,
155
- root_metadata=root_metadata,
200
+ error_info: Optional[ErrorInfoDict]
201
+ trace_additional_metadata: Dict[str, Any] = {}
202
+
203
+ error_str = run_dict.get("error")
204
+ outputs = None
205
+ error_info = None
206
+
207
+ if error_str is not None:
208
+ if not self._should_skip_error(error_str):
209
+ error_info = ErrorInfoDict(
210
+ exception_type="Exception",
211
+ traceback=error_str,
212
+ )
213
+ else:
214
+ outputs = ERROR_SKIPPED_OUTPUTS
215
+ elif (outputs := run_dict.get("outputs")) is not None:
216
+ outputs, trace_additional_metadata = (
217
+ langchain_helpers.split_big_langgraph_outputs(outputs)
156
218
  )
157
- return
158
219
 
159
- current_span_data = opik_context.get_current_span_data()
160
- if current_span_data is not None:
161
- self._attach_span_to_existing_span(
220
+ if not self._opik_context_read_only_mode:
221
+ self._ensure_no_hanging_opik_tracer_spans()
222
+
223
+ span_data = self._span_data_map.get(run.id)
224
+ if (
225
+ span_data is None
226
+ or span_data.trace_id not in self._externally_created_traces_ids
227
+ ):
228
+ self._finalize_trace(
229
+ run_id=run.id,
162
230
  run_dict=run_dict,
163
- current_span_data=current_span_data,
164
- root_metadata=root_metadata,
231
+ trace_additional_metadata=trace_additional_metadata,
232
+ outputs=outputs,
233
+ error_info=error_info,
165
234
  )
166
- return
167
235
 
168
- current_trace_data = opik_context.get_current_trace_data()
169
- if current_trace_data is not None:
170
- self._attach_span_to_existing_trace(
171
- run_dict=run_dict,
172
- current_trace_data=current_trace_data,
173
- root_metadata=root_metadata,
236
+ def _finalize_trace(
237
+ self,
238
+ run_id: UUID,
239
+ run_dict: Dict[str, Any],
240
+ trace_additional_metadata: Optional[Dict[str, Any]],
241
+ outputs: Optional[Dict[str, Any]],
242
+ error_info: Optional[ErrorInfoDict],
243
+ ) -> None:
244
+ trace_data = self._created_traces_data_map.get(run_id)
245
+ if trace_data is None:
246
+ LOGGER.warning(
247
+ f"Trace data for run '{run_id}' not found in the traces data map. Skipping processing of _finalize_trace."
174
248
  )
175
249
  return
176
250
 
177
- self._initialize_span_and_trace_from_scratch(
178
- run_dict=run_dict, root_metadata=root_metadata
179
- )
251
+ # workaround for `.astream()` method usage
252
+ if trace_data.input == {"input": ""}:
253
+ trace_data.input = run_dict["inputs"]
180
254
 
181
- def _initialize_span_and_trace_from_scratch(
182
- self, run_dict: Dict[str, Any], root_metadata: Dict[str, Any]
183
- ) -> None:
184
- trace_data = trace.TraceData(
185
- name=run_dict["name"],
186
- input=run_dict["inputs"],
187
- metadata=root_metadata,
188
- tags=self._trace_default_tags,
189
- project_name=self._project_name,
255
+ if trace_additional_metadata:
256
+ trace_data.update(metadata=trace_additional_metadata)
257
+
258
+ trace_data.init_end_time().update(output=outputs, error_info=error_info)
259
+ trace_ = self._opik_client.trace(**trace_data.as_parameters)
260
+
261
+ assert trace_ is not None
262
+ self._created_traces.append(trace_)
263
+ if not self._opik_context_read_only_mode:
264
+ self._opik_context_storage.pop_trace_data(ensure_id=trace_data.id)
265
+
266
+ def _ensure_no_hanging_opik_tracer_spans(self) -> None:
267
+ root_run_external_parent_span_id = self._root_run_external_parent_span_id.get()
268
+ there_were_no_external_spans_before_chain_invocation = (
269
+ root_run_external_parent_span_id is None
190
270
  )
191
271
 
192
- self._created_traces_data_map[run_dict["id"]] = trace_data
272
+ if there_were_no_external_spans_before_chain_invocation:
273
+ self._opik_context_storage.clear_spans()
274
+ else:
275
+ assert root_run_external_parent_span_id is not None
276
+ self._opik_context_storage.trim_span_data_stack_to_certain_span(
277
+ root_run_external_parent_span_id
278
+ )
279
+
280
+ def _track_root_run(
281
+ self, run_dict: Dict[str, Any], allow_duplicating_root_span: bool
282
+ ) -> TrackRootRunResult:
283
+ run_metadata = _get_run_metadata(run_dict)
284
+ root_metadata = dict_utils.deepmerge(self._trace_default_metadata, run_metadata)
285
+ self._update_thread_id_from_metadata(run_dict)
286
+
287
+ # Track the parent span ID for LangGraph cleanup later
288
+ current_span_data = self._opik_context_storage.top_span_data()
289
+ parent_span_id_when_langgraph_started = (
290
+ current_span_data.id if current_span_data is not None else None
291
+ )
292
+ self._root_run_external_parent_span_id.set(
293
+ parent_span_id_when_langgraph_started
294
+ )
193
295
 
194
- span_ = span.SpanData(
195
- trace_id=trace_data.id,
196
- parent_span_id=None,
296
+ start_span_arguments = arguments_helpers.StartSpanParameters(
197
297
  name=run_dict["name"],
198
298
  input=run_dict["inputs"],
199
299
  type=_get_span_type(run_dict),
200
- metadata=root_metadata,
201
300
  tags=self._trace_default_tags,
301
+ metadata=root_metadata,
202
302
  project_name=self._project_name,
303
+ thread_id=self._thread_id,
203
304
  )
204
305
 
205
- self._span_data_map[run_dict["id"]] = span_
306
+ span_creation_result = span_creation_handler.create_span_respecting_context(
307
+ start_span_arguments=start_span_arguments,
308
+ distributed_trace_headers=self._distributed_headers,
309
+ opik_context_storage=self._opik_context_storage,
310
+ )
206
311
 
207
- def _attach_span_to_existing_span(
208
- self,
209
- run_dict: Dict[str, Any],
210
- current_span_data: span.SpanData,
211
- root_metadata: Dict[str, Any],
212
- ) -> None:
213
- project_name = helpers.resolve_child_span_project_name(
214
- current_span_data.project_name,
215
- self._project_name,
312
+ trace_created_externally = (
313
+ span_creation_result.trace_data is None
314
+ and not self._is_opik_trace_created_by_this_tracer(
315
+ span_creation_result.span_data.trace_id
316
+ )
216
317
  )
318
+ if trace_created_externally:
319
+ self._externally_created_traces_ids.add(
320
+ span_creation_result.span_data.trace_id
321
+ )
217
322
 
218
- span_data = span.SpanData(
219
- trace_id=current_span_data.trace_id,
220
- parent_span_id=current_span_data.id,
221
- name=run_dict["name"],
222
- input=run_dict["inputs"],
223
- metadata=root_metadata,
224
- tags=self._trace_default_tags,
225
- project_name=project_name,
226
- type=_get_span_type(run_dict),
323
+ should_skip_root_span_creation = (
324
+ span_creation_result.trace_data is not None
325
+ and _is_root_run(run_dict)
326
+ and not allow_duplicating_root_span
227
327
  )
228
- self._span_data_map[run_dict["id"]] = span_data
229
- self._externally_created_traces_ids.add(span_data.trace_id)
328
+ if should_skip_root_span_creation:
329
+ return TrackRootRunResult(
330
+ new_trace_data=span_creation_result.trace_data,
331
+ new_span_data=None,
332
+ )
230
333
 
231
- def _attach_span_to_existing_trace(
232
- self,
233
- run_dict: Dict[str, Any],
234
- current_trace_data: trace.TraceData,
235
- root_metadata: Dict[str, Any],
334
+ return TrackRootRunResult(
335
+ new_trace_data=span_creation_result.trace_data,
336
+ new_span_data=span_creation_result.span_data,
337
+ )
338
+
339
+ def _process_start_span(self, run: Run, allow_duplicating_root_span: bool) -> None:
340
+ try:
341
+ self._process_start_span_unsafe(run, allow_duplicating_root_span)
342
+ except Exception as e:
343
+ LOGGER.error("Failed during _process_start_span: %s", e, exc_info=True)
344
+
345
+ def _process_start_span_unsafe(
346
+ self, run: Run, allow_duplicating_root_span: bool
236
347
  ) -> None:
348
+ run_dict: Dict[str, Any] = run.dict()
349
+
350
+ if not run.parent_run_id:
351
+ self._create_root_trace_and_span(
352
+ run_id=run.id,
353
+ run_dict=run_dict,
354
+ allow_duplicating_root_span=allow_duplicating_root_span,
355
+ )
356
+ return
357
+
358
+ # Check if the parent is a skipped LangGraph/LangChain root run.
359
+ # If so, attach children directly to trace.
360
+ # Otherwise, attach to the parent span.
361
+ if run.parent_run_id in self._skipped_langgraph_root_run_ids:
362
+ self._attach_span_to_local_or_distributed_trace(
363
+ run_id=run.id,
364
+ parent_run_id=run.parent_run_id,
365
+ run_dict=run_dict,
366
+ )
367
+ else:
368
+ self._attach_span_to_parent_span(
369
+ run_id=run.id, parent_run_id=run.parent_run_id, run_dict=run_dict
370
+ )
371
+
372
+ def _create_root_trace_and_span(
373
+ self, run_id: UUID, run_dict: Dict[str, Any], allow_duplicating_root_span: bool
374
+ ) -> None:
375
+ """
376
+ Creates a root trace and span for a given run and stores the relevant trace and span
377
+ data in local storage for future reference.
378
+
379
+ The new span is only created if no new trace is created, i.e., when attached to an existing span
380
+ or distributed headers. If a new trace is created, the span is skipped and only the
381
+ trace data is stored in local storage for future reference.
382
+ """
383
+ # This is the first run for the chain.
384
+ root_run_result = self._track_root_run(run_dict, allow_duplicating_root_span)
385
+ if root_run_result.new_trace_data is not None:
386
+ if not self._opik_context_read_only_mode:
387
+ self._opik_context_storage.set_trace_data(
388
+ root_run_result.new_trace_data
389
+ )
390
+
391
+ if (
392
+ self._opik_client.config.log_start_trace_span
393
+ and tracing_runtime_config.is_tracing_active()
394
+ ):
395
+ self._opik_client.trace(
396
+ **root_run_result.new_trace_data.as_start_parameters
397
+ )
398
+
399
+ # If this is a LangGraph/LangChain root run under fresh trace, skip creating the span
400
+ if root_run_result.new_span_data is None:
401
+ # Mark this run as skipped and store trace data for child runs
402
+ self._skipped_langgraph_root_run_ids.add(run_id)
403
+
404
+ # Store parent span ID if LangGraph was attached to the existing span
405
+ parent_span_id = self._root_run_external_parent_span_id.get()
406
+ self._langgraph_parent_span_ids[run_id] = parent_span_id
407
+
408
+ # Store trace data if we created a new trace but skip span data
409
+ if root_run_result.new_trace_data is not None:
410
+ self._save_span_trace_data_to_local_maps(
411
+ run_id=run_id,
412
+ span_data=None,
413
+ trace_data=root_run_result.new_trace_data,
414
+ )
415
+ else:
416
+ # save new span and trace data to local maps to be able to retrieve them later
417
+ self._save_span_trace_data_to_local_maps(
418
+ run_id=run_id,
419
+ span_data=root_run_result.new_span_data,
420
+ trace_data=root_run_result.new_trace_data,
421
+ )
422
+
423
+ if not self._opik_context_read_only_mode:
424
+ self._opik_context_storage.add_span_data(root_run_result.new_span_data)
425
+
426
+ if (
427
+ self._opik_client.config.log_start_trace_span
428
+ and tracing_runtime_config.is_tracing_active()
429
+ ):
430
+ self._opik_client.span(
431
+ **root_run_result.new_span_data.as_start_parameters
432
+ )
433
+
434
+ def _attach_span_to_parent_span(
435
+ self, run_id: UUID, parent_run_id: UUID, run_dict: Dict[str, Any]
436
+ ) -> None:
437
+ """
438
+ Attaches child span to a parent span and updates relevant context storage.
439
+
440
+ This method is responsible for creating a new span data object associated with a
441
+ run, linking it to the parent span data, and saving it to local and external maps.
442
+ Additionally, it updates the context storage and logs the span if tracing is active.
443
+ """
444
+ parent_span_data = self._span_data_map[parent_run_id]
445
+
237
446
  project_name = helpers.resolve_child_span_project_name(
238
- current_trace_data.project_name,
447
+ parent_span_data.project_name,
239
448
  self._project_name,
240
449
  )
241
450
 
242
- span_data = span.SpanData(
243
- trace_id=current_trace_data.id,
244
- parent_span_id=None,
245
- name=run_dict["name"],
451
+ new_span_data = span.SpanData(
452
+ trace_id=parent_span_data.trace_id,
453
+ parent_span_id=parent_span_data.id,
246
454
  input=run_dict["inputs"],
247
- metadata=root_metadata,
248
- tags=self._trace_default_tags,
249
- project_name=project_name,
455
+ metadata=_get_run_metadata(run_dict),
456
+ name=run_dict["name"],
250
457
  type=_get_span_type(run_dict),
458
+ project_name=project_name,
251
459
  )
252
- self._span_data_map[run_dict["id"]] = span_data
253
- self._externally_created_traces_ids.add(current_trace_data.id)
460
+ new_span_data.update(metadata={"created_from": "langchain"})
254
461
 
255
- def _attach_span_to_distributed_headers(
256
- self,
257
- run_dict: Dict[str, Any],
258
- root_metadata: Dict[str, Any],
462
+ self._save_span_trace_data_to_local_maps(
463
+ run_id=run_id,
464
+ span_data=new_span_data,
465
+ trace_data=None,
466
+ )
467
+
468
+ if new_span_data.trace_id not in self._externally_created_traces_ids:
469
+ self._created_traces_data_map[run_id] = self._created_traces_data_map[
470
+ parent_run_id
471
+ ]
472
+
473
+ if not self._opik_context_read_only_mode:
474
+ self._opik_context_storage.add_span_data(new_span_data)
475
+
476
+ if (
477
+ self._opik_client.config.log_start_trace_span
478
+ and tracing_runtime_config.is_tracing_active()
479
+ ):
480
+ self._opik_client.span(**new_span_data.as_start_parameters)
481
+
482
+ def _attach_span_to_local_or_distributed_trace(
483
+ self, run_id: UUID, parent_run_id: UUID, run_dict: Dict[str, Any]
259
484
  ) -> None:
260
- if self._distributed_headers is None:
261
- raise ValueError("Distributed headers are not set")
485
+ """
486
+ Attaches child span directly to a trace by checking trace data or distributed
487
+ headers and creates new span data based on the provided run information.
488
+ """
489
+ # Check if we have trace data (new trace) or distributed headers
490
+ if parent_run_id in self._created_traces_data_map:
491
+ # LangGraph created a new trace - attach children directly to trace
492
+ trace_data = self._created_traces_data_map[parent_run_id]
493
+ project_name = helpers.resolve_child_span_project_name(
494
+ trace_data.project_name,
495
+ self._project_name,
496
+ )
262
497
 
263
- span_data = span.SpanData(
264
- trace_id=self._distributed_headers["opik_trace_id"],
265
- parent_span_id=self._distributed_headers["opik_parent_span_id"],
266
- name=run_dict["name"],
267
- input=run_dict["inputs"],
268
- metadata=root_metadata,
269
- tags=self._trace_default_tags,
270
- project_name=self._project_name,
271
- type=_get_span_type(run_dict),
272
- )
273
- self._span_data_map[run_dict["id"]] = span_data
274
- self._externally_created_traces_ids.add(span_data.trace_id)
498
+ new_span_data = span.SpanData(
499
+ trace_id=trace_data.id,
500
+ parent_span_id=None, # Direct child of trace
501
+ input=run_dict["inputs"],
502
+ metadata=_get_run_metadata(run_dict),
503
+ name=run_dict["name"],
504
+ type=_get_span_type(run_dict),
505
+ project_name=project_name,
506
+ )
507
+ if new_span_data.trace_id not in self._externally_created_traces_ids:
508
+ self._created_traces_data_map[run_id] = trace_data
509
+
510
+ elif self._distributed_headers:
511
+ # LangGraph with distributed headers - attach to distributed trace
512
+ new_span_data = span.SpanData(
513
+ trace_id=self._distributed_headers["opik_trace_id"],
514
+ parent_span_id=self._distributed_headers["opik_parent_span_id"],
515
+ name=run_dict["name"],
516
+ input=run_dict["inputs"],
517
+ metadata=_get_run_metadata(run_dict),
518
+ tags=self._trace_default_tags,
519
+ project_name=self._project_name,
520
+ type=_get_span_type(run_dict),
521
+ )
522
+ self._externally_created_traces_ids.add(new_span_data.trace_id)
275
523
 
276
- def _process_end_span(self, run: "Run") -> None:
277
- run_dict: Dict[str, Any] = run.dict()
278
- span_data = self._span_data_map[run.id]
279
- usage_info = llm_usage.LLMUsageInfo()
524
+ elif (
525
+ current_trace_data := self._opik_context_storage.get_trace_data()
526
+ ) is not None:
527
+ # LangGraph attached to existing trace - attach children directly to trace
528
+ project_name = helpers.resolve_child_span_project_name(
529
+ current_trace_data.project_name,
530
+ self._project_name,
531
+ )
280
532
 
281
- if openai_run_helpers.is_openai_run(run):
282
- usage_info = openai_run_helpers.get_llm_usage_info(run_dict)
283
- elif google_run_helpers.is_google_run(run):
284
- usage_info = google_run_helpers.get_llm_usage_info(run_dict)
533
+ new_span_data = span.SpanData(
534
+ trace_id=current_trace_data.id,
535
+ parent_span_id=None,
536
+ name=run_dict["name"],
537
+ input=run_dict["inputs"],
538
+ metadata=_get_run_metadata(run_dict),
539
+ tags=self._trace_default_tags,
540
+ project_name=project_name,
541
+ type=_get_span_type(run_dict),
542
+ )
285
543
 
286
- # workaround for `.astream()` method usage
287
- if span_data.input == {"input": ""}:
288
- span_data.input = run_dict["inputs"]
289
-
290
- span_data.init_end_time().update(
291
- output=run_dict["outputs"],
292
- usage=usage_info.usage.provider_usage.model_dump()
293
- if isinstance(usage_info.usage, llm_usage.OpikUsage)
294
- else usage_info.usage,
295
- provider=usage_info.provider,
296
- model=usage_info.model,
544
+ if not self._is_opik_trace_created_by_this_tracer(current_trace_data.id):
545
+ self._externally_created_traces_ids.add(current_trace_data.id)
546
+ else:
547
+ LOGGER.warning(
548
+ f"Cannot find trace data or distributed headers for LangGraph child run '{run_id}'"
549
+ )
550
+ return
551
+
552
+ new_span_data.update(metadata={"created_from": "langchain"})
553
+ self._save_span_trace_data_to_local_maps(
554
+ run_id=run_id,
555
+ span_data=new_span_data,
556
+ trace_data=None,
297
557
  )
298
558
 
299
- self._opik_client.span(**span_data.__dict__)
559
+ if not self._opik_context_read_only_mode:
560
+ self._opik_context_storage.add_span_data(new_span_data)
561
+
562
+ if (
563
+ self._opik_client.config.log_start_trace_span
564
+ and tracing_runtime_config.is_tracing_active()
565
+ ):
566
+ self._opik_client.span(**new_span_data.as_start_parameters)
567
+
568
+ def _process_end_span(self, run: Run) -> None:
569
+ span_data = None
570
+ try:
571
+ # Skip processing if this is a skipped LangGraph root run
572
+ if run.id in self._skipped_langgraph_root_run_ids:
573
+ return
574
+
575
+ if run.id not in self._span_data_map:
576
+ LOGGER.warning(
577
+ f"Span data for run '{run.id}' not found in the span data map. Skipping processing of end span."
578
+ )
579
+ return
580
+ span_data = self._span_data_map[run.id]
581
+ run_dict: Dict[str, Any] = run.dict()
582
+
583
+ usage_info = provider_usage_extractors.try_extract_provider_usage_data(
584
+ run_dict
585
+ )
586
+ if usage_info is None:
587
+ usage_info = llm_usage.LLMUsageInfo()
300
588
 
301
- def _process_end_span_with_error(self, run: "Run") -> None:
302
- run_dict: Dict[str, Any] = run.dict()
303
- span_data = self._span_data_map[run.id]
304
- error_info: ErrorInfoDict = {
305
- "exception_type": "Exception",
306
- "traceback": run_dict["error"],
307
- }
589
+ # workaround for `.astream()` method usage
590
+ if span_data.input == {"input": ""}:
591
+ span_data.input = run_dict["inputs"]
308
592
 
309
- span_data.init_end_time().update(
310
- output=None,
311
- error_info=error_info,
312
- )
313
- self._opik_client.span(**span_data.__dict__)
593
+ filtered_output, additional_metadata = (
594
+ langchain_helpers.split_big_langgraph_outputs(run_dict["outputs"])
595
+ )
596
+
597
+ if additional_metadata:
598
+ span_data.update(metadata=additional_metadata)
599
+
600
+ span_data.init_end_time().update(
601
+ output=filtered_output,
602
+ usage=(
603
+ usage_info.usage.provider_usage.model_dump()
604
+ if isinstance(usage_info.usage, llm_usage.OpikUsage)
605
+ else usage_info.usage
606
+ ),
607
+ provider=usage_info.provider,
608
+ model=usage_info.model,
609
+ )
610
+
611
+ if tracing_runtime_config.is_tracing_active():
612
+ self._opik_client.span(**span_data.as_parameters)
613
+ except Exception as e:
614
+ LOGGER.error(f"Failed during _process_end_span: {e}", exc_info=True)
615
+ finally:
616
+ if span_data is not None and not self._opik_context_read_only_mode:
617
+ self._opik_context_storage.trim_span_data_stack_to_certain_span(
618
+ span_id=span_data.id
619
+ )
620
+ self._opik_context_storage.pop_span_data(ensure_id=span_data.id)
621
+
622
+ def _should_skip_error(self, error_str: str) -> bool:
623
+ if self._skip_error_callback is None:
624
+ return False
625
+
626
+ return self._skip_error_callback(error_str)
627
+
628
+ def _process_end_span_with_error(self, run: Run) -> None:
629
+ # Skip processing if this is a skipped LangGraph root run
630
+ if run.id in self._skipped_langgraph_root_run_ids:
631
+ return
632
+
633
+ if run.id not in self._span_data_map:
634
+ LOGGER.warning(
635
+ f"Span data for run '{run.id}' not found in the span data map. Skipping processing of _process_end_span_with_error."
636
+ )
637
+ return
638
+
639
+ span_data = None
640
+ try:
641
+ run_dict: Dict[str, Any] = run.dict()
642
+ span_data = self._span_data_map[run.id]
643
+ error_str = run_dict["error"]
644
+
645
+ if self._should_skip_error(error_str):
646
+ span_data.init_end_time().update(output=ERROR_SKIPPED_OUTPUTS)
647
+ else:
648
+ error_info = ErrorInfoDict(
649
+ exception_type="Exception",
650
+ traceback=error_str,
651
+ )
652
+ span_data.init_end_time().update(
653
+ output=None,
654
+ error_info=error_info,
655
+ )
656
+
657
+ if tracing_runtime_config.is_tracing_active():
658
+ self._opik_client.span(**span_data.as_parameters)
659
+ except Exception as e:
660
+ LOGGER.debug(f"Failed during _process_end_span_with_error: {e}")
661
+ finally:
662
+ if span_data is not None and not self._opik_context_read_only_mode:
663
+ self._opik_context_storage.trim_span_data_stack_to_certain_span(
664
+ span_id=span_data.id
665
+ )
666
+ self._opik_context_storage.pop_span_data(ensure_id=span_data.id)
667
+
668
+ def _update_thread_id_from_metadata(self, run_dict: Dict[str, Any]) -> None:
669
+ if not self._thread_id:
670
+ # We want to default to any manually set thread_id, so only update if self._thread_id is not already set
671
+ thread_id = _get_run_metadata(run_dict).get("thread_id")
672
+
673
+ if thread_id:
674
+ self._thread_id = thread_id
675
+
676
+ def _save_span_trace_data_to_local_maps(
677
+ self,
678
+ run_id: UUID,
679
+ span_data: Optional[span.SpanData],
680
+ trace_data: Optional[trace.TraceData],
681
+ ) -> None:
682
+ if span_data is not None:
683
+ self._span_data_map[run_id] = span_data
684
+
685
+ if trace_data is not None:
686
+ self._created_traces_data_map[run_id] = trace_data
314
687
 
315
688
  def flush(self) -> None:
316
689
  """
@@ -327,70 +700,132 @@ class OpikTracer(BaseTracer):
327
700
  """
328
701
  return self._created_traces
329
702
 
703
+ def get_current_span_data_for_run(self, run_id: UUID) -> Optional[span.SpanData]:
704
+ return self._span_data_map.get(run_id)
705
+
330
706
  def _skip_tracking(self) -> bool:
331
- config = self._opik_client.config
332
- if config.track_disable:
707
+ if not tracing_runtime_config.is_tracing_active():
333
708
  return True
334
709
 
335
710
  return False
336
711
 
337
- def _on_llm_start(self, run: "Run") -> None:
712
+ def _on_llm_start(self, run: Run) -> None:
338
713
  """Process the LLM Run upon start."""
339
714
  if self._skip_tracking():
340
715
  return
341
716
 
342
- self._process_start_span(run)
717
+ self._process_start_span(run, allow_duplicating_root_span=True)
718
+
719
+ def on_chat_model_start(
720
+ self,
721
+ serialized: Dict[str, Any],
722
+ messages: List[List["BaseMessage"]],
723
+ *,
724
+ run_id: UUID,
725
+ tags: Optional[List[str]] = None,
726
+ parent_run_id: Optional[UUID] = None,
727
+ metadata: Optional[Dict[str, Any]] = None,
728
+ name: Optional[str] = None,
729
+ **kwargs: Any,
730
+ ) -> Run:
731
+ """Start a trace for an LLM run.
732
+
733
+ Duplicated from Langchain tracer, it is disabled by default in all tracers, see https://github.com/langchain-ai/langchain/blob/fdda1aaea14b257845a19023e8af5e20140ec9fe/libs/core/langchain_core/callbacks/manager.py#L270-L289 and https://github.com/langchain-ai/langchain/blob/fdda1aaea14b257845a19023e8af5e20140ec9fe/libs/core/langchain_core/tracers/core.py#L168-L180
734
+
735
+ Args:
736
+ serialized: The serialized model.
737
+ messages: The messages.
738
+ run_id: The run ID.
739
+ tags: The tags. Defaults to None.
740
+ parent_run_id: The parent run ID. Defaults to None.
741
+ metadata: The metadata. Defaults to None.
742
+ name: The name. Defaults to None.
743
+ kwargs: Additional keyword arguments.
744
+
745
+ Returns:
746
+ Run: The run.
747
+ """
748
+ start_time = datetime.datetime.now(datetime.timezone.utc)
749
+ if metadata:
750
+ kwargs.update({"metadata": metadata})
751
+
752
+ # We switched from langchain dumpd to model_dump() as we don't need all the langchain stuff
753
+ chat_model_run = Run(
754
+ id=run_id,
755
+ parent_run_id=parent_run_id,
756
+ serialized=serialized,
757
+ inputs={
758
+ "messages": [[msg.model_dump() for msg in batch] for batch in messages]
759
+ },
760
+ extra=kwargs,
761
+ events=[{"name": "start", "time": start_time}],
762
+ start_time=start_time,
763
+ run_type="llm",
764
+ tags=tags,
765
+ name=name, # type: ignore[arg-type]
766
+ )
767
+
768
+ self._start_trace(chat_model_run)
769
+ self._on_chat_model_start(chat_model_run)
770
+ return chat_model_run
771
+
772
+ def _on_chat_model_start(self, run: Run) -> None:
773
+ """Process the Chat Model Run upon start."""
774
+ if self._skip_tracking():
775
+ return
776
+
777
+ self._process_start_span(run, allow_duplicating_root_span=True)
343
778
 
344
- def _on_llm_end(self, run: "Run") -> None:
779
+ def _on_llm_end(self, run: Run) -> None:
345
780
  """Process the LLM Run."""
346
781
  if self._skip_tracking():
347
782
  return
348
783
 
349
784
  self._process_end_span(run)
350
785
 
351
- def _on_llm_error(self, run: "Run") -> None:
786
+ def _on_llm_error(self, run: Run) -> None:
352
787
  """Process the LLM Run upon error."""
353
788
  if self._skip_tracking():
354
789
  return
355
790
 
356
791
  self._process_end_span_with_error(run)
357
792
 
358
- def _on_chain_start(self, run: "Run") -> None:
793
+ def _on_chain_start(self, run: Run) -> None:
359
794
  """Process the Chain Run upon start."""
360
795
  if self._skip_tracking():
361
796
  return
362
797
 
363
- self._process_start_span(run)
798
+ self._process_start_span(run, allow_duplicating_root_span=False)
364
799
 
365
- def _on_chain_end(self, run: "Run") -> None:
800
+ def _on_chain_end(self, run: Run) -> None:
366
801
  """Process the Chain Run."""
367
802
  if self._skip_tracking():
368
803
  return
369
804
 
370
805
  self._process_end_span(run)
371
806
 
372
- def _on_chain_error(self, run: "Run") -> None:
807
+ def _on_chain_error(self, run: Run) -> None:
373
808
  """Process the Chain Run upon error."""
374
809
  if self._skip_tracking():
375
810
  return
376
811
 
377
812
  self._process_end_span_with_error(run)
378
813
 
379
- def _on_tool_start(self, run: "Run") -> None:
814
+ def _on_tool_start(self, run: Run) -> None:
380
815
  """Process the Tool Run upon start."""
381
816
  if self._skip_tracking():
382
817
  return
383
818
 
384
- self._process_start_span(run)
819
+ self._process_start_span(run, allow_duplicating_root_span=True)
385
820
 
386
- def _on_tool_end(self, run: "Run") -> None:
821
+ def _on_tool_end(self, run: Run) -> None:
387
822
  """Process the Tool Run."""
388
823
  if self._skip_tracking():
389
824
  return
390
825
 
391
826
  self._process_end_span(run)
392
827
 
393
- def _on_tool_error(self, run: "Run") -> None:
828
+ def _on_tool_error(self, run: Run) -> None:
394
829
  """Process the Tool Run upon error."""
395
830
  if self._skip_tracking():
396
831
  return