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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (592) hide show
  1. opik/__init__.py +19 -3
  2. opik/anonymizer/__init__.py +5 -0
  3. opik/anonymizer/anonymizer.py +12 -0
  4. opik/anonymizer/factory.py +80 -0
  5. opik/anonymizer/recursive_anonymizer.py +64 -0
  6. opik/anonymizer/rules.py +56 -0
  7. opik/anonymizer/rules_anonymizer.py +35 -0
  8. opik/api_objects/attachment/attachment_context.py +36 -0
  9. opik/api_objects/attachment/attachments_extractor.py +153 -0
  10. opik/api_objects/attachment/client.py +1 -0
  11. opik/api_objects/attachment/converters.py +2 -0
  12. opik/api_objects/attachment/decoder.py +18 -0
  13. opik/api_objects/attachment/decoder_base64.py +83 -0
  14. opik/api_objects/attachment/decoder_helpers.py +137 -0
  15. opik/api_objects/data_helpers.py +79 -0
  16. opik/api_objects/dataset/dataset.py +64 -4
  17. opik/api_objects/dataset/rest_operations.py +11 -2
  18. opik/api_objects/experiment/experiment.py +57 -57
  19. opik/api_objects/experiment/experiment_item.py +2 -1
  20. opik/api_objects/experiment/experiments_client.py +64 -0
  21. opik/api_objects/experiment/helpers.py +35 -11
  22. opik/api_objects/experiment/rest_operations.py +65 -5
  23. opik/api_objects/helpers.py +8 -5
  24. opik/api_objects/local_recording.py +81 -0
  25. opik/api_objects/opik_client.py +600 -108
  26. opik/api_objects/opik_query_language.py +39 -5
  27. opik/api_objects/prompt/__init__.py +12 -2
  28. opik/api_objects/prompt/base_prompt.py +69 -0
  29. opik/api_objects/prompt/base_prompt_template.py +29 -0
  30. opik/api_objects/prompt/chat/__init__.py +1 -0
  31. opik/api_objects/prompt/chat/chat_prompt.py +210 -0
  32. opik/api_objects/prompt/chat/chat_prompt_template.py +350 -0
  33. opik/api_objects/prompt/chat/content_renderer_registry.py +203 -0
  34. opik/api_objects/prompt/client.py +189 -47
  35. opik/api_objects/prompt/text/__init__.py +1 -0
  36. opik/api_objects/prompt/text/prompt.py +174 -0
  37. opik/api_objects/prompt/{prompt_template.py → text/prompt_template.py} +10 -6
  38. opik/api_objects/prompt/types.py +23 -0
  39. opik/api_objects/search_helpers.py +89 -0
  40. opik/api_objects/span/span_data.py +35 -25
  41. opik/api_objects/threads/threads_client.py +39 -5
  42. opik/api_objects/trace/trace_client.py +52 -2
  43. opik/api_objects/trace/trace_data.py +15 -24
  44. opik/api_objects/validation_helpers.py +3 -3
  45. opik/cli/__init__.py +5 -0
  46. opik/cli/__main__.py +6 -0
  47. opik/cli/configure.py +66 -0
  48. opik/cli/exports/__init__.py +131 -0
  49. opik/cli/exports/dataset.py +278 -0
  50. opik/cli/exports/experiment.py +784 -0
  51. opik/cli/exports/project.py +685 -0
  52. opik/cli/exports/prompt.py +578 -0
  53. opik/cli/exports/utils.py +406 -0
  54. opik/cli/harbor.py +39 -0
  55. opik/cli/healthcheck.py +21 -0
  56. opik/cli/imports/__init__.py +439 -0
  57. opik/cli/imports/dataset.py +143 -0
  58. opik/cli/imports/experiment.py +1192 -0
  59. opik/cli/imports/project.py +262 -0
  60. opik/cli/imports/prompt.py +177 -0
  61. opik/cli/imports/utils.py +280 -0
  62. opik/cli/main.py +49 -0
  63. opik/cli/proxy.py +93 -0
  64. opik/cli/usage_report/__init__.py +16 -0
  65. opik/cli/usage_report/charts.py +783 -0
  66. opik/cli/usage_report/cli.py +274 -0
  67. opik/cli/usage_report/constants.py +9 -0
  68. opik/cli/usage_report/extraction.py +749 -0
  69. opik/cli/usage_report/pdf.py +244 -0
  70. opik/cli/usage_report/statistics.py +78 -0
  71. opik/cli/usage_report/utils.py +235 -0
  72. opik/config.py +13 -7
  73. opik/configurator/configure.py +17 -0
  74. opik/datetime_helpers.py +12 -0
  75. opik/decorator/arguments_helpers.py +9 -1
  76. opik/decorator/base_track_decorator.py +205 -133
  77. opik/decorator/context_manager/span_context_manager.py +123 -0
  78. opik/decorator/context_manager/trace_context_manager.py +84 -0
  79. opik/decorator/opik_args/__init__.py +13 -0
  80. opik/decorator/opik_args/api_classes.py +71 -0
  81. opik/decorator/opik_args/helpers.py +120 -0
  82. opik/decorator/span_creation_handler.py +25 -6
  83. opik/dict_utils.py +3 -3
  84. opik/evaluation/__init__.py +13 -2
  85. opik/evaluation/engine/engine.py +272 -75
  86. opik/evaluation/engine/evaluation_tasks_executor.py +6 -3
  87. opik/evaluation/engine/helpers.py +31 -6
  88. opik/evaluation/engine/metrics_evaluator.py +237 -0
  89. opik/evaluation/evaluation_result.py +168 -2
  90. opik/evaluation/evaluator.py +533 -62
  91. opik/evaluation/metrics/__init__.py +103 -4
  92. opik/evaluation/metrics/aggregated_metric.py +35 -6
  93. opik/evaluation/metrics/base_metric.py +1 -1
  94. opik/evaluation/metrics/conversation/__init__.py +48 -0
  95. opik/evaluation/metrics/conversation/conversation_thread_metric.py +56 -2
  96. opik/evaluation/metrics/conversation/g_eval_wrappers.py +19 -0
  97. opik/evaluation/metrics/conversation/helpers.py +14 -15
  98. opik/evaluation/metrics/conversation/heuristics/__init__.py +14 -0
  99. opik/evaluation/metrics/conversation/heuristics/degeneration/__init__.py +3 -0
  100. opik/evaluation/metrics/conversation/heuristics/degeneration/metric.py +189 -0
  101. opik/evaluation/metrics/conversation/heuristics/degeneration/phrases.py +12 -0
  102. opik/evaluation/metrics/conversation/heuristics/knowledge_retention/__init__.py +3 -0
  103. opik/evaluation/metrics/conversation/heuristics/knowledge_retention/metric.py +172 -0
  104. opik/evaluation/metrics/conversation/llm_judges/__init__.py +32 -0
  105. opik/evaluation/metrics/conversation/{conversational_coherence → llm_judges/conversational_coherence}/metric.py +22 -17
  106. opik/evaluation/metrics/conversation/{conversational_coherence → llm_judges/conversational_coherence}/templates.py +1 -1
  107. opik/evaluation/metrics/conversation/llm_judges/g_eval_wrappers.py +442 -0
  108. opik/evaluation/metrics/conversation/{session_completeness → llm_judges/session_completeness}/metric.py +13 -7
  109. opik/evaluation/metrics/conversation/{session_completeness → llm_judges/session_completeness}/templates.py +1 -1
  110. opik/evaluation/metrics/conversation/llm_judges/user_frustration/__init__.py +0 -0
  111. opik/evaluation/metrics/conversation/{user_frustration → llm_judges/user_frustration}/metric.py +21 -14
  112. opik/evaluation/metrics/conversation/{user_frustration → llm_judges/user_frustration}/templates.py +1 -1
  113. opik/evaluation/metrics/conversation/types.py +4 -5
  114. opik/evaluation/metrics/conversation_types.py +9 -0
  115. opik/evaluation/metrics/heuristics/bertscore.py +107 -0
  116. opik/evaluation/metrics/heuristics/bleu.py +35 -15
  117. opik/evaluation/metrics/heuristics/chrf.py +127 -0
  118. opik/evaluation/metrics/heuristics/contains.py +47 -11
  119. opik/evaluation/metrics/heuristics/distribution_metrics.py +331 -0
  120. opik/evaluation/metrics/heuristics/gleu.py +113 -0
  121. opik/evaluation/metrics/heuristics/language_adherence.py +123 -0
  122. opik/evaluation/metrics/heuristics/meteor.py +119 -0
  123. opik/evaluation/metrics/heuristics/prompt_injection.py +150 -0
  124. opik/evaluation/metrics/heuristics/readability.py +129 -0
  125. opik/evaluation/metrics/heuristics/rouge.py +26 -9
  126. opik/evaluation/metrics/heuristics/spearman.py +88 -0
  127. opik/evaluation/metrics/heuristics/tone.py +155 -0
  128. opik/evaluation/metrics/heuristics/vader_sentiment.py +77 -0
  129. opik/evaluation/metrics/llm_judges/answer_relevance/metric.py +20 -5
  130. opik/evaluation/metrics/llm_judges/context_precision/metric.py +20 -6
  131. opik/evaluation/metrics/llm_judges/context_recall/metric.py +20 -6
  132. opik/evaluation/metrics/llm_judges/g_eval/__init__.py +5 -0
  133. opik/evaluation/metrics/llm_judges/g_eval/metric.py +219 -68
  134. opik/evaluation/metrics/llm_judges/g_eval/parser.py +102 -52
  135. opik/evaluation/metrics/llm_judges/g_eval/presets.py +209 -0
  136. opik/evaluation/metrics/llm_judges/g_eval_presets/__init__.py +36 -0
  137. opik/evaluation/metrics/llm_judges/g_eval_presets/agent_assessment.py +77 -0
  138. opik/evaluation/metrics/llm_judges/g_eval_presets/bias_classifier.py +181 -0
  139. opik/evaluation/metrics/llm_judges/g_eval_presets/compliance_risk.py +41 -0
  140. opik/evaluation/metrics/llm_judges/g_eval_presets/prompt_uncertainty.py +41 -0
  141. opik/evaluation/metrics/llm_judges/g_eval_presets/qa_suite.py +146 -0
  142. opik/evaluation/metrics/llm_judges/hallucination/metric.py +16 -3
  143. opik/evaluation/metrics/llm_judges/llm_juries/__init__.py +3 -0
  144. opik/evaluation/metrics/llm_judges/llm_juries/metric.py +76 -0
  145. opik/evaluation/metrics/llm_judges/moderation/metric.py +16 -4
  146. opik/evaluation/metrics/llm_judges/structure_output_compliance/__init__.py +0 -0
  147. opik/evaluation/metrics/llm_judges/structure_output_compliance/metric.py +144 -0
  148. opik/evaluation/metrics/llm_judges/structure_output_compliance/parser.py +79 -0
  149. opik/evaluation/metrics/llm_judges/structure_output_compliance/schema.py +15 -0
  150. opik/evaluation/metrics/llm_judges/structure_output_compliance/template.py +50 -0
  151. opik/evaluation/metrics/llm_judges/syc_eval/__init__.py +0 -0
  152. opik/evaluation/metrics/llm_judges/syc_eval/metric.py +252 -0
  153. opik/evaluation/metrics/llm_judges/syc_eval/parser.py +82 -0
  154. opik/evaluation/metrics/llm_judges/syc_eval/template.py +155 -0
  155. opik/evaluation/metrics/llm_judges/trajectory_accuracy/metric.py +20 -5
  156. opik/evaluation/metrics/llm_judges/usefulness/metric.py +16 -4
  157. opik/evaluation/metrics/ragas_metric.py +43 -23
  158. opik/evaluation/models/__init__.py +8 -0
  159. opik/evaluation/models/base_model.py +107 -1
  160. opik/evaluation/models/langchain/langchain_chat_model.py +15 -7
  161. opik/evaluation/models/langchain/message_converters.py +97 -15
  162. opik/evaluation/models/litellm/litellm_chat_model.py +156 -29
  163. opik/evaluation/models/litellm/util.py +125 -0
  164. opik/evaluation/models/litellm/warning_filters.py +16 -4
  165. opik/evaluation/models/model_capabilities.py +187 -0
  166. opik/evaluation/models/models_factory.py +25 -3
  167. opik/evaluation/preprocessing.py +92 -0
  168. opik/evaluation/report.py +70 -12
  169. opik/evaluation/rest_operations.py +49 -45
  170. opik/evaluation/samplers/__init__.py +4 -0
  171. opik/evaluation/samplers/base_dataset_sampler.py +40 -0
  172. opik/evaluation/samplers/random_dataset_sampler.py +48 -0
  173. opik/evaluation/score_statistics.py +66 -0
  174. opik/evaluation/scorers/__init__.py +4 -0
  175. opik/evaluation/scorers/scorer_function.py +55 -0
  176. opik/evaluation/scorers/scorer_wrapper_metric.py +130 -0
  177. opik/evaluation/test_case.py +3 -2
  178. opik/evaluation/test_result.py +1 -0
  179. opik/evaluation/threads/evaluator.py +31 -3
  180. opik/evaluation/threads/helpers.py +3 -2
  181. opik/evaluation/types.py +9 -1
  182. opik/exceptions.py +33 -0
  183. opik/file_upload/file_uploader.py +13 -0
  184. opik/file_upload/upload_options.py +2 -0
  185. opik/hooks/__init__.py +23 -0
  186. opik/hooks/anonymizer_hook.py +36 -0
  187. opik/hooks/httpx_client_hook.py +112 -0
  188. opik/httpx_client.py +12 -9
  189. opik/id_helpers.py +18 -0
  190. opik/integrations/adk/graph/subgraph_edges_builders.py +1 -2
  191. opik/integrations/adk/helpers.py +16 -7
  192. opik/integrations/adk/legacy_opik_tracer.py +7 -4
  193. opik/integrations/adk/opik_tracer.py +14 -1
  194. opik/integrations/adk/patchers/adk_otel_tracer/opik_adk_otel_tracer.py +7 -3
  195. opik/integrations/adk/recursive_callback_injector.py +4 -7
  196. opik/integrations/bedrock/converse/__init__.py +0 -0
  197. opik/integrations/bedrock/converse/chunks_aggregator.py +188 -0
  198. opik/integrations/bedrock/{converse_decorator.py → converse/converse_decorator.py} +4 -3
  199. opik/integrations/bedrock/invoke_agent_decorator.py +5 -4
  200. opik/integrations/bedrock/invoke_model/__init__.py +0 -0
  201. opik/integrations/bedrock/invoke_model/chunks_aggregator/__init__.py +78 -0
  202. opik/integrations/bedrock/invoke_model/chunks_aggregator/api.py +45 -0
  203. opik/integrations/bedrock/invoke_model/chunks_aggregator/base.py +23 -0
  204. opik/integrations/bedrock/invoke_model/chunks_aggregator/claude.py +121 -0
  205. opik/integrations/bedrock/invoke_model/chunks_aggregator/format_detector.py +107 -0
  206. opik/integrations/bedrock/invoke_model/chunks_aggregator/llama.py +108 -0
  207. opik/integrations/bedrock/invoke_model/chunks_aggregator/mistral.py +118 -0
  208. opik/integrations/bedrock/invoke_model/chunks_aggregator/nova.py +99 -0
  209. opik/integrations/bedrock/invoke_model/invoke_model_decorator.py +178 -0
  210. opik/integrations/bedrock/invoke_model/response_types.py +34 -0
  211. opik/integrations/bedrock/invoke_model/stream_wrappers.py +122 -0
  212. opik/integrations/bedrock/invoke_model/usage_converters.py +87 -0
  213. opik/integrations/bedrock/invoke_model/usage_extraction.py +108 -0
  214. opik/integrations/bedrock/opik_tracker.py +42 -4
  215. opik/integrations/bedrock/types.py +19 -0
  216. opik/integrations/crewai/crewai_decorator.py +8 -51
  217. opik/integrations/crewai/opik_tracker.py +31 -10
  218. opik/integrations/crewai/patchers/__init__.py +5 -0
  219. opik/integrations/crewai/patchers/flow.py +118 -0
  220. opik/integrations/crewai/patchers/litellm_completion.py +30 -0
  221. opik/integrations/crewai/patchers/llm_client.py +207 -0
  222. opik/integrations/dspy/callback.py +80 -17
  223. opik/integrations/dspy/parsers.py +168 -0
  224. opik/integrations/harbor/__init__.py +17 -0
  225. opik/integrations/harbor/experiment_service.py +269 -0
  226. opik/integrations/harbor/opik_tracker.py +528 -0
  227. opik/integrations/haystack/opik_connector.py +2 -2
  228. opik/integrations/haystack/opik_tracer.py +3 -7
  229. opik/integrations/langchain/__init__.py +3 -1
  230. opik/integrations/langchain/helpers.py +96 -0
  231. opik/integrations/langchain/langgraph_async_context_bridge.py +131 -0
  232. opik/integrations/langchain/langgraph_tracer_injector.py +88 -0
  233. opik/integrations/langchain/opik_encoder_extension.py +1 -1
  234. opik/integrations/langchain/opik_tracer.py +474 -229
  235. opik/integrations/litellm/__init__.py +5 -0
  236. opik/integrations/litellm/completion_chunks_aggregator.py +115 -0
  237. opik/integrations/litellm/litellm_completion_decorator.py +242 -0
  238. opik/integrations/litellm/opik_tracker.py +43 -0
  239. opik/integrations/litellm/stream_patchers.py +151 -0
  240. opik/integrations/llama_index/callback.py +146 -107
  241. opik/integrations/openai/agents/opik_tracing_processor.py +1 -2
  242. opik/integrations/openai/openai_chat_completions_decorator.py +2 -16
  243. opik/integrations/openai/opik_tracker.py +1 -1
  244. opik/integrations/sagemaker/auth.py +5 -1
  245. opik/llm_usage/google_usage.py +3 -1
  246. opik/llm_usage/opik_usage.py +7 -8
  247. opik/llm_usage/opik_usage_factory.py +4 -2
  248. opik/logging_messages.py +6 -0
  249. opik/message_processing/batching/base_batcher.py +14 -21
  250. opik/message_processing/batching/batch_manager.py +22 -10
  251. opik/message_processing/batching/batch_manager_constuctors.py +10 -0
  252. opik/message_processing/batching/batchers.py +59 -27
  253. opik/message_processing/batching/flushing_thread.py +0 -3
  254. opik/message_processing/emulation/__init__.py +0 -0
  255. opik/message_processing/emulation/emulator_message_processor.py +578 -0
  256. opik/message_processing/emulation/local_emulator_message_processor.py +140 -0
  257. opik/message_processing/emulation/models.py +162 -0
  258. opik/message_processing/encoder_helpers.py +79 -0
  259. opik/message_processing/messages.py +56 -1
  260. opik/message_processing/preprocessing/__init__.py +0 -0
  261. opik/message_processing/preprocessing/attachments_preprocessor.py +70 -0
  262. opik/message_processing/preprocessing/batching_preprocessor.py +53 -0
  263. opik/message_processing/preprocessing/constants.py +1 -0
  264. opik/message_processing/preprocessing/file_upload_preprocessor.py +38 -0
  265. opik/message_processing/preprocessing/preprocessor.py +36 -0
  266. opik/message_processing/processors/__init__.py +0 -0
  267. opik/message_processing/processors/attachments_extraction_processor.py +146 -0
  268. opik/message_processing/processors/message_processors.py +92 -0
  269. opik/message_processing/processors/message_processors_chain.py +96 -0
  270. opik/message_processing/{message_processors.py → processors/online_message_processor.py} +85 -29
  271. opik/message_processing/queue_consumer.py +9 -3
  272. opik/message_processing/streamer.py +71 -33
  273. opik/message_processing/streamer_constructors.py +43 -10
  274. opik/opik_context.py +16 -4
  275. opik/plugins/pytest/hooks.py +5 -3
  276. opik/rest_api/__init__.py +346 -15
  277. opik/rest_api/alerts/__init__.py +7 -0
  278. opik/rest_api/alerts/client.py +667 -0
  279. opik/rest_api/alerts/raw_client.py +1015 -0
  280. opik/rest_api/alerts/types/__init__.py +7 -0
  281. opik/rest_api/alerts/types/get_webhook_examples_request_alert_type.py +5 -0
  282. opik/rest_api/annotation_queues/__init__.py +4 -0
  283. opik/rest_api/annotation_queues/client.py +668 -0
  284. opik/rest_api/annotation_queues/raw_client.py +1019 -0
  285. opik/rest_api/automation_rule_evaluators/client.py +34 -2
  286. opik/rest_api/automation_rule_evaluators/raw_client.py +24 -0
  287. opik/rest_api/client.py +15 -0
  288. opik/rest_api/dashboards/__init__.py +4 -0
  289. opik/rest_api/dashboards/client.py +462 -0
  290. opik/rest_api/dashboards/raw_client.py +648 -0
  291. opik/rest_api/datasets/client.py +1310 -44
  292. opik/rest_api/datasets/raw_client.py +2269 -358
  293. opik/rest_api/experiments/__init__.py +2 -2
  294. opik/rest_api/experiments/client.py +191 -5
  295. opik/rest_api/experiments/raw_client.py +301 -7
  296. opik/rest_api/experiments/types/__init__.py +4 -1
  297. opik/rest_api/experiments/types/experiment_update_status.py +5 -0
  298. opik/rest_api/experiments/types/experiment_update_type.py +5 -0
  299. opik/rest_api/experiments/types/experiment_write_status.py +5 -0
  300. opik/rest_api/feedback_definitions/types/find_feedback_definitions_request_type.py +1 -1
  301. opik/rest_api/llm_provider_key/client.py +20 -0
  302. opik/rest_api/llm_provider_key/raw_client.py +20 -0
  303. opik/rest_api/llm_provider_key/types/provider_api_key_write_provider.py +1 -1
  304. opik/rest_api/manual_evaluation/__init__.py +4 -0
  305. opik/rest_api/manual_evaluation/client.py +347 -0
  306. opik/rest_api/manual_evaluation/raw_client.py +543 -0
  307. opik/rest_api/optimizations/client.py +145 -9
  308. opik/rest_api/optimizations/raw_client.py +237 -13
  309. opik/rest_api/optimizations/types/optimization_update_status.py +3 -1
  310. opik/rest_api/prompts/__init__.py +2 -2
  311. opik/rest_api/prompts/client.py +227 -6
  312. opik/rest_api/prompts/raw_client.py +331 -2
  313. opik/rest_api/prompts/types/__init__.py +3 -1
  314. opik/rest_api/prompts/types/create_prompt_version_detail_template_structure.py +5 -0
  315. opik/rest_api/prompts/types/prompt_write_template_structure.py +5 -0
  316. opik/rest_api/spans/__init__.py +0 -2
  317. opik/rest_api/spans/client.py +238 -76
  318. opik/rest_api/spans/raw_client.py +307 -95
  319. opik/rest_api/spans/types/__init__.py +0 -2
  320. opik/rest_api/traces/client.py +572 -161
  321. opik/rest_api/traces/raw_client.py +736 -229
  322. opik/rest_api/types/__init__.py +352 -17
  323. opik/rest_api/types/aggregation_data.py +1 -0
  324. opik/rest_api/types/alert.py +33 -0
  325. opik/rest_api/types/alert_alert_type.py +5 -0
  326. opik/rest_api/types/alert_page_public.py +24 -0
  327. opik/rest_api/types/alert_public.py +33 -0
  328. opik/rest_api/types/alert_public_alert_type.py +5 -0
  329. opik/rest_api/types/alert_trigger.py +27 -0
  330. opik/rest_api/types/alert_trigger_config.py +28 -0
  331. opik/rest_api/types/alert_trigger_config_public.py +28 -0
  332. opik/rest_api/types/alert_trigger_config_public_type.py +10 -0
  333. opik/rest_api/types/alert_trigger_config_type.py +10 -0
  334. opik/rest_api/types/alert_trigger_config_write.py +22 -0
  335. opik/rest_api/types/alert_trigger_config_write_type.py +10 -0
  336. opik/rest_api/types/alert_trigger_event_type.py +19 -0
  337. opik/rest_api/types/alert_trigger_public.py +27 -0
  338. opik/rest_api/types/alert_trigger_public_event_type.py +19 -0
  339. opik/rest_api/types/alert_trigger_write.py +23 -0
  340. opik/rest_api/types/alert_trigger_write_event_type.py +19 -0
  341. opik/rest_api/types/alert_write.py +28 -0
  342. opik/rest_api/types/alert_write_alert_type.py +5 -0
  343. opik/rest_api/types/annotation_queue.py +42 -0
  344. opik/rest_api/types/annotation_queue_batch.py +27 -0
  345. opik/rest_api/types/annotation_queue_item_ids.py +19 -0
  346. opik/rest_api/types/annotation_queue_page_public.py +28 -0
  347. opik/rest_api/types/annotation_queue_public.py +38 -0
  348. opik/rest_api/types/annotation_queue_public_scope.py +5 -0
  349. opik/rest_api/types/annotation_queue_reviewer.py +20 -0
  350. opik/rest_api/types/annotation_queue_reviewer_public.py +20 -0
  351. opik/rest_api/types/annotation_queue_scope.py +5 -0
  352. opik/rest_api/types/annotation_queue_write.py +31 -0
  353. opik/rest_api/types/annotation_queue_write_scope.py +5 -0
  354. opik/rest_api/types/audio_url.py +19 -0
  355. opik/rest_api/types/audio_url_public.py +19 -0
  356. opik/rest_api/types/audio_url_write.py +19 -0
  357. opik/rest_api/types/automation_rule_evaluator.py +62 -2
  358. opik/rest_api/types/automation_rule_evaluator_llm_as_judge.py +2 -0
  359. opik/rest_api/types/automation_rule_evaluator_llm_as_judge_public.py +2 -0
  360. opik/rest_api/types/automation_rule_evaluator_llm_as_judge_write.py +2 -0
  361. opik/rest_api/types/automation_rule_evaluator_object_object_public.py +155 -0
  362. opik/rest_api/types/automation_rule_evaluator_page_public.py +3 -2
  363. opik/rest_api/types/automation_rule_evaluator_public.py +57 -2
  364. opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge.py +22 -0
  365. opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_public.py +22 -0
  366. opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_write.py +22 -0
  367. opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python.py +22 -0
  368. opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python_public.py +22 -0
  369. opik/rest_api/types/automation_rule_evaluator_span_user_defined_metric_python_write.py +22 -0
  370. opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge.py +2 -0
  371. opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_public.py +2 -0
  372. opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_write.py +2 -0
  373. opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python.py +2 -0
  374. opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_public.py +2 -0
  375. opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_write.py +2 -0
  376. opik/rest_api/types/automation_rule_evaluator_update.py +51 -1
  377. opik/rest_api/types/automation_rule_evaluator_update_llm_as_judge.py +2 -0
  378. opik/rest_api/types/automation_rule_evaluator_update_span_llm_as_judge.py +22 -0
  379. opik/rest_api/types/automation_rule_evaluator_update_span_user_defined_metric_python.py +22 -0
  380. opik/rest_api/types/automation_rule_evaluator_update_trace_thread_llm_as_judge.py +2 -0
  381. opik/rest_api/types/automation_rule_evaluator_update_trace_thread_user_defined_metric_python.py +2 -0
  382. opik/rest_api/types/automation_rule_evaluator_update_user_defined_metric_python.py +2 -0
  383. opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python.py +2 -0
  384. opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_public.py +2 -0
  385. opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_write.py +2 -0
  386. opik/rest_api/types/automation_rule_evaluator_write.py +51 -1
  387. opik/rest_api/types/boolean_feedback_definition.py +25 -0
  388. opik/rest_api/types/boolean_feedback_definition_create.py +20 -0
  389. opik/rest_api/types/boolean_feedback_definition_public.py +25 -0
  390. opik/rest_api/types/boolean_feedback_definition_update.py +20 -0
  391. opik/rest_api/types/boolean_feedback_detail.py +29 -0
  392. opik/rest_api/types/boolean_feedback_detail_create.py +29 -0
  393. opik/rest_api/types/boolean_feedback_detail_public.py +29 -0
  394. opik/rest_api/types/boolean_feedback_detail_update.py +29 -0
  395. opik/rest_api/types/dashboard_page_public.py +24 -0
  396. opik/rest_api/types/dashboard_public.py +30 -0
  397. opik/rest_api/types/dataset.py +4 -0
  398. opik/rest_api/types/dataset_expansion.py +42 -0
  399. opik/rest_api/types/dataset_expansion_response.py +39 -0
  400. opik/rest_api/types/dataset_item.py +2 -0
  401. opik/rest_api/types/dataset_item_changes_public.py +5 -0
  402. opik/rest_api/types/dataset_item_compare.py +2 -0
  403. opik/rest_api/types/dataset_item_filter.py +27 -0
  404. opik/rest_api/types/dataset_item_filter_operator.py +21 -0
  405. opik/rest_api/types/dataset_item_page_compare.py +5 -0
  406. opik/rest_api/types/dataset_item_page_public.py +5 -0
  407. opik/rest_api/types/dataset_item_public.py +2 -0
  408. opik/rest_api/types/dataset_item_update.py +39 -0
  409. opik/rest_api/types/dataset_item_write.py +1 -0
  410. opik/rest_api/types/dataset_public.py +4 -0
  411. opik/rest_api/types/dataset_public_status.py +5 -0
  412. opik/rest_api/types/dataset_status.py +5 -0
  413. opik/rest_api/types/dataset_version_diff.py +22 -0
  414. opik/rest_api/types/dataset_version_diff_stats.py +24 -0
  415. opik/rest_api/types/dataset_version_page_public.py +23 -0
  416. opik/rest_api/types/dataset_version_public.py +59 -0
  417. opik/rest_api/types/dataset_version_summary.py +46 -0
  418. opik/rest_api/types/dataset_version_summary_public.py +46 -0
  419. opik/rest_api/types/experiment.py +7 -2
  420. opik/rest_api/types/experiment_group_response.py +2 -0
  421. opik/rest_api/types/experiment_public.py +7 -2
  422. opik/rest_api/types/experiment_public_status.py +5 -0
  423. opik/rest_api/types/experiment_score.py +20 -0
  424. opik/rest_api/types/experiment_score_public.py +20 -0
  425. opik/rest_api/types/experiment_score_write.py +20 -0
  426. opik/rest_api/types/experiment_status.py +5 -0
  427. opik/rest_api/types/feedback.py +25 -1
  428. opik/rest_api/types/feedback_create.py +20 -1
  429. opik/rest_api/types/feedback_object_public.py +27 -1
  430. opik/rest_api/types/feedback_public.py +25 -1
  431. opik/rest_api/types/feedback_score_batch_item.py +2 -1
  432. opik/rest_api/types/feedback_score_batch_item_thread.py +2 -1
  433. opik/rest_api/types/feedback_score_public.py +4 -0
  434. opik/rest_api/types/feedback_update.py +20 -1
  435. opik/rest_api/types/group_content_with_aggregations.py +1 -0
  436. opik/rest_api/types/group_detail.py +19 -0
  437. opik/rest_api/types/group_details.py +20 -0
  438. opik/rest_api/types/guardrail.py +1 -0
  439. opik/rest_api/types/guardrail_write.py +1 -0
  440. opik/rest_api/types/ids_holder.py +19 -0
  441. opik/rest_api/types/image_url.py +20 -0
  442. opik/rest_api/types/image_url_public.py +20 -0
  443. opik/rest_api/types/image_url_write.py +20 -0
  444. opik/rest_api/types/llm_as_judge_message.py +5 -1
  445. opik/rest_api/types/llm_as_judge_message_content.py +26 -0
  446. opik/rest_api/types/llm_as_judge_message_content_public.py +26 -0
  447. opik/rest_api/types/llm_as_judge_message_content_write.py +26 -0
  448. opik/rest_api/types/llm_as_judge_message_public.py +5 -1
  449. opik/rest_api/types/llm_as_judge_message_write.py +5 -1
  450. opik/rest_api/types/llm_as_judge_model_parameters.py +3 -0
  451. opik/rest_api/types/llm_as_judge_model_parameters_public.py +3 -0
  452. opik/rest_api/types/llm_as_judge_model_parameters_write.py +3 -0
  453. opik/rest_api/types/manual_evaluation_request.py +38 -0
  454. opik/rest_api/types/manual_evaluation_request_entity_type.py +5 -0
  455. opik/rest_api/types/manual_evaluation_response.py +27 -0
  456. opik/rest_api/types/optimization.py +4 -2
  457. opik/rest_api/types/optimization_public.py +4 -2
  458. opik/rest_api/types/optimization_public_status.py +3 -1
  459. opik/rest_api/types/optimization_status.py +3 -1
  460. opik/rest_api/types/optimization_studio_config.py +27 -0
  461. opik/rest_api/types/optimization_studio_config_public.py +27 -0
  462. opik/rest_api/types/optimization_studio_config_write.py +27 -0
  463. opik/rest_api/types/optimization_studio_log.py +22 -0
  464. opik/rest_api/types/optimization_write.py +4 -2
  465. opik/rest_api/types/optimization_write_status.py +3 -1
  466. opik/rest_api/types/project.py +1 -0
  467. opik/rest_api/types/project_detailed.py +1 -0
  468. opik/rest_api/types/project_reference.py +31 -0
  469. opik/rest_api/types/project_reference_public.py +31 -0
  470. opik/rest_api/types/project_stats_summary_item.py +1 -0
  471. opik/rest_api/types/prompt.py +6 -0
  472. opik/rest_api/types/prompt_detail.py +6 -0
  473. opik/rest_api/types/prompt_detail_template_structure.py +5 -0
  474. opik/rest_api/types/prompt_public.py +6 -0
  475. opik/rest_api/types/prompt_public_template_structure.py +5 -0
  476. opik/rest_api/types/prompt_template_structure.py +5 -0
  477. opik/rest_api/types/prompt_version.py +3 -0
  478. opik/rest_api/types/prompt_version_detail.py +3 -0
  479. opik/rest_api/types/prompt_version_detail_template_structure.py +5 -0
  480. opik/rest_api/types/prompt_version_link.py +1 -0
  481. opik/rest_api/types/prompt_version_link_public.py +1 -0
  482. opik/rest_api/types/prompt_version_page_public.py +5 -0
  483. opik/rest_api/types/prompt_version_public.py +3 -0
  484. opik/rest_api/types/prompt_version_public_template_structure.py +5 -0
  485. opik/rest_api/types/prompt_version_template_structure.py +5 -0
  486. opik/rest_api/types/prompt_version_update.py +33 -0
  487. opik/rest_api/types/provider_api_key.py +9 -0
  488. opik/rest_api/types/provider_api_key_provider.py +1 -1
  489. opik/rest_api/types/provider_api_key_public.py +9 -0
  490. opik/rest_api/types/provider_api_key_public_provider.py +1 -1
  491. opik/rest_api/types/score_name.py +1 -0
  492. opik/rest_api/types/service_toggles_config.py +18 -0
  493. opik/rest_api/types/span.py +1 -2
  494. opik/rest_api/types/span_enrichment_options.py +31 -0
  495. opik/rest_api/types/span_experiment_item_bulk_write_view.py +1 -2
  496. opik/rest_api/types/span_filter.py +23 -0
  497. opik/rest_api/types/span_filter_operator.py +21 -0
  498. opik/rest_api/types/span_filter_write.py +23 -0
  499. opik/rest_api/types/span_filter_write_operator.py +21 -0
  500. opik/rest_api/types/span_llm_as_judge_code.py +27 -0
  501. opik/rest_api/types/span_llm_as_judge_code_public.py +27 -0
  502. opik/rest_api/types/span_llm_as_judge_code_write.py +27 -0
  503. opik/rest_api/types/span_public.py +1 -2
  504. opik/rest_api/types/span_update.py +46 -0
  505. opik/rest_api/types/span_user_defined_metric_python_code.py +20 -0
  506. opik/rest_api/types/span_user_defined_metric_python_code_public.py +20 -0
  507. opik/rest_api/types/span_user_defined_metric_python_code_write.py +20 -0
  508. opik/rest_api/types/span_write.py +1 -2
  509. opik/rest_api/types/studio_evaluation.py +20 -0
  510. opik/rest_api/types/studio_evaluation_public.py +20 -0
  511. opik/rest_api/types/studio_evaluation_write.py +20 -0
  512. opik/rest_api/types/studio_llm_model.py +21 -0
  513. opik/rest_api/types/studio_llm_model_public.py +21 -0
  514. opik/rest_api/types/studio_llm_model_write.py +21 -0
  515. opik/rest_api/types/studio_message.py +20 -0
  516. opik/rest_api/types/studio_message_public.py +20 -0
  517. opik/rest_api/types/studio_message_write.py +20 -0
  518. opik/rest_api/types/studio_metric.py +21 -0
  519. opik/rest_api/types/studio_metric_public.py +21 -0
  520. opik/rest_api/types/studio_metric_write.py +21 -0
  521. opik/rest_api/types/studio_optimizer.py +21 -0
  522. opik/rest_api/types/studio_optimizer_public.py +21 -0
  523. opik/rest_api/types/studio_optimizer_write.py +21 -0
  524. opik/rest_api/types/studio_prompt.py +20 -0
  525. opik/rest_api/types/studio_prompt_public.py +20 -0
  526. opik/rest_api/types/studio_prompt_write.py +20 -0
  527. opik/rest_api/types/trace.py +11 -2
  528. opik/rest_api/types/trace_enrichment_options.py +32 -0
  529. opik/rest_api/types/trace_experiment_item_bulk_write_view.py +1 -2
  530. opik/rest_api/types/trace_filter.py +23 -0
  531. opik/rest_api/types/trace_filter_operator.py +21 -0
  532. opik/rest_api/types/trace_filter_write.py +23 -0
  533. opik/rest_api/types/trace_filter_write_operator.py +21 -0
  534. opik/rest_api/types/trace_public.py +11 -2
  535. opik/rest_api/types/trace_thread_filter_write.py +23 -0
  536. opik/rest_api/types/trace_thread_filter_write_operator.py +21 -0
  537. opik/rest_api/types/trace_thread_identifier.py +1 -0
  538. opik/rest_api/types/trace_update.py +39 -0
  539. opik/rest_api/types/trace_write.py +1 -2
  540. opik/rest_api/types/value_entry.py +2 -0
  541. opik/rest_api/types/value_entry_compare.py +2 -0
  542. opik/rest_api/types/value_entry_experiment_item_bulk_write_view.py +2 -0
  543. opik/rest_api/types/value_entry_public.py +2 -0
  544. opik/rest_api/types/video_url.py +19 -0
  545. opik/rest_api/types/video_url_public.py +19 -0
  546. opik/rest_api/types/video_url_write.py +19 -0
  547. opik/rest_api/types/webhook.py +28 -0
  548. opik/rest_api/types/webhook_examples.py +19 -0
  549. opik/rest_api/types/webhook_public.py +28 -0
  550. opik/rest_api/types/webhook_test_result.py +23 -0
  551. opik/rest_api/types/webhook_test_result_status.py +5 -0
  552. opik/rest_api/types/webhook_write.py +23 -0
  553. opik/rest_api/types/welcome_wizard_tracking.py +22 -0
  554. opik/rest_api/types/workspace_configuration.py +5 -0
  555. opik/rest_api/welcome_wizard/__init__.py +4 -0
  556. opik/rest_api/welcome_wizard/client.py +195 -0
  557. opik/rest_api/welcome_wizard/raw_client.py +208 -0
  558. opik/rest_api/workspaces/client.py +14 -2
  559. opik/rest_api/workspaces/raw_client.py +10 -0
  560. opik/s3_httpx_client.py +14 -1
  561. opik/simulation/__init__.py +6 -0
  562. opik/simulation/simulated_user.py +99 -0
  563. opik/simulation/simulator.py +108 -0
  564. opik/synchronization.py +5 -6
  565. opik/{decorator/tracing_runtime_config.py → tracing_runtime_config.py} +6 -7
  566. opik/types.py +36 -0
  567. opik/validation/chat_prompt_messages.py +241 -0
  568. opik/validation/feedback_score.py +3 -3
  569. opik/validation/validator.py +28 -0
  570. opik-1.9.71.dist-info/METADATA +370 -0
  571. opik-1.9.71.dist-info/RECORD +1110 -0
  572. opik/api_objects/prompt/prompt.py +0 -112
  573. opik/cli.py +0 -193
  574. opik/hooks.py +0 -13
  575. opik/integrations/bedrock/chunks_aggregator.py +0 -55
  576. opik/integrations/bedrock/helpers.py +0 -8
  577. opik/rest_api/types/automation_rule_evaluator_object_public.py +0 -100
  578. opik/rest_api/types/json_node_experiment_item_bulk_write_view.py +0 -5
  579. opik-1.8.39.dist-info/METADATA +0 -339
  580. opik-1.8.39.dist-info/RECORD +0 -790
  581. /opik/{evaluation/metrics/conversation/conversational_coherence → decorator/context_manager}/__init__.py +0 -0
  582. /opik/evaluation/metrics/conversation/{session_completeness → llm_judges/conversational_coherence}/__init__.py +0 -0
  583. /opik/evaluation/metrics/conversation/{conversational_coherence → llm_judges/conversational_coherence}/schema.py +0 -0
  584. /opik/evaluation/metrics/conversation/{user_frustration → llm_judges/session_completeness}/__init__.py +0 -0
  585. /opik/evaluation/metrics/conversation/{session_completeness → llm_judges/session_completeness}/schema.py +0 -0
  586. /opik/evaluation/metrics/conversation/{user_frustration → llm_judges/user_frustration}/schema.py +0 -0
  587. /opik/integrations/bedrock/{stream_wrappers.py → converse/stream_wrappers.py} +0 -0
  588. /opik/rest_api/{spans/types → types}/span_update_type.py +0 -0
  589. {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/WHEEL +0 -0
  590. {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/entry_points.txt +0 -0
  591. {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/licenses/LICENSE +0 -0
  592. {opik-1.8.39.dist-info → opik-1.9.71.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,5 @@
1
+ # This litellm integration is currently not exposed in the documentation.
2
+
3
+ from .opik_tracker import track_completion
4
+
5
+ __all__ = ["track_completion"]
@@ -0,0 +1,115 @@
1
+ import logging
2
+ from typing import Any, Dict, List, Optional
3
+
4
+ import litellm.types.utils
5
+
6
+ LOGGER = logging.getLogger(__name__)
7
+
8
+
9
+ def _initialize_aggregated_response(
10
+ first_chunk: litellm.types.utils.ModelResponse,
11
+ ) -> Dict[str, Any]:
12
+ return {
13
+ "choices": [{"index": 0, "message": {"role": "", "content": ""}}],
14
+ "created": getattr(first_chunk, "created", 0),
15
+ "id": getattr(first_chunk, "id", ""),
16
+ "model": getattr(first_chunk, "model", ""),
17
+ "object": "chat.completion",
18
+ "system_fingerprint": getattr(first_chunk, "system_fingerprint", None),
19
+ "usage": None,
20
+ }
21
+
22
+
23
+ def _extract_role_from_delta(delta: Any, current_role: str) -> str:
24
+ if hasattr(delta, "role") and delta.role and not current_role:
25
+ return delta.role
26
+ return current_role
27
+
28
+
29
+ def _extract_content_from_delta(delta: Any) -> Optional[str]:
30
+ if hasattr(delta, "content") and delta.content:
31
+ return delta.content
32
+ return None
33
+
34
+
35
+ def _extract_finish_reason_from_choice(choice: Any) -> Optional[str]:
36
+ if hasattr(choice, "finish_reason") and choice.finish_reason:
37
+ return choice.finish_reason
38
+ return None
39
+
40
+
41
+ def _extract_usage_from_chunk(
42
+ chunk: litellm.types.utils.ModelResponse,
43
+ ) -> Optional[Dict[str, Any]]:
44
+ if not hasattr(chunk, "usage") or chunk.usage is None:
45
+ return None
46
+
47
+ try:
48
+ if hasattr(chunk.usage, "model_dump"):
49
+ usage_dict = chunk.usage.model_dump()
50
+ elif hasattr(chunk.usage, "dict"):
51
+ usage_dict = chunk.usage.dict()
52
+ elif isinstance(chunk.usage, dict):
53
+ usage_dict = chunk.usage
54
+ else:
55
+ return None
56
+
57
+ if usage_dict and isinstance(usage_dict, dict):
58
+ filtered_usage = {k: v for k, v in usage_dict.items() if v is not None}
59
+ return filtered_usage if filtered_usage else None
60
+ return None
61
+ except Exception as exception:
62
+ LOGGER.debug(
63
+ "Error extracting usage from streaming chunk: %s",
64
+ str(exception),
65
+ exc_info=True,
66
+ )
67
+ return None
68
+
69
+
70
+ def aggregate(
71
+ items: List[litellm.types.utils.ModelResponse],
72
+ ) -> Optional[litellm.types.utils.ModelResponse]:
73
+ try:
74
+ if not items:
75
+ return None
76
+
77
+ aggregated_response = _initialize_aggregated_response(items[0])
78
+ text_chunks: List[str] = []
79
+
80
+ for chunk in items:
81
+ if not hasattr(chunk, "choices") or not chunk.choices:
82
+ continue
83
+
84
+ choice = chunk.choices[0]
85
+
86
+ if hasattr(choice, "delta") and choice.delta:
87
+ delta = choice.delta
88
+
89
+ current_role = aggregated_response["choices"][0]["message"]["role"]
90
+ aggregated_response["choices"][0]["message"]["role"] = (
91
+ _extract_role_from_delta(delta, current_role)
92
+ )
93
+
94
+ content = _extract_content_from_delta(delta)
95
+ if content:
96
+ text_chunks.append(content)
97
+
98
+ finish_reason = _extract_finish_reason_from_choice(choice)
99
+ if finish_reason:
100
+ aggregated_response["choices"][0]["finish_reason"] = finish_reason
101
+
102
+ chunk_usage = _extract_usage_from_chunk(chunk)
103
+ if chunk_usage:
104
+ aggregated_response["usage"] = chunk_usage
105
+
106
+ aggregated_response["choices"][0]["message"]["content"] = "".join(text_chunks)
107
+ return litellm.types.utils.ModelResponse(**aggregated_response)
108
+
109
+ except Exception as exception:
110
+ LOGGER.error(
111
+ "Failed to aggregate LiteLLM streaming chunks: %s",
112
+ str(exception),
113
+ exc_info=True,
114
+ )
115
+ return None
@@ -0,0 +1,242 @@
1
+ import logging
2
+ from typing import (
3
+ Any,
4
+ Callable,
5
+ Dict,
6
+ List,
7
+ Optional,
8
+ Tuple,
9
+ Union,
10
+ )
11
+ from typing_extensions import override
12
+
13
+ import opik.dict_utils as dict_utils
14
+ import opik.llm_usage as llm_usage
15
+ from opik.api_objects import span
16
+ from opik.decorator import arguments_helpers, base_track_decorator
17
+ from opik.types import LLMProvider
18
+
19
+ import litellm
20
+ import litellm.types.utils
21
+ import litellm.litellm_core_utils.streaming_handler
22
+
23
+ from . import stream_patchers, completion_chunks_aggregator
24
+
25
+ LOGGER = logging.getLogger(__name__)
26
+
27
+ KWARGS_KEYS_TO_LOG_AS_INPUTS: List[str] = [
28
+ "messages",
29
+ "functions",
30
+ "function_call",
31
+ "tools",
32
+ "tool_choice",
33
+ "response_format",
34
+ "stop",
35
+ ]
36
+ RESPONSE_KEYS_TO_LOG_AS_OUTPUT: List[str] = ["choices"]
37
+
38
+ # Sensitive parameters that should never be logged
39
+ SENSITIVE_PARAMS_TO_EXCLUDE: List[str] = [
40
+ "api_key",
41
+ "aws_access_key_id",
42
+ "aws_secret_access_key",
43
+ "azure_ad_token",
44
+ "azure_ad_token_provider",
45
+ "azure_key",
46
+ "azure_password",
47
+ "azure_username",
48
+ "client_secret",
49
+ "vertex_credentials",
50
+ "vertex_project",
51
+ "vertex_location",
52
+ "anthropic_api_key",
53
+ "openai_api_key",
54
+ "cohere_api_key",
55
+ "replicate_api_key",
56
+ "huggingface_api_key",
57
+ "togetherai_api_key",
58
+ "baseten_api_key",
59
+ "openrouter_api_key",
60
+ ]
61
+
62
+ PROVIDER_MAPPING: Dict[str, LLMProvider] = {
63
+ "openai": LLMProvider.OPENAI,
64
+ "vertex_ai": LLMProvider.GOOGLE_VERTEXAI,
65
+ "vertex_ai-language-models": LLMProvider.GOOGLE_VERTEXAI,
66
+ "gemini": LLMProvider.GOOGLE_AI,
67
+ "anthropic": LLMProvider.ANTHROPIC,
68
+ "vertex_ai-anthropic_models": LLMProvider.ANTHROPIC_VERTEXAI,
69
+ "bedrock": LLMProvider.BEDROCK,
70
+ "bedrock_converse": LLMProvider.BEDROCK,
71
+ "groq": LLMProvider.GROQ,
72
+ }
73
+
74
+
75
+ def _extract_provider_from_model(model_name: str) -> Optional[LLMProvider]:
76
+ try:
77
+ provider_info = litellm.get_llm_provider(model_name)
78
+ provider_name = provider_info[1] if len(provider_info) > 1 else None
79
+ if provider_name is None:
80
+ return None
81
+ return PROVIDER_MAPPING.get(provider_name, None)
82
+ except Exception:
83
+ return None
84
+
85
+
86
+ def _convert_response_to_dict(
87
+ output: Union[
88
+ litellm.types.utils.ModelResponse,
89
+ Dict[str, Any],
90
+ ],
91
+ ) -> Dict[str, Any]:
92
+ if hasattr(output, "model_dump"):
93
+ return output.model_dump(mode="json")
94
+ elif isinstance(output, dict):
95
+ return output
96
+ else:
97
+ return dict(output)
98
+
99
+
100
+ def _extract_usage_from_response(
101
+ response_dict: Dict[str, Any],
102
+ ) -> Optional[llm_usage.OpikUsage]:
103
+ usage_data = response_dict.get("usage")
104
+ if usage_data is None:
105
+ return None
106
+
107
+ opik_usage = llm_usage.try_build_opik_usage_or_log_error(
108
+ provider=LLMProvider.OPENAI,
109
+ usage=usage_data,
110
+ logger=LOGGER,
111
+ error_message="Failed to log token usage from litellm call",
112
+ )
113
+
114
+ if opik_usage is None:
115
+ opik_usage = llm_usage.build_opik_usage_from_unknown_provider(
116
+ usage=usage_data,
117
+ )
118
+
119
+ return opik_usage
120
+
121
+
122
+ def _calculate_completion_cost(
123
+ output: Union[
124
+ litellm.types.utils.ModelResponse,
125
+ Dict[str, Any],
126
+ ],
127
+ ) -> Optional[float]:
128
+ try:
129
+ return litellm.completion_cost(completion_response=output)
130
+ except Exception as exception:
131
+ LOGGER.debug(
132
+ "Failed to calculate cost from litellm response: %s",
133
+ str(exception),
134
+ exc_info=True,
135
+ )
136
+ return None
137
+
138
+
139
+ class LiteLLMCompletionTrackDecorator(base_track_decorator.BaseTrackDecorator):
140
+ @override
141
+ def _start_span_inputs_preprocessor(
142
+ self,
143
+ func: Callable,
144
+ track_options: arguments_helpers.TrackOptions,
145
+ args: Tuple,
146
+ kwargs: Dict[str, Any],
147
+ ) -> arguments_helpers.StartSpanParameters:
148
+ name = track_options.name if track_options.name is not None else func.__name__
149
+ metadata = track_options.metadata if track_options.metadata is not None else {}
150
+
151
+ # Filter out sensitive parameters before logging
152
+ filtered_kwargs = {
153
+ key: value
154
+ for key, value in kwargs.items()
155
+ if key not in SENSITIVE_PARAMS_TO_EXCLUDE
156
+ }
157
+
158
+ input_data, new_metadata = dict_utils.split_dict_by_keys(
159
+ filtered_kwargs, keys=KWARGS_KEYS_TO_LOG_AS_INPUTS
160
+ )
161
+ metadata = dict_utils.deepmerge(metadata, new_metadata)
162
+ metadata["created_from"] = "litellm"
163
+
164
+ model_name = kwargs.get("model", "")
165
+ provider = _extract_provider_from_model(model_name)
166
+
167
+ return arguments_helpers.StartSpanParameters(
168
+ name=name,
169
+ input=input_data,
170
+ type=track_options.type,
171
+ tags=["litellm"],
172
+ metadata=metadata,
173
+ project_name=track_options.project_name,
174
+ model=model_name,
175
+ provider=provider,
176
+ )
177
+
178
+ @override
179
+ def _end_span_inputs_preprocessor(
180
+ self,
181
+ output: Any,
182
+ capture_output: bool,
183
+ current_span_data: span.SpanData,
184
+ ) -> arguments_helpers.EndSpanParameters:
185
+ assert isinstance(
186
+ output,
187
+ (
188
+ litellm.types.utils.ModelResponse,
189
+ dict,
190
+ ),
191
+ ), f"Expected ModelResponse or dict, got {type(output)}"
192
+
193
+ response_dict = _convert_response_to_dict(output)
194
+ output_data, metadata = dict_utils.split_dict_by_keys(
195
+ response_dict, RESPONSE_KEYS_TO_LOG_AS_OUTPUT
196
+ )
197
+
198
+ model = response_dict.get("model")
199
+ provider = _extract_provider_from_model(model) if model else None
200
+ opik_usage = _extract_usage_from_response(response_dict)
201
+ total_cost = _calculate_completion_cost(output)
202
+
203
+ return arguments_helpers.EndSpanParameters(
204
+ output=output_data,
205
+ usage=opik_usage,
206
+ metadata=metadata,
207
+ model=model,
208
+ provider=provider.value if provider else None,
209
+ total_cost=total_cost,
210
+ )
211
+
212
+ @override
213
+ def _streams_handler( # type: ignore
214
+ self,
215
+ output: Any,
216
+ capture_output: bool,
217
+ generations_aggregator: Optional[
218
+ Callable[
219
+ [List[litellm.types.utils.ModelResponse]],
220
+ Optional[litellm.types.utils.ModelResponse],
221
+ ]
222
+ ],
223
+ ) -> Optional[litellm.litellm_core_utils.streaming_handler.CustomStreamWrapper]:
224
+ assert (
225
+ generations_aggregator is not None
226
+ ), "LiteLLM decorator will always get aggregator function as input"
227
+
228
+ is_litellm_stream = isinstance(
229
+ output, litellm.litellm_core_utils.streaming_handler.CustomStreamWrapper
230
+ )
231
+ if not is_litellm_stream:
232
+ return None
233
+
234
+ span_to_end, trace_to_end = base_track_decorator.pop_end_candidates()
235
+
236
+ return stream_patchers.patch_stream(
237
+ stream=output,
238
+ span_to_end=span_to_end,
239
+ trace_to_end=trace_to_end,
240
+ generations_aggregator=completion_chunks_aggregator.aggregate,
241
+ finally_callback=self._after_call,
242
+ )
@@ -0,0 +1,43 @@
1
+ from typing import Callable, Optional, TypeVar
2
+
3
+ from . import litellm_completion_decorator
4
+ from . import completion_chunks_aggregator
5
+
6
+ F = TypeVar("F", bound=Callable)
7
+
8
+
9
+ def track_completion(
10
+ project_name: Optional[str] = None,
11
+ ) -> Callable[[F], F]:
12
+ """Decorator for tracking LiteLLM function calls with Opik.
13
+
14
+ Can be used within other Opik-tracked functions to create proper span hierarchy.
15
+
16
+ Supported (streaming and non-streaming modes):
17
+ * `litellm.completion`
18
+ * `litellm.acompletion`
19
+
20
+ Example:
21
+ ```python
22
+ import litellm
23
+ from opik.integrations.litellm import track_completion
24
+
25
+ tracked_completion = track_completion(project_name="my-project")(litellm.completion)
26
+ response = tracked_completion(model="gpt-3.5-turbo", messages=[...])
27
+ ```
28
+
29
+ Args:
30
+ project_name: The name of the project to log data.
31
+
32
+ Returns:
33
+ Decorator function that wraps the completion function with Opik tracking.
34
+ """
35
+
36
+ decorator_factory = litellm_completion_decorator.LiteLLMCompletionTrackDecorator()
37
+
38
+ return decorator_factory.track( # type: ignore
39
+ type="llm",
40
+ name=None, # Use the function's name (completion or acompletion)
41
+ project_name=project_name,
42
+ generations_aggregator=completion_chunks_aggregator.aggregate,
43
+ )
@@ -0,0 +1,151 @@
1
+ import logging
2
+ import functools
3
+ from typing import Any, List, Optional, Callable, TypeVar
4
+
5
+ from opik.api_objects import trace, span
6
+ from opik.decorator import generator_wrappers, error_info_collector
7
+ import litellm.litellm_core_utils.streaming_handler
8
+
9
+
10
+ LOGGER = logging.getLogger(__name__)
11
+
12
+ StreamItem = TypeVar("StreamItem")
13
+ AggregatedResult = TypeVar("AggregatedResult")
14
+
15
+ _original_next = (
16
+ litellm.litellm_core_utils.streaming_handler.CustomStreamWrapper.__next__
17
+ )
18
+ _original_anext = (
19
+ litellm.litellm_core_utils.streaming_handler.CustomStreamWrapper.__anext__
20
+ )
21
+
22
+
23
+ def _create_sync_next_wrapper(
24
+ original_next: Callable,
25
+ generations_aggregator: Callable,
26
+ finally_callback: generator_wrappers.FinishGeneratorCallback,
27
+ ) -> Callable:
28
+ @functools.wraps(original_next)
29
+ def wrapper(
30
+ self: litellm.litellm_core_utils.streaming_handler.CustomStreamWrapper,
31
+ ) -> Any:
32
+ if not hasattr(self, "_opik_accumulated_items"):
33
+ if hasattr(self, "opik_tracked_instance"):
34
+ self._opik_accumulated_items = []
35
+ self._opik_error_info = None
36
+
37
+ try:
38
+ item = original_next(self)
39
+ if hasattr(self, "_opik_accumulated_items"):
40
+ self._opik_accumulated_items.append(item)
41
+ return item
42
+ except StopIteration:
43
+ if hasattr(self, "_opik_accumulated_items"):
44
+ try:
45
+ output = generations_aggregator(self._opik_accumulated_items)
46
+ finally_callback(
47
+ output=output,
48
+ error_info=self._opik_error_info,
49
+ capture_output=True,
50
+ generators_span_to_end=self.span_to_end,
51
+ generators_trace_to_end=self.trace_to_end,
52
+ )
53
+ finally:
54
+ if hasattr(self, "_opik_accumulated_items"):
55
+ delattr(self, "_opik_accumulated_items")
56
+ if hasattr(self, "_opik_error_info"):
57
+ delattr(self, "_opik_error_info")
58
+ if hasattr(self, "opik_tracked_instance"):
59
+ delattr(self, "opik_tracked_instance")
60
+ raise
61
+ except Exception as exception:
62
+ if hasattr(self, "_opik_accumulated_items"):
63
+ self._opik_error_info = error_info_collector.collect(exception)
64
+ LOGGER.debug(
65
+ "Exception raised from LiteLLM stream: %s",
66
+ str(exception),
67
+ exc_info=True,
68
+ )
69
+ raise
70
+
71
+ return wrapper
72
+
73
+
74
+ def _create_async_next_wrapper(
75
+ original_anext: Callable,
76
+ generations_aggregator: Callable,
77
+ finally_callback: generator_wrappers.FinishGeneratorCallback,
78
+ ) -> Callable:
79
+ @functools.wraps(original_anext)
80
+ async def wrapper(
81
+ self: litellm.litellm_core_utils.streaming_handler.CustomStreamWrapper,
82
+ ) -> Any:
83
+ if not hasattr(self, "_opik_accumulated_items_async"):
84
+ if hasattr(self, "opik_tracked_instance_async"):
85
+ self._opik_accumulated_items_async = []
86
+ self._opik_error_info_async = None
87
+
88
+ try:
89
+ item = await original_anext(self)
90
+ if hasattr(self, "_opik_accumulated_items_async"):
91
+ self._opik_accumulated_items_async.append(item)
92
+ return item
93
+ except StopAsyncIteration:
94
+ if hasattr(self, "_opik_accumulated_items_async"):
95
+ try:
96
+ output = generations_aggregator(self._opik_accumulated_items_async)
97
+ finally_callback(
98
+ output=output,
99
+ error_info=self._opik_error_info_async,
100
+ capture_output=True,
101
+ generators_span_to_end=self.span_to_end_async,
102
+ generators_trace_to_end=self.trace_to_end_async,
103
+ )
104
+ finally:
105
+ if hasattr(self, "_opik_accumulated_items_async"):
106
+ delattr(self, "_opik_accumulated_items_async")
107
+ if hasattr(self, "_opik_error_info_async"):
108
+ delattr(self, "_opik_error_info_async")
109
+ if hasattr(self, "opik_tracked_instance_async"):
110
+ delattr(self, "opik_tracked_instance_async")
111
+ raise
112
+ except Exception as exception:
113
+ if hasattr(self, "_opik_accumulated_items_async"):
114
+ self._opik_error_info_async = error_info_collector.collect(exception)
115
+ LOGGER.debug(
116
+ "Exception raised from LiteLLM async stream: %s",
117
+ str(exception),
118
+ exc_info=True,
119
+ )
120
+ raise
121
+
122
+ return wrapper
123
+
124
+
125
+ def patch_stream(
126
+ stream: litellm.litellm_core_utils.streaming_handler.CustomStreamWrapper,
127
+ span_to_end: span.SpanData,
128
+ trace_to_end: Optional[trace.TraceData],
129
+ generations_aggregator: Callable[[List[StreamItem]], Optional[AggregatedResult]],
130
+ finally_callback: generator_wrappers.FinishGeneratorCallback,
131
+ ) -> litellm.litellm_core_utils.streaming_handler.CustomStreamWrapper:
132
+ litellm.litellm_core_utils.streaming_handler.CustomStreamWrapper.__next__ = (
133
+ _create_sync_next_wrapper(
134
+ _original_next, generations_aggregator, finally_callback
135
+ )
136
+ )
137
+ litellm.litellm_core_utils.streaming_handler.CustomStreamWrapper.__anext__ = (
138
+ _create_async_next_wrapper(
139
+ _original_anext, generations_aggregator, finally_callback
140
+ )
141
+ )
142
+
143
+ stream.opik_tracked_instance = True
144
+ stream.span_to_end = span_to_end
145
+ stream.trace_to_end = trace_to_end
146
+
147
+ stream.opik_tracked_instance_async = True
148
+ stream.span_to_end_async = span_to_end
149
+ stream.trace_to_end_async = trace_to_end
150
+
151
+ return stream