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,36 @@
1
+ import abc
2
+ from typing import Optional
3
+
4
+ from opik.message_processing import messages
5
+
6
+
7
+ class MessagePreprocessor(abc.ABC):
8
+ """
9
+ Abstract base class for message preprocessing.
10
+
11
+ This class provides a common interface for pre-processing messages, allowing
12
+ derived classes to implement custom preprocessing logic tailored to specific
13
+ requirements. Instances of this class cannot be created directly; it must be
14
+ subclassed with the `preprocess` method implemented.
15
+ """
16
+
17
+ @abc.abstractmethod
18
+ def preprocess(
19
+ self, message: Optional[messages.BaseMessage]
20
+ ) -> Optional[messages.BaseMessage]:
21
+ """
22
+ Processes and preprocesses the given message to prepare it for further operations.
23
+
24
+ This is an abstract method and needs to be implemented in any concrete subclass. The
25
+ preprocessing step is typically used for transformations or checks on the given input
26
+ message before further processing.
27
+
28
+ Args:
29
+ message: The input message to be preprocessed. This can
30
+ optionally be None.
31
+
32
+ Returns:
33
+ The processed message after preprocessing. Returns None if the input message is None
34
+ or if a message was fully consumed here and no further processing is required.
35
+ """
36
+ pass
File without changes
@@ -0,0 +1,146 @@
1
+ import logging
2
+ from typing import Optional, NamedTuple, List, Literal, cast
3
+
4
+ from opik.api_objects.attachment import (
5
+ attachments_extractor,
6
+ attachment_context,
7
+ converters,
8
+ )
9
+
10
+ from . import message_processors
11
+ from ..preprocessing import constants
12
+ from .. import messages, streamer
13
+
14
+
15
+ LOGGER = logging.getLogger(__name__)
16
+
17
+
18
+ class EntityDetails(NamedTuple):
19
+ entity_type: Literal["span", "trace"]
20
+ entity_id: str
21
+ project_name: str
22
+
23
+
24
+ class AttachmentsExtractionProcessor(message_processors.BaseMessageProcessor):
25
+ """
26
+ Class for processing message attachments through extraction and further handling.
27
+
28
+ The AttachmentsExtractionProcessor class is designed to handle attachments from incoming
29
+ messages. It checks the type of messages and processes them if they support
30
+ attachments. This includes extracting attachment data, replacing them with references,
31
+ and streaming processed or original messages through a pipeline. The class provides a
32
+ mechanism to toggle processing activity and ensures proper handling of messages with
33
+ embedded attachment information.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ min_attachment_size: int,
39
+ messages_streamer: streamer.Streamer,
40
+ url_override: str,
41
+ is_active: bool = True,
42
+ ):
43
+ """
44
+ Initializes an object with essential components for managing message streaming
45
+ and attachment extraction.
46
+
47
+ Args:
48
+ min_attachment_size: Minimum size for an attachment to be extracted.
49
+ messages_streamer: The streamer that is responsible for managing
50
+ messages broadcasts.
51
+ url_override: A custom URL to override default configurations if set.
52
+ is_active: Indicator of whether this instance is active. Default is True.
53
+ """
54
+ self._is_active = is_active
55
+ self.extractor = attachments_extractor.AttachmentsExtractor(min_attachment_size)
56
+ self.messages_streamer = messages_streamer
57
+ self._url_override = url_override
58
+
59
+ self.attachment_attributes = ["input", "output", "metadata"]
60
+
61
+ def is_active(self) -> bool:
62
+ return self._is_active
63
+
64
+ def process(self, message: messages.BaseMessage) -> None:
65
+ if not isinstance(message, messages.AttachmentSupportingMessage):
66
+ return
67
+
68
+ if self._is_active:
69
+ # do attachment processing only if the processor is active
70
+ try:
71
+ self._process_attachments_in_message(message.original_message)
72
+ except Exception as ex:
73
+ LOGGER.error(
74
+ "Failed to process attachment support message: %s", ex, exc_info=ex
75
+ )
76
+
77
+ # put the original message into the streamer for further processing
78
+ original_message = message.original_message
79
+ setattr(original_message, constants.MARKER_ATTRIBUTE_NAME, True)
80
+ self.messages_streamer.put(original_message)
81
+
82
+ def _process_attachments_in_message(self, original: messages.BaseMessage) -> None:
83
+ entity_details = entity_type_from_attachment_message(original)
84
+ if entity_details is None:
85
+ LOGGER.error(
86
+ "Failed to extract entity details from message - %s. Skipping embedded attachments processing.",
87
+ original.__class__.__name__,
88
+ )
89
+ return
90
+
91
+ attachments = []
92
+
93
+ for attribute in self.attachment_attributes:
94
+ if getattr(original, attribute, None):
95
+ results = self.extractor.extract_and_replace(
96
+ data=getattr(original, attribute),
97
+ entity_type=entity_details.entity_type,
98
+ entity_id=entity_details.entity_id,
99
+ project_name=entity_details.project_name,
100
+ context=cast(Literal["input", "output", "metadata"], attribute),
101
+ )
102
+ attachments.extend(results)
103
+
104
+ if len(attachments) > 0:
105
+ LOGGER.debug(
106
+ "Extracted %d attachments from %s (entity: %s/%s)",
107
+ len(attachments),
108
+ original.__class__.__name__,
109
+ entity_details.entity_type,
110
+ entity_details.entity_id,
111
+ )
112
+
113
+ self._process_attachments(attachments)
114
+ else:
115
+ LOGGER.debug(
116
+ "No attachments found in the message - %s.", original.__class__.__name__
117
+ )
118
+
119
+ def _process_attachments(
120
+ self, attachments: List[attachment_context.AttachmentWithContext]
121
+ ) -> None:
122
+ for attachment in attachments:
123
+ create_attachment_message = converters.attachment_to_message(
124
+ attachment_data=attachment.attachment_data,
125
+ entity_type=attachment.entity_type,
126
+ entity_id=attachment.entity_id,
127
+ project_name=attachment.project_name,
128
+ url_override=self._url_override,
129
+ delete_after_upload=True, # make sure to delete attachments after upload to avoid leaking space and data
130
+ )
131
+ self.messages_streamer.put(create_attachment_message)
132
+
133
+
134
+ def entity_type_from_attachment_message(
135
+ message: messages.BaseMessage,
136
+ ) -> Optional[EntityDetails]:
137
+ if isinstance(message, (messages.CreateSpanMessage, messages.UpdateSpanMessage)):
138
+ return EntityDetails("span", message.span_id, project_name=message.project_name)
139
+ elif isinstance(
140
+ message, (messages.CreateTraceMessage, messages.UpdateTraceMessage)
141
+ ):
142
+ return EntityDetails(
143
+ "trace", message.trace_id, project_name=message.project_name
144
+ )
145
+ else:
146
+ return None
@@ -0,0 +1,92 @@
1
+ import abc
2
+ import logging
3
+ from typing import List, Optional, TypeVar, Type
4
+
5
+ from .. import messages
6
+
7
+ import opik.exceptions
8
+
9
+
10
+ LOGGER = logging.getLogger(__name__)
11
+
12
+ T = TypeVar("T")
13
+
14
+
15
+ class BaseMessageProcessor(abc.ABC):
16
+ @abc.abstractmethod
17
+ def process(self, message: messages.BaseMessage) -> None:
18
+ pass
19
+
20
+ @abc.abstractmethod
21
+ def is_active(self) -> bool:
22
+ return False
23
+
24
+
25
+ class ChainedMessageProcessor(BaseMessageProcessor):
26
+ """
27
+ Processes messages through a chain of message processors.
28
+
29
+ This class allows for the sequential processing of a message by a list of
30
+ `BaseMessageProcessor` instances. Each processor in the chain is invoked in the
31
+ order provided. If an exception occurs during the processing by a specific
32
+ processor, it is logged, and the process continues with the next processor in
33
+ the chain.
34
+ """
35
+
36
+ def __init__(self, processors: List[BaseMessageProcessor]) -> None:
37
+ self._processors = processors
38
+
39
+ def is_active(self) -> bool:
40
+ return True
41
+
42
+ def process(self, message: messages.BaseMessage) -> None:
43
+ rate_limit_error: Optional[opik.exceptions.OpikCloudRequestsRateLimited] = None
44
+
45
+ for processor in self._processors:
46
+ try:
47
+ processor.process(message)
48
+ except opik.exceptions.OpikCloudRequestsRateLimited as ex:
49
+ rate_limit_error = ex
50
+ except Exception as ex:
51
+ LOGGER.error(
52
+ "Unexpected error while processing message: %s with message processor: %s",
53
+ ex,
54
+ type(processor),
55
+ exc_info=True,
56
+ )
57
+
58
+ # Rate limit error is a special case that is handled by the caller.
59
+ if rate_limit_error is not None:
60
+ raise rate_limit_error
61
+
62
+ def get_processor_by_type(self, processor_type: Type[T]) -> Optional[T]:
63
+ """
64
+ Retrieves a processor from the available processors that matches the specified type.
65
+
66
+ This method iterates through the list of processors and checks if any of them is
67
+ an instance of the given class type. If a match is found, it returns the processor.
68
+
69
+ Args:
70
+ processor_type: Concrete processor class to search for.
71
+
72
+ Returns:
73
+ The processor matching the specified type if found, else None.
74
+ """
75
+ for processor in self._processors:
76
+ if isinstance(processor, processor_type):
77
+ return processor
78
+ return None
79
+
80
+ def add_first(self, processor: BaseMessageProcessor) -> None:
81
+ """
82
+ Inserts a processor at the first position in the list of processors.
83
+
84
+ This method allows prioritizing a given processor by placing it
85
+ at the beginning of the internal processor list. As a result, the
86
+ provided processor will be executed before others.
87
+
88
+ Args:
89
+ processor: The message processor to be
90
+ added to the front of the processor list.
91
+ """
92
+ self._processors.insert(0, processor)
@@ -0,0 +1,96 @@
1
+ import logging
2
+ from typing import Optional
3
+
4
+ from opik.rest_api import client as rest_api_client
5
+
6
+ from . import (
7
+ message_processors,
8
+ online_message_processor,
9
+ )
10
+ from ..emulation import local_emulator_message_processor
11
+
12
+
13
+ LOGGER = logging.getLogger(__name__)
14
+
15
+
16
+ def create_message_processors_chain(
17
+ rest_client: rest_api_client.OpikApi,
18
+ ) -> message_processors.ChainedMessageProcessor:
19
+ """
20
+ Creates a chain of message processors by combining an online processor and a
21
+ local emulator processor. The chain is primarily useful for processing messages
22
+ in a sequence where each processor in the chain contributes its functionality.
23
+
24
+ The online processor is initialized using the provided REST API client. The local
25
+ emulator processor is included but remains inactive by default. The constructed
26
+ chain ensures combined and streamlined processing, accommodating both online
27
+ and local simulation needs based on evaluation activation.
28
+
29
+ Args:
30
+ rest_client: REST API client instance used to configure the online message
31
+ processor.
32
+
33
+ Returns:
34
+ A chained message processor containing the online and local emulator processors.
35
+ """
36
+ online = online_message_processor.OpikMessageProcessor(rest_client=rest_client)
37
+ # is not active by default - will be activated during evaluation
38
+ local = local_emulator_message_processor.LocalEmulatorMessageProcessor(active=False)
39
+
40
+ return message_processors.ChainedMessageProcessor(processors=[online, local])
41
+
42
+
43
+ def toggle_local_emulator_message_processor(
44
+ active: bool, chain: message_processors.ChainedMessageProcessor, reset: bool = True
45
+ ) -> None:
46
+ """
47
+ Toggles the state of the Local Emulator Message Processor within a given
48
+ ChainedMessageProcessor. This function either activates or deactivates the
49
+ processor based on the `active` parameter and resets its state if being
50
+ activated. Logs a warning if the Local Emulator Message Processor is not
51
+ found in the chain.
52
+
53
+ Args:
54
+ active: Determines whether to activate or deactivate the Local
55
+ Emulator Message Processor. If True, the processor is activated.
56
+ chain: The message processor
57
+ chain containing the Local Emulator Message Processor to be toggled.
58
+ reset: Determines whether to reset the Local Emulator Message Processor.
59
+ This can be used to clear the state of the Local Emulator before
60
+ evaluation. Also, it can be used to clean up the state of the Local Emulator
61
+ after evaluation to release system resources (memory).
62
+ """
63
+ local = chain.get_processor_by_type(
64
+ local_emulator_message_processor.LocalEmulatorMessageProcessor
65
+ )
66
+ if local is None:
67
+ LOGGER.warning("Local emulator message processor not found in the chain.")
68
+ return
69
+
70
+ if reset:
71
+ local.reset()
72
+
73
+ local.set_active(active=active)
74
+
75
+
76
+ def get_local_emulator_message_processor(
77
+ chain: message_processors.ChainedMessageProcessor,
78
+ ) -> Optional[local_emulator_message_processor.LocalEmulatorMessageProcessor]:
79
+ """
80
+ Retrieves the local emulator message processor from a given chain of message processors.
81
+
82
+ This function searches through the provided chain and looks for a processor of type
83
+ LocalEmulatorMessageProcessor. If one is found, it is returned; otherwise, None is returned.
84
+
85
+ Args:
86
+ chain: A chain of message processors that may contain a
87
+ LocalEmulatorMessageProcessor.
88
+
89
+ Returns:
90
+ The LocalEmulatorMessageProcessor if found in the chain,
91
+ otherwise None.
92
+ """
93
+ local = chain.get_processor_by_type(
94
+ local_emulator_message_processor.LocalEmulatorMessageProcessor
95
+ )
96
+ return local
@@ -1,22 +1,21 @@
1
- import abc
2
1
  import logging
3
- from typing import Any, Callable, Dict, Type
2
+ from typing import Callable, Dict, Type, Any
3
+
4
4
  import pydantic
5
5
  import tenacity
6
6
 
7
- import opik.logging_messages as logging_messages
8
- import opik.exceptions as exceptions
9
- from . import messages
10
- from ..jsonable_encoder import encode
11
- from .. import dict_utils
12
- from ..rate_limit import rate_limit
13
- from ..rest_api.types import (
7
+ from opik import dict_utils, exceptions, logging_messages
8
+ from opik.rate_limit import rate_limit
9
+ from opik.rest_api import client as rest_api_client, core as rest_api_core
10
+ from opik.rest_api.types import (
14
11
  feedback_score_batch_item,
15
- guardrail,
16
12
  feedback_score_batch_item_thread,
13
+ guardrail,
14
+ experiment_item,
17
15
  )
18
- from ..rest_api import core as rest_api_core
19
- from ..rest_api import client as rest_api_client
16
+
17
+ from . import message_processors
18
+ from .. import encoder_helpers, messages
20
19
 
21
20
  LOGGER = logging.getLogger(__name__)
22
21
 
@@ -24,21 +23,16 @@ LOGGER = logging.getLogger(__name__)
24
23
  MessageProcessingHandler = Callable[[messages.BaseMessage], None]
25
24
 
26
25
 
27
- class BaseMessageProcessor(abc.ABC):
28
- @abc.abstractmethod
29
- def process(
30
- self,
31
- message: messages.BaseMessage,
32
- ) -> None:
33
- pass
34
-
35
-
36
- class OpikMessageProcessor(BaseMessageProcessor):
26
+ class OpikMessageProcessor(message_processors.BaseMessageProcessor):
37
27
  def __init__(
38
- self, rest_client: rest_api_client.OpikApi, batch_memory_limit_mb: int = 50
28
+ self,
29
+ rest_client: rest_api_client.OpikApi,
30
+ batch_memory_limit_mb: int = 50,
31
+ active: bool = True,
39
32
  ):
40
33
  self._rest_client = rest_client
41
34
  self._batch_memory_limit_mb = batch_memory_limit_mb
35
+ self._is_active = active
42
36
 
43
37
  self._handlers: Dict[Type, MessageProcessingHandler] = {
44
38
  messages.CreateSpanMessage: self._process_create_span_message, # type: ignore
@@ -51,9 +45,17 @@ class OpikMessageProcessor(BaseMessageProcessor):
51
45
  messages.CreateSpansBatchMessage: self._process_create_spans_batch_message, # type: ignore
52
46
  messages.CreateTraceBatchMessage: self._process_create_traces_batch_message, # type: ignore
53
47
  messages.GuardrailBatchMessage: self._process_guardrail_batch_message, # type: ignore
48
+ messages.CreateExperimentItemsBatchMessage: self._process_create_experiment_items_batch_message, # type: ignore
49
+ messages.AttachmentSupportingMessage: self._noop_handler, # type: ignore
54
50
  }
55
51
 
52
+ def is_active(self) -> bool:
53
+ return self._is_active
54
+
56
55
  def process(self, message: messages.BaseMessage) -> None:
56
+ if not self.is_active():
57
+ return
58
+
57
59
  message_type = type(message)
58
60
  handler = self._handlers.get(message_type)
59
61
  if handler is None:
@@ -92,6 +94,7 @@ class OpikMessageProcessor(BaseMessageProcessor):
92
94
  f"{cause.__class__.__name__} - {cause}",
93
95
  extra={"error_tracking_extra": error_tracking_extra},
94
96
  )
97
+ LOGGER.warning(logging_messages.MAKE_SURE_OPIK_IS_CONFIGURED_CORRECTLY)
95
98
  except pydantic.ValidationError as validation_error:
96
99
  error_tracking_extra = _generate_error_tracking_extra(
97
100
  validation_error, message
@@ -112,6 +115,7 @@ class OpikMessageProcessor(BaseMessageProcessor):
112
115
  exc_info=True,
113
116
  extra={"error_tracking_extra": error_tracking_extra},
114
117
  )
118
+ LOGGER.warning(logging_messages.MAKE_SURE_OPIK_IS_CONFIGURED_CORRECTLY)
115
119
 
116
120
  def _process_create_span_message(
117
121
  self,
@@ -121,7 +125,12 @@ class OpikMessageProcessor(BaseMessageProcessor):
121
125
  cleaned_create_span_kwargs = dict_utils.remove_none_from_dict(
122
126
  create_span_kwargs
123
127
  )
124
- cleaned_create_span_kwargs = encode(cleaned_create_span_kwargs)
128
+ cleaned_create_span_kwargs = encoder_helpers.encode_and_anonymize(
129
+ cleaned_create_span_kwargs,
130
+ fields_to_anonymize=message.fields_to_anonymize(),
131
+ object_type="span",
132
+ )
133
+
125
134
  LOGGER.debug("Create span request: %s", cleaned_create_span_kwargs)
126
135
  self._rest_client.spans.create_span(**cleaned_create_span_kwargs)
127
136
 
@@ -133,7 +142,12 @@ class OpikMessageProcessor(BaseMessageProcessor):
133
142
  cleaned_create_trace_kwargs = dict_utils.remove_none_from_dict(
134
143
  create_trace_kwargs
135
144
  )
136
- cleaned_create_trace_kwargs = encode(cleaned_create_trace_kwargs)
145
+ cleaned_create_trace_kwargs = encoder_helpers.encode_and_anonymize(
146
+ cleaned_create_trace_kwargs,
147
+ fields_to_anonymize=message.fields_to_anonymize(),
148
+ object_type="trace",
149
+ )
150
+
137
151
  LOGGER.debug("Create trace request: %s", cleaned_create_trace_kwargs)
138
152
  self._rest_client.traces.create_trace(**cleaned_create_trace_kwargs)
139
153
 
@@ -146,7 +160,12 @@ class OpikMessageProcessor(BaseMessageProcessor):
146
160
  cleaned_update_span_kwargs = dict_utils.remove_none_from_dict(
147
161
  update_span_kwargs
148
162
  )
149
- cleaned_update_span_kwargs = encode(cleaned_update_span_kwargs)
163
+ cleaned_update_span_kwargs = encoder_helpers.encode_and_anonymize(
164
+ cleaned_update_span_kwargs,
165
+ fields_to_anonymize=message.fields_to_anonymize(),
166
+ object_type="span",
167
+ )
168
+
150
169
  LOGGER.debug("Update span request: %s", cleaned_update_span_kwargs)
151
170
  self._rest_client.spans.update_span(**cleaned_update_span_kwargs)
152
171
 
@@ -159,7 +178,12 @@ class OpikMessageProcessor(BaseMessageProcessor):
159
178
  cleaned_update_trace_kwargs = dict_utils.remove_none_from_dict(
160
179
  update_trace_kwargs
161
180
  )
162
- cleaned_update_trace_kwargs = encode(cleaned_update_trace_kwargs)
181
+ cleaned_update_trace_kwargs = encoder_helpers.encode_and_anonymize(
182
+ cleaned_update_trace_kwargs,
183
+ fields_to_anonymize=message.fields_to_anonymize(),
184
+ object_type="trace",
185
+ )
186
+
163
187
  LOGGER.debug("Update trace request: %s", cleaned_update_trace_kwargs)
164
188
  self._rest_client.traces.update_trace(**cleaned_update_trace_kwargs)
165
189
  LOGGER.debug("Sent trace %s", message.trace_id)
@@ -253,6 +277,35 @@ class OpikMessageProcessor(BaseMessageProcessor):
253
277
 
254
278
  self._rest_client.guardrails.create_guardrails(guardrails=batch)
255
279
 
280
+ def _process_create_experiment_items_batch_message(
281
+ self,
282
+ message: messages.CreateExperimentItemsBatchMessage,
283
+ ) -> None:
284
+ experiment_items_batch = [
285
+ experiment_item.ExperimentItem(
286
+ id=item.id,
287
+ experiment_id=item.experiment_id,
288
+ dataset_item_id=item.dataset_item_id,
289
+ trace_id=item.trace_id,
290
+ )
291
+ for item in message.batch
292
+ ]
293
+
294
+ LOGGER.debug(
295
+ "Create experiment items batch request of size %d",
296
+ len(experiment_items_batch),
297
+ )
298
+ self._rest_client.experiments.create_experiment_items(
299
+ experiment_items=experiment_items_batch
300
+ )
301
+ LOGGER.debug(
302
+ "Sent experiment items batch of size %d", len(experiment_items_batch)
303
+ )
304
+
305
+ def _noop_handler(self, message: messages.BaseMessage) -> None:
306
+ # just ignore the message
307
+ pass
308
+
256
309
 
257
310
  def _generate_error_tracking_extra(
258
311
  exception: Exception, message: messages.BaseMessage
@@ -260,8 +313,11 @@ def _generate_error_tracking_extra(
260
313
  result: Dict[str, Any] = {"exception": exception}
261
314
 
262
315
  if isinstance(exception, rest_api_core.ApiError):
263
- fingerprint = [type(message).__name__, type(exception).__name__]
264
- fingerprint.append(str(exception.status_code))
316
+ fingerprint = [
317
+ type(message).__name__,
318
+ type(exception).__name__,
319
+ str(exception.status_code),
320
+ ]
265
321
  result["fingerprint"] = fingerprint
266
322
  result["status_code"] = exception.status_code
267
323
 
@@ -4,7 +4,8 @@ import time
4
4
  from queue import Empty
5
5
  from typing import Optional
6
6
 
7
- from . import message_processors, message_queue, messages
7
+ from . import message_queue, messages
8
+ from .processors import message_processors
8
9
  from .. import exceptions, _logging
9
10
 
10
11
  LOGGER = logging.getLogger(__name__)
@@ -49,8 +50,9 @@ class QueueConsumer(threading.Thread):
49
50
 
50
51
  if message is None:
51
52
  return
52
- elif message.delivery_time <= now:
53
- self._message_processor.process(message)
53
+
54
+ if message.delivery_time <= now:
55
+ self._process_message(message)
54
56
  else:
55
57
  # put a message back to keep an order in the queue
56
58
  self._push_message_back(message)
@@ -87,4 +89,8 @@ class QueueConsumer(threading.Thread):
87
89
  "The message queue size limit has been reached. The current message has been returned to the queue, and the newest message has been discarded.",
88
90
  logger=LOGGER,
89
91
  )
92
+ message.delivery_attempts += 1
90
93
  self._message_queue.put_back(message)
94
+
95
+ def _process_message(self, message: messages.BaseMessage) -> None:
96
+ self._message_processor.process(message)