opik-optimizer 2.0.0__tar.gz → 2.1.0__tar.gz
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-2.0.0 → opik_optimizer-2.1.0}/PKG-INFO +4 -2
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/README.md +1 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/pyproject.toml +3 -2
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/__init__.py +10 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/base_optimizer.py +33 -0
- opik_optimizer-2.1.0/src/opik_optimizer/optimization_result.py +437 -0
- opik_optimizer-2.1.0/src/opik_optimizer/parameter_optimizer/__init__.py +11 -0
- opik_optimizer-2.1.0/src/opik_optimizer/parameter_optimizer/parameter_optimizer.py +382 -0
- opik_optimizer-2.1.0/src/opik_optimizer/parameter_optimizer/parameter_search_space.py +125 -0
- opik_optimizer-2.1.0/src/opik_optimizer/parameter_optimizer/parameter_spec.py +214 -0
- opik_optimizer-2.1.0/src/opik_optimizer/parameter_optimizer/search_space_types.py +24 -0
- opik_optimizer-2.1.0/src/opik_optimizer/parameter_optimizer/sensitivity_analysis.py +71 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer.egg-info/PKG-INFO +4 -2
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer.egg-info/SOURCES.txt +6 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer.egg-info/requires.txt +2 -1
- opik_optimizer-2.0.0/src/opik_optimizer/optimization_result.py +0 -216
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/LICENSE +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/setup.cfg +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/setup.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/_throttle.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/cache_config.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/data/context7_eval.jsonl +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/data/hotpot-500.json +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/datasets/__init__.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/datasets/ai2_arc.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/datasets/cnn_dailymail.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/datasets/context7_eval.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/datasets/election_questions.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/datasets/gsm8k.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/datasets/halu_eval.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/datasets/hotpot_qa.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/datasets/medhallu.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/datasets/rag_hallucinations.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/datasets/ragbench.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/datasets/tiny_test.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/datasets/truthful_qa.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/demo/__init__.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/demo/cache.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/demo/datasets.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/evolutionary_optimizer/__init__.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/evolutionary_optimizer/crossover_ops.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/evolutionary_optimizer/evaluation_ops.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/evolutionary_optimizer/evolutionary_optimizer.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/evolutionary_optimizer/helpers.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/evolutionary_optimizer/llm_support.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/evolutionary_optimizer/mcp.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/evolutionary_optimizer/mutation_ops.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/evolutionary_optimizer/population_ops.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/evolutionary_optimizer/prompts.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/evolutionary_optimizer/reporting.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/evolutionary_optimizer/style_ops.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/few_shot_bayesian_optimizer/__init__.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/few_shot_bayesian_optimizer/few_shot_bayesian_optimizer.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/few_shot_bayesian_optimizer/reporting.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/gepa_optimizer/__init__.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/gepa_optimizer/adapter.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/gepa_optimizer/gepa_optimizer.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/gepa_optimizer/reporting.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/logging_config.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/mcp_utils/__init__.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/mcp_utils/mcp.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/mcp_utils/mcp_second_pass.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/mcp_utils/mcp_simulator.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/mcp_utils/mcp_workflow.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/meta_prompt_optimizer/__init__.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/meta_prompt_optimizer/meta_prompt_optimizer.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/meta_prompt_optimizer/reporting.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/mipro_optimizer/__init__.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/mipro_optimizer/_lm.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/mipro_optimizer/_mipro_optimizer_v2.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/mipro_optimizer/mipro_optimizer.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/mipro_optimizer/utils.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/optimizable_agent.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/optimization_config/__init__.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/optimization_config/chat_prompt.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/optimization_config/configs.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/optimization_config/mappers.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/py.typed +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/reporting_utils.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/task_evaluator.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/utils/__init__.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/utils/colbert.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/utils/core.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/utils/dataset_utils.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer/utils/prompt_segments.py +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer.egg-info/dependency_links.txt +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/src/opik_optimizer.egg-info/top_level.txt +0 -0
- {opik_optimizer-2.0.0 → opik_optimizer-2.1.0}/tests/test_setup.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: opik_optimizer
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.1.0
|
4
4
|
Summary: Agent optimization with Opik
|
5
5
|
Home-page: https://github.com/comet-ml/opik
|
6
6
|
Author: Comet ML
|
@@ -15,6 +15,7 @@ Requires-Dist: datasets
|
|
15
15
|
Requires-Dist: deap>=1.4.3
|
16
16
|
Requires-Dist: diskcache
|
17
17
|
Requires-Dist: dspy<3
|
18
|
+
Requires-Dist: gepa>=0.0.7
|
18
19
|
Requires-Dist: ujson
|
19
20
|
Requires-Dist: hf_xet
|
20
21
|
Requires-Dist: litellm
|
@@ -30,8 +31,8 @@ Provides-Extra: dev
|
|
30
31
|
Requires-Dist: pytest; extra == "dev"
|
31
32
|
Requires-Dist: pytest-cov; extra == "dev"
|
32
33
|
Requires-Dist: langgraph; extra == "dev"
|
33
|
-
Requires-Dist: gepa>=0.0.7; extra == "dev"
|
34
34
|
Requires-Dist: pre-commit; extra == "dev"
|
35
|
+
Requires-Dist: scikit-learn; extra == "dev"
|
35
36
|
Dynamic: author
|
36
37
|
Dynamic: home-page
|
37
38
|
Dynamic: license-file
|
@@ -51,6 +52,7 @@ The Opik Agent Optimizer refines your prompts to achieve better performance from
|
|
51
52
|
* **MetaPromptOptimizer** - Employs meta-prompting techniques for optimization
|
52
53
|
* **MiproOptimizer** - Implements MIPRO (Multi-Input Prompt Optimization) algorithm
|
53
54
|
* **GepaOptimizer** - Leverages GEPA (Genetic-Pareto) optimization approach
|
55
|
+
* **ParameterOptimizer** - Optimizes LLM call parameters (temperature, top_p, etc.) using Bayesian optimization
|
54
56
|
|
55
57
|
## 🎯 Key Features
|
56
58
|
|
@@ -12,6 +12,7 @@ The Opik Agent Optimizer refines your prompts to achieve better performance from
|
|
12
12
|
* **MetaPromptOptimizer** - Employs meta-prompting techniques for optimization
|
13
13
|
* **MiproOptimizer** - Implements MIPRO (Multi-Input Prompt Optimization) algorithm
|
14
14
|
* **GepaOptimizer** - Leverages GEPA (Genetic-Pareto) optimization approach
|
15
|
+
* **ParameterOptimizer** - Optimizes LLM call parameters (temperature, top_p, etc.) using Bayesian optimization
|
15
16
|
|
16
17
|
## 🎯 Key Features
|
17
18
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "opik_optimizer"
|
3
|
-
version = "2.
|
3
|
+
version = "2.1.0"
|
4
4
|
description = "Agent optimization with Opik"
|
5
5
|
authors = [
|
6
6
|
{name = "Comet ML", email = "support@comet.com"}
|
@@ -13,6 +13,7 @@ dependencies = [
|
|
13
13
|
"deap>=1.4.3",
|
14
14
|
"diskcache",
|
15
15
|
"dspy<3",
|
16
|
+
"gepa>=0.0.7",
|
16
17
|
"ujson",
|
17
18
|
"hf_xet",
|
18
19
|
"litellm",
|
@@ -32,8 +33,8 @@ dev = [
|
|
32
33
|
"pytest-cov",
|
33
34
|
# "google-adk",
|
34
35
|
"langgraph",
|
35
|
-
"gepa>=0.0.7",
|
36
36
|
"pre-commit",
|
37
|
+
"scikit-learn",
|
37
38
|
]
|
38
39
|
|
39
40
|
[tool.setuptools.packages.find]
|
@@ -18,6 +18,12 @@ from .meta_prompt_optimizer import MetaPromptOptimizer
|
|
18
18
|
from .mipro_optimizer import MiproOptimizer
|
19
19
|
from .optimization_config.configs import TaskConfig
|
20
20
|
from .optimization_result import OptimizationResult
|
21
|
+
from .parameter_optimizer import (
|
22
|
+
ParameterOptimizer,
|
23
|
+
ParameterSearchSpace,
|
24
|
+
ParameterSpec,
|
25
|
+
ParameterType,
|
26
|
+
)
|
21
27
|
|
22
28
|
__version__ = importlib.metadata.version("opik_optimizer")
|
23
29
|
|
@@ -34,9 +40,13 @@ __all__ = [
|
|
34
40
|
"MetaPromptOptimizer",
|
35
41
|
"MiproOptimizer",
|
36
42
|
"EvolutionaryOptimizer",
|
43
|
+
"ParameterOptimizer",
|
37
44
|
"OptimizationResult",
|
38
45
|
"OptimizableAgent",
|
39
46
|
"setup_logging",
|
40
47
|
"datasets",
|
41
48
|
"TaskConfig",
|
49
|
+
"ParameterSearchSpace",
|
50
|
+
"ParameterSpec",
|
51
|
+
"ParameterType",
|
42
52
|
]
|
@@ -470,6 +470,39 @@ class BaseOptimizer(ABC):
|
|
470
470
|
f"{self.__class__.__name__} does not implement optimize_mcp yet."
|
471
471
|
)
|
472
472
|
|
473
|
+
def optimize_parameter(
|
474
|
+
self,
|
475
|
+
prompt: "chat_prompt.ChatPrompt",
|
476
|
+
dataset: Dataset,
|
477
|
+
metric: Callable,
|
478
|
+
parameter_space: Any,
|
479
|
+
experiment_config: dict | None = None,
|
480
|
+
n_trials: int | None = None,
|
481
|
+
n_samples: int | None = None,
|
482
|
+
agent_class: type[OptimizableAgent] | None = None,
|
483
|
+
**kwargs: Any,
|
484
|
+
) -> optimization_result.OptimizationResult:
|
485
|
+
"""
|
486
|
+
Optimize LLM call parameters such as temperature or top_k.
|
487
|
+
|
488
|
+
Args:
|
489
|
+
prompt: The chat prompt to evaluate with tuned parameters
|
490
|
+
dataset: Dataset providing evaluation examples
|
491
|
+
metric: Objective function to maximize
|
492
|
+
parameter_space: Definition of the search space for tunable parameters
|
493
|
+
experiment_config: Optional experiment metadata
|
494
|
+
n_trials: Number of trials to run (optimizer specific default if None)
|
495
|
+
n_samples: Number of dataset samples to evaluate per trial (None for all)
|
496
|
+
agent_class: Optional custom agent class to execute evaluations
|
497
|
+
**kwargs: Additional optimizer specific settings
|
498
|
+
|
499
|
+
Returns:
|
500
|
+
OptimizationResult: Structured result describing the best parameters found
|
501
|
+
"""
|
502
|
+
raise NotImplementedError(
|
503
|
+
f"{self.__class__.__name__} does not implement optimize_parameter yet."
|
504
|
+
)
|
505
|
+
|
473
506
|
def get_history(self) -> list[OptimizationRound]:
|
474
507
|
"""
|
475
508
|
Get the optimization history.
|
@@ -0,0 +1,437 @@
|
|
1
|
+
"""Module containing the OptimizationResult class."""
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
import pydantic
|
6
|
+
import rich
|
7
|
+
|
8
|
+
from .reporting_utils import get_console, get_link_text, get_optimization_run_url_by_id
|
9
|
+
|
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
|
+
|
18
|
+
class OptimizationResult(pydantic.BaseModel):
|
19
|
+
"""Result oan optimization run."""
|
20
|
+
|
21
|
+
optimizer: str = "Optimizer"
|
22
|
+
|
23
|
+
prompt: list[dict[str, str]]
|
24
|
+
score: float
|
25
|
+
metric_name: str
|
26
|
+
|
27
|
+
optimization_id: str | None = None
|
28
|
+
dataset_id: str | None = None
|
29
|
+
|
30
|
+
# Initial score
|
31
|
+
initial_prompt: list[dict[str, str]] | None = None
|
32
|
+
initial_score: float | None = None
|
33
|
+
|
34
|
+
details: dict[str, Any] = pydantic.Field(default_factory=dict)
|
35
|
+
history: list[dict[str, Any]] = []
|
36
|
+
llm_calls: int | None = None
|
37
|
+
tool_calls: int | None = None
|
38
|
+
|
39
|
+
# MIPRO specific
|
40
|
+
demonstrations: list[dict[str, Any]] | None = None
|
41
|
+
mipro_prompt: str | None = None
|
42
|
+
tool_prompts: dict[str, str] | None = None
|
43
|
+
|
44
|
+
model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
|
45
|
+
|
46
|
+
def get_run_link(self) -> str:
|
47
|
+
return get_optimization_run_url_by_id(
|
48
|
+
optimization_id=self.optimization_id, dataset_id=self.dataset_id
|
49
|
+
)
|
50
|
+
|
51
|
+
def model_dump(self, *kargs: Any, **kwargs: Any) -> dict[str, Any]:
|
52
|
+
return super().model_dump(*kargs, **kwargs)
|
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
|
+
|
98
|
+
def _calculate_improvement_str(self) -> str:
|
99
|
+
"""Helper to calculate improvement percentage string."""
|
100
|
+
initial_s = self.initial_score
|
101
|
+
final_s = self.score
|
102
|
+
|
103
|
+
# Check if initial score exists and is a number
|
104
|
+
if not isinstance(initial_s, (int, float)):
|
105
|
+
return "[dim]N/A (no initial score)[/dim]"
|
106
|
+
|
107
|
+
# Proceed with calculation only if initial_s is valid
|
108
|
+
if initial_s != 0:
|
109
|
+
improvement_pct = (final_s - initial_s) / abs(initial_s)
|
110
|
+
# Basic coloring for rich, plain for str
|
111
|
+
color_start = ""
|
112
|
+
color_end = ""
|
113
|
+
if improvement_pct > 0:
|
114
|
+
color_start, color_end = "[bold green]", "[/bold green]"
|
115
|
+
elif improvement_pct < 0:
|
116
|
+
color_start, color_end = "[bold red]", "[/bold red]"
|
117
|
+
return f"{color_start}{improvement_pct:.2%}{color_end}"
|
118
|
+
elif final_s > 0:
|
119
|
+
return "[bold green]infinite[/bold green] (initial score was 0)"
|
120
|
+
else:
|
121
|
+
return "0.00% (no improvement from 0)"
|
122
|
+
|
123
|
+
def __str__(self) -> str:
|
124
|
+
"""Provides a clean, well-formatted plain-text summary."""
|
125
|
+
separator = "=" * 80
|
126
|
+
rounds_ran = len(self.details.get("rounds", []))
|
127
|
+
initial_score = self.initial_score
|
128
|
+
initial_score_str = (
|
129
|
+
f"{initial_score:.4f}" if isinstance(initial_score, (int, float)) else "N/A"
|
130
|
+
)
|
131
|
+
final_score_str = f"{self.score:.4f}"
|
132
|
+
improvement_str = (
|
133
|
+
self._calculate_improvement_str()
|
134
|
+
.replace("[bold green]", "")
|
135
|
+
.replace("[/bold green]", "")
|
136
|
+
.replace("[bold red]", "")
|
137
|
+
.replace("[/bold red]", "")
|
138
|
+
.replace("[dim]", "")
|
139
|
+
.replace("[/dim]", "")
|
140
|
+
)
|
141
|
+
|
142
|
+
model_name = self.details.get("model", "N/A")
|
143
|
+
temp = self.details.get("temperature")
|
144
|
+
temp_str = f"{temp:.1f}" if isinstance(temp, (int, float)) else "N/A"
|
145
|
+
|
146
|
+
try:
|
147
|
+
final_prompt_display = "\n".join(
|
148
|
+
[
|
149
|
+
f" {msg.get('role', 'unknown')}: {str(msg.get('content', ''))[:150]}..."
|
150
|
+
for msg in self.prompt
|
151
|
+
]
|
152
|
+
)
|
153
|
+
except Exception:
|
154
|
+
final_prompt_display = str(self.prompt)
|
155
|
+
|
156
|
+
output = [
|
157
|
+
f"\n{separator}",
|
158
|
+
"OPTIMIZATION COMPLETE",
|
159
|
+
f"{separator}",
|
160
|
+
f"Optimizer: {self.optimizer}",
|
161
|
+
f"Model Used: {model_name} (Temp: {temp_str})",
|
162
|
+
f"Metric Evaluated: {self.metric_name}",
|
163
|
+
f"Initial Score: {initial_score_str}",
|
164
|
+
f"Final Best Score: {final_score_str}",
|
165
|
+
f"Total Improvement:{improvement_str.rjust(max(0, 18 - len('Total Improvement:')))}",
|
166
|
+
f"Rounds Completed: {rounds_ran}",
|
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
|
+
)
|
258
|
+
return "\n".join(output)
|
259
|
+
|
260
|
+
def __rich__(self) -> rich.panel.Panel:
|
261
|
+
"""Provides a rich, formatted output for terminals supporting Rich."""
|
262
|
+
improvement_str = self._calculate_improvement_str()
|
263
|
+
rounds_ran = len(self.details.get("rounds", []))
|
264
|
+
initial_score = self.initial_score
|
265
|
+
initial_score_str = (
|
266
|
+
f"{initial_score:.4f}"
|
267
|
+
if isinstance(initial_score, (int, float))
|
268
|
+
else "[dim]N/A[/dim]"
|
269
|
+
)
|
270
|
+
final_score_str = f"{self.score:.4f}"
|
271
|
+
|
272
|
+
model_name = self.details.get("model", "[dim]N/A[/dim]")
|
273
|
+
|
274
|
+
table = rich.table.Table.grid(padding=(0, 1))
|
275
|
+
table.add_column(style="dim")
|
276
|
+
table.add_column()
|
277
|
+
|
278
|
+
table.add_row(
|
279
|
+
"Optimizer:",
|
280
|
+
f"[bold]{self.optimizer}[/bold]",
|
281
|
+
)
|
282
|
+
table.add_row("Model Used:", f"{model_name}")
|
283
|
+
table.add_row("Metric Evaluated:", f"[bold]{self.metric_name}[/bold]")
|
284
|
+
table.add_row("Initial Score:", initial_score_str)
|
285
|
+
table.add_row("Final Best Score:", f"[bold cyan]{final_score_str}[/bold cyan]")
|
286
|
+
table.add_row("Total Improvement:", improvement_str)
|
287
|
+
table.add_row("Rounds Completed:", str(rounds_ran))
|
288
|
+
table.add_row(
|
289
|
+
"Optimization run link:",
|
290
|
+
get_link_text(
|
291
|
+
pre_text="",
|
292
|
+
link_text="Open in Opik Dashboard",
|
293
|
+
dataset_id=self.dataset_id,
|
294
|
+
optimization_id=self.optimization_id,
|
295
|
+
),
|
296
|
+
)
|
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
|
+
|
303
|
+
# Display Chat Structure if available
|
304
|
+
panel_title = "[bold]Final Optimized Prompt[/bold]"
|
305
|
+
try:
|
306
|
+
chat_group_items = []
|
307
|
+
for msg in self.prompt:
|
308
|
+
role = msg.get("role", "unknown")
|
309
|
+
content = str(msg.get("content", ""))
|
310
|
+
role_style = (
|
311
|
+
"bold green"
|
312
|
+
if role == "user"
|
313
|
+
else (
|
314
|
+
"bold blue"
|
315
|
+
if role == "assistant"
|
316
|
+
else ("bold magenta" if role == "system" else "")
|
317
|
+
)
|
318
|
+
)
|
319
|
+
chat_group_items.append(
|
320
|
+
f"[{role_style}]{role.capitalize()}:[/] {content}"
|
321
|
+
)
|
322
|
+
chat_group_items.append("---") # Separator
|
323
|
+
prompt_renderable = rich.console.Group(*chat_group_items)
|
324
|
+
|
325
|
+
except Exception:
|
326
|
+
# Fallback to simple text prompt
|
327
|
+
prompt_renderable = rich.text.Text(str(self.prompt or ""), overflow="fold")
|
328
|
+
panel_title = "[bold]Final Optimized Prompt (Instruction - fallback)[/bold]"
|
329
|
+
|
330
|
+
prompt_panel = rich.panel.Panel(
|
331
|
+
prompt_renderable, title=panel_title, border_style="blue", padding=(1, 2)
|
332
|
+
)
|
333
|
+
|
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)
|
415
|
+
|
416
|
+
return rich.panel.Panel(
|
417
|
+
content_group,
|
418
|
+
title="[bold yellow]Optimization Complete[/bold yellow]",
|
419
|
+
border_style="yellow",
|
420
|
+
box=rich.box.DOUBLE_EDGE,
|
421
|
+
padding=1,
|
422
|
+
)
|
423
|
+
|
424
|
+
def display(self) -> None:
|
425
|
+
"""
|
426
|
+
Displays the OptimizationResult using rich formatting
|
427
|
+
"""
|
428
|
+
console = get_console()
|
429
|
+
console.print(self)
|
430
|
+
# Gracefully handle cases where optimization tracking isn't available
|
431
|
+
if self.dataset_id and self.optimization_id:
|
432
|
+
try:
|
433
|
+
print("Optimization run link:", self.get_run_link())
|
434
|
+
except Exception:
|
435
|
+
print("Optimization run link: No optimization run link available")
|
436
|
+
else:
|
437
|
+
print("Optimization run link: No optimization run link available")
|
@@ -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
|
+
]
|