opik-optimizer 0.7.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.
@@ -0,0 +1,211 @@
1
+ """Module containing the OptimizationResult class."""
2
+
3
+ from typing import Dict, List, Any, Optional, Union, Literal
4
+ import pydantic
5
+ from opik.evaluation.metrics import BaseMetric
6
+ from pydantic import BaseModel, Field
7
+ from .base_optimizer import OptimizationRound # Adjust import as necessary
8
+ import rich
9
+
10
+
11
+ class OptimizationResult(pydantic.BaseModel):
12
+ """Result of an optimization run."""
13
+
14
+ prompt: Union[str, List[Dict[Literal["role", "content"], str]]]
15
+ score: float
16
+ metric_name: str
17
+ metadata: Dict[str, Any] = pydantic.Field(
18
+ default_factory=dict
19
+ ) # Default empty dict
20
+ details: Dict[str, Any] = pydantic.Field(default_factory=dict) # Default empty dict
21
+ best_prompt: Optional[str] = None
22
+ best_score: Optional[float] = None
23
+ best_metric_name: Optional[str] = None
24
+ best_details: Optional[Dict[str, Any]] = None
25
+ all_results: Optional[List[Dict[str, Any]]] = None
26
+ history: List[Dict[str, Any]] = []
27
+ metric: Optional[BaseMetric] = None
28
+ demonstrations: Optional[List[Dict[str, Any]]] = None
29
+ optimizer: str = "Optimizer"
30
+ tool_prompts: Optional[Dict[str, str]] = None
31
+
32
+ model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
33
+
34
+ def _calculate_improvement_str(self) -> str:
35
+ """Helper to calculate improvement percentage string."""
36
+ initial_s = self.details.get("initial_score")
37
+ final_s = self.score
38
+
39
+ # Check if initial score exists and is a number
40
+ if not isinstance(initial_s, (int, float)):
41
+ return "[dim]N/A (no initial score)[/dim]"
42
+
43
+ # Proceed with calculation only if initial_s is valid
44
+ if initial_s != 0:
45
+ improvement_pct = (final_s - initial_s) / abs(initial_s)
46
+ # Basic coloring for rich, plain for str
47
+ color_start = ""
48
+ color_end = ""
49
+ if improvement_pct > 0:
50
+ color_start, color_end = "[bold green]", "[/bold green]"
51
+ elif improvement_pct < 0:
52
+ color_start, color_end = "[bold red]", "[/bold red]"
53
+ return f"{color_start}{improvement_pct:.2%}{color_end}"
54
+ elif final_s > 0:
55
+ return "[bold green]infinite[/bold green] (initial score was 0)"
56
+ else:
57
+ return "0.00% (no improvement from 0)"
58
+
59
+ def __str__(self) -> str:
60
+ """Provides a clean, well-formatted plain-text summary."""
61
+ separator = "=" * 80
62
+ rounds_ran = len(self.details.get("rounds", []))
63
+ initial_score = self.details.get("initial_score")
64
+ initial_score_str = (
65
+ f"{initial_score:.4f}" if isinstance(initial_score, (int, float)) else "N/A"
66
+ )
67
+ final_score_str = f"{self.score:.4f}"
68
+ improvement_str = (
69
+ self._calculate_improvement_str()
70
+ .replace("[bold green]", "")
71
+ .replace("[/bold green]", "")
72
+ .replace("[bold red]", "")
73
+ .replace("[/bold red]", "")
74
+ .replace("[dim]", "")
75
+ .replace("[/dim]", "")
76
+ )
77
+ stopped_early = self.details.get("stopped_early", "N/A")
78
+
79
+ model_name = self.details.get("model", "N/A")
80
+ temp = self.details.get("temperature")
81
+ temp_str = f"{temp:.1f}" if isinstance(temp, (int, float)) else "N/A"
82
+
83
+ final_prompt_display = self.prompt
84
+ if self.details.get("prompt_type") == "chat" and self.details.get(
85
+ "chat_messages"
86
+ ):
87
+ try:
88
+ chat_display = "\n".join(
89
+ [
90
+ f" {msg.get('role', 'unknown')}: {str(msg.get('content', ''))[:150]}..."
91
+ for msg in self.details["chat_messages"]
92
+ ]
93
+ )
94
+ final_prompt_display = f"Instruction:\n {self.prompt}\nFew-Shot Examples (Chat Structure):\n{chat_display}"
95
+ except Exception:
96
+ pass
97
+
98
+ output = [
99
+ f"\n{separator}",
100
+ f"OPTIMIZATION COMPLETE",
101
+ f"{separator}",
102
+ f"Optimizer: {self.details.get('optimizer', type(self).__name__)}",
103
+ f"Model Used: {model_name} (Temp: {temp_str})",
104
+ f"Metric Evaluated: {self.metric_name}",
105
+ f"Initial Score: {initial_score_str}",
106
+ f"Final Best Score: {final_score_str}",
107
+ f"Total Improvement:{improvement_str.rjust(max(0, 18 - len('Total Improvement:')))}",
108
+ f"Rounds Completed: {rounds_ran}",
109
+ f"Stopped Early: {stopped_early}",
110
+ f"\nFINAL OPTIMIZED PROMPT / STRUCTURE:",
111
+ f"--------------------------------------------------------------------------------",
112
+ f"{final_prompt_display}",
113
+ f"--------------------------------------------------------------------------------",
114
+ f"{separator}",
115
+ ]
116
+ return "\n".join(output)
117
+
118
+ def __rich__(self) -> rich.panel.Panel:
119
+ """Provides a rich, formatted output for terminals supporting Rich."""
120
+ improvement_str = self._calculate_improvement_str()
121
+ rounds_ran = len(self.details.get("rounds", []))
122
+ initial_score = self.details.get("initial_score")
123
+ initial_score_str = (
124
+ f"{initial_score:.4f}"
125
+ if isinstance(initial_score, (int, float))
126
+ else "[dim]N/A[/dim]"
127
+ )
128
+ final_score_str = f"{self.score:.4f}"
129
+ stopped_early = self.details.get("stopped_early", "N/A")
130
+
131
+ model_name = self.details.get("model", "[dim]N/A[/dim]")
132
+ temp = self.details.get("temperature")
133
+ temp_str = f"{temp:.1f}" if isinstance(temp, (int, float)) else "[dim]N/A[/dim]"
134
+
135
+ table = rich.table.Table.grid(padding=(0, 1))
136
+ table.add_column(style="dim")
137
+ table.add_column()
138
+
139
+ table.add_row(
140
+ "Optimizer:",
141
+ f"[bold]{self.details.get('optimizer', type(self).__name__)}[/bold]",
142
+ )
143
+ table.add_row("Model Used:", f"{model_name} ([dim]Temp:[/dim] {temp_str})")
144
+ table.add_row("Metric Evaluated:", f"[bold]{self.metric_name}[/bold]")
145
+ table.add_row("Initial Score:", initial_score_str)
146
+ table.add_row("Final Best Score:", f"[bold cyan]{final_score_str}[/bold cyan]")
147
+ table.add_row("Total Improvement:", improvement_str)
148
+ table.add_row("Rounds Completed:", str(rounds_ran))
149
+ table.add_row("Stopped Early:", str(stopped_early))
150
+
151
+ # Display Chat Structure if available
152
+ prompt_renderable: Any = rich.text.Text(
153
+ self.prompt or "", overflow="fold"
154
+ ) # Default to text
155
+ panel_title = "[bold]Final Optimized Prompt (Instruction)[/bold]"
156
+
157
+ if self.details.get("prompt_type") == "chat" and self.details.get(
158
+ "chat_messages"
159
+ ):
160
+ panel_title = "[bold]Final Optimized Prompt (Chat Structure)[/bold]"
161
+ try:
162
+ chat_group_items = [
163
+ f"[dim]Instruction:[/dim] [i]{self.prompt}[/i]\n---"
164
+ ]
165
+ for msg in self.details["chat_messages"]:
166
+ role = msg.get("role", "unknown")
167
+ content = str(msg.get("content", ""))
168
+ role_style = (
169
+ "bold green"
170
+ if role == "user"
171
+ else (
172
+ "bold blue"
173
+ if role == "assistant"
174
+ else ("bold magenta" if role == "system" else "")
175
+ )
176
+ )
177
+ chat_group_items.append(
178
+ f"[{role_style}]{role.capitalize()}:[/] {content}"
179
+ )
180
+ chat_group_items.append("---") # Separator
181
+ prompt_renderable = rich.console.Group(*chat_group_items)
182
+
183
+ except Exception:
184
+ # Fallback to simple text prompt
185
+ prompt_renderable = rich.text.Text(self.prompt or "", overflow="fold")
186
+ panel_title = (
187
+ "[bold]Final Optimized Prompt (Instruction - fallback)[/bold]"
188
+ )
189
+
190
+ prompt_panel = rich.panel.Panel(
191
+ prompt_renderable, title=panel_title, border_style="blue", padding=(1, 2)
192
+ )
193
+
194
+ content_group = rich.console.Group(table, "\n", prompt_panel)
195
+
196
+ return rich.panel.Panel(
197
+ content_group,
198
+ title="[bold yellow]Optimization Complete[/bold yellow]",
199
+ border_style="yellow",
200
+ box=rich.box.DOUBLE_EDGE,
201
+ padding=1,
202
+ )
203
+
204
+ def model_dump(self) -> Dict[str, Any]:
205
+ return super().model_dump()
206
+
207
+ def display(self) -> None:
208
+ """
209
+ Displays the OptimizationResult using rich formatting
210
+ """
211
+ rich.print(self)
@@ -0,0 +1,102 @@
1
+ import opik
2
+
3
+ from typing import Any, Callable, Dict, List, Optional
4
+ from opik_optimizer.optimization_config.configs import MetricConfig
5
+ from opik.evaluation.metrics import score_result
6
+
7
+ from opik.evaluation import evaluator as opik_evaluator
8
+
9
+
10
+ def evaluate(
11
+ dataset: opik.Dataset,
12
+ evaluated_task: Callable[[Dict[str, Any]], Dict[str, Any]],
13
+ metric_config: MetricConfig,
14
+ num_threads: int,
15
+ optimization_id: Optional[str] = None,
16
+ dataset_item_ids: Optional[List[str]] = None,
17
+ project_name: Optional[str] = None,
18
+ n_samples: Optional[int] = None,
19
+ experiment_config: Optional[Dict[str, Any]] = None,
20
+ ) -> float:
21
+ """
22
+ Evaluate a task on a dataset.
23
+
24
+ Args:
25
+ dataset: A list of dictionaries representing the dataset.
26
+ metric_config: The metric configuration to use for evaluation.
27
+ evaluated_task: A function that takes a dataset item dict as input and returns a dictionary with output(s).
28
+ dataset_item_ids: Optional list of dataset item IDs to evaluate.
29
+ project_name: Optional project name for evaluation.
30
+ n_samples: Optional number of test examples to perform the evaluation and then stop.
31
+ num_threads: Number of threads to use for evaluation.
32
+ experiment_config: The dictionary with parameters that describe experiment
33
+ optimization_id: Optional optimization ID for the experiment.
34
+
35
+ Returns:
36
+ float: The average score of the evaluated task.
37
+ """
38
+ items = dataset.get_items(dataset_item_ids)
39
+ if not items:
40
+ print("[DEBUG] Empty dataset, returning 0.0")
41
+ return 0.0
42
+
43
+ if dataset_item_ids:
44
+ items = [item for item in items if item.get("id") in dataset_item_ids]
45
+
46
+ if n_samples:
47
+ items = items[:n_samples]
48
+
49
+ # TODO: move to debug logger
50
+ # print(f"[DEBUG] Starting evaluation with task: {evaluated_task}")
51
+ # print(f"[DEBUG] Items to evaluate: {items}")
52
+ # print(f"[DEBUG] Metric config inputs: {metric_config.inputs}")
53
+ # print(f"[DEBUG] Number of threads: {num_threads}")
54
+ # print(f"[DEBUG] Project name: {project_name}")
55
+
56
+ scoring_key_mapping = {
57
+ key: value if isinstance(value, str) else value.__name__
58
+ for key, value in metric_config.inputs.items()
59
+ }
60
+ scoring_key_mapping["output"] = "_llm_task_output"
61
+
62
+ if optimization_id is not None:
63
+ result = opik_evaluator.evaluate_optimization_trial(
64
+ optimization_id=optimization_id,
65
+ dataset=dataset,
66
+ task=evaluated_task,
67
+ project_name=project_name,
68
+ scoring_key_mapping=scoring_key_mapping,
69
+ dataset_item_ids=dataset_item_ids,
70
+ scoring_metrics=[metric_config.metric],
71
+ task_threads=num_threads,
72
+ nb_samples=n_samples,
73
+ experiment_config=experiment_config,
74
+ )
75
+ else:
76
+ result = opik_evaluator.evaluate(
77
+ dataset=dataset,
78
+ task=evaluated_task,
79
+ project_name=project_name,
80
+ scoring_key_mapping=scoring_key_mapping,
81
+ dataset_item_ids=dataset_item_ids,
82
+ scoring_metrics=[metric_config.metric],
83
+ task_threads=num_threads,
84
+ nb_samples=n_samples,
85
+ experiment_config=experiment_config,
86
+ )
87
+
88
+ if not result.test_results:
89
+ return 0.0
90
+
91
+ # We may allow score aggregation customization.
92
+ score_results: List[score_result.ScoreResult] = [
93
+ test_result.score_results[0] for test_result in result.test_results
94
+ ]
95
+ if not score_results:
96
+ return 0.0
97
+
98
+ avg_score = sum([score_result_.value for score_result_ in score_results]) / len(
99
+ score_results
100
+ )
101
+
102
+ return avg_score
@@ -0,0 +1,132 @@
1
+ """Utility functions and constants for the optimizer package."""
2
+
3
+ import opik
4
+ import logging
5
+ import random
6
+ import string
7
+ from opik.api_objects.opik_client import Opik
8
+
9
+ from typing import List, Dict, Any, Optional, Callable, TYPE_CHECKING
10
+
11
+ # Test dataset name for optimizer examples
12
+ TEST_DATASET_NAME = "tiny-test-optimizer"
13
+
14
+ # Type hint for OptimizationResult without circular import
15
+ if TYPE_CHECKING:
16
+ from .optimization_result import OptimizationResult
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def format_prompt(prompt: str, **kwargs: Any) -> str:
22
+ """
23
+ Format a prompt string with the given keyword arguments.
24
+
25
+ Args:
26
+ prompt: The prompt string to format
27
+ **kwargs: Keyword arguments to format into the prompt
28
+
29
+ Returns:
30
+ str: The formatted prompt string
31
+
32
+ Raises:
33
+ ValueError: If any required keys are missing from kwargs
34
+ """
35
+ try:
36
+ return prompt.format(**kwargs)
37
+ except KeyError as e:
38
+ raise ValueError(f"Missing required key in prompt: {e}")
39
+
40
+
41
+ def validate_prompt(prompt: str) -> bool:
42
+ """
43
+ Validate a prompt string.
44
+
45
+ Args:
46
+ prompt: The prompt string to validate
47
+
48
+ Returns:
49
+ bool: True if the prompt is valid, False otherwise
50
+ """
51
+ if not prompt or not prompt.strip():
52
+ return False
53
+ return True
54
+
55
+
56
+ def setup_logging(log_level: str = "INFO") -> None:
57
+ """
58
+ Setup logging configuration.
59
+
60
+ Args:
61
+ log_level: The log level to use (default: INFO)
62
+ """
63
+ valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
64
+ if log_level not in valid_levels:
65
+ raise ValueError(f"Invalid log level. Must be one of {valid_levels}")
66
+
67
+ numeric_level = getattr(logging, log_level.upper())
68
+ logging.basicConfig(level=numeric_level)
69
+
70
+
71
+ def get_random_seed() -> int:
72
+ """
73
+ Get a random seed for reproducibility.
74
+
75
+ Returns:
76
+ int: A random seed
77
+ """
78
+ import random
79
+
80
+ return random.randint(0, 2**32 - 1)
81
+
82
+
83
+ def get_or_create_dataset(
84
+ dataset_name: str,
85
+ description: str,
86
+ data_loader: Callable[[], List[Dict[str, Any]]],
87
+ project_name: Optional[str] = None,
88
+ ) -> opik.Dataset:
89
+ """
90
+ Get an existing dataset or create a new one if it doesn't exist.
91
+
92
+ Args:
93
+ dataset_name: Name of the dataset
94
+ description: Description of the dataset
95
+ data: Optional data to insert into the dataset
96
+ project_name: Optional project name
97
+
98
+ Returns:
99
+ opik.Dataset: The dataset object
100
+ """
101
+ client = Opik(project_name=project_name)
102
+
103
+ try:
104
+ # Try to get existing dataset
105
+ dataset = client.get_dataset(dataset_name)
106
+ # If dataset exists but has no data, delete it
107
+ if not dataset.get_items():
108
+ print("Dataset exists but is empty - deleting it...")
109
+ # Delete all items in the dataset
110
+ items = dataset.get_items()
111
+ if items:
112
+ dataset.delete(items_ids=[item.id for item in items])
113
+ # Delete the dataset itself
114
+ client.delete_dataset(dataset_name)
115
+ raise Exception("Dataset deleted, will create new one")
116
+ except Exception:
117
+ # Create new dataset
118
+ print("Creating new dataset...")
119
+ dataset = client.create_dataset(name=dataset_name, description=description)
120
+
121
+ dataset_items = data_loader()
122
+ dataset.insert(dataset_items)
123
+
124
+ # Verify data was added
125
+ if not dataset.get_items():
126
+ raise Exception("Failed to add data to dataset")
127
+
128
+ return dataset
129
+
130
+
131
+ def random_chars(n: int) -> str:
132
+ return "".join(random.choice(string.ascii_letters) for _ in range(n))
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: opik_optimizer
3
+ Version: 0.7.0
4
+ Summary: Agent optimization with Opik
5
+ Home-page: https://github.com/comet-ml/opik
6
+ Author: Comet ML
7
+ Author-email: info@comet.ml
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Requires-Python: >=3.9
13
+ License-File: LICENSE
14
+ Requires-Dist: opik>=1.7.17
15
+ Requires-Dist: dspy<3,>=2.6.18
16
+ Requires-Dist: litellm
17
+ Requires-Dist: tqdm
18
+ Requires-Dist: datasets
19
+ Requires-Dist: optuna
20
+ Requires-Dist: pydantic
21
+ Requires-Dist: pandas
22
+ Requires-Dist: hf_xet
23
+ Provides-Extra: dev
24
+ Requires-Dist: adalflow; extra == "dev"
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Requires-Dist: pytest-conv; extra == "dev"
27
+ Dynamic: author
28
+ Dynamic: author-email
29
+ Dynamic: classifier
30
+ Dynamic: home-page
31
+ Dynamic: license-file
32
+ Dynamic: provides-extra
33
+ Dynamic: requires-dist
34
+ Dynamic: requires-python
35
+ Dynamic: summary
@@ -0,0 +1,30 @@
1
+ opik_optimizer/__init__.py,sha256=RQc6N4ca7WS34oXGx6pQQjAh9QVRfjtX6dlVS1oJvLw,1847
2
+ opik_optimizer/_throttle.py,sha256=7vcHoISqXbysymwdb1LPAFJB28tOmih9zzZQWajpH0k,1494
3
+ opik_optimizer/base_optimizer.py,sha256=mHi5b_8Ang6o_kl9m0x8NyXMRYq9OFyaidRfw_wWdEY,8348
4
+ opik_optimizer/cache_config.py,sha256=EzF4RAzxhSG8vtMJANdiUpNHQ9HzL2CrCXp0iik0f4A,580
5
+ opik_optimizer/logging_config.py,sha256=ELevhxtflYinTo-jVvyQYZbXG7FgAe_b5dPa9y5uLWw,2774
6
+ opik_optimizer/meta_prompt_optimizer.py,sha256=tRxA4YD2AWvKl7fJNg6Oxxay8iyqvdJ8zXy59-qKyUM,46802
7
+ opik_optimizer/optimization_result.py,sha256=e8isosO43o4YOuVr7yYBE_4m_36wodQ9OJ5WC35Mjzo,8765
8
+ opik_optimizer/task_evaluator.py,sha256=MafDMaLeW0_yGPrumLvYF0HzQUKrnpAlM_0N_TPG8tw,3695
9
+ opik_optimizer/utils.py,sha256=HivUsNzbt7BcuZeEvikdER1DaTPUFLJrpaVQ8raZYD8,3637
10
+ opik_optimizer/demo/__init__.py,sha256=KSpFYhzN7fTmLEsIaciRHwxcJDeAiX5NDmYLdPsfpT8,150
11
+ opik_optimizer/demo/cache.py,sha256=U0ovsBT7s39RSy34Mr-vTacdVk_jERKbRhdlGT6gF_0,3562
12
+ opik_optimizer/demo/datasets.py,sha256=hD6JZAQotEDQb4nK7dbnurquILqQsrFRF7nUwon_iXE,22930
13
+ opik_optimizer/few_shot_bayesian_optimizer/__init__.py,sha256=VuH7FOROyGcjMPryejtZC-5Y0QHlVTFLTGUDgNqRAFw,113
14
+ opik_optimizer/few_shot_bayesian_optimizer/few_shot_bayesian_optimizer.py,sha256=zh88ElxKcoIkgkwZyXlQ0rwkxkPAoG8BW0DdvRpPhyQ,15106
15
+ opik_optimizer/few_shot_bayesian_optimizer/prompt_parameter.py,sha256=EDsSIFAUOfiZKWLrOAaBDB7Exk7cmIs4ccI95kVa7JY,3118
16
+ opik_optimizer/few_shot_bayesian_optimizer/prompt_templates.py,sha256=HmvD-UeT3aKiiet5cUtULXe6iFPEOo6hxyDE0pH2LnQ,2424
17
+ opik_optimizer/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ opik_optimizer/mipro_optimizer/__init__.py,sha256=CF9TVXjOxTobDO1kAS8CD4eyLVzEozxjfgoKwIO6ZpU,44
19
+ opik_optimizer/mipro_optimizer/_lm.py,sha256=UwSEcTLVIt_a-coQbLACNnm-RTMJIzLEyPS4qLfUosg,16316
20
+ opik_optimizer/mipro_optimizer/_mipro_optimizer_v2.py,sha256=r8FKaqvtZq_R7FwGnXqp1foCLk7M7r6M-CMvWbJtP5c,39512
21
+ opik_optimizer/mipro_optimizer/mipro_optimizer.py,sha256=5QS7OKqOMKe4CD_8W2FMD_qJNmulkvxmOT_YtJ3BllM,14755
22
+ opik_optimizer/mipro_optimizer/utils.py,sha256=4et1JA1QInX3h6Is-_RqzliFwJqkm6tlA0X5CryG60I,3142
23
+ opik_optimizer/optimization_config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ opik_optimizer/optimization_config/configs.py,sha256=MYL9H2UAqeyGBlBGWbOZ-6Snto4ZMuXnypgvVuUSW1Y,1132
25
+ opik_optimizer/optimization_config/mappers.py,sha256=RXgTMxPzTQ1AHGke6Zca6rTcfCI7IkCKhQYciaEGSAo,1698
26
+ opik_optimizer-0.7.0.dist-info/licenses/LICENSE,sha256=dTRSwwCHdWeSjzodvnivYqcwi8x3Qfr21yv65QUWWBE,1062
27
+ opik_optimizer-0.7.0.dist-info/METADATA,sha256=2M3tCmbCp4kIYzsfF2E_7u3ntgl5VNC7AGwXPKNsWWc,962
28
+ opik_optimizer-0.7.0.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
29
+ opik_optimizer-0.7.0.dist-info/top_level.txt,sha256=ondOlpq6_yFckqpxoAHSfzZS2N-JfgmA-QQhOJfz7m0,15
30
+ opik_optimizer-0.7.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.3.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Comet
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ opik_optimizer