opik-optimizer 0.9.2__py3-none-any.whl → 1.0.1__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 (45) hide show
  1. opik_optimizer/__init__.py +7 -5
  2. opik_optimizer/_throttle.py +8 -8
  3. opik_optimizer/base_optimizer.py +98 -45
  4. opik_optimizer/cache_config.py +5 -3
  5. opik_optimizer/datasets/ai2_arc.py +15 -13
  6. opik_optimizer/datasets/cnn_dailymail.py +19 -15
  7. opik_optimizer/datasets/election_questions.py +10 -11
  8. opik_optimizer/datasets/gsm8k.py +16 -11
  9. opik_optimizer/datasets/halu_eval.py +6 -5
  10. opik_optimizer/datasets/hotpot_qa.py +17 -16
  11. opik_optimizer/datasets/medhallu.py +10 -7
  12. opik_optimizer/datasets/rag_hallucinations.py +11 -8
  13. opik_optimizer/datasets/ragbench.py +17 -9
  14. opik_optimizer/datasets/tiny_test.py +33 -37
  15. opik_optimizer/datasets/truthful_qa.py +18 -12
  16. opik_optimizer/demo/cache.py +6 -6
  17. opik_optimizer/demo/datasets.py +3 -7
  18. opik_optimizer/evolutionary_optimizer/__init__.py +3 -1
  19. opik_optimizer/evolutionary_optimizer/evolutionary_optimizer.py +722 -429
  20. opik_optimizer/evolutionary_optimizer/reporting.py +155 -74
  21. opik_optimizer/few_shot_bayesian_optimizer/few_shot_bayesian_optimizer.py +271 -188
  22. opik_optimizer/few_shot_bayesian_optimizer/reporting.py +79 -28
  23. opik_optimizer/logging_config.py +19 -15
  24. opik_optimizer/meta_prompt_optimizer/meta_prompt_optimizer.py +209 -129
  25. opik_optimizer/meta_prompt_optimizer/reporting.py +121 -46
  26. opik_optimizer/mipro_optimizer/__init__.py +2 -0
  27. opik_optimizer/mipro_optimizer/_lm.py +38 -9
  28. opik_optimizer/mipro_optimizer/_mipro_optimizer_v2.py +37 -26
  29. opik_optimizer/mipro_optimizer/mipro_optimizer.py +132 -63
  30. opik_optimizer/mipro_optimizer/utils.py +5 -2
  31. opik_optimizer/optimizable_agent.py +179 -0
  32. opik_optimizer/optimization_config/chat_prompt.py +143 -73
  33. opik_optimizer/optimization_config/configs.py +4 -3
  34. opik_optimizer/optimization_config/mappers.py +18 -6
  35. opik_optimizer/optimization_result.py +22 -13
  36. opik_optimizer/py.typed +0 -0
  37. opik_optimizer/reporting_utils.py +89 -58
  38. opik_optimizer/task_evaluator.py +12 -14
  39. opik_optimizer/utils.py +117 -14
  40. {opik_optimizer-0.9.2.dist-info → opik_optimizer-1.0.1.dist-info}/METADATA +8 -8
  41. opik_optimizer-1.0.1.dist-info/RECORD +50 -0
  42. opik_optimizer-0.9.2.dist-info/RECORD +0 -48
  43. {opik_optimizer-0.9.2.dist-info → opik_optimizer-1.0.1.dist-info}/WHEEL +0 -0
  44. {opik_optimizer-0.9.2.dist-info → opik_optimizer-1.0.1.dist-info}/licenses/LICENSE +0 -0
  45. {opik_optimizer-0.9.2.dist-info → opik_optimizer-1.0.1.dist-info}/top_level.txt +0 -0
@@ -1,106 +1,176 @@
1
- from typing import Any, Dict, List, Literal, Optional
1
+ from typing import Any, Dict, List, Optional, Union, Callable
2
+
3
+ import copy
2
4
 
3
5
  from pydantic import BaseModel, Field
4
6
 
7
+ from opik import track
8
+
5
9
 
6
10
  class Tool(BaseModel):
7
- name: str =Field(
8
- ...,
9
- description="Name of the tool"
10
- )
11
- description: str = Field(
12
- ...,
13
- description="Description of the tool"
14
- )
11
+ name: str = Field(..., description="Name of the tool")
12
+ description: str = Field(..., description="Description of the tool")
15
13
  parameters: Dict[str, Any] = Field(
16
- ...,
17
- description="JSON Schema defining the input parameters for the tool"
14
+ ..., description="JSON Schema defining the input parameters for the tool"
18
15
  )
19
16
 
17
+
20
18
  class ChatPrompt:
21
- system: str
22
- prompt: str
23
- messages: List[Dict[Literal["role", "content"], str]]
19
+ """
20
+ The ChatPrompt lies at the core of Opik Optimizer. It is
21
+ either a series of messages, or a system and/or prompt.
22
+
23
+ The ChatPrompt must make reference to at least one field
24
+ in the associated database when used with optimizations.
25
+
26
+ Args:
27
+ system: the system prompt
28
+ prompt: contains {input-dataset-field}, if given
29
+ messages: a list of dictionaries with role/content, with
30
+ a content containing {input-dataset-field}
31
+ """
24
32
 
25
33
  def __init__(
26
34
  self,
35
+ name: str = "chat-prompt",
27
36
  system: Optional[str] = None,
28
- prompt: Optional[str] = None,
29
- messages: Optional[List[Dict[Literal["role", "content"], str]]] = None,
30
- tools: Optional[List[Tool]] = None
31
- ):
37
+ user: Optional[str] = None,
38
+ messages: Optional[List[Dict[str, str]]] = None,
39
+ tools: Optional[List[Dict[str, Any]]] = None,
40
+ function_map: Optional[Dict[str, Callable]] = None,
41
+ model: Optional[str] = None,
42
+ invoke: Optional[Callable] = None,
43
+ project_name: Optional[str] = "Default Project",
44
+ **model_kwargs: Any,
45
+ ) -> None:
46
+ if system is None and user is None and messages is None:
47
+ raise ValueError(
48
+ "At least one of `system`, `user`, or `messages` must be provided"
49
+ )
50
+
51
+ if user is not None and messages is not None:
52
+ raise ValueError("`user` and `messages` cannot be provided together")
53
+
54
+ if system is not None and messages is not None:
55
+ raise ValueError("`system` and `messages` cannot be provided together")
56
+
57
+ if system is not None and not isinstance(system, str):
58
+ raise ValueError("`system` must be a string")
59
+
60
+ if user is not None and not isinstance(user, str):
61
+ raise ValueError("`user` must be a string")
62
+
63
+ if messages is not None:
64
+ if not isinstance(messages, list):
65
+ raise ValueError("`messages` must be a list")
66
+ else:
67
+ for message in messages:
68
+ if not isinstance(message, dict):
69
+ raise ValueError("`messages` must be a dictionary")
70
+ elif "role" not in message or "content" not in message:
71
+ raise ValueError(
72
+ "`message` must have 'role' and 'content' keys."
73
+ )
74
+ self.name = name
32
75
  self.system = system
33
- self.prompt = prompt
76
+ self.user = user
34
77
  self.messages = messages
78
+ # ALl of the rest are just for the ChatPrompt LLM
79
+ # These are used from the prompt as controls:
80
+ self.tools = tools
81
+ if function_map:
82
+ self.function_map = {
83
+ key: (
84
+ value
85
+ if hasattr(value, "__wrapped__")
86
+ else track(type="tool")(value)
87
+ )
88
+ for key, value in function_map.items()
89
+ }
90
+ else:
91
+ self.function_map = {}
92
+ # These are used for the LiteLLMAgent class:
93
+ self.model = model
94
+ self.model_kwargs = model_kwargs
95
+ self.invoke = invoke
96
+ self.project_name = project_name
35
97
 
36
- self.formatted_messages = self._standardize_prompts()
37
-
38
- def _standardize_prompts(
39
- self, **kwargs: Any
40
- ) -> List[Dict[Literal["role", "content"], str]]:
41
- if (self.system is None and self.prompt is None and self.messages is None):
42
- raise ValueError(
43
- "At least one of `system`, `prompt` or `messages` must be provided"
44
- )
98
+ def get_messages(
99
+ self,
100
+ dataset_item: Optional[Dict[str, str]] = None,
101
+ ) -> List[Dict[str, str]]:
102
+ # This is a copy, so we can alter the messages:
103
+ messages = self._standardize_prompts()
45
104
 
46
- if (self.prompt is not None and self.messages is not None):
47
- raise ValueError(
48
- "`prompt` and `messages` cannot be provided together"
49
- )
50
-
51
- if (self.system is not None and not isinstance(self.system, str)):
52
- raise ValueError(
53
- "`system` must be a string"
54
- )
55
-
56
- if (self.prompt is not None and not isinstance(self.prompt, str)):
57
- raise ValueError(
58
- "`prompt` must be a string"
59
- )
105
+ if dataset_item:
106
+ for key, value in dataset_item.items():
107
+ for message in messages:
108
+ # Only replace user message content:
109
+ label = "{" + key + "}"
110
+ if label in message["content"]:
111
+ message["content"] = message["content"].replace(
112
+ label, str(value)
113
+ )
114
+ return messages
60
115
 
61
- if (self.messages is not None and not isinstance(self.messages, list)):
62
- raise ValueError(
63
- "`messages` must be a list"
64
- )
116
+ def _standardize_prompts(self, **kwargs: Any) -> List[Dict[str, str]]:
117
+ standardize_messages: List[Dict[str, str]] = []
65
118
 
66
- standardize_messages = []
67
-
68
- if (self.system is not None):
119
+ if self.system is not None:
69
120
  standardize_messages.append({"role": "system", "content": self.system})
70
-
71
- if (self.prompt is not None):
72
- standardize_messages.append({"role": "user", "content": self.prompt})
73
-
74
- if (self.messages is not None):
121
+
122
+ if self.messages is not None:
75
123
  for message in self.messages:
76
124
  standardize_messages.append(message)
77
-
78
- return standardize_messages
79
125
 
80
- def format(self, **kwargs: Any) -> str:
81
- return self.prompt.format(**kwargs)
126
+ if self.user is not None:
127
+ standardize_messages.append({"role": "user", "content": self.user})
82
128
 
83
- def to_dict(self) -> Dict[str, Any]:
129
+ return copy.deepcopy(standardize_messages)
130
+
131
+ def to_dict(self) -> Dict[str, Union[str, List[Dict[str, str]]]]:
84
132
  """Convert ChatPrompt to a dictionary for JSON serialization.
85
-
133
+
86
134
  Returns:
87
135
  Dict containing the serializable representation of this ChatPrompt
88
136
  """
89
- return {
90
- "system": self.system,
91
- "prompt": self.prompt,
92
- "messages": self.messages,
93
- "formatted_messages": self.formatted_messages
94
- }
137
+ retval: Dict[str, Union[str, List[Dict[str, str]]]] = {}
138
+ if self.system is not None:
139
+ retval["system"] = self.system
140
+ if self.user is not None:
141
+ retval["user"] = self.user
142
+ if self.messages is not None:
143
+ retval["messages"] = self.messages
144
+ return retval
145
+
146
+ def copy(self) -> "ChatPrompt":
147
+ return ChatPrompt(
148
+ system=self.system,
149
+ user=self.user,
150
+ messages=copy.deepcopy(self.messages),
151
+ tools=self.tools,
152
+ function_map=self.function_map,
153
+ )
154
+
155
+ def set_messages(self, messages: List[Dict[str, Any]]) -> None:
156
+ self.system = None
157
+ self.user = None
158
+ self.messages = copy.deepcopy(messages)
95
159
 
96
160
  @classmethod
97
- def model_validate(cls, obj: Any, *, strict: Optional[bool] = None, from_attributes: Optional[bool] = None,
98
- context: Optional[Any] = None, by_alias: Optional[bool] = None, by_name: Optional[bool] = None) -> 'ChatPrompt':
161
+ def model_validate(
162
+ cls,
163
+ obj: Any,
164
+ *,
165
+ strict: Optional[bool] = None,
166
+ from_attributes: Optional[bool] = None,
167
+ context: Optional[Any] = None,
168
+ by_alias: Optional[bool] = None,
169
+ by_name: Optional[bool] = None,
170
+ ) -> "ChatPrompt":
99
171
  """Custom validation method to handle nested objects during deserialization."""
100
172
  return ChatPrompt(
101
- system=obj.get('system', None),
102
- prompt=obj.get('prompt', None),
103
- messages=obj.get('messages', None),
104
-
173
+ system=obj.get("system", None),
174
+ prompt=obj.get("prompt", None),
175
+ messages=obj.get("messages", None),
105
176
  )
106
-
@@ -1,15 +1,16 @@
1
1
  """Module containing configuration classes for optimization."""
2
2
 
3
- from typing import Any, Dict, List, Literal, Union
3
+ from typing import Any, List
4
4
 
5
5
  import pydantic
6
6
 
7
7
 
8
8
  class TaskConfig(pydantic.BaseModel):
9
9
  """Configuration for a prompt task."""
10
+
10
11
  model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
11
-
12
- instruction_prompt: Union[str, List[Dict[Literal["role", "content"], str]]]
12
+
13
+ instruction_prompt: str
13
14
  use_chat_prompt: bool = False
14
15
  input_dataset_fields: List[str]
15
16
  output_dataset_field: str
@@ -2,16 +2,21 @@ from typing import Dict, Callable, Optional, Any, Union
2
2
 
3
3
  EVALUATED_LLM_TASK_OUTPUT = "llm_output"
4
4
 
5
+
5
6
  class Mapper:
6
7
  """Base class for mapping functions that transform data between different formats."""
7
-
8
- def __init__(self, name: Optional[str] = None, transform: Optional[Callable[[Any], Any]] = None):
8
+
9
+ def __init__(
10
+ self,
11
+ name: Optional[str] = None,
12
+ transform: Optional[Callable[[Any], Any]] = None,
13
+ ):
9
14
  if name is not None and transform is not None:
10
15
  raise ValueError("Only one of name or transform can be provided")
11
-
16
+
12
17
  self.name = name
13
18
  self.transform = transform
14
-
19
+
15
20
  def __call__(self, data: Any) -> Any:
16
21
  if self.transform is not None:
17
22
  return self.transform(data)
@@ -19,7 +24,12 @@ class Mapper:
19
24
  return data[self.name]
20
25
  return data
21
26
 
22
- def from_dataset_field(*, name: str = None, transform: Optional[Callable[[Dict[str, Any]], Any]] = None) -> Union[str, Callable[[Dict[str, Any]], Any]]:
27
+
28
+ def from_dataset_field(
29
+ *,
30
+ name: Optional[str] = None,
31
+ transform: Optional[Callable[[Dict[str, Any]], Any]] = None,
32
+ ) -> Union[str, Callable[[Dict[str, Any]], Any]]:
23
33
  if name is not None and transform is not None:
24
34
  raise ValueError("Only one of name or transform can be provided")
25
35
 
@@ -36,7 +46,9 @@ def from_llm_response_text() -> str:
36
46
  return EVALUATED_LLM_TASK_OUTPUT
37
47
 
38
48
 
39
- def from_agent_output(*, name: str = None, transform: Optional[Callable[[Any], Any]] = None) -> Union[str, Callable[[Any], Any]]:
49
+ def from_agent_output(
50
+ *, name: Optional[str] = None, transform: Optional[Callable[[Any], Any]] = None
51
+ ) -> Union[str, Callable[[Any], Any]]:
40
52
  if name is not None and transform is not None:
41
53
  raise ValueError("Only one of name or transform can be provided")
42
54
 
@@ -1,26 +1,29 @@
1
1
  """Module containing the OptimizationResult class."""
2
2
 
3
- from typing import Any, Dict, List, Literal, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
 
5
5
  import pydantic
6
6
  import rich
7
7
 
8
- from .reporting_utils import get_console
8
+ from .reporting_utils import get_console, get_link_text
9
9
 
10
10
 
11
11
  class OptimizationResult(pydantic.BaseModel):
12
12
  """Result oan optimization run."""
13
13
 
14
14
  optimizer: str = "Optimizer"
15
-
16
- prompt: List[Dict[Literal["role", "content"], str]]
15
+
16
+ prompt: List[Dict[str, str]]
17
17
  score: float
18
18
  metric_name: str
19
-
19
+
20
+ optimization_id: Optional[str] = None
21
+ dataset_id: Optional[str] = None
22
+
20
23
  # Initial score
21
- initial_prompt: Optional[List[Dict[Literal["role", "content"], str]]] = None
24
+ initial_prompt: Optional[List[Dict[str, str]]] = None
22
25
  initial_score: Optional[float] = None
23
-
26
+
24
27
  details: Dict[str, Any] = pydantic.Field(default_factory=dict)
25
28
  history: List[Dict[str, Any]] = []
26
29
  llm_calls: Optional[int] = None
@@ -29,10 +32,10 @@ class OptimizationResult(pydantic.BaseModel):
29
32
  demonstrations: Optional[List[Dict[str, Any]]] = None
30
33
  mipro_prompt: Optional[str] = None
31
34
  tool_prompts: Optional[Dict[str, str]] = None
32
-
35
+
33
36
  model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
34
37
 
35
- def model_dump(self, *kargs, **kwargs) -> Dict[str, Any]:
38
+ def model_dump(self, *kargs: Any, **kwargs: Any) -> Dict[str, Any]:
36
39
  return super().model_dump(*kargs, **kwargs)
37
40
 
38
41
  def _calculate_improvement_str(self) -> str:
@@ -123,7 +126,6 @@ class OptimizationResult(pydantic.BaseModel):
123
126
  else "[dim]N/A[/dim]"
124
127
  )
125
128
  final_score_str = f"{self.score:.4f}"
126
- stopped_early = self.details.get("stopped_early", "N/A")
127
129
 
128
130
  model_name = self.details.get("model", "[dim]N/A[/dim]")
129
131
 
@@ -141,6 +143,15 @@ class OptimizationResult(pydantic.BaseModel):
141
143
  table.add_row("Final Best Score:", f"[bold cyan]{final_score_str}[/bold cyan]")
142
144
  table.add_row("Total Improvement:", improvement_str)
143
145
  table.add_row("Rounds Completed:", str(rounds_ran))
146
+ table.add_row(
147
+ "Optimization run link:",
148
+ get_link_text(
149
+ pre_text="",
150
+ link_text="Open in Opik Dashboard",
151
+ dataset_id=self.dataset_id,
152
+ optimization_id=self.optimization_id,
153
+ ),
154
+ )
144
155
 
145
156
  # Display Chat Structure if available
146
157
  panel_title = "[bold]Final Optimized Prompt[/bold]"
@@ -167,9 +178,7 @@ class OptimizationResult(pydantic.BaseModel):
167
178
  except Exception:
168
179
  # Fallback to simple text prompt
169
180
  prompt_renderable = rich.text.Text(str(self.prompt or ""), overflow="fold")
170
- panel_title = (
171
- "[bold]Final Optimized Prompt (Instruction - fallback)[/bold]"
172
- )
181
+ panel_title = "[bold]Final Optimized Prompt (Instruction - fallback)[/bold]"
173
182
 
174
183
  prompt_panel = rich.panel.Panel(
175
184
  prompt_renderable, title=panel_title, border_style="blue", padding=(1, 2)
File without changes
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  from contextlib import contextmanager
3
- from typing import Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional, Union
4
4
 
5
5
  from rich import box
6
6
  from rich.console import Console, Group
@@ -12,30 +12,29 @@ from .utils import get_optimization_run_url_by_id
12
12
 
13
13
  PANEL_WIDTH = 70
14
14
 
15
- def get_console(*args, **kwargs):
15
+
16
+ def get_console(*args: Any, **kwargs: Any) -> Console:
16
17
  console = Console(*args, **kwargs)
17
18
  console.is_jupyter = False
18
19
  return console
19
20
 
21
+
20
22
  @contextmanager
21
- def convert_tqdm_to_rich(description: Optional[str] = None, verbose: int = 1):
23
+ def convert_tqdm_to_rich(description: Optional[str] = None, verbose: int = 1) -> Any:
22
24
  """Context manager to convert tqdm to rich."""
23
25
  import opik.evaluation.engine.evaluation_tasks_executor
24
-
25
- def _tqdm_to_track(iterable, desc, disable, total):
26
+
27
+ def _tqdm_to_track(iterable: Any, desc: str, disable: bool, total: int) -> Any:
26
28
  disable = verbose == 0
27
29
  return track(
28
- iterable,
29
- description=description or desc,
30
- disable=disable,
31
- total=total
30
+ iterable, description=description or desc, disable=disable, total=total
32
31
  )
33
32
 
34
33
  original__tqdm = opik.evaluation.engine.evaluation_tasks_executor._tqdm
35
34
  opik.evaluation.engine.evaluation_tasks_executor._tqdm = _tqdm_to_track
36
35
 
37
-
38
36
  from opik.evaluation import report
37
+
39
38
  report.display_experiment_results = lambda *args, **kwargs: None
40
39
  report.display_experiment_link = lambda *args, **kwargs: None
41
40
 
@@ -45,32 +44,32 @@ def convert_tqdm_to_rich(description: Optional[str] = None, verbose: int = 1):
45
44
  opik.evaluation.engine.evaluation_tasks_executor._tqdm = original__tqdm
46
45
 
47
46
 
48
-
49
47
  @contextmanager
50
- def suppress_opik_logs():
48
+ def suppress_opik_logs() -> Any:
51
49
  """Suppress Opik startup logs by temporarily increasing the log level."""
52
50
  # Optimizer log level
53
- optimizer_logger = logging.getLogger('opik_optimizer')
54
-
51
+ optimizer_logger = logging.getLogger("opik_optimizer")
52
+
55
53
  # Get the Opik logger
56
54
  opik_logger = logging.getLogger("opik.api_objects.opik_client")
57
-
55
+
58
56
  # Store original log level
59
57
  original_level = opik_logger.level
60
-
58
+
61
59
  # Set log level to ERROR to suppress INFO messages
62
60
  opik_logger.setLevel(optimizer_logger.level)
63
-
61
+
64
62
  try:
65
63
  yield
66
64
  finally:
67
65
  # Restore original log level
68
66
  opik_logger.setLevel(original_level)
69
67
 
70
- def display_messages(messages: List[Dict[str, str]], prefix: str = ""):
68
+
69
+ def display_messages(messages: List[Dict[str, str]], prefix: str = "") -> None:
71
70
  for i, msg in enumerate(messages):
72
71
  panel = Panel(
73
- Text(msg.get('content', ''), overflow="fold"),
72
+ Text(msg.get("content", ""), overflow="fold"),
74
73
  title=f"{msg.get('role', 'message')}",
75
74
  title_align="left",
76
75
  border_style="dim",
@@ -90,68 +89,97 @@ def display_messages(messages: List[Dict[str, str]], prefix: str = ""):
90
89
  for line in rendered_panel.splitlines():
91
90
  console.print(Text(prefix) + Text.from_ansi(line))
92
91
 
93
- def display_header(
94
- algorithm: str,
95
- optimization_id: Optional[str]=None,
96
- dataset_id: Optional[str]=None,
97
- verbose: int = 1
98
- ):
99
- if verbose < 1:
100
- return
101
92
 
102
- if optimization_id is not None and dataset_id is not None:
93
+ def get_link_text(
94
+ pre_text: str,
95
+ link_text: str,
96
+ optimization_id: Optional[str] = None,
97
+ dataset_id: Optional[str] = None,
98
+ ) -> Text:
99
+ if optimization_id is not None and dataset_id is not None:
103
100
  optimization_url = get_optimization_run_url_by_id(
104
- optimization_id=optimization_id,
105
- dataset_id=dataset_id
101
+ optimization_id=optimization_id, dataset_id=dataset_id
106
102
  )
107
-
103
+
108
104
  # Create a visually appealing panel with an icon and ensure link doesn't wrap
109
-
110
- link_text = Text("-> View optimization details in your Opik dashboard")
111
- link_text.stylize(f"link {optimization_url}", 28, len(link_text))
105
+ link_text = Text(pre_text + link_text)
106
+ link_text.stylize(f"link {optimization_url}", len(pre_text), len(link_text)) # type: ignore
112
107
  else:
113
108
  link_text = Text("No optimization run link available", style="dim")
114
109
 
110
+ return link_text
111
+
112
+
113
+ def display_header(
114
+ algorithm: str,
115
+ optimization_id: Optional[str] = None,
116
+ dataset_id: Optional[str] = None,
117
+ verbose: int = 1,
118
+ ) -> None:
119
+ if verbose < 1:
120
+ return
121
+
122
+ link_text = get_link_text(
123
+ pre_text="-> View optimization details ",
124
+ link_text="in your Opik dashboard",
125
+ optimization_id=optimization_id,
126
+ dataset_id=dataset_id,
127
+ )
128
+
115
129
  content = Text.assemble(
116
- ("● ", "green"),
117
- "Running Opik Evaluation - ",
118
- (algorithm, "blue"),
119
- "\n\n"
130
+ ("● ", "green"), "Running Opik Evaluation - ", (algorithm, "blue"), "\n\n"
120
131
  ).append(link_text)
121
132
 
122
-
123
- panel = Panel(
124
- content,
125
- box=box.ROUNDED,
126
- width=PANEL_WIDTH
127
- )
133
+ panel = Panel(content, box=box.ROUNDED, width=PANEL_WIDTH)
128
134
 
129
135
  console = get_console()
130
136
  console.print(panel)
131
137
  console.print("\n")
132
138
 
133
139
 
134
- def display_result(initial_score, best_score, best_prompt, verbose: int = 1):
140
+ def display_result(
141
+ initial_score: float,
142
+ best_score: float,
143
+ best_prompt: List[Dict[str, str]],
144
+ verbose: int = 1,
145
+ ) -> None:
135
146
  if verbose < 1:
136
147
  return
137
148
 
138
149
  console = get_console()
139
150
  console.print(Text("\n> Optimization complete\n"))
140
-
151
+
152
+ content: Union[Text, Panel] = []
153
+
141
154
  if best_score > initial_score:
142
155
  if initial_score == 0:
143
- content = [Text(f"Prompt was optimized and improved from {initial_score:.4f} to {best_score:.4f}", style="bold green")]
156
+ content += [
157
+ Text(
158
+ f"Prompt was optimized and improved from {initial_score:.4f} to {best_score:.4f}",
159
+ style="bold green",
160
+ )
161
+ ]
144
162
  else:
145
163
  perc_change = (best_score - initial_score) / initial_score
146
- content = [Text(f"Prompt was optimized and improved from {initial_score:.4f} to {best_score:.4f} ({perc_change:.2%})", style="bold green")]
164
+ content += [
165
+ Text(
166
+ f"Prompt was optimized and improved from {initial_score:.4f} to {best_score:.4f} ({perc_change:.2%})",
167
+ style="bold green",
168
+ )
169
+ ]
147
170
  else:
148
- content = [Text(f"Optimization run did not find a better prompt than the initial one.\nScore: {best_score:.4f}", style="dim bold red")]
149
-
171
+ content += [
172
+ Text(
173
+ f"Optimization run did not find a better prompt than the initial one.\nScore: {best_score:.4f}",
174
+ style="dim bold red",
175
+ )
176
+ ]
177
+
150
178
  content.append(Text("\nOptimized prompt:"))
151
179
  for i, msg in enumerate(best_prompt):
152
180
  content.append(
153
181
  Panel(
154
- Text(msg.get('content', ''), overflow="fold"),
182
+ Text(msg.get("content", ""), overflow="fold"),
155
183
  title=f"{msg.get('role', 'message')}",
156
184
  title_align="left",
157
185
  border_style="dim",
@@ -167,12 +195,14 @@ def display_result(initial_score, best_score, best_prompt, verbose: int = 1):
167
195
  title_align="left",
168
196
  border_style="green",
169
197
  width=PANEL_WIDTH,
170
- padding=(1, 2)
198
+ padding=(1, 2),
171
199
  )
172
200
  )
173
201
 
174
202
 
175
- def display_configuration(messages: List[Dict[str, str]], optimizer_config: Dict[str, str], verbose: int = 1):
203
+ def display_configuration(
204
+ messages: List[Dict[str, str]], optimizer_config: Dict[str, Any], verbose: int = 1
205
+ ) -> None:
176
206
  """Displays the LLM messages and optimizer configuration using Rich panels."""
177
207
 
178
208
  if verbose < 1:
@@ -185,15 +215,16 @@ def display_configuration(messages: List[Dict[str, str]], optimizer_config: Dict
185
215
  display_messages(messages)
186
216
 
187
217
  # Panel for configuration
188
- console.print(Text(f"\nUsing {optimizer_config['optimizer']} with the parameters: "))
189
-
218
+ console.print(
219
+ Text(f"\nUsing {optimizer_config['optimizer']} with the parameters: ")
220
+ )
221
+
190
222
  for key, value in optimizer_config.items():
191
223
  if key == "optimizer": # Already displayed in the introductory text
192
224
  continue
193
225
  parameter_text = Text.assemble(
194
- Text(f" - {key}: ", style="dim"),
195
- Text(str(value), style="cyan")
226
+ Text(f" - {key}: ", style="dim"), Text(str(value), style="cyan")
196
227
  )
197
228
  console.print(parameter_text)
198
-
229
+
199
230
  console.print("\n")