opik 1.9.5__py3-none-any.whl → 1.9.39__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 (248) hide show
  1. opik/__init__.py +10 -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/dataset/rest_operations.py +5 -0
  9. opik/api_objects/experiment/experiment.py +46 -49
  10. opik/api_objects/experiment/helpers.py +34 -10
  11. opik/api_objects/local_recording.py +8 -3
  12. opik/api_objects/opik_client.py +230 -48
  13. opik/api_objects/opik_query_language.py +9 -0
  14. opik/api_objects/prompt/__init__.py +11 -3
  15. opik/api_objects/prompt/base_prompt.py +69 -0
  16. opik/api_objects/prompt/base_prompt_template.py +29 -0
  17. opik/api_objects/prompt/chat/__init__.py +1 -0
  18. opik/api_objects/prompt/chat/chat_prompt.py +193 -0
  19. opik/api_objects/prompt/chat/chat_prompt_template.py +350 -0
  20. opik/api_objects/prompt/{chat_content_renderer_registry.py → chat/content_renderer_registry.py} +37 -35
  21. opik/api_objects/prompt/client.py +101 -30
  22. opik/api_objects/prompt/text/__init__.py +1 -0
  23. opik/api_objects/prompt/text/prompt.py +174 -0
  24. opik/api_objects/prompt/{prompt_template.py → text/prompt_template.py} +10 -6
  25. opik/api_objects/prompt/types.py +1 -1
  26. opik/cli/export.py +6 -2
  27. opik/cli/usage_report/charts.py +39 -10
  28. opik/cli/usage_report/cli.py +164 -45
  29. opik/cli/usage_report/pdf.py +14 -1
  30. opik/config.py +0 -5
  31. opik/decorator/base_track_decorator.py +37 -40
  32. opik/decorator/context_manager/span_context_manager.py +9 -0
  33. opik/decorator/context_manager/trace_context_manager.py +5 -0
  34. opik/dict_utils.py +3 -3
  35. opik/evaluation/__init__.py +13 -2
  36. opik/evaluation/engine/engine.py +195 -223
  37. opik/evaluation/engine/helpers.py +8 -7
  38. opik/evaluation/engine/metrics_evaluator.py +237 -0
  39. opik/evaluation/evaluation_result.py +35 -1
  40. opik/evaluation/evaluator.py +318 -30
  41. opik/evaluation/models/litellm/util.py +78 -6
  42. opik/evaluation/models/model_capabilities.py +33 -0
  43. opik/evaluation/report.py +14 -2
  44. opik/evaluation/rest_operations.py +36 -33
  45. opik/evaluation/test_case.py +2 -2
  46. opik/evaluation/types.py +9 -1
  47. opik/exceptions.py +17 -0
  48. opik/hooks/__init__.py +17 -1
  49. opik/hooks/anonymizer_hook.py +36 -0
  50. opik/id_helpers.py +18 -0
  51. opik/integrations/adk/helpers.py +16 -7
  52. opik/integrations/adk/legacy_opik_tracer.py +7 -4
  53. opik/integrations/adk/opik_tracer.py +3 -1
  54. opik/integrations/adk/patchers/adk_otel_tracer/opik_adk_otel_tracer.py +7 -3
  55. opik/integrations/adk/recursive_callback_injector.py +1 -6
  56. opik/integrations/dspy/callback.py +1 -4
  57. opik/integrations/haystack/opik_connector.py +2 -2
  58. opik/integrations/haystack/opik_tracer.py +2 -4
  59. opik/integrations/langchain/opik_tracer.py +273 -82
  60. opik/integrations/llama_index/callback.py +110 -108
  61. opik/integrations/openai/agents/opik_tracing_processor.py +1 -2
  62. opik/integrations/openai/opik_tracker.py +1 -1
  63. opik/message_processing/batching/batchers.py +11 -7
  64. opik/message_processing/encoder_helpers.py +79 -0
  65. opik/message_processing/messages.py +25 -1
  66. opik/message_processing/online_message_processor.py +23 -8
  67. opik/opik_context.py +7 -7
  68. opik/rest_api/__init__.py +188 -12
  69. opik/rest_api/client.py +3 -0
  70. opik/rest_api/dashboards/__init__.py +4 -0
  71. opik/rest_api/dashboards/client.py +462 -0
  72. opik/rest_api/dashboards/raw_client.py +648 -0
  73. opik/rest_api/datasets/client.py +893 -89
  74. opik/rest_api/datasets/raw_client.py +1328 -87
  75. opik/rest_api/experiments/client.py +30 -2
  76. opik/rest_api/experiments/raw_client.py +26 -0
  77. opik/rest_api/feedback_definitions/types/find_feedback_definitions_request_type.py +1 -1
  78. opik/rest_api/optimizations/client.py +302 -0
  79. opik/rest_api/optimizations/raw_client.py +463 -0
  80. opik/rest_api/optimizations/types/optimization_update_status.py +3 -1
  81. opik/rest_api/prompts/__init__.py +2 -2
  82. opik/rest_api/prompts/client.py +34 -4
  83. opik/rest_api/prompts/raw_client.py +32 -2
  84. opik/rest_api/prompts/types/__init__.py +3 -1
  85. opik/rest_api/prompts/types/create_prompt_version_detail_template_structure.py +5 -0
  86. opik/rest_api/prompts/types/prompt_write_template_structure.py +5 -0
  87. opik/rest_api/spans/__init__.py +0 -2
  88. opik/rest_api/spans/client.py +148 -64
  89. opik/rest_api/spans/raw_client.py +210 -83
  90. opik/rest_api/spans/types/__init__.py +0 -2
  91. opik/rest_api/traces/client.py +241 -73
  92. opik/rest_api/traces/raw_client.py +344 -90
  93. opik/rest_api/types/__init__.py +200 -15
  94. opik/rest_api/types/aggregation_data.py +1 -0
  95. opik/rest_api/types/alert_trigger_config_public_type.py +6 -1
  96. opik/rest_api/types/alert_trigger_config_type.py +6 -1
  97. opik/rest_api/types/alert_trigger_config_write_type.py +6 -1
  98. opik/rest_api/types/automation_rule_evaluator.py +23 -1
  99. opik/rest_api/types/automation_rule_evaluator_llm_as_judge.py +2 -0
  100. opik/rest_api/types/automation_rule_evaluator_llm_as_judge_public.py +2 -0
  101. opik/rest_api/types/automation_rule_evaluator_llm_as_judge_write.py +2 -0
  102. opik/rest_api/types/{automation_rule_evaluator_object_public.py → automation_rule_evaluator_object_object_public.py} +32 -10
  103. opik/rest_api/types/automation_rule_evaluator_page_public.py +2 -2
  104. opik/rest_api/types/automation_rule_evaluator_public.py +23 -1
  105. opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge.py +22 -0
  106. opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_public.py +22 -0
  107. opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_write.py +22 -0
  108. opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge.py +2 -0
  109. opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_public.py +2 -0
  110. opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_write.py +2 -0
  111. opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python.py +2 -0
  112. opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_public.py +2 -0
  113. opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_write.py +2 -0
  114. opik/rest_api/types/automation_rule_evaluator_update.py +23 -1
  115. opik/rest_api/types/automation_rule_evaluator_update_llm_as_judge.py +2 -0
  116. opik/rest_api/types/automation_rule_evaluator_update_span_llm_as_judge.py +22 -0
  117. opik/rest_api/types/automation_rule_evaluator_update_trace_thread_llm_as_judge.py +2 -0
  118. opik/rest_api/types/automation_rule_evaluator_update_trace_thread_user_defined_metric_python.py +2 -0
  119. opik/rest_api/types/automation_rule_evaluator_update_user_defined_metric_python.py +2 -0
  120. opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python.py +2 -0
  121. opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_public.py +2 -0
  122. opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_write.py +2 -0
  123. opik/rest_api/types/automation_rule_evaluator_write.py +23 -1
  124. opik/rest_api/types/boolean_feedback_definition.py +25 -0
  125. opik/rest_api/types/boolean_feedback_definition_create.py +20 -0
  126. opik/rest_api/types/boolean_feedback_definition_public.py +25 -0
  127. opik/rest_api/types/boolean_feedback_definition_update.py +20 -0
  128. opik/rest_api/types/boolean_feedback_detail.py +29 -0
  129. opik/rest_api/types/boolean_feedback_detail_create.py +29 -0
  130. opik/rest_api/types/boolean_feedback_detail_public.py +29 -0
  131. opik/rest_api/types/boolean_feedback_detail_update.py +29 -0
  132. opik/rest_api/types/dashboard_page_public.py +24 -0
  133. opik/rest_api/types/dashboard_public.py +30 -0
  134. opik/rest_api/types/dataset.py +2 -0
  135. opik/rest_api/types/dataset_item.py +2 -0
  136. opik/rest_api/types/dataset_item_compare.py +2 -0
  137. opik/rest_api/types/dataset_item_filter.py +23 -0
  138. opik/rest_api/types/dataset_item_filter_operator.py +21 -0
  139. opik/rest_api/types/dataset_item_page_compare.py +1 -0
  140. opik/rest_api/types/dataset_item_page_public.py +1 -0
  141. opik/rest_api/types/dataset_item_public.py +2 -0
  142. opik/rest_api/types/dataset_item_update.py +39 -0
  143. opik/rest_api/types/dataset_item_write.py +1 -0
  144. opik/rest_api/types/dataset_public.py +2 -0
  145. opik/rest_api/types/dataset_public_status.py +5 -0
  146. opik/rest_api/types/dataset_status.py +5 -0
  147. opik/rest_api/types/dataset_version_diff.py +22 -0
  148. opik/rest_api/types/dataset_version_diff_stats.py +24 -0
  149. opik/rest_api/types/dataset_version_page_public.py +23 -0
  150. opik/rest_api/types/dataset_version_public.py +49 -0
  151. opik/rest_api/types/experiment.py +2 -0
  152. opik/rest_api/types/experiment_public.py +2 -0
  153. opik/rest_api/types/experiment_score.py +20 -0
  154. opik/rest_api/types/experiment_score_public.py +20 -0
  155. opik/rest_api/types/experiment_score_write.py +20 -0
  156. opik/rest_api/types/feedback.py +20 -1
  157. opik/rest_api/types/feedback_create.py +16 -1
  158. opik/rest_api/types/feedback_object_public.py +22 -1
  159. opik/rest_api/types/feedback_public.py +20 -1
  160. opik/rest_api/types/feedback_score_public.py +4 -0
  161. opik/rest_api/types/feedback_update.py +16 -1
  162. opik/rest_api/types/image_url.py +20 -0
  163. opik/rest_api/types/image_url_public.py +20 -0
  164. opik/rest_api/types/image_url_write.py +20 -0
  165. opik/rest_api/types/llm_as_judge_message.py +5 -1
  166. opik/rest_api/types/llm_as_judge_message_content.py +24 -0
  167. opik/rest_api/types/llm_as_judge_message_content_public.py +24 -0
  168. opik/rest_api/types/llm_as_judge_message_content_write.py +24 -0
  169. opik/rest_api/types/llm_as_judge_message_public.py +5 -1
  170. opik/rest_api/types/llm_as_judge_message_write.py +5 -1
  171. opik/rest_api/types/llm_as_judge_model_parameters.py +2 -0
  172. opik/rest_api/types/llm_as_judge_model_parameters_public.py +2 -0
  173. opik/rest_api/types/llm_as_judge_model_parameters_write.py +2 -0
  174. opik/rest_api/types/optimization.py +2 -0
  175. opik/rest_api/types/optimization_public.py +2 -0
  176. opik/rest_api/types/optimization_public_status.py +3 -1
  177. opik/rest_api/types/optimization_status.py +3 -1
  178. opik/rest_api/types/optimization_studio_config.py +27 -0
  179. opik/rest_api/types/optimization_studio_config_public.py +27 -0
  180. opik/rest_api/types/optimization_studio_config_write.py +27 -0
  181. opik/rest_api/types/optimization_studio_log.py +22 -0
  182. opik/rest_api/types/optimization_write.py +2 -0
  183. opik/rest_api/types/optimization_write_status.py +3 -1
  184. opik/rest_api/types/prompt.py +6 -0
  185. opik/rest_api/types/prompt_detail.py +6 -0
  186. opik/rest_api/types/prompt_detail_template_structure.py +5 -0
  187. opik/rest_api/types/prompt_public.py +6 -0
  188. opik/rest_api/types/prompt_public_template_structure.py +5 -0
  189. opik/rest_api/types/prompt_template_structure.py +5 -0
  190. opik/rest_api/types/prompt_version.py +2 -0
  191. opik/rest_api/types/prompt_version_detail.py +2 -0
  192. opik/rest_api/types/prompt_version_detail_template_structure.py +5 -0
  193. opik/rest_api/types/prompt_version_public.py +2 -0
  194. opik/rest_api/types/prompt_version_public_template_structure.py +5 -0
  195. opik/rest_api/types/prompt_version_template_structure.py +5 -0
  196. opik/rest_api/types/score_name.py +1 -0
  197. opik/rest_api/types/service_toggles_config.py +6 -0
  198. opik/rest_api/types/span_enrichment_options.py +31 -0
  199. opik/rest_api/types/span_filter.py +23 -0
  200. opik/rest_api/types/span_filter_operator.py +21 -0
  201. opik/rest_api/types/span_filter_write.py +23 -0
  202. opik/rest_api/types/span_filter_write_operator.py +21 -0
  203. opik/rest_api/types/span_llm_as_judge_code.py +27 -0
  204. opik/rest_api/types/span_llm_as_judge_code_public.py +27 -0
  205. opik/rest_api/types/span_llm_as_judge_code_write.py +27 -0
  206. opik/rest_api/types/span_update.py +46 -0
  207. opik/rest_api/types/studio_evaluation.py +20 -0
  208. opik/rest_api/types/studio_evaluation_public.py +20 -0
  209. opik/rest_api/types/studio_evaluation_write.py +20 -0
  210. opik/rest_api/types/studio_llm_model.py +21 -0
  211. opik/rest_api/types/studio_llm_model_public.py +21 -0
  212. opik/rest_api/types/studio_llm_model_write.py +21 -0
  213. opik/rest_api/types/studio_message.py +20 -0
  214. opik/rest_api/types/studio_message_public.py +20 -0
  215. opik/rest_api/types/studio_message_write.py +20 -0
  216. opik/rest_api/types/studio_metric.py +21 -0
  217. opik/rest_api/types/studio_metric_public.py +21 -0
  218. opik/rest_api/types/studio_metric_write.py +21 -0
  219. opik/rest_api/types/studio_optimizer.py +21 -0
  220. opik/rest_api/types/studio_optimizer_public.py +21 -0
  221. opik/rest_api/types/studio_optimizer_write.py +21 -0
  222. opik/rest_api/types/studio_prompt.py +20 -0
  223. opik/rest_api/types/studio_prompt_public.py +20 -0
  224. opik/rest_api/types/studio_prompt_write.py +20 -0
  225. opik/rest_api/types/trace.py +6 -0
  226. opik/rest_api/types/trace_public.py +6 -0
  227. opik/rest_api/types/trace_thread_filter_write.py +23 -0
  228. opik/rest_api/types/trace_thread_filter_write_operator.py +21 -0
  229. opik/rest_api/types/trace_thread_update.py +19 -0
  230. opik/rest_api/types/trace_update.py +39 -0
  231. opik/rest_api/types/value_entry.py +2 -0
  232. opik/rest_api/types/value_entry_compare.py +2 -0
  233. opik/rest_api/types/value_entry_experiment_item_bulk_write_view.py +2 -0
  234. opik/rest_api/types/value_entry_public.py +2 -0
  235. opik/rest_api/types/video_url.py +19 -0
  236. opik/rest_api/types/video_url_public.py +19 -0
  237. opik/rest_api/types/video_url_write.py +19 -0
  238. opik/synchronization.py +5 -6
  239. opik/{decorator/tracing_runtime_config.py → tracing_runtime_config.py} +6 -7
  240. {opik-1.9.5.dist-info → opik-1.9.39.dist-info}/METADATA +5 -4
  241. {opik-1.9.5.dist-info → opik-1.9.39.dist-info}/RECORD +246 -151
  242. opik/api_objects/prompt/chat_prompt_template.py +0 -164
  243. opik/api_objects/prompt/prompt.py +0 -131
  244. /opik/rest_api/{spans/types → types}/span_update_type.py +0 -0
  245. {opik-1.9.5.dist-info → opik-1.9.39.dist-info}/WHEEL +0 -0
  246. {opik-1.9.5.dist-info → opik-1.9.39.dist-info}/entry_points.txt +0 -0
  247. {opik-1.9.5.dist-info → opik-1.9.39.dist-info}/licenses/LICENSE +0 -0
  248. {opik-1.9.5.dist-info → opik-1.9.39.dist-info}/top_level.txt +0 -0
@@ -24,8 +24,8 @@ from .dataset import rest_operations as dataset_rest_operations
24
24
  from .experiment import experiments_client
25
25
  from .experiment import helpers as experiment_helpers
26
26
  from .experiment import rest_operations as experiment_rest_operations
27
- from .prompt import Prompt, PromptType
28
- from .prompt.client import PromptClient
27
+ from . import prompt as prompt_module
28
+ from .prompt import client as prompt_client
29
29
  from .threads import threads_client
30
30
  from .trace import migration as trace_migration, trace_client
31
31
  from .. import (
@@ -847,8 +847,13 @@ class Opik:
847
847
  self._rest_client, dataset_name
848
848
  )
849
849
 
850
+ experiments_client = self.get_experiments_client()
850
851
  experiments = dataset_rest_operations.get_dataset_experiments(
851
- self._rest_client, dataset_id, max_results, streamer=self._streamer
852
+ rest_client=self._rest_client,
853
+ dataset_id=dataset_id,
854
+ max_results=max_results,
855
+ streamer=self._streamer,
856
+ experiments_client=experiments_client,
852
857
  )
853
858
 
854
859
  return experiments
@@ -912,8 +917,8 @@ class Opik:
912
917
  dataset_name: str,
913
918
  name: Optional[str] = None,
914
919
  experiment_config: Optional[Dict[str, Any]] = None,
915
- prompt: Optional[Prompt] = None,
916
- prompts: Optional[List[Prompt]] = None,
920
+ prompt: Optional[prompt_module.base_prompt.BasePrompt] = None,
921
+ prompts: Optional[List[prompt_module.base_prompt.BasePrompt]] = None,
917
922
  type: Literal["regular", "trial", "mini-batch"] = "regular",
918
923
  optimization_id: Optional[str] = None,
919
924
  ) -> experiment.Experiment:
@@ -961,11 +966,48 @@ class Opik:
961
966
  dataset_name=dataset_name,
962
967
  rest_client=self._rest_client,
963
968
  streamer=self._streamer,
969
+ experiments_client=self.get_experiments_client(),
964
970
  prompts=checked_prompts,
965
971
  )
966
972
 
967
973
  return experiment_
968
974
 
975
+ def update_experiment(
976
+ self,
977
+ id: str,
978
+ name: Optional[str] = None,
979
+ experiment_config: Optional[Dict[str, Any]] = None,
980
+ ) -> None:
981
+ """
982
+ Update an experiment's name and/or configuration.
983
+
984
+ Args:
985
+ id: The experiment ID.
986
+ name: The new name for the experiment. If None, the name will not be updated.
987
+ experiment_config: The new configuration for the experiment. If None, the configuration will not be updated.
988
+
989
+ Raises:
990
+ ValueError: if id is None or empty, or if both name and experiment_config are None
991
+ """
992
+ if not id:
993
+ raise ValueError(
994
+ f"id must be provided and can not be None or empty, id: {id}"
995
+ )
996
+
997
+ if name is None and experiment_config is None:
998
+ raise ValueError(
999
+ "At least one of 'name' or 'experiment_config' must be provided"
1000
+ )
1001
+
1002
+ # Only include parameters that are provided to avoid clearing fields
1003
+ request_params: Dict[str, Any] = {}
1004
+ if name is not None:
1005
+ request_params["name"] = name
1006
+ if experiment_config is not None:
1007
+ request_params["metadata"] = experiment_config
1008
+
1009
+ self._rest_client.experiments.update_experiment(id, **request_params)
1010
+
969
1011
  def get_experiment_by_name(self, name: str) -> experiment.Experiment:
970
1012
  """
971
1013
  Returns an existing experiment by its name.
@@ -985,18 +1027,20 @@ class Opik:
985
1027
 
986
1028
  return experiment.Experiment(
987
1029
  id=experiment_public.id,
988
- name=name,
1030
+ name=experiment_public.name,
989
1031
  dataset_name=experiment_public.dataset_name,
990
1032
  rest_client=self._rest_client,
991
1033
  streamer=self._streamer,
1034
+ experiments_client=self.get_experiments_client(),
992
1035
  )
993
1036
 
994
1037
  def get_experiments_by_name(self, name: str) -> List[experiment.Experiment]:
995
1038
  """
996
- Returns a list of existing experiments by its name.
1039
+ Returns a list of existing experiments containing the given string in their name.
1040
+ Search is case-insensitive.
997
1041
 
998
1042
  Args:
999
- name: The name of the experiment(s).
1043
+ name: The string to search for in the experiment names.
1000
1044
 
1001
1045
  Returns:
1002
1046
  List[experiment.Experiment]: List of existing experiments.
@@ -1009,10 +1053,11 @@ class Opik:
1009
1053
  for public_experiment in experiments_public:
1010
1054
  experiment_ = experiment.Experiment(
1011
1055
  id=public_experiment.id,
1012
- name=name,
1056
+ name=public_experiment.name,
1013
1057
  dataset_name=public_experiment.dataset_name,
1014
1058
  rest_client=self._rest_client,
1015
1059
  streamer=self._streamer,
1060
+ experiments_client=self.get_experiments_client(),
1016
1061
  )
1017
1062
  result.append(experiment_)
1018
1063
 
@@ -1045,6 +1090,7 @@ class Opik:
1045
1090
  dataset_name=experiment_public.dataset_name,
1046
1091
  rest_client=self._rest_client,
1047
1092
  streamer=self._streamer,
1093
+ experiments_client=self.get_experiments_client(),
1048
1094
  )
1049
1095
 
1050
1096
  def end(self, timeout: Optional[int] = None) -> None:
@@ -1348,70 +1394,188 @@ class Opik:
1348
1394
  name: str,
1349
1395
  prompt: str,
1350
1396
  metadata: Optional[Dict[str, Any]] = None,
1351
- type: PromptType = PromptType.MUSTACHE,
1352
- ) -> Prompt:
1397
+ type: prompt_module.PromptType = prompt_module.PromptType.MUSTACHE,
1398
+ ) -> prompt_module.Prompt:
1353
1399
  """
1354
- Creates a new prompt with the given name and template.
1355
- If a prompt with the same name already exists, it will create a new version of the existing prompt if the templates differ.
1400
+ Creates a new text prompt with the given name and template.
1401
+ If a text prompt with the same name already exists, it will create a new version of the existing prompt if the templates differ.
1356
1402
 
1357
1403
  Parameters:
1358
1404
  name: The name of the prompt.
1359
1405
  prompt: The template content of the prompt.
1360
1406
  metadata: Optional metadata to be included in the prompt.
1407
+ type: The template type (MUSTACHE or JINJA2).
1361
1408
 
1362
1409
  Returns:
1363
1410
  A Prompt object containing details of the created or retrieved prompt.
1364
1411
 
1365
1412
  Raises:
1366
- ApiError: If there is an error during the creation of the prompt and the status code is not 409.
1413
+ PromptTemplateStructureMismatch: If a chat prompt with the same name already exists (template structure is immutable).
1414
+ ApiError: If there is an error during the creation of the prompt.
1367
1415
  """
1368
- prompt_client = PromptClient(self._rest_client)
1369
- prompt_version = prompt_client.create_prompt(
1416
+ prompt_client_ = prompt_client.PromptClient(self._rest_client)
1417
+ prompt_version = prompt_client_.create_prompt(
1370
1418
  name=name, prompt=prompt, metadata=metadata, type=type
1371
1419
  )
1372
- return Prompt.from_fern_prompt_version(name, prompt_version)
1420
+ return prompt_module.Prompt.from_fern_prompt_version(name, prompt_version)
1421
+
1422
+ def create_chat_prompt(
1423
+ self,
1424
+ name: str,
1425
+ messages: List[Dict[str, Any]],
1426
+ metadata: Optional[Dict[str, Any]] = None,
1427
+ type: prompt_module.PromptType = prompt_module.PromptType.MUSTACHE,
1428
+ ) -> prompt_module.ChatPrompt:
1429
+ """
1430
+ Creates a new chat prompt with the given name and message templates.
1431
+ If a chat prompt with the same name already exists, it will create a new version if the messages differ.
1432
+
1433
+ Parameters:
1434
+ name: The name of the chat prompt.
1435
+ messages: List of message dictionaries with 'role' and 'content' fields.
1436
+ metadata: Optional metadata to be included in the prompt.
1437
+ type: The template type (MUSTACHE or JINJA2).
1438
+
1439
+ Returns:
1440
+ A ChatPrompt object containing details of the created or retrieved chat prompt.
1441
+
1442
+ Raises:
1443
+ PromptTemplateStructureMismatch: If a text prompt with the same name already exists (template structure is immutable).
1444
+ ApiError: If there is an error during the creation of the prompt.
1445
+ """
1446
+ return prompt_module.ChatPrompt(
1447
+ name=name, messages=messages, metadata=metadata, type=type
1448
+ )
1373
1449
 
1374
1450
  def get_prompt(
1375
1451
  self,
1376
1452
  name: str,
1377
1453
  commit: Optional[str] = None,
1378
- ) -> Optional[Prompt]:
1454
+ ) -> Optional[prompt_module.Prompt]:
1379
1455
  """
1380
- Retrieve the prompt detail for a given prompt name and commit version.
1456
+ Retrieve a text prompt by name and optional commit version.
1457
+
1458
+ This method only returns text prompts.
1381
1459
 
1382
1460
  Parameters:
1383
1461
  name: The name of the prompt.
1384
1462
  commit: An optional commit version of the prompt. If not provided, the latest version is retrieved.
1385
1463
 
1386
1464
  Returns:
1387
- Prompt: The details of the specified prompt.
1465
+ Prompt: The details of the specified text prompt, or None if not found.
1466
+
1467
+ Raises:
1468
+ PromptTemplateStructureMismatch: If the prompt exists but is a chat prompt (template structure mismatch).
1388
1469
  """
1389
- prompt_client = PromptClient(self._rest_client)
1390
- fern_prompt_version = prompt_client.get_prompt(name=name, commit=commit)
1470
+ prompt_client_ = prompt_client.PromptClient(self._rest_client)
1471
+ fern_prompt_version = prompt_client_.get_prompt(
1472
+ name=name, commit=commit, raise_if_not_template_structure="text"
1473
+ )
1474
+
1391
1475
  if fern_prompt_version is None:
1392
1476
  return None
1393
1477
 
1394
- return Prompt.from_fern_prompt_version(name, fern_prompt_version)
1478
+ return prompt_module.Prompt.from_fern_prompt_version(name, fern_prompt_version)
1395
1479
 
1396
- def get_prompt_history(self, name: str) -> List[Prompt]:
1480
+ def get_chat_prompt(
1481
+ self,
1482
+ name: str,
1483
+ commit: Optional[str] = None,
1484
+ ) -> Optional[prompt_module.ChatPrompt]:
1397
1485
  """
1398
- Retrieve all the prompt versions history for a given prompt name.
1486
+ Retrieve a chat prompt by name and optional commit version.
1487
+
1488
+ This method only returns chat prompts.
1399
1489
 
1400
1490
  Parameters:
1401
1491
  name: The name of the prompt.
1492
+ commit: An optional commit version of the prompt. If not provided, the latest version is retrieved.
1402
1493
 
1403
1494
  Returns:
1404
- List[Prompt]: A list of Prompt instances for the given name.
1495
+ ChatPrompt: The details of the specified chat prompt, or None if not found.
1496
+
1497
+ Raises:
1498
+ PromptTemplateStructureMismatch: If the prompt exists but is a text prompt (template structure mismatch).
1405
1499
  """
1406
- prompt_client = PromptClient(self._rest_client)
1407
- fern_prompt_versions = prompt_client.get_all_prompt_versions(name=name)
1500
+ prompt_client_ = prompt_client.PromptClient(self._rest_client)
1501
+ fern_prompt_version = prompt_client_.get_prompt(
1502
+ name=name, commit=commit, raise_if_not_template_structure="chat"
1503
+ )
1504
+
1505
+ if fern_prompt_version is None:
1506
+ return None
1507
+
1508
+ return prompt_module.ChatPrompt.from_fern_prompt_version(
1509
+ name, fern_prompt_version
1510
+ )
1511
+
1512
+ def get_prompt_history(self, name: str) -> List[prompt_module.Prompt]:
1513
+ """
1514
+ Retrieve all text prompt versions history for a given prompt name.
1515
+
1516
+ Parameters:
1517
+ name: The name of the prompt.
1518
+
1519
+ Returns:
1520
+ List[Prompt]: A list of text Prompt instances for the given name, or an empty list if not found.
1521
+
1522
+ Raises:
1523
+ PromptTemplateStructureMismatch: If the prompt exists but is a chat prompt (template structure mismatch).
1524
+ """
1525
+ prompt_client_ = prompt_client.PromptClient(self._rest_client)
1526
+
1527
+ # First, validate that this is a text prompt by trying to get the latest version
1528
+ # Let PromptTemplateStructureMismatch exception propagate - this is a hard error
1529
+ latest_version = prompt_client_.get_prompt(
1530
+ name=name, raise_if_not_template_structure="text"
1531
+ )
1532
+
1533
+ if latest_version is None:
1534
+ return []
1535
+
1536
+ # Now get all versions (we know it's a text prompt)
1537
+ fern_prompt_versions = prompt_client_.get_all_prompt_versions(name=name)
1538
+
1408
1539
  result = [
1409
- Prompt.from_fern_prompt_version(name, version)
1540
+ prompt_module.Prompt.from_fern_prompt_version(name, version)
1410
1541
  for version in fern_prompt_versions
1411
1542
  ]
1412
1543
  return result
1413
1544
 
1414
- def get_all_prompts(self, name: str) -> List[Prompt]:
1545
+ def get_chat_prompt_history(self, name: str) -> List[prompt_module.ChatPrompt]:
1546
+ """
1547
+ Retrieve all chat prompt versions history for a given prompt name.
1548
+
1549
+ Parameters:
1550
+ name: The name of the prompt.
1551
+
1552
+ Returns:
1553
+ List[ChatPrompt]: A list of ChatPrompt instances for the given name, or an empty list if not found.
1554
+
1555
+ Raises:
1556
+ PromptTemplateStructureMismatch: If the prompt exists but is a text prompt (template structure mismatch).
1557
+ """
1558
+ prompt_client_ = prompt_client.PromptClient(self._rest_client)
1559
+
1560
+ # First, validate that this is a chat prompt by trying to get the latest version
1561
+ # Let PromptTemplateStructureMismatch exception propagate - this is a hard error
1562
+ latest_version = prompt_client_.get_prompt(
1563
+ name=name, raise_if_not_template_structure="chat"
1564
+ )
1565
+
1566
+ if latest_version is None:
1567
+ return []
1568
+
1569
+ # Now get all versions (we know it's a chat prompt)
1570
+ fern_prompt_versions = prompt_client_.get_all_prompt_versions(name=name)
1571
+
1572
+ result = [
1573
+ prompt_module.ChatPrompt.from_fern_prompt_version(name, version)
1574
+ for version in fern_prompt_versions
1575
+ ]
1576
+ return result
1577
+
1578
+ def get_all_prompts(self, name: str) -> List[prompt_module.Prompt]:
1415
1579
  """
1416
1580
  DEPRECATED: Please use Opik.get_prompt_history() instead.
1417
1581
  Retrieve all the prompt versions history for a given prompt name.
@@ -1420,16 +1584,18 @@ class Opik:
1420
1584
  name: The name of the prompt.
1421
1585
 
1422
1586
  Returns:
1423
- List[Prompt]: A list of Prompt instances for the given name.
1587
+ List[prompt_module.Prompt]: A list of Prompt instances for the given name.
1424
1588
  """
1425
1589
  LOGGER.warning(
1426
1590
  "Opik.get_all_prompts() is deprecated. Please use Opik.get_prompt_history() instead."
1427
1591
  )
1428
1592
  return self.get_prompt_history(name)
1429
1593
 
1430
- def search_prompts(self, filter_string: Optional[str] = None) -> List[Prompt]:
1594
+ def search_prompts(
1595
+ self, filter_string: Optional[str] = None
1596
+ ) -> List[Union[prompt_module.Prompt, prompt_module.ChatPrompt]]:
1431
1597
  """
1432
- Retrieve the latest prompt versions for the given search parameters.
1598
+ Retrieve the latest prompt versions (both string and chat prompts) for the given search parameters.
1433
1599
 
1434
1600
  Parameters:
1435
1601
  filter_string: A filter string to narrow down the search using Opik Query Language (OQL).
@@ -1439,11 +1605,13 @@ class Opik:
1439
1605
  - `id`, `name`: String fields
1440
1606
  - `tags`: List field (use "contains" operator only)
1441
1607
  - `created_by`: String field
1608
+ - `template_structure`: String field ("string" or "chat")
1442
1609
 
1443
1610
  Supported operators by column:
1444
1611
  - `id`: =, !=, contains, not_contains, starts_with, ends_with, >, <
1445
1612
  - `name`: =, !=, contains, not_contains, starts_with, ends_with, >, <
1446
1613
  - `created_by`: =, !=, contains, not_contains, starts_with, ends_with, >, <
1614
+ - `template_structure`: =, !=
1447
1615
  - `tags`: contains (only)
1448
1616
 
1449
1617
  Examples:
@@ -1452,23 +1620,36 @@ class Opik:
1452
1620
  - `name contains "summary"` - Filter by name substring
1453
1621
  - `created_by = "user@example.com"` - Filter by creator
1454
1622
  - `id starts_with "prompt_"` - Filter by ID prefix
1623
+ - `template_structure = "text"` - Only text prompts
1624
+ - `template_structure = "chat"` - Only chat prompts
1455
1625
 
1456
- If not provided, all prompts matching the name filter will be returned.
1626
+ If not provided, all prompts (both text and chat) will be returned.
1457
1627
 
1458
1628
  Returns:
1459
- List[Prompt]: A list of Prompt instances found.
1460
- """
1461
- parsed_filters = None
1462
- if filter_string:
1463
- oql = opik_query_language.OpikQueryLanguage(filter_string)
1464
- parsed_filters = oql.get_filter_expressions()
1465
-
1466
- prompt_client = PromptClient(self._rest_client)
1467
- name_and_versions = prompt_client.search_prompts(parsed_filters=parsed_filters)
1468
- prompts: List[Prompt] = [
1469
- Prompt.from_fern_prompt_version(prompt_name, version)
1470
- for (prompt_name, version) in name_and_versions
1471
- ]
1629
+ List[Union[Prompt, ChatPrompt]]: A list of Prompt and/or ChatPrompt instances found.
1630
+ """
1631
+ oql = opik_query_language.OpikQueryLanguage(filter_string or "")
1632
+ parsed_filters = oql.get_filter_expressions()
1633
+
1634
+ prompt_client_ = prompt_client.PromptClient(self._rest_client)
1635
+ search_results = prompt_client_.search_prompts(parsed_filters=parsed_filters)
1636
+
1637
+ # Convert to Prompt or ChatPrompt objects based on template_structure
1638
+ prompts: List[Union[prompt_module.Prompt, prompt_module.ChatPrompt]] = []
1639
+ for result in search_results:
1640
+ if result.template_structure == "chat":
1641
+ prompts.append(
1642
+ prompt_module.ChatPrompt.from_fern_prompt_version(
1643
+ result.name, result.prompt_version_detail
1644
+ )
1645
+ )
1646
+ else:
1647
+ prompts.append(
1648
+ prompt_module.Prompt.from_fern_prompt_version(
1649
+ result.name, result.prompt_version_detail
1650
+ )
1651
+ )
1652
+
1472
1653
  return prompts
1473
1654
 
1474
1655
  def create_optimization(
@@ -1477,8 +1658,9 @@ class Opik:
1477
1658
  objective_name: str,
1478
1659
  name: Optional[str] = None,
1479
1660
  metadata: Optional[Dict[str, Any]] = None,
1661
+ optimization_id: Optional[str] = None,
1480
1662
  ) -> optimization.Optimization:
1481
- id = id_helpers.generate_id()
1663
+ id = optimization_id or id_helpers.generate_id()
1482
1664
 
1483
1665
  self._rest_client.optimizations.create_optimization(
1484
1666
  id=id,
@@ -29,6 +29,7 @@ COLUMNS = {
29
29
  "type": "string",
30
30
  "model": "string",
31
31
  "provider": "string",
32
+ "template_structure": "string",
32
33
  }
33
34
 
34
35
  SUPPORTED_OPERATORS = {
@@ -121,6 +122,14 @@ SUPPORTED_OPERATORS = {
121
122
  ">",
122
123
  "<",
123
124
  ],
125
+ "template_structure": [
126
+ "=",
127
+ "contains",
128
+ "not_contains",
129
+ "starts_with",
130
+ "ends_with",
131
+ "!=",
132
+ ],
124
133
  }
125
134
 
126
135
 
@@ -1,9 +1,17 @@
1
- from .prompt import Prompt
1
+ from .text.prompt import Prompt
2
+ from .text.prompt_template import PromptTemplate
3
+ from .chat.chat_prompt import ChatPrompt
4
+ from .chat.chat_prompt_template import ChatPromptTemplate
2
5
  from .types import PromptType
3
- from .chat_prompt_template import ChatPromptTemplate
6
+ from .base_prompt import BasePrompt
7
+ from .base_prompt_template import BasePromptTemplate
4
8
 
5
9
  __all__ = [
6
- "Prompt",
7
10
  "PromptType",
11
+ "Prompt",
12
+ "ChatPrompt",
13
+ "PromptTemplate",
8
14
  "ChatPromptTemplate",
15
+ "BasePrompt",
16
+ "BasePromptTemplate",
9
17
  ]
@@ -0,0 +1,69 @@
1
+ """
2
+ Base class for prompts.
3
+
4
+ Defines abstract interface that both string and chat prompt variants must implement.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import Any, Dict, Optional
9
+
10
+ from . import types as prompt_types
11
+
12
+
13
+ class BasePrompt(ABC):
14
+ """
15
+ Abstract base class for prompts (string and chat).
16
+
17
+ All prompt implementations must provide common properties and methods
18
+ for interacting with the backend API.
19
+ """
20
+
21
+ @property
22
+ @abstractmethod
23
+ def name(self) -> str:
24
+ """The name of the prompt."""
25
+ pass
26
+
27
+ @property
28
+ @abstractmethod
29
+ def commit(self) -> Optional[str]:
30
+ """The commit hash of the prompt version."""
31
+ pass
32
+
33
+ @property
34
+ @abstractmethod
35
+ def metadata(self) -> Optional[Dict[str, Any]]:
36
+ """The metadata dictionary associated with the prompt."""
37
+ pass
38
+
39
+ @property
40
+ @abstractmethod
41
+ def type(self) -> prompt_types.PromptType:
42
+ """The prompt type (MUSTACHE or JINJA2)."""
43
+ pass
44
+
45
+ # Internal API fields for backend synchronization
46
+ __internal_api__prompt_id__: str
47
+ __internal_api__version_id__: str
48
+
49
+ @abstractmethod
50
+ def format(self, *args: Any, **kwargs: Any) -> Any:
51
+ """
52
+ Format the prompt with the provided variables.
53
+
54
+ Returns:
55
+ Formatted output. Type depends on the implementation:
56
+ - Prompt returns str
57
+ - ChatPrompt returns List[Dict[str, MessageContent]]
58
+ """
59
+ pass
60
+
61
+ @abstractmethod
62
+ def __internal_api__to_info_dict__(self) -> Dict[str, Any]:
63
+ """
64
+ Convert the prompt to an info dictionary for serialization.
65
+
66
+ Returns:
67
+ Dictionary containing prompt metadata and version information.
68
+ """
69
+ pass
@@ -0,0 +1,29 @@
1
+ """
2
+ Base class for prompt templates.
3
+
4
+ Defines abstract interface that both string and chat template variants must implement.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import Any
9
+
10
+
11
+ class BasePromptTemplate(ABC):
12
+ """
13
+ Abstract base class for prompt templates (string and chat).
14
+
15
+ All prompt template implementations must provide a format method
16
+ that takes variables and returns formatted output.
17
+ """
18
+
19
+ @abstractmethod
20
+ def format(self, *args: Any, **kwargs: Any) -> Any:
21
+ """
22
+ Format the template with the provided variables.
23
+
24
+ Returns:
25
+ Formatted output. Type depends on the implementation:
26
+ - PromptTemplate returns str
27
+ - ChatPromptTemplate returns List[Dict[str, MessageContent]]
28
+ """
29
+ pass
@@ -0,0 +1 @@
1
+ # Empty - all exports handled by parent __init__.py