opik-optimizer 2.1.2__py3-none-any.whl → 2.2.0__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 +2 -2
- opik_optimizer/base_optimizer.py +314 -145
- opik_optimizer/evolutionary_optimizer/crossover_ops.py +31 -4
- opik_optimizer/evolutionary_optimizer/evaluation_ops.py +23 -3
- opik_optimizer/evolutionary_optimizer/evolutionary_optimizer.py +122 -95
- opik_optimizer/evolutionary_optimizer/mcp.py +11 -6
- opik_optimizer/evolutionary_optimizer/mutation_ops.py +25 -5
- opik_optimizer/evolutionary_optimizer/population_ops.py +26 -10
- opik_optimizer/evolutionary_optimizer/reporting.py +5 -5
- opik_optimizer/few_shot_bayesian_optimizer/few_shot_bayesian_optimizer.py +53 -99
- opik_optimizer/few_shot_bayesian_optimizer/reporting.py +4 -4
- opik_optimizer/gepa_optimizer/gepa_optimizer.py +183 -172
- opik_optimizer/gepa_optimizer/reporting.py +164 -22
- opik_optimizer/hierarchical_reflective_optimizer/hierarchical_reflective_optimizer.py +221 -245
- opik_optimizer/hierarchical_reflective_optimizer/hierarchical_root_cause_analyzer.py +38 -14
- opik_optimizer/hierarchical_reflective_optimizer/prompts.py +7 -1
- opik_optimizer/hierarchical_reflective_optimizer/reporting.py +287 -132
- opik_optimizer/meta_prompt_optimizer/meta_prompt_optimizer.py +185 -205
- opik_optimizer/meta_prompt_optimizer/reporting.py +4 -4
- opik_optimizer/mipro_optimizer/__init__.py +2 -2
- opik_optimizer/mipro_optimizer/_lm.py +4 -4
- opik_optimizer/mipro_optimizer/{_mipro_optimizer_v2.py → mipro_optimizer_v2.py} +1 -7
- opik_optimizer/mipro_optimizer/utils.py +1 -0
- opik_optimizer/multi_metric_objective.py +33 -0
- opik_optimizer/optimizable_agent.py +7 -4
- opik_optimizer/optimization_config/chat_prompt.py +7 -10
- opik_optimizer/parameter_optimizer/parameter_optimizer.py +188 -40
- opik_optimizer/parameter_optimizer/reporting.py +148 -0
- opik_optimizer/reporting_utils.py +42 -15
- opik_optimizer/task_evaluator.py +26 -9
- opik_optimizer/utils/core.py +16 -2
- opik_optimizer/utils/prompt_segments.py +1 -2
- {opik_optimizer-2.1.2.dist-info → opik_optimizer-2.2.0.dist-info}/METADATA +2 -3
- {opik_optimizer-2.1.2.dist-info → opik_optimizer-2.2.0.dist-info}/RECORD +37 -37
- opik_optimizer/evolutionary_optimizer/llm_support.py +0 -136
- opik_optimizer/mipro_optimizer/mipro_optimizer.py +0 -680
- {opik_optimizer-2.1.2.dist-info → opik_optimizer-2.2.0.dist-info}/WHEEL +0 -0
- {opik_optimizer-2.1.2.dist-info → opik_optimizer-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {opik_optimizer-2.1.2.dist-info → opik_optimizer-2.2.0.dist-info}/top_level.txt +0 -0
|
@@ -4,11 +4,19 @@ from typing import Any
|
|
|
4
4
|
from rich.table import Table
|
|
5
5
|
from rich.text import Text
|
|
6
6
|
from rich.panel import Panel
|
|
7
|
+
from rich.progress import (
|
|
8
|
+
Progress,
|
|
9
|
+
SpinnerColumn,
|
|
10
|
+
TextColumn,
|
|
11
|
+
BarColumn,
|
|
12
|
+
TimeRemainingColumn,
|
|
13
|
+
MofNCompleteColumn,
|
|
14
|
+
)
|
|
7
15
|
|
|
8
|
-
from ..reporting_utils import (
|
|
9
|
-
display_configuration,
|
|
10
|
-
display_header,
|
|
11
|
-
display_result,
|
|
16
|
+
from ..reporting_utils import ( # noqa: F401
|
|
17
|
+
display_configuration,
|
|
18
|
+
display_header,
|
|
19
|
+
display_result,
|
|
12
20
|
get_console,
|
|
13
21
|
convert_tqdm_to_rich,
|
|
14
22
|
suppress_opik_logs,
|
|
@@ -18,16 +26,38 @@ console = get_console()
|
|
|
18
26
|
|
|
19
27
|
|
|
20
28
|
class RichGEPAOptimizerLogger:
|
|
21
|
-
"""Adapter for GEPA's logger that provides concise Rich output."""
|
|
29
|
+
"""Adapter for GEPA's logger that provides concise Rich output with progress tracking."""
|
|
22
30
|
|
|
23
31
|
SUPPRESS_PREFIXES = (
|
|
24
32
|
"Linear pareto front program index",
|
|
25
33
|
"New program candidate index",
|
|
26
34
|
)
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
# Additional messages to suppress (too technical for users)
|
|
37
|
+
SUPPRESS_KEYWORDS = (
|
|
38
|
+
"Individual valset scores for new program",
|
|
39
|
+
"New valset pareto front scores",
|
|
40
|
+
"Updated valset pareto front programs",
|
|
41
|
+
"Best program as per aggregate score on train_val",
|
|
42
|
+
"Best program as per aggregate score on valset",
|
|
43
|
+
"New program is on the linear pareto front",
|
|
44
|
+
"Full train_val score for new program",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
optimizer: Any,
|
|
50
|
+
verbose: int = 1,
|
|
51
|
+
progress: Progress | None = None,
|
|
52
|
+
task_id: Any | None = None,
|
|
53
|
+
max_trials: int = 10,
|
|
54
|
+
) -> None:
|
|
29
55
|
self.optimizer = optimizer
|
|
30
56
|
self.verbose = verbose
|
|
57
|
+
self.progress = progress
|
|
58
|
+
self.task_id = task_id
|
|
59
|
+
self.max_trials = max_trials
|
|
60
|
+
self.current_iteration = 0
|
|
31
61
|
|
|
32
62
|
def log(self, message: str) -> None:
|
|
33
63
|
if self.verbose < 1:
|
|
@@ -43,30 +73,117 @@ class RichGEPAOptimizerLogger:
|
|
|
43
73
|
|
|
44
74
|
first = lines[0]
|
|
45
75
|
|
|
76
|
+
# Track iteration changes and add separation
|
|
46
77
|
if first.startswith("Iteration "):
|
|
47
78
|
colon = first.find(":")
|
|
48
79
|
head = first[:colon] if colon != -1 else first
|
|
49
80
|
parts = head.split()
|
|
50
81
|
if len(parts) >= 2 and parts[1].isdigit():
|
|
51
82
|
try:
|
|
52
|
-
|
|
83
|
+
iteration = int(parts[1])
|
|
84
|
+
|
|
85
|
+
# Add separator when starting a new iteration (except iteration 0)
|
|
86
|
+
if iteration > 0 and iteration != self.current_iteration:
|
|
87
|
+
console.print("│")
|
|
88
|
+
|
|
89
|
+
self.optimizer._gepa_current_iteration = iteration # type: ignore[attr-defined]
|
|
90
|
+
self.current_iteration = iteration
|
|
91
|
+
|
|
92
|
+
# Update progress bar
|
|
93
|
+
if self.progress and self.task_id is not None:
|
|
94
|
+
self.progress.update(self.task_id, completed=iteration)
|
|
95
|
+
|
|
96
|
+
# Add explanatory text for iteration start
|
|
97
|
+
if "Base program full valset score" in first:
|
|
98
|
+
# Extract score
|
|
99
|
+
score_match = first.split(":")[-1].strip()
|
|
100
|
+
console.print(
|
|
101
|
+
f"│ Baseline evaluation: {score_match}", style="bold"
|
|
102
|
+
)
|
|
103
|
+
return
|
|
104
|
+
elif "Selected program" in first:
|
|
105
|
+
# Extract program number and score
|
|
106
|
+
parts_info = first.split(":")
|
|
107
|
+
if "Selected program" in parts_info[1]:
|
|
108
|
+
program_info = parts_info[1].strip()
|
|
109
|
+
score_info = (
|
|
110
|
+
parts_info[2].strip() if len(parts_info) > 2 else ""
|
|
111
|
+
)
|
|
112
|
+
console.print(
|
|
113
|
+
f"│ Trial {iteration}: {program_info}, score: {score_info}",
|
|
114
|
+
style="bold cyan",
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
console.print(f"│ Trial {iteration}", style="bold cyan")
|
|
118
|
+
console.print("│ ├─ Testing new prompt variant...")
|
|
119
|
+
return
|
|
53
120
|
except Exception:
|
|
54
121
|
pass
|
|
55
122
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
snippet = snippet[:120] + "…"
|
|
61
|
-
first = "Proposed new text · system_prompt: " + snippet
|
|
62
|
-
elif len(first) > 160:
|
|
63
|
-
first = first[:160] + "…"
|
|
123
|
+
# Check if this message should be suppressed
|
|
124
|
+
for keyword in self.SUPPRESS_KEYWORDS:
|
|
125
|
+
if keyword in first:
|
|
126
|
+
return
|
|
64
127
|
|
|
65
128
|
for prefix in self.SUPPRESS_PREFIXES:
|
|
66
129
|
if prefix in first:
|
|
67
130
|
return
|
|
68
131
|
|
|
69
|
-
|
|
132
|
+
# Format proposed prompts
|
|
133
|
+
if "Proposed new text" in first and "system_prompt:" in first:
|
|
134
|
+
_, _, rest = first.partition("system_prompt:")
|
|
135
|
+
snippet = rest.strip()
|
|
136
|
+
if len(snippet) > 100:
|
|
137
|
+
snippet = snippet[:100] + "…"
|
|
138
|
+
console.print(f"│ │ Proposed: {snippet}", style="dim")
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
# Format subsample evaluation results
|
|
142
|
+
if "New subsample score" in first and "is not better than" in first:
|
|
143
|
+
console.print("│ └─ Rejected - no improvement", style="dim yellow")
|
|
144
|
+
console.print("│") # Add spacing after rejected trials
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
if "New subsample score" in first and "is better than" in first:
|
|
148
|
+
console.print("│ ├─ Promising! Running full validation...", style="green")
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
# Format final validation score
|
|
152
|
+
if "Full valset score for new program" in first:
|
|
153
|
+
# Extract score
|
|
154
|
+
parts = first.split(":")
|
|
155
|
+
if len(parts) >= 2:
|
|
156
|
+
score = parts[-1].strip()
|
|
157
|
+
console.print(f"│ ├─ Validation complete: {score}", style="bold green")
|
|
158
|
+
else:
|
|
159
|
+
console.print("│ ├─ Validation complete", style="green")
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
# Format best score updates
|
|
163
|
+
if (
|
|
164
|
+
"Best valset aggregate score so far" in first
|
|
165
|
+
or "Best score on valset" in first
|
|
166
|
+
):
|
|
167
|
+
# Extract score
|
|
168
|
+
parts = first.split(":")
|
|
169
|
+
if len(parts) >= 2:
|
|
170
|
+
score = parts[-1].strip()
|
|
171
|
+
console.print(f"│ └─ New best: {score} ✓", style="bold green")
|
|
172
|
+
console.print("│") # Add spacing after successful trials
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
# Suppress redundant "Iteration X:" prefix from detailed messages
|
|
176
|
+
if first.startswith(f"Iteration {self.current_iteration}:"):
|
|
177
|
+
# Remove the iteration prefix for cleaner output
|
|
178
|
+
first = first.split(":", 1)[1].strip() if ":" in first else first
|
|
179
|
+
|
|
180
|
+
# Truncate very long messages
|
|
181
|
+
if len(first) > 160:
|
|
182
|
+
first = first[:160] + "…"
|
|
183
|
+
|
|
184
|
+
# Default: print with standard prefix only if not already handled
|
|
185
|
+
if first:
|
|
186
|
+
console.print(f"│ {first}", style="dim")
|
|
70
187
|
|
|
71
188
|
|
|
72
189
|
@contextmanager
|
|
@@ -85,20 +202,45 @@ def baseline_evaluation(verbose: int = 1) -> Any:
|
|
|
85
202
|
|
|
86
203
|
|
|
87
204
|
@contextmanager
|
|
88
|
-
def start_gepa_optimization(verbose: int = 1) -> Any:
|
|
205
|
+
def start_gepa_optimization(verbose: int = 1, max_trials: int = 10) -> Any:
|
|
89
206
|
if verbose >= 1:
|
|
90
207
|
console.print("> Starting GEPA optimization")
|
|
91
208
|
|
|
92
209
|
class Reporter:
|
|
210
|
+
progress: Progress | None = None
|
|
211
|
+
task_id: Any | None = None
|
|
212
|
+
|
|
93
213
|
def info(self, message: str) -> None:
|
|
94
214
|
if verbose >= 1:
|
|
95
215
|
console.print(f"│ {message}")
|
|
96
216
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
217
|
+
with suppress_opik_logs():
|
|
218
|
+
try:
|
|
219
|
+
# Create Rich progress bar
|
|
220
|
+
if verbose >= 1:
|
|
221
|
+
Reporter.progress = Progress(
|
|
222
|
+
SpinnerColumn(),
|
|
223
|
+
TextColumn("[bold blue]{task.description}"),
|
|
224
|
+
BarColumn(),
|
|
225
|
+
MofNCompleteColumn(),
|
|
226
|
+
TextColumn("•"),
|
|
227
|
+
TimeRemainingColumn(),
|
|
228
|
+
console=console,
|
|
229
|
+
transient=True, # Make progress bar disappear when done
|
|
230
|
+
)
|
|
231
|
+
Reporter.progress.start()
|
|
232
|
+
Reporter.task_id = Reporter.progress.add_task(
|
|
233
|
+
"GEPA Optimization", total=max_trials
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
yield Reporter()
|
|
237
|
+
finally:
|
|
238
|
+
if verbose >= 1:
|
|
239
|
+
if Reporter.progress and Reporter.task_id is not None:
|
|
240
|
+
# Mark as complete before stopping
|
|
241
|
+
Reporter.progress.update(Reporter.task_id, completed=max_trials)
|
|
242
|
+
Reporter.progress.stop()
|
|
243
|
+
console.print("")
|
|
102
244
|
|
|
103
245
|
|
|
104
246
|
def display_candidate_scores(
|