opik 0.1.3__tar.gz

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 (227) hide show
  1. opik-0.1.3/PKG-INFO +42 -0
  2. opik-0.1.3/README.md +8 -0
  3. opik-0.1.3/pyproject.toml +6 -0
  4. opik-0.1.3/setup.cfg +4 -0
  5. opik-0.1.3/setup.py +56 -0
  6. opik-0.1.3/src/opik/__init__.py +24 -0
  7. opik-0.1.3/src/opik/_logging.py +34 -0
  8. opik-0.1.3/src/opik/api_objects/__init__.py +0 -0
  9. opik-0.1.3/src/opik/api_objects/constants.py +6 -0
  10. opik-0.1.3/src/opik/api_objects/dataset/__init__.py +4 -0
  11. opik-0.1.3/src/opik/api_objects/dataset/converters.py +71 -0
  12. opik-0.1.3/src/opik/api_objects/dataset/dataset.py +227 -0
  13. opik-0.1.3/src/opik/api_objects/dataset/dataset_item.py +37 -0
  14. opik-0.1.3/src/opik/api_objects/experiment/__init__.py +3 -0
  15. opik-0.1.3/src/opik/api_objects/experiment/experiment.py +45 -0
  16. opik-0.1.3/src/opik/api_objects/experiment/experiment_item.py +10 -0
  17. opik-0.1.3/src/opik/api_objects/helpers.py +24 -0
  18. opik-0.1.3/src/opik/api_objects/opik_client.py +385 -0
  19. opik-0.1.3/src/opik/api_objects/span.py +207 -0
  20. opik-0.1.3/src/opik/api_objects/trace.py +196 -0
  21. opik-0.1.3/src/opik/api_objects/validation_helpers.py +43 -0
  22. opik-0.1.3/src/opik/config.py +105 -0
  23. opik-0.1.3/src/opik/context_storage.py +79 -0
  24. opik-0.1.3/src/opik/datetime_helpers.py +10 -0
  25. opik-0.1.3/src/opik/decorator/__init__.py +0 -0
  26. opik-0.1.3/src/opik/decorator/arguments_helpers.py +34 -0
  27. opik-0.1.3/src/opik/decorator/base_track_decorator.py +478 -0
  28. opik-0.1.3/src/opik/decorator/generator_wrappers.py +90 -0
  29. opik-0.1.3/src/opik/decorator/inspect_helpers.py +37 -0
  30. opik-0.1.3/src/opik/decorator/tracker.py +65 -0
  31. opik-0.1.3/src/opik/dict_utils.py +47 -0
  32. opik-0.1.3/src/opik/evaluation/__init__.py +3 -0
  33. opik-0.1.3/src/opik/evaluation/evaluator.py +80 -0
  34. opik-0.1.3/src/opik/evaluation/metrics/__init__.py +28 -0
  35. opik-0.1.3/src/opik/evaluation/metrics/base_metric.py +26 -0
  36. opik-0.1.3/src/opik/evaluation/metrics/exceptions.py +4 -0
  37. opik-0.1.3/src/opik/evaluation/metrics/heuristics/__init__.py +0 -0
  38. opik-0.1.3/src/opik/evaluation/metrics/heuristics/contains.py +58 -0
  39. opik-0.1.3/src/opik/evaluation/metrics/heuristics/equals.py +57 -0
  40. opik-0.1.3/src/opik/evaluation/metrics/heuristics/is_json.py +47 -0
  41. opik-0.1.3/src/opik/evaluation/metrics/heuristics/levenshtein_ratio.py +61 -0
  42. opik-0.1.3/src/opik/evaluation/metrics/heuristics/regex_match.py +55 -0
  43. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/__init__.py +0 -0
  44. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/answer_relevance/__init__.py +0 -0
  45. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/answer_relevance/metric.py +121 -0
  46. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/answer_relevance/template.py +136 -0
  47. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/context_precision/__init__.py +0 -0
  48. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/context_precision/metric.py +143 -0
  49. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/context_precision/template.py +127 -0
  50. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/context_recall/__init__.py +0 -0
  51. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/context_recall/metric.py +141 -0
  52. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/context_recall/template.py +144 -0
  53. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/factuality/__init__.py +0 -0
  54. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/factuality/metric.py +135 -0
  55. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/factuality/template.py +128 -0
  56. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/hallucination/__init__.py +0 -0
  57. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/hallucination/metric.py +124 -0
  58. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/hallucination/template.py +73 -0
  59. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/moderation/__init__.py +0 -0
  60. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/moderation/metric.py +114 -0
  61. opik-0.1.3/src/opik/evaluation/metrics/llm_judges/moderation/template.py +83 -0
  62. opik-0.1.3/src/opik/evaluation/metrics/models.py +8 -0
  63. opik-0.1.3/src/opik/evaluation/metrics/score_result.py +11 -0
  64. opik-0.1.3/src/opik/evaluation/models/__init__.py +0 -0
  65. opik-0.1.3/src/opik/evaluation/models/base_model.py +21 -0
  66. opik-0.1.3/src/opik/evaluation/models/langchain_chat_model.py +23 -0
  67. opik-0.1.3/src/opik/evaluation/models/models_factory.py +13 -0
  68. opik-0.1.3/src/opik/evaluation/report.py +83 -0
  69. opik-0.1.3/src/opik/evaluation/scores_logger.py +26 -0
  70. opik-0.1.3/src/opik/evaluation/task_output.py +16 -0
  71. opik-0.1.3/src/opik/evaluation/tasks_scorer.py +115 -0
  72. opik-0.1.3/src/opik/evaluation/test_case.py +9 -0
  73. opik-0.1.3/src/opik/evaluation/test_result.py +12 -0
  74. opik-0.1.3/src/opik/evaluation/types.py +4 -0
  75. opik-0.1.3/src/opik/exceptions.py +10 -0
  76. opik-0.1.3/src/opik/hooks.py +5 -0
  77. opik-0.1.3/src/opik/httpx_client.py +29 -0
  78. opik-0.1.3/src/opik/integrations/__init__.py +0 -0
  79. opik-0.1.3/src/opik/integrations/langchain/__init__.py +4 -0
  80. opik-0.1.3/src/opik/integrations/langchain/openai_run_helpers.py +50 -0
  81. opik-0.1.3/src/opik/integrations/langchain/opik_tracer.py +233 -0
  82. opik-0.1.3/src/opik/integrations/openai/__init__.py +3 -0
  83. opik-0.1.3/src/opik/integrations/openai/chunks_aggregator.py +64 -0
  84. opik-0.1.3/src/opik/integrations/openai/openai_decorator.py +161 -0
  85. opik-0.1.3/src/opik/integrations/openai/opik_tracker.py +32 -0
  86. opik-0.1.3/src/opik/integrations/openai/stream_wrappers.py +60 -0
  87. opik-0.1.3/src/opik/logging_messages.py +39 -0
  88. opik-0.1.3/src/opik/message_processing/__init__.py +0 -0
  89. opik-0.1.3/src/opik/message_processing/api.py +0 -0
  90. opik-0.1.3/src/opik/message_processing/jsonable_encoder.py +57 -0
  91. opik-0.1.3/src/opik/message_processing/message_processors.py +165 -0
  92. opik-0.1.3/src/opik/message_processing/messages.py +90 -0
  93. opik-0.1.3/src/opik/message_processing/queue_consumer.py +48 -0
  94. opik-0.1.3/src/opik/message_processing/streamer.py +57 -0
  95. opik-0.1.3/src/opik/message_processing/streamer_constructors.py +35 -0
  96. opik-0.1.3/src/opik/opik_context.py +31 -0
  97. opik-0.1.3/src/opik/package_version.py +11 -0
  98. opik-0.1.3/src/opik/plugins/__init__.py +0 -0
  99. opik-0.1.3/src/opik/plugins/pytest/__init__.py +0 -0
  100. opik-0.1.3/src/opik/plugins/pytest/decorator.py +125 -0
  101. opik-0.1.3/src/opik/plugins/pytest/experiment_runner.py +78 -0
  102. opik-0.1.3/src/opik/plugins/pytest/hooks.py +55 -0
  103. opik-0.1.3/src/opik/plugins/pytest/test_run_content.py +13 -0
  104. opik-0.1.3/src/opik/plugins/pytest/test_runs_storage.py +16 -0
  105. opik-0.1.3/src/opik/rest_api/__init__.py +195 -0
  106. opik-0.1.3/src/opik/rest_api/client.py +164 -0
  107. opik-0.1.3/src/opik/rest_api/core/__init__.py +30 -0
  108. opik-0.1.3/src/opik/rest_api/core/api_error.py +17 -0
  109. opik-0.1.3/src/opik/rest_api/core/client_wrapper.py +57 -0
  110. opik-0.1.3/src/opik/rest_api/core/datetime_utils.py +30 -0
  111. opik-0.1.3/src/opik/rest_api/core/file.py +43 -0
  112. opik-0.1.3/src/opik/rest_api/core/http_client.py +545 -0
  113. opik-0.1.3/src/opik/rest_api/core/jsonable_encoder.py +105 -0
  114. opik-0.1.3/src/opik/rest_api/core/pydantic_utilities.py +28 -0
  115. opik-0.1.3/src/opik/rest_api/core/query_encoder.py +39 -0
  116. opik-0.1.3/src/opik/rest_api/core/remove_none_from_dict.py +11 -0
  117. opik-0.1.3/src/opik/rest_api/core/request_options.py +32 -0
  118. opik-0.1.3/src/opik/rest_api/datasets/__init__.py +1 -0
  119. opik-0.1.3/src/opik/rest_api/datasets/client.py +1407 -0
  120. opik-0.1.3/src/opik/rest_api/environment.py +7 -0
  121. opik-0.1.3/src/opik/rest_api/errors/__init__.py +15 -0
  122. opik-0.1.3/src/opik/rest_api/errors/bad_request_error.py +9 -0
  123. opik-0.1.3/src/opik/rest_api/errors/conflict_error.py +9 -0
  124. opik-0.1.3/src/opik/rest_api/errors/not_found_error.py +10 -0
  125. opik-0.1.3/src/opik/rest_api/errors/not_implemented_error.py +10 -0
  126. opik-0.1.3/src/opik/rest_api/errors/unprocessable_entity_error.py +9 -0
  127. opik-0.1.3/src/opik/rest_api/experiments/__init__.py +1 -0
  128. opik-0.1.3/src/opik/rest_api/experiments/client.py +646 -0
  129. opik-0.1.3/src/opik/rest_api/feedback_definitions/__init__.py +5 -0
  130. opik-0.1.3/src/opik/rest_api/feedback_definitions/client.py +542 -0
  131. opik-0.1.3/src/opik/rest_api/feedback_definitions/types/__init__.py +5 -0
  132. opik-0.1.3/src/opik/rest_api/feedback_definitions/types/find_feedback_definitions_request_type.py +7 -0
  133. opik-0.1.3/src/opik/rest_api/projects/__init__.py +1 -0
  134. opik-0.1.3/src/opik/rest_api/projects/client.py +557 -0
  135. opik-0.1.3/src/opik/rest_api/spans/__init__.py +5 -0
  136. opik-0.1.3/src/opik/rest_api/spans/client.py +1153 -0
  137. opik-0.1.3/src/opik/rest_api/spans/types/__init__.py +6 -0
  138. opik-0.1.3/src/opik/rest_api/spans/types/get_spans_by_project_request_type.py +7 -0
  139. opik-0.1.3/src/opik/rest_api/spans/types/span_write_type.py +5 -0
  140. opik-0.1.3/src/opik/rest_api/traces/__init__.py +1 -0
  141. opik-0.1.3/src/opik/rest_api/traces/client.py +1047 -0
  142. opik-0.1.3/src/opik/rest_api/types/__init__.py +173 -0
  143. opik-0.1.3/src/opik/rest_api/types/categorical_feedback_definition.py +47 -0
  144. opik-0.1.3/src/opik/rest_api/types/categorical_feedback_definition_create.py +43 -0
  145. opik-0.1.3/src/opik/rest_api/types/categorical_feedback_definition_public.py +47 -0
  146. opik-0.1.3/src/opik/rest_api/types/categorical_feedback_definition_update.py +43 -0
  147. opik-0.1.3/src/opik/rest_api/types/categorical_feedback_detail.py +42 -0
  148. opik-0.1.3/src/opik/rest_api/types/categorical_feedback_detail_create.py +42 -0
  149. opik-0.1.3/src/opik/rest_api/types/categorical_feedback_detail_public.py +42 -0
  150. opik-0.1.3/src/opik/rest_api/types/categorical_feedback_detail_update.py +42 -0
  151. opik-0.1.3/src/opik/rest_api/types/dataset.py +50 -0
  152. opik-0.1.3/src/opik/rest_api/types/dataset_item.py +56 -0
  153. opik-0.1.3/src/opik/rest_api/types/dataset_item_batch.py +53 -0
  154. opik-0.1.3/src/opik/rest_api/types/dataset_item_compare.py +56 -0
  155. opik-0.1.3/src/opik/rest_api/types/dataset_item_compare_source.py +7 -0
  156. opik-0.1.3/src/opik/rest_api/types/dataset_item_page_compare.py +46 -0
  157. opik-0.1.3/src/opik/rest_api/types/dataset_item_page_public.py +46 -0
  158. opik-0.1.3/src/opik/rest_api/types/dataset_item_public.py +56 -0
  159. opik-0.1.3/src/opik/rest_api/types/dataset_item_public_source.py +7 -0
  160. opik-0.1.3/src/opik/rest_api/types/dataset_item_source.py +7 -0
  161. opik-0.1.3/src/opik/rest_api/types/dataset_item_write.py +50 -0
  162. opik-0.1.3/src/opik/rest_api/types/dataset_item_write_source.py +7 -0
  163. opik-0.1.3/src/opik/rest_api/types/dataset_page_public.py +46 -0
  164. opik-0.1.3/src/opik/rest_api/types/dataset_public.py +50 -0
  165. opik-0.1.3/src/opik/rest_api/types/delete_feedback_score.py +42 -0
  166. opik-0.1.3/src/opik/rest_api/types/error_message.py +42 -0
  167. opik-0.1.3/src/opik/rest_api/types/error_message_public.py +44 -0
  168. opik-0.1.3/src/opik/rest_api/types/experiment.py +52 -0
  169. opik-0.1.3/src/opik/rest_api/types/experiment_item.py +54 -0
  170. opik-0.1.3/src/opik/rest_api/types/experiment_item_compare.py +54 -0
  171. opik-0.1.3/src/opik/rest_api/types/experiment_item_public.py +49 -0
  172. opik-0.1.3/src/opik/rest_api/types/experiment_page_public.py +46 -0
  173. opik-0.1.3/src/opik/rest_api/types/experiment_public.py +51 -0
  174. opik-0.1.3/src/opik/rest_api/types/feedback.py +149 -0
  175. opik-0.1.3/src/opik/rest_api/types/feedback_create.py +127 -0
  176. opik-0.1.3/src/opik/rest_api/types/feedback_definition_page_public.py +46 -0
  177. opik-0.1.3/src/opik/rest_api/types/feedback_object_public.py +151 -0
  178. opik-0.1.3/src/opik/rest_api/types/feedback_public.py +149 -0
  179. opik-0.1.3/src/opik/rest_api/types/feedback_score.py +51 -0
  180. opik-0.1.3/src/opik/rest_api/types/feedback_score_average.py +43 -0
  181. opik-0.1.3/src/opik/rest_api/types/feedback_score_average_public.py +43 -0
  182. opik-0.1.3/src/opik/rest_api/types/feedback_score_batch.py +43 -0
  183. opik-0.1.3/src/opik/rest_api/types/feedback_score_batch_item.py +53 -0
  184. opik-0.1.3/src/opik/rest_api/types/feedback_score_batch_item_source.py +5 -0
  185. opik-0.1.3/src/opik/rest_api/types/feedback_score_compare.py +51 -0
  186. opik-0.1.3/src/opik/rest_api/types/feedback_score_compare_source.py +5 -0
  187. opik-0.1.3/src/opik/rest_api/types/feedback_score_public.py +51 -0
  188. opik-0.1.3/src/opik/rest_api/types/feedback_score_public_source.py +5 -0
  189. opik-0.1.3/src/opik/rest_api/types/feedback_score_source.py +5 -0
  190. opik-0.1.3/src/opik/rest_api/types/feedback_update.py +127 -0
  191. opik-0.1.3/src/opik/rest_api/types/json_node.py +5 -0
  192. opik-0.1.3/src/opik/rest_api/types/json_node_compare.py +5 -0
  193. opik-0.1.3/src/opik/rest_api/types/json_node_public.py +5 -0
  194. opik-0.1.3/src/opik/rest_api/types/json_node_write.py +5 -0
  195. opik-0.1.3/src/opik/rest_api/types/numerical_feedback_definition.py +47 -0
  196. opik-0.1.3/src/opik/rest_api/types/numerical_feedback_definition_create.py +43 -0
  197. opik-0.1.3/src/opik/rest_api/types/numerical_feedback_definition_public.py +47 -0
  198. opik-0.1.3/src/opik/rest_api/types/numerical_feedback_definition_update.py +43 -0
  199. opik-0.1.3/src/opik/rest_api/types/numerical_feedback_detail.py +43 -0
  200. opik-0.1.3/src/opik/rest_api/types/numerical_feedback_detail_create.py +43 -0
  201. opik-0.1.3/src/opik/rest_api/types/numerical_feedback_detail_public.py +43 -0
  202. opik-0.1.3/src/opik/rest_api/types/numerical_feedback_detail_update.py +43 -0
  203. opik-0.1.3/src/opik/rest_api/types/project.py +48 -0
  204. opik-0.1.3/src/opik/rest_api/types/project_page_public.py +46 -0
  205. opik-0.1.3/src/opik/rest_api/types/project_public.py +48 -0
  206. opik-0.1.3/src/opik/rest_api/types/span.py +67 -0
  207. opik-0.1.3/src/opik/rest_api/types/span_page_public.py +46 -0
  208. opik-0.1.3/src/opik/rest_api/types/span_public.py +62 -0
  209. opik-0.1.3/src/opik/rest_api/types/span_public_type.py +5 -0
  210. opik-0.1.3/src/opik/rest_api/types/span_type.py +5 -0
  211. opik-0.1.3/src/opik/rest_api/types/trace.py +62 -0
  212. opik-0.1.3/src/opik/rest_api/types/trace_page_public.py +46 -0
  213. opik-0.1.3/src/opik/rest_api/types/trace_public.py +57 -0
  214. opik-0.1.3/src/opik/synchronization.py +21 -0
  215. opik-0.1.3/src/opik/types.py +60 -0
  216. opik-0.1.3/src/opik/validation/__init__.py +0 -0
  217. opik-0.1.3/src/opik/validation/feedback_score.py +47 -0
  218. opik-0.1.3/src/opik/validation/result.py +28 -0
  219. opik-0.1.3/src/opik/validation/usage.py +49 -0
  220. opik-0.1.3/src/opik/validation/validator.py +9 -0
  221. opik-0.1.3/src/opik.egg-info/PKG-INFO +42 -0
  222. opik-0.1.3/src/opik.egg-info/SOURCES.txt +225 -0
  223. opik-0.1.3/src/opik.egg-info/dependency_links.txt +1 -0
  224. opik-0.1.3/src/opik.egg-info/entry_points.txt +2 -0
  225. opik-0.1.3/src/opik.egg-info/not-zip-safe +1 -0
  226. opik-0.1.3/src/opik.egg-info/requires.txt +12 -0
  227. opik-0.1.3/src/opik.egg-info/top_level.txt +1 -0
opik-0.1.3/PKG-INFO ADDED
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.1
2
+ Name: opik
3
+ Version: 0.1.3
4
+ Summary: Comet tool for logging and evaluating LLM traces
5
+ Home-page: https://www.comet.com
6
+ Author: Comet ML Inc.
7
+ Author-email: mail@comet.com
8
+ License: MIT
9
+ Project-URL: Source code, https://github.com/comet-ml/opik
10
+ Keywords: opik
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Natural Language :: English
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: httpx<1.0.0
23
+ Requires-Dist: langchain_community<1.0.0
24
+ Requires-Dist: langchain_openai<1.0.0
25
+ Requires-Dist: levenshtein~=0.25.1
26
+ Requires-Dist: openai<2.0.0
27
+ Requires-Dist: pandas<3.0.0,>=2.0.0
28
+ Requires-Dist: pydantic-settings<3.0.0,>=2.0.0
29
+ Requires-Dist: pydantic<3.0.0,>=2.0.0
30
+ Requires-Dist: pytest
31
+ Requires-Dist: tqdm
32
+ Requires-Dist: uuid7<1.0.0
33
+ Requires-Dist: rich
34
+
35
+ # opik-python
36
+
37
+ To install package in development mode run `pip install -e .` from the repository root directory.
38
+
39
+ # Linters
40
+
41
+ Before pushing your changes please run `pre-commit run --all-files` from the python/sdk directory.
42
+ It will install and run required linters.
opik-0.1.3/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # opik-python
2
+
3
+ To install package in development mode run `pip install -e .` from the repository root directory.
4
+
5
+ # Linters
6
+
7
+ Before pushing your changes please run `pre-commit run --all-files` from the python/sdk directory.
8
+ It will install and run required linters.
@@ -0,0 +1,6 @@
1
+ [tool.mypy]
2
+ follow_imports = "skip"
3
+ ignore_missing_imports = true
4
+ disallow_untyped_defs = true
5
+ disallow_untyped_calls = true
6
+ check_untyped_defs = true
opik-0.1.3/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
opik-0.1.3/setup.py ADDED
@@ -0,0 +1,56 @@
1
+ from pathlib import Path
2
+
3
+ from setuptools import find_packages, setup
4
+ import os
5
+
6
+ project_urls = {"Source code": "https://github.com/comet-ml/opik"}
7
+ this_directory = Path(__file__).parent
8
+
9
+ setup(
10
+ author="Comet ML Inc.",
11
+ author_email="mail@comet.com",
12
+ python_requires=">=3.8",
13
+ classifiers=[
14
+ "Development Status :: 2 - Pre-Alpha",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Natural Language :: English",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ ],
24
+ description="Comet tool for logging and evaluating LLM traces",
25
+ long_description=open("README.md", encoding="utf-8").read(),
26
+ long_description_content_type="text/markdown",
27
+ install_requires=[
28
+ "httpx<1.0.0",
29
+ "langchain_community<1.0.0",
30
+ "langchain_openai<1.0.0",
31
+ "levenshtein~=0.25.1",
32
+ "openai<2.0.0",
33
+ "pandas>=2.0.0,<3.0.0",
34
+ "pydantic-settings>=2.0.0,<3.0.0",
35
+ "pydantic>=2.0.0,<3.0.0",
36
+ "pytest",
37
+ "tqdm",
38
+ "uuid7<1.0.0",
39
+ "rich",
40
+ ],
41
+ entry_points={
42
+ "pytest11": [
43
+ "opik = opik.plugins.pytest.hooks",
44
+ ]
45
+ },
46
+ include_package_data=True,
47
+ keywords="opik",
48
+ name="opik",
49
+ packages=find_packages("src"),
50
+ package_dir={"": "src"},
51
+ url="https://www.comet.com",
52
+ project_urls=project_urls,
53
+ version=os.environ.get("VERSION", "0.0.1"),
54
+ zip_safe=False,
55
+ license="MIT",
56
+ )
@@ -0,0 +1,24 @@
1
+ from .decorator.tracker import track, flush_tracker
2
+ from .api_objects.opik_client import Opik
3
+ from .api_objects.trace import Trace
4
+ from .api_objects.span import Span
5
+ from .api_objects.dataset.dataset_item import DatasetItem
6
+ from .api_objects.dataset import Dataset
7
+ from . import _logging
8
+ from . import package_version
9
+ from .plugins.pytest.decorator import llm_unit
10
+
11
+ _logging.setup()
12
+
13
+ __version__ = package_version.VERSION
14
+ __all__ = [
15
+ "__version__",
16
+ "track",
17
+ "flush_tracker",
18
+ "Opik",
19
+ "Trace",
20
+ "Span",
21
+ "DatasetItem",
22
+ "Dataset",
23
+ "llm_unit",
24
+ ]
@@ -0,0 +1,34 @@
1
+ import logging
2
+
3
+ import sys
4
+
5
+ from . import config
6
+
7
+ CONSOLE_MSG_FORMAT = "OPIK: %(message)s"
8
+
9
+ FILE_MSG_FORMAT = "%(asctime)s OPIK %(levelname)s: %(message)s"
10
+
11
+
12
+ def setup() -> None:
13
+ opik_root_logger = logging.getLogger("opik")
14
+ config_ = config.OpikConfig()
15
+
16
+ console_handler = logging.StreamHandler(sys.stdout)
17
+ console_level = config_.console_logging_level
18
+ console_handler.setLevel(console_level)
19
+ console_handler.setFormatter(logging.Formatter(CONSOLE_MSG_FORMAT))
20
+
21
+ opik_root_logger.addHandler(console_handler)
22
+
23
+ root_level = console_handler.level
24
+
25
+ if config_.file_logging_level is not None:
26
+ file_handler = logging.FileHandler(config_.logging_file)
27
+ file_level = config_.file_logging_level
28
+ file_handler.setLevel(file_level)
29
+ file_handler.setFormatter(logging.Formatter(FILE_MSG_FORMAT))
30
+ opik_root_logger.addHandler(file_handler)
31
+
32
+ root_level = min(root_level, file_handler.level)
33
+
34
+ opik_root_logger.setLevel(level=root_level)
File without changes
@@ -0,0 +1,6 @@
1
+ FEEDBACK_SCORE_SOURCE_SDK = "sdk"
2
+ DATASET_SOURCE_SDK = "sdk"
3
+
4
+ FEEDBACK_SCORES_MAX_BATCH_SIZE = 1000
5
+ EXPERIMENT_ITEMS_MAX_BATCH_SIZE = 1000
6
+ DATASET_ITEMS_MAX_BATCH_SIZE = 1000
@@ -0,0 +1,4 @@
1
+ from .dataset import Dataset
2
+
3
+
4
+ __all__ = ["Dataset"]
@@ -0,0 +1,71 @@
1
+ import pandas as pd
2
+ import json
3
+
4
+ from typing import List, Callable, Any, Dict
5
+
6
+ from . import dataset_item
7
+
8
+ ItemConstructor = Callable[[Any], dataset_item.DatasetItem]
9
+
10
+
11
+ def to_pandas(
12
+ items: List[dataset_item.DatasetItem], keys_mapping: Dict[str, str]
13
+ ) -> pd.DataFrame:
14
+ new_item_dicts = []
15
+
16
+ for item in items:
17
+ new_item_dict = {
18
+ keys_mapping.get(key, key): value for key, value in item.__dict__.items()
19
+ }
20
+ new_item_dicts.append(new_item_dict)
21
+
22
+ return pd.DataFrame(new_item_dicts)
23
+
24
+
25
+ def from_pandas(
26
+ dataframe: pd.DataFrame,
27
+ keys_mapping: Dict[str, str],
28
+ ignore_keys: List[str],
29
+ ) -> List[dataset_item.DatasetItem]:
30
+ result = []
31
+ ignore_keys = [] if ignore_keys is None else ignore_keys
32
+ for _, row in dataframe.iterrows():
33
+ item_kwargs = {
34
+ keys_mapping.get(key, key): value
35
+ for key, value in row.items()
36
+ if key not in ignore_keys
37
+ }
38
+ result.append(dataset_item.DatasetItem(**item_kwargs))
39
+
40
+ return result
41
+
42
+
43
+ def to_json(items: List[dataset_item.DatasetItem], keys_mapping: Dict[str, str]) -> str:
44
+ new_item_dicts = []
45
+
46
+ for item in items:
47
+ item_dict = item.__dict__
48
+ new_item_dict = {
49
+ keys_mapping.get(key, key): value for key, value in item_dict.items()
50
+ }
51
+ new_item_dicts.append(new_item_dict)
52
+
53
+ result: str = json.dumps(new_item_dicts, indent=2)
54
+ return result
55
+
56
+
57
+ def from_json(
58
+ value: str, keys_mapping: Dict[str, str], ignore_keys: List[str]
59
+ ) -> List[dataset_item.DatasetItem]:
60
+ result = []
61
+ item_dicts: List[Dict[str, Any]] = json.loads(value)
62
+
63
+ for item_dict in item_dicts:
64
+ item_kwargs = {
65
+ keys_mapping.get(key, key): value
66
+ for key, value in item_dict.items()
67
+ if key not in ignore_keys
68
+ }
69
+ result.append(dataset_item.DatasetItem(**item_kwargs))
70
+
71
+ return result
@@ -0,0 +1,227 @@
1
+ import logging
2
+ import json
3
+ from typing import Optional, Any, List, Dict, Union, Sequence
4
+
5
+ from opik.rest_api import client as rest_api_client
6
+ from opik.rest_api.types import dataset_item as rest_dataset_item
7
+ from opik import exceptions
8
+
9
+ from .. import helpers, constants
10
+ from . import dataset_item, converters
11
+
12
+ import pandas
13
+
14
+ LOGGER = logging.getLogger(__name__)
15
+
16
+
17
+ class Dataset:
18
+ def __init__(
19
+ self,
20
+ name: str,
21
+ description: Optional[str],
22
+ rest_client: rest_api_client.OpikApi,
23
+ ) -> None:
24
+ """
25
+ A Dataset object. This object should not be created directly, instead use :meth:`opik.Opik.create_dataset` or :meth:`opik.Opik.get_dataset`.
26
+ """
27
+ self._name = name
28
+ self._description = description
29
+ self._rest_client = rest_client
30
+
31
+ @property
32
+ def name(self) -> str:
33
+ """The name of the dataset."""
34
+ return self._name
35
+
36
+ @property
37
+ def description(self) -> Optional[str]:
38
+ """The description of the dataset."""
39
+ return self._description
40
+
41
+ def insert(
42
+ self, items: Sequence[Union[dataset_item.DatasetItem, Dict[str, Any]]]
43
+ ) -> None:
44
+ """
45
+ Insert new items into the dataset.
46
+
47
+ Args:
48
+ items: List of DatasetItem objects or dicts (which will be converted to DatasetItem objects)
49
+ to add to the dataset.
50
+ """
51
+ items: List[dataset_item.DatasetItem] = [ # type: ignore
52
+ (dataset_item.DatasetItem(**item) if isinstance(item, dict) else item)
53
+ for item in items
54
+ ]
55
+
56
+ rest_items = [
57
+ rest_dataset_item.DatasetItem(
58
+ id=item.id if item.id is not None else helpers.generate_id(), # type: ignore
59
+ input=item.input, # type: ignore
60
+ expected_output=item.expected_output, # type: ignore
61
+ metadata=item.metadata, # type: ignore
62
+ trace_id=item.trace_id, # type: ignore
63
+ span_id=item.span_id, # type: ignore
64
+ source=item.source, # type: ignore
65
+ )
66
+ for item in items
67
+ ]
68
+
69
+ batches = helpers.list_to_batches(
70
+ rest_items, batch_size=constants.DATASET_ITEMS_MAX_BATCH_SIZE
71
+ )
72
+
73
+ for batch in batches:
74
+ LOGGER.debug("Sending dataset items batch: %s", batch)
75
+ self._rest_client.datasets.create_or_update_dataset_items(
76
+ dataset_name=self._name, items=batch
77
+ )
78
+
79
+ def update(self, items: List[dataset_item.DatasetItem]) -> None:
80
+ """
81
+ Update existing items in the dataset.
82
+
83
+ Args:
84
+ items: List of DatasetItem objects to update in the dataset. You need to provide the full item object as it will override what has been supplied previously.
85
+
86
+ Raises:
87
+ DatasetItemUpdateOperationRequiresItemId: If any item in the list is missing an id.
88
+ """
89
+ for item in items:
90
+ if item.id is None: # TODO: implement proper exception
91
+ raise exceptions.DatasetItemUpdateOperationRequiresItemId(
92
+ "Missing id for dataset item to update: %s", item
93
+ )
94
+
95
+ self.insert(items)
96
+
97
+ def delete(self, items_ids: List[str]) -> None:
98
+ """
99
+ Delete items from the dataset.
100
+
101
+ Args:
102
+ items_ids: List of item ids to delete.
103
+ """
104
+ batches = helpers.list_to_batches(
105
+ items_ids, batch_size=constants.DATASET_ITEMS_MAX_BATCH_SIZE
106
+ )
107
+
108
+ for batch in batches:
109
+ LOGGER.debug("Deleting dataset items batch: %s", batch)
110
+ self._rest_client.datasets.delete_dataset_items(item_ids=batch)
111
+
112
+ def clear(self) -> None:
113
+ """
114
+ Delete all items from the given dataset.
115
+ """
116
+ all_items = self.get_all_items()
117
+ item_ids = [item.id for item in all_items if item.id is not None]
118
+ self.delete(item_ids)
119
+
120
+ def to_pandas(self) -> pandas.DataFrame:
121
+ """
122
+ Convert the dataset to a pandas DataFrame.
123
+
124
+ Returns:
125
+ A pandas DataFrame containing all items in the dataset.
126
+ """
127
+ dataset_items = self.get_all_items()
128
+
129
+ return converters.to_pandas(dataset_items, keys_mapping={})
130
+
131
+ def to_json(self) -> str:
132
+ """
133
+ Convert the dataset to a JSON string.
134
+
135
+ Returns:
136
+ A JSON string representation of all items in the dataset.
137
+ """
138
+ dataset_items = self.get_all_items()
139
+
140
+ return converters.to_json(dataset_items, keys_mapping={})
141
+
142
+ def get_all_items(self) -> List[dataset_item.DatasetItem]:
143
+ """
144
+ Retrieve all items from the dataset.
145
+
146
+ Returns:
147
+ A list of DatasetItem objects representing all items in the dataset.
148
+ """
149
+ results: List[dataset_item.DatasetItem] = []
150
+
151
+ while True:
152
+ stream = self._rest_client.datasets.stream_dataset_items(
153
+ dataset_name=self._name,
154
+ last_retrieved_id=results[-1].id if len(results) > 0 else None,
155
+ )
156
+
157
+ item_bytes = b"".join(stream)
158
+ stream_results: List[dataset_item.DatasetItem] = []
159
+ for line in item_bytes.split(b"\n"):
160
+ if len(line) == 0:
161
+ continue
162
+
163
+ item_content: Dict[str, Any] = json.loads(line.decode("utf-8").strip())
164
+
165
+ item = dataset_item.DatasetItem(
166
+ id=item_content.get("id"), # type: ignore
167
+ input=item_content.get("input"), # type: ignore
168
+ expected_output=item_content.get("expected_output"), # type: ignore
169
+ metadata=item_content.get("metadata"), # type: ignore
170
+ trace_id=item_content.get("trace_id"), # type: ignore
171
+ span_id=item_content.get("span_id"), # type: ignore
172
+ source=item_content.get("source"), # type: ignore
173
+ )
174
+
175
+ stream_results.append(item)
176
+
177
+ if len(stream_results) == 0:
178
+ break
179
+
180
+ results.extend(stream_results)
181
+
182
+ return results
183
+
184
+ def insert_from_json(
185
+ self,
186
+ json_array: str,
187
+ keys_mapping: Optional[Dict[str, str]] = None,
188
+ ignore_keys: Optional[List[str]] = None,
189
+ ) -> None:
190
+ """
191
+ Args:
192
+ json_array: json string of format: "[{...}, {...}, {...}]" where every dictionary
193
+ is to be transformed into dataset item
194
+ keys_mapping: dictionary that maps json keys to item fields names
195
+ Example: {'Expected output': 'expected_output'}
196
+ ignore_keys: if your json dicts contain keys that are not needed for DatasetItem
197
+ construction - pass them as ignore_keys argument
198
+ """
199
+ keys_mapping = {} if keys_mapping is None else keys_mapping
200
+ ignore_keys = [] if ignore_keys is None else ignore_keys
201
+
202
+ new_items = converters.from_json(
203
+ json_array, keys_mapping=keys_mapping, ignore_keys=ignore_keys
204
+ )
205
+
206
+ self.insert(new_items)
207
+
208
+ def insert_from_pandas(
209
+ self,
210
+ dataframe: pandas.DataFrame,
211
+ keys_mapping: Optional[Dict[str, str]] = None,
212
+ ignore_keys: Optional[List[str]] = None,
213
+ ) -> None:
214
+ """
215
+ Args:
216
+ dataframe: pandas dataframe
217
+ keys_mapping: Dictionary that maps dataframe column names to dataset item field names.
218
+ Example: {'Expected output': 'expected_output'}
219
+ ignore_keys: if your dataframe contains columns that are not needed for DatasetItem
220
+ construction - pass them as ignore_keys argument
221
+ """
222
+ keys_mapping = {} if keys_mapping is None else keys_mapping
223
+ ignore_keys = [] if ignore_keys is None else ignore_keys
224
+
225
+ new_items = converters.from_pandas(dataframe, keys_mapping, ignore_keys)
226
+
227
+ self.insert(new_items)
@@ -0,0 +1,37 @@
1
+ from typing import Optional, Dict, Any
2
+ import pydantic
3
+ from .. import constants
4
+
5
+
6
+ class DatasetItem(pydantic.BaseModel):
7
+ """A DatasetItem object representing an item in a dataset."""
8
+
9
+ model_config = pydantic.ConfigDict(strict=True)
10
+
11
+ input: Dict[str, Any]
12
+ """The input data for the dataset item."""
13
+
14
+ expected_output: Optional[Dict[str, Any]] = None
15
+ """The expected output for the dataset item."""
16
+
17
+ metadata: Optional[Dict[str, Any]] = None
18
+ """Additional metadata associated with the dataset item."""
19
+
20
+ trace_id: Optional[str] = None
21
+ """The ID of the trace associated with this dataset item."""
22
+
23
+ span_id: Optional[str] = None
24
+ """The ID of the span associated with this dataset item."""
25
+
26
+ id: Optional[str] = None
27
+ """The unique identifier for this dataset item."""
28
+
29
+ source: str = constants.DATASET_SOURCE_SDK
30
+ """The source of the dataset item. Defaults to DATASET_SOURCE_SDK."""
31
+
32
+ def get_content(self) -> Dict[str, Any]:
33
+ return {
34
+ "input": self.input,
35
+ "expected_output": self.expected_output,
36
+ "metadata": self.metadata,
37
+ }
@@ -0,0 +1,3 @@
1
+ from .experiment import Experiment
2
+
3
+ __all__ = ["Experiment"]
@@ -0,0 +1,45 @@
1
+ import logging
2
+ from typing import List
3
+
4
+ from opik.rest_api import client as rest_api_client
5
+ from opik.rest_api.types import experiment_item as rest_experiment_item
6
+ from . import experiment_item
7
+ from .. import helpers, constants
8
+
9
+
10
+ LOGGER = logging.getLogger(__name__)
11
+
12
+
13
+ class Experiment:
14
+ def __init__(
15
+ self,
16
+ id: str,
17
+ name: str,
18
+ dataset_name: str,
19
+ rest_client: rest_api_client.OpikApi,
20
+ ) -> None:
21
+ self._id = id
22
+ self._name = name
23
+ self._dataset_name = dataset_name
24
+ self._rest_client = rest_client
25
+
26
+ def insert(self, experiment_items: List[experiment_item.ExperimentItem]) -> None:
27
+ rest_experiment_items = [
28
+ rest_experiment_item.ExperimentItem(
29
+ id=item.id if item.id is not None else helpers.generate_id(),
30
+ experiment_id=self._id,
31
+ dataset_item_id=item.dataset_item_id,
32
+ trace_id=item.trace_id,
33
+ )
34
+ for item in experiment_items
35
+ ]
36
+
37
+ batches = helpers.list_to_batches(
38
+ rest_experiment_items, batch_size=constants.EXPERIMENT_ITEMS_MAX_BATCH_SIZE
39
+ )
40
+
41
+ for batch in batches:
42
+ LOGGER.debug("Sending experiment items batch: %s", batch)
43
+ self._rest_client.experiments.create_experiment_items(
44
+ experiment_items=batch,
45
+ )
@@ -0,0 +1,10 @@
1
+ import dataclasses
2
+
3
+ from typing import Optional
4
+
5
+
6
+ @dataclasses.dataclass
7
+ class ExperimentItem:
8
+ dataset_item_id: str
9
+ trace_id: str
10
+ id: Optional[str] = None
@@ -0,0 +1,24 @@
1
+ import datetime
2
+ import uuid_extensions
3
+
4
+ from typing import List, Any
5
+
6
+ from .. import datetime_helpers
7
+ from typing import Optional
8
+
9
+
10
+ def generate_id() -> str:
11
+ return str(uuid_extensions.uuid7())
12
+
13
+
14
+ def datetime_to_iso8601_if_not_None(
15
+ value: Optional[datetime.datetime],
16
+ ) -> Optional[str]:
17
+ if value is None:
18
+ return None
19
+
20
+ return datetime_helpers.datetime_to_iso8601(value)
21
+
22
+
23
+ def list_to_batches(items: List[Any], batch_size: int) -> List[List[Any]]:
24
+ return [items[i : i + batch_size] for i in range(0, len(items), batch_size)]