opik-optimizer 2.0.1__py3-none-any.whl → 2.1.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.
@@ -0,0 +1,49 @@
1
+ """Type definitions for the Reflective Optimizer."""
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class FailureMode(BaseModel):
7
+ """Model for a single failure mode identified in evaluation."""
8
+
9
+ name: str
10
+ description: str
11
+ root_cause: str
12
+
13
+
14
+ class RootCauseAnalysis(BaseModel):
15
+ """Model for root cause analysis response."""
16
+
17
+ failure_modes: list[FailureMode]
18
+
19
+
20
+ class BatchAnalysis(BaseModel):
21
+ """Model for a single batch analysis result."""
22
+
23
+ batch_number: int
24
+ start_index: int
25
+ end_index: int
26
+ failure_modes: list[FailureMode]
27
+
28
+
29
+ class HierarchicalRootCauseAnalysis(BaseModel):
30
+ """Model for the final hierarchical root cause analysis."""
31
+
32
+ total_test_cases: int
33
+ num_batches: int
34
+ unified_failure_modes: list[FailureMode]
35
+ synthesis_notes: str
36
+
37
+
38
+ class PromptMessage(BaseModel):
39
+ """Model for a single prompt message."""
40
+
41
+ role: str
42
+ content: str
43
+
44
+
45
+ class ImprovedPrompt(BaseModel):
46
+ """Model for improved prompt response."""
47
+
48
+ reasoning: str
49
+ messages: list[PromptMessage]
@@ -8,6 +8,13 @@ import rich
8
8
  from .reporting_utils import get_console, get_link_text, get_optimization_run_url_by_id
9
9
 
10
10
 
11
+ def _format_float(value: Any, digits: int = 6) -> str:
12
+ """Format float values with specified precision."""
13
+ if isinstance(value, float):
14
+ return f"{value:.{digits}f}"
15
+ return str(value)
16
+
17
+
11
18
  class OptimizationResult(pydantic.BaseModel):
12
19
  """Result oan optimization run."""
13
20
 
@@ -44,6 +51,50 @@ class OptimizationResult(pydantic.BaseModel):
44
51
  def model_dump(self, *kargs: Any, **kwargs: Any) -> dict[str, Any]:
45
52
  return super().model_dump(*kargs, **kwargs)
46
53
 
54
+ def get_optimized_model_kwargs(self) -> dict[str, Any]:
55
+ """
56
+ Extract optimized model_kwargs for use in other optimizers.
57
+
58
+ Returns:
59
+ Dictionary of optimized model kwargs, empty dict if not available
60
+ """
61
+ return self.details.get("optimized_model_kwargs", {})
62
+
63
+ def get_optimized_model(self) -> str | None:
64
+ """
65
+ Extract optimized model name.
66
+
67
+ Returns:
68
+ Model name string if available, None otherwise
69
+ """
70
+ return self.details.get("optimized_model")
71
+
72
+ def get_optimized_parameters(self) -> dict[str, Any]:
73
+ """
74
+ Extract optimized parameter values.
75
+
76
+ Returns:
77
+ Dictionary of optimized parameters, empty dict if not available
78
+ """
79
+ return self.details.get("optimized_parameters", {})
80
+
81
+ def apply_to_prompt(self, prompt: Any) -> Any:
82
+ """
83
+ Apply optimized parameters to a prompt.
84
+
85
+ Args:
86
+ prompt: ChatPrompt instance to apply optimizations to
87
+
88
+ Returns:
89
+ New ChatPrompt instance with optimized parameters applied
90
+ """
91
+ prompt_copy = prompt.copy()
92
+ if "optimized_model_kwargs" in self.details:
93
+ prompt_copy.model_kwargs = self.details["optimized_model_kwargs"]
94
+ if "optimized_model" in self.details:
95
+ prompt_copy.model = self.details["optimized_model"]
96
+ return prompt_copy
97
+
47
98
  def _calculate_improvement_str(self) -> str:
48
99
  """Helper to calculate improvement percentage string."""
49
100
  initial_s = self.initial_score
@@ -113,12 +164,97 @@ class OptimizationResult(pydantic.BaseModel):
113
164
  f"Final Best Score: {final_score_str}",
114
165
  f"Total Improvement:{improvement_str.rjust(max(0, 18 - len('Total Improvement:')))}",
115
166
  f"Rounds Completed: {rounds_ran}",
116
- "\nFINAL OPTIMIZED PROMPT / STRUCTURE:",
117
- "--------------------------------------------------------------------------------",
118
- f"{final_prompt_display}",
119
- "--------------------------------------------------------------------------------",
120
- f"{separator}",
121
167
  ]
168
+
169
+ optimized_params = self.details.get("optimized_parameters") or {}
170
+ parameter_importance = self.details.get("parameter_importance") or {}
171
+ search_ranges = self.details.get("search_ranges") or {}
172
+ precision = self.details.get("parameter_precision", 6)
173
+
174
+ if optimized_params:
175
+
176
+ def _format_range(desc: dict[str, Any]) -> str:
177
+ if "min" in desc and "max" in desc:
178
+ step_str = (
179
+ f", step={_format_float(desc['step'], precision)}"
180
+ if desc.get("step") is not None
181
+ else ""
182
+ )
183
+ return f"[{_format_float(desc['min'], precision)}, {_format_float(desc['max'], precision)}{step_str}]"
184
+ if desc.get("choices"):
185
+ return f"choices={desc['choices']}"
186
+ return str(desc)
187
+
188
+ rows = []
189
+ stage_order = [
190
+ record.get("stage")
191
+ for record in self.details.get("search_stages", [])
192
+ if record.get("stage") in search_ranges
193
+ ]
194
+ if not stage_order:
195
+ stage_order = sorted(search_ranges)
196
+
197
+ for name in sorted(optimized_params):
198
+ contribution = parameter_importance.get(name)
199
+ stage_ranges = []
200
+ for stage in stage_order:
201
+ params = search_ranges.get(stage) or {}
202
+ if name in params:
203
+ stage_ranges.append(f"{stage}: {_format_range(params[name])}")
204
+ if not stage_ranges:
205
+ for stage, params in search_ranges.items():
206
+ if name in params:
207
+ stage_ranges.append(
208
+ f"{stage}: {_format_range(params[name])}"
209
+ )
210
+ joined_ranges = "\n".join(stage_ranges) if stage_ranges else "N/A"
211
+ rows.append(
212
+ {
213
+ "parameter": name,
214
+ "value": optimized_params[name],
215
+ "contribution": contribution,
216
+ "ranges": joined_ranges,
217
+ }
218
+ )
219
+
220
+ if rows:
221
+ output.append("Parameter Summary:")
222
+ # Compute overall improvement fraction for gain calculation
223
+ total_improvement = None
224
+ if isinstance(self.initial_score, (int, float)) and isinstance(
225
+ self.score, (int, float)
226
+ ):
227
+ if self.initial_score != 0:
228
+ total_improvement = (self.score - self.initial_score) / abs(
229
+ self.initial_score
230
+ )
231
+ else:
232
+ total_improvement = self.score
233
+ for row in rows:
234
+ value_str = _format_float(row["value"], precision)
235
+ contrib_val = row["contribution"]
236
+ if contrib_val is not None:
237
+ contrib_percent = contrib_val * 100
238
+ gain_str = ""
239
+ if total_improvement is not None:
240
+ gain_value = contrib_val * total_improvement * 100
241
+ gain_str = f" ({gain_value:+.2f}%)"
242
+ contrib_str = f"{contrib_percent:.1f}%{gain_str}"
243
+ else:
244
+ contrib_str = "N/A"
245
+ output.append(
246
+ f"- {row['parameter']}: value={value_str}, contribution={contrib_str}, ranges=\n {row['ranges']}"
247
+ )
248
+
249
+ output.extend(
250
+ [
251
+ "\nFINAL OPTIMIZED PROMPT / STRUCTURE:",
252
+ "--------------------------------------------------------------------------------",
253
+ f"{final_prompt_display}",
254
+ "--------------------------------------------------------------------------------",
255
+ f"{separator}",
256
+ ]
257
+ )
122
258
  return "\n".join(output)
123
259
 
124
260
  def __rich__(self) -> rich.panel.Panel:
@@ -159,6 +295,11 @@ class OptimizationResult(pydantic.BaseModel):
159
295
  ),
160
296
  )
161
297
 
298
+ optimized_params = self.details.get("optimized_parameters") or {}
299
+ parameter_importance = self.details.get("parameter_importance") or {}
300
+ search_ranges = self.details.get("search_ranges") or {}
301
+ precision = self.details.get("parameter_precision", 6)
302
+
162
303
  # Display Chat Structure if available
163
304
  panel_title = "[bold]Final Optimized Prompt[/bold]"
164
305
  try:
@@ -190,7 +331,87 @@ class OptimizationResult(pydantic.BaseModel):
190
331
  prompt_renderable, title=panel_title, border_style="blue", padding=(1, 2)
191
332
  )
192
333
 
193
- content_group = rich.console.Group(table, "\n", prompt_panel)
334
+ renderables: list[rich.console.RenderableType] = [table, "\n"]
335
+
336
+ if optimized_params:
337
+ summary_table = rich.table.Table(
338
+ title="Parameter Summary", show_header=True, title_style="bold"
339
+ )
340
+ summary_table.add_column("Parameter", justify="left", style="cyan")
341
+ summary_table.add_column("Value", justify="left")
342
+ summary_table.add_column("Importance", justify="left", style="magenta")
343
+ summary_table.add_column("Gain", justify="left", style="dim")
344
+ summary_table.add_column("Ranges", justify="left")
345
+
346
+ stage_order = [
347
+ record.get("stage")
348
+ for record in self.details.get("search_stages", [])
349
+ if record.get("stage") in search_ranges
350
+ ]
351
+ if not stage_order:
352
+ stage_order = sorted(search_ranges)
353
+
354
+ def _format_range(desc: dict[str, Any]) -> str:
355
+ if "min" in desc and "max" in desc:
356
+ step_str = (
357
+ f", step={_format_float(desc['step'], precision)}"
358
+ if desc.get("step") is not None
359
+ else ""
360
+ )
361
+ return f"[{_format_float(desc['min'], precision)}, {_format_float(desc['max'], precision)}{step_str}]"
362
+ if desc.get("choices"):
363
+ return ",".join(map(str, desc["choices"]))
364
+ return str(desc)
365
+
366
+ total_improvement = None
367
+ if isinstance(self.initial_score, (int, float)) and isinstance(
368
+ self.score, (int, float)
369
+ ):
370
+ if self.initial_score != 0:
371
+ total_improvement = (self.score - self.initial_score) / abs(
372
+ self.initial_score
373
+ )
374
+ else:
375
+ total_improvement = self.score
376
+
377
+ for name in sorted(optimized_params):
378
+ value_str = _format_float(optimized_params[name], precision)
379
+ contrib_val = parameter_importance.get(name)
380
+ if contrib_val is not None:
381
+ contrib_str = f"{contrib_val:.1%}"
382
+ gain_str = (
383
+ f"{contrib_val * total_improvement:+.2%}"
384
+ if total_improvement is not None
385
+ else "N/A"
386
+ )
387
+ else:
388
+ contrib_str = "N/A"
389
+ gain_str = "N/A"
390
+ ranges_parts = []
391
+ for stage in stage_order:
392
+ params = search_ranges.get(stage) or {}
393
+ if name in params:
394
+ ranges_parts.append(f"{stage}: {_format_range(params[name])}")
395
+ if not ranges_parts:
396
+ for stage, params in search_ranges.items():
397
+ if name in params:
398
+ ranges_parts.append(
399
+ f"{stage}: {_format_range(params[name])}"
400
+ )
401
+
402
+ summary_table.add_row(
403
+ name,
404
+ value_str,
405
+ contrib_str,
406
+ gain_str,
407
+ "\n".join(ranges_parts) if ranges_parts else "N/A",
408
+ )
409
+
410
+ renderables.extend([summary_table, "\n"])
411
+
412
+ renderables.append(prompt_panel)
413
+
414
+ content_group = rich.console.Group(*renderables)
194
415
 
195
416
  return rich.panel.Panel(
196
417
  content_group,
@@ -0,0 +1,11 @@
1
+ from .parameter_optimizer import ParameterOptimizer
2
+ from .parameter_search_space import ParameterSearchSpace
3
+ from .parameter_spec import ParameterSpec
4
+ from .search_space_types import ParameterType
5
+
6
+ __all__ = [
7
+ "ParameterOptimizer",
8
+ "ParameterSearchSpace",
9
+ "ParameterSpec",
10
+ "ParameterType",
11
+ ]