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.
- opik_optimizer/__init__.py +7 -5
- opik_optimizer/_throttle.py +8 -8
- opik_optimizer/base_optimizer.py +98 -45
- opik_optimizer/cache_config.py +5 -3
- opik_optimizer/datasets/ai2_arc.py +15 -13
- opik_optimizer/datasets/cnn_dailymail.py +19 -15
- opik_optimizer/datasets/election_questions.py +10 -11
- opik_optimizer/datasets/gsm8k.py +16 -11
- opik_optimizer/datasets/halu_eval.py +6 -5
- opik_optimizer/datasets/hotpot_qa.py +17 -16
- opik_optimizer/datasets/medhallu.py +10 -7
- opik_optimizer/datasets/rag_hallucinations.py +11 -8
- opik_optimizer/datasets/ragbench.py +17 -9
- opik_optimizer/datasets/tiny_test.py +33 -37
- opik_optimizer/datasets/truthful_qa.py +18 -12
- opik_optimizer/demo/cache.py +6 -6
- opik_optimizer/demo/datasets.py +3 -7
- opik_optimizer/evolutionary_optimizer/__init__.py +3 -1
- opik_optimizer/evolutionary_optimizer/evolutionary_optimizer.py +722 -429
- opik_optimizer/evolutionary_optimizer/reporting.py +155 -74
- opik_optimizer/few_shot_bayesian_optimizer/few_shot_bayesian_optimizer.py +271 -188
- opik_optimizer/few_shot_bayesian_optimizer/reporting.py +79 -28
- opik_optimizer/logging_config.py +19 -15
- opik_optimizer/meta_prompt_optimizer/meta_prompt_optimizer.py +209 -129
- opik_optimizer/meta_prompt_optimizer/reporting.py +121 -46
- opik_optimizer/mipro_optimizer/__init__.py +2 -0
- opik_optimizer/mipro_optimizer/_lm.py +38 -9
- opik_optimizer/mipro_optimizer/_mipro_optimizer_v2.py +37 -26
- opik_optimizer/mipro_optimizer/mipro_optimizer.py +132 -63
- opik_optimizer/mipro_optimizer/utils.py +5 -2
- opik_optimizer/optimizable_agent.py +179 -0
- opik_optimizer/optimization_config/chat_prompt.py +143 -73
- opik_optimizer/optimization_config/configs.py +4 -3
- opik_optimizer/optimization_config/mappers.py +18 -6
- opik_optimizer/optimization_result.py +22 -13
- opik_optimizer/py.typed +0 -0
- opik_optimizer/reporting_utils.py +89 -58
- opik_optimizer/task_evaluator.py +12 -14
- opik_optimizer/utils.py +117 -14
- {opik_optimizer-0.9.2.dist-info → opik_optimizer-1.0.1.dist-info}/METADATA +8 -8
- opik_optimizer-1.0.1.dist-info/RECORD +50 -0
- opik_optimizer-0.9.2.dist-info/RECORD +0 -48
- {opik_optimizer-0.9.2.dist-info → opik_optimizer-1.0.1.dist-info}/WHEEL +0 -0
- {opik_optimizer-0.9.2.dist-info → opik_optimizer-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {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,
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
29
|
-
messages: Optional[List[Dict[
|
30
|
-
tools: Optional[List[
|
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.
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
62
|
-
|
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
|
-
|
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
|
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
|
-
|
81
|
-
|
126
|
+
if self.user is not None:
|
127
|
+
standardize_messages.append({"role": "user", "content": self.user})
|
82
128
|
|
83
|
-
|
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
|
-
|
90
|
-
|
91
|
-
"
|
92
|
-
|
93
|
-
"
|
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(
|
98
|
-
|
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(
|
102
|
-
prompt=obj.get(
|
103
|
-
messages=obj.get(
|
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,
|
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:
|
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__(
|
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
|
-
|
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(
|
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,
|
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[
|
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[
|
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)
|
opik_optimizer/py.typed
ADDED
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
|
-
|
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(
|
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
|
-
|
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(
|
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
|
-
|
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
|
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(
|
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
|
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
|
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
|
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(
|
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(
|
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(
|
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")
|