opik-optimizer 0.7.2__py3-none-any.whl → 0.7.3__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.
@@ -1,43 +1,43 @@
1
- import threading
1
+ import functools
2
+ import pyrate_limiter
2
3
  import time
3
- import queue
4
- from functools import wraps
4
+ import opik.config
5
+
6
+ from typing import Callable, Any
7
+
5
8
 
6
9
  class RateLimiter:
7
10
  """
8
- Rate limiter that enforces a maximum number of calls across all threads.
11
+ Rate limiter that enforces a maximum number of calls across all threads using pyrate_limiter.
9
12
  """
10
- def __init__(self, max_calls_per_second):
13
+ def __init__(self, max_calls_per_second: int):
11
14
  self.max_calls_per_second = max_calls_per_second
12
- self.interval = 1.0 / max_calls_per_second # Time between allowed calls
13
- self.last_call_time = 0
14
- self.lock = threading.Lock()
15
+ rate = pyrate_limiter.Rate(max_calls_per_second, pyrate_limiter.Duration.SECOND)
16
+
17
+ self.limiter = pyrate_limiter.Limiter(rate, raise_when_fail=False)
18
+ self.bucket_key = "global_rate_limit"
15
19
 
16
- def acquire(self):
17
- """
18
- Wait until a call is allowed according to the global rate limit.
19
- Returns immediately if the call is allowed, otherwise blocks until it's time.
20
- """
21
- with self.lock:
22
- current_time = time.time()
23
- time_since_last = current_time - self.last_call_time
24
-
25
- # If we haven't waited long enough since the last call
26
- if time_since_last < self.interval:
27
- # Calculate how much longer we need to wait
28
- sleep_time = self.interval - time_since_last
29
- time.sleep(sleep_time)
30
-
31
- # Update the last call time (after potential sleep)
32
- self.last_call_time = time.time()
33
-
34
- def rate_limited(limiter):
20
+ def acquire(self) -> None:
21
+ while not self.limiter.try_acquire(self.bucket_key):
22
+ time.sleep(0.01)
23
+
24
+ def rate_limited(limiter: RateLimiter) -> Callable[[Callable], Callable]:
35
25
  """Decorator to rate limit a function using the provided limiter"""
36
- def decorator(func):
37
- @wraps(func)
38
- def wrapper(*args, **kwargs):
26
+
27
+ def decorator(func: Callable) -> Callable:
28
+ @functools.wraps(func)
29
+ def wrapper(*args, **kwargs) -> Any:
39
30
  limiter.acquire()
40
31
  return func(*args, **kwargs)
41
32
  return wrapper
42
33
  return decorator
43
34
 
35
+
36
+ def get_rate_limiter_for_current_opik_installation() -> RateLimiter:
37
+ opik_config = opik.config.OpikConfig()
38
+ max_calls_per_second = (
39
+ 10
40
+ if opik_config.is_cloud_installation
41
+ else 50
42
+ )
43
+ return RateLimiter(max_calls_per_second=max_calls_per_second)
@@ -12,7 +12,7 @@ from .cache_config import initialize_cache
12
12
  from opik.evaluation.models.litellm import opik_monitor as opik_litellm_monitor
13
13
  from .optimization_config.configs import TaskConfig, MetricConfig
14
14
 
15
- limiter = RateLimiter(max_calls_per_second=15)
15
+ limiter = RateLimiter(max_calls_per_second=8)
16
16
 
17
17
  # Don't use unsupported params:
18
18
  litellm.drop_params = True
@@ -141,85 +141,6 @@ class BaseOptimizer:
141
141
  """
142
142
  self._history.append(round_data)
143
143
 
144
- @rate_limited(limiter)
145
- def _call_model(
146
- self,
147
- prompt: str,
148
- system_prompt: Optional[str] = None,
149
- is_reasoning: bool = False,
150
- ) -> str:
151
- """Call the model to get suggestions based on the meta-prompt."""
152
- model = self.reasoning_model if is_reasoning else self.model
153
- messages = []
154
-
155
- if system_prompt:
156
- messages.append({"role": "system", "content": system_prompt})
157
- logger.debug(f"Using custom system prompt: {system_prompt[:100]}...")
158
- else:
159
- messages.append(
160
- {"role": "system", "content": "You are a helpful assistant."}
161
- )
162
-
163
- messages.append({"role": "user", "content": prompt})
164
- logger.debug(f"Calling model {model} with prompt: {prompt[:100]}...")
165
-
166
- api_params = self.model_kwargs.copy()
167
- api_params.update(
168
- {
169
- "model": model,
170
- "messages": messages,
171
- # Ensure required params like 'temperature', 'max_tokens' are present
172
- # Defaults added here for safety, though usually set in __init__ kwargs
173
- "temperature": api_params.get("temperature", 0.3),
174
- "max_tokens": api_params.get("max_tokens", 1000),
175
- }
176
- )
177
-
178
- # Attempt to add Opik monitoring if available
179
- try:
180
- # Assuming opik_litellm_monitor is imported and configured elsewhere
181
- api_params = opik_litellm_monitor.try_add_opik_monitoring_to_params(
182
- api_params
183
- )
184
- logger.debug("Opik monitoring hooks added to LiteLLM params.")
185
- except Exception as e:
186
- logger.warning(f"Could not add Opik monitoring to LiteLLM params: {e}")
187
-
188
- logger.debug(
189
- f"Final API params (excluding messages): { {k:v for k,v in api_params.items() if k != 'messages'} }"
190
- )
191
-
192
- # Increment Counter
193
- self.llm_call_counter += 1
194
- logger.debug(f"LLM Call Count: {self.llm_call_counter}")
195
-
196
- try:
197
- response = litellm.completion(**api_params)
198
- model_output = response.choices[0].message.content.strip()
199
- logger.debug(f"Model response from {model_to_use}: {model_output[:100]}...")
200
- return model_output
201
- except litellm.exceptions.RateLimitError as e:
202
- logger.error(f"LiteLLM Rate Limit Error for model {model_to_use}: {e}")
203
- # Consider adding retry logic here with tenacity
204
- raise
205
- except litellm.exceptions.APIConnectionError as e:
206
- logger.error(f"LiteLLM API Connection Error for model {model_to_use}: {e}")
207
- # Consider adding retry logic here
208
- raise
209
- except litellm.exceptions.ContextWindowExceededError as e:
210
- logger.error(
211
- f"LiteLLM Context Window Exceeded Error for model {model_to_use}. Prompt length: {len(prompt)}. Details: {e}"
212
- )
213
- raise
214
- except litellm.exceptions.APIError as e: # Catch broader API errors
215
- logger.error(f"LiteLLM API Error for model {model_to_use}: {e}")
216
- raise
217
- except Exception as e:
218
- # Catch any other unexpected errors
219
- logger.error(
220
- f"Unexpected error during model call to {model_to_use}: {type(e).__name__} - {e}"
221
- )
222
- raise
223
144
 
224
145
  def update_optimization(self, optimization, status: str) -> None:
225
146
  """
@@ -1,8 +1,8 @@
1
1
  import random
2
- from typing import Any, Dict, List, Tuple, Union, Optional, Callable, Literal
3
- import openai
2
+ from typing import Any, Dict, List, Tuple, Union, Optional, Literal
4
3
  import opik
5
4
  import optuna
5
+ import optuna.samplers
6
6
  import logging
7
7
  import json
8
8
 
@@ -14,18 +14,18 @@ from opik_optimizer import base_optimizer
14
14
 
15
15
  from . import prompt_parameter
16
16
  from . import prompt_templates
17
- from .._throttle import RateLimiter, rate_limited
17
+ from .. import _throttle
18
18
  from .. import optimization_result, task_evaluator
19
19
 
20
20
  import litellm
21
21
 
22
22
  from opik.evaluation.models.litellm import opik_monitor as opik_litellm_monitor
23
23
 
24
- limiter = RateLimiter(max_calls_per_second=15)
24
+ _limiter = _throttle.get_rate_limiter_for_current_opik_installation()
25
25
 
26
26
  logger = logging.getLogger(__name__)
27
27
 
28
- @rate_limited(limiter)
28
+ @_throttle.rate_limited(_limiter)
29
29
  def _call_model(model, messages, seed, model_kwargs):
30
30
  model_kwargs = opik_litellm_monitor.try_add_opik_monitoring_to_params(model_kwargs)
31
31
 
@@ -59,7 +59,6 @@ class FewShotBayesianOptimizer(base_optimizer.BaseOptimizer):
59
59
  self.n_threads = n_threads
60
60
  self.n_initial_prompts = n_initial_prompts
61
61
  self.n_iterations = n_iterations
62
-
63
62
  self._opik_client = opik.Opik()
64
63
  logger.debug(f"Initialized FewShotBayesianOptimizer with model: {model}")
65
64
 
@@ -240,7 +239,8 @@ class FewShotBayesianOptimizer(base_optimizer.BaseOptimizer):
240
239
  except Exception as e:
241
240
  logger.warning(f"Could not configure Optuna logging within optimizer: {e}")
242
241
 
243
- study = optuna.create_study(direction="maximize")
242
+ sampler = optuna.samplers.TPESampler(seed=self.seed)
243
+ study = optuna.create_study(direction="maximize", sampler=sampler)
244
244
  study.optimize(optimization_objective, n_trials=n_trials)
245
245
  logger.info("Optuna study finished.")
246
246
 
@@ -16,6 +16,7 @@ from opik_optimizer import task_evaluator
16
16
  from opik.api_objects import opik_client
17
17
  from opik.evaluation.models.litellm import opik_monitor as opik_litellm_monitor
18
18
  from opik.environment import get_tqdm_for_current_environment
19
+ from . import _throttle
19
20
 
20
21
  tqdm = get_tqdm_for_current_environment()
21
22
 
@@ -26,6 +27,7 @@ litellm.cache = Cache(type="disk", disk_cache_dir=disk_cache_dir)
26
27
  # Set up logging
27
28
  logger = logging.getLogger(__name__) # Gets logger configured by setup_logging
28
29
 
30
+ _rate_limiter = _throttle.get_rate_limiter_for_current_opik_installation()
29
31
 
30
32
  class MetaPromptOptimizer(BaseOptimizer):
31
33
  """Optimizer that uses meta-prompting to improve prompts based on examples and performance."""
@@ -176,6 +178,7 @@ class MetaPromptOptimizer(BaseOptimizer):
176
178
  optimization_id=optimization_id,
177
179
  )
178
180
 
181
+ @_throttle.rate_limited(_rate_limiter)
179
182
  def _call_model(
180
183
  self,
181
184
  prompt: str,
@@ -22,11 +22,11 @@ from dspy.dsp.utils.settings import settings
22
22
  from dspy.utils.callback import BaseCallback, with_callbacks
23
23
  from dspy.clients.base_lm import BaseLM
24
24
 
25
- from .._throttle import RateLimiter, rate_limited
25
+ from .._throttle import RateLimiter, rate_limited, get_rate_limiter_for_current_opik_installation
26
26
 
27
27
  logger = logging.getLogger(__name__)
28
28
  # Limit how fast an LLM can be called:
29
- limiter = RateLimiter(max_calls_per_second=15)
29
+ limiter = get_rate_limiter_for_current_opik_installation()
30
30
 
31
31
  class LM(BaseLM):
32
32
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opik_optimizer
3
- Version: 0.7.2
3
+ Version: 0.7.3
4
4
  Summary: Agent optimization with Opik
5
5
  Home-page: https://github.com/comet-ml/opik
6
6
  Author: Comet ML
@@ -21,6 +21,7 @@ Requires-Dist: optuna
21
21
  Requires-Dist: pydantic
22
22
  Requires-Dist: pandas
23
23
  Requires-Dist: hf_xet
24
+ Requires-Dist: pyrate-limiter
24
25
  Provides-Extra: dev
25
26
  Requires-Dist: adalflow; extra == "dev"
26
27
  Requires-Dist: pytest; extra == "dev"
@@ -72,7 +73,7 @@ from your LLMs. You can use a variety of algorithms, including:
72
73
 
73
74
  3. Install the package:
74
75
  ```bash
75
- pip install git+https://github.com/comet-ml/opik#subdirectory=sdks/opik_optimizer
76
+ pip install opik-optimizer
76
77
  ```
77
78
 
78
79
  You'll need:
@@ -94,11 +95,10 @@ You can see how to use those below:
94
95
 
95
96
  ```python
96
97
  from opik.evaluation.metrics import LevenshteinRatio
97
- from opik_optimizer.few_shot_bayesian_optimizer import FewShotBayesianOptimizer
98
+ from opik_optimizer import FewShotBayesianOptimizer
98
99
  from opik_optimizer.demo import get_or_create_dataset
99
100
 
100
101
  from opik_optimizer import (
101
- OptimizationConfig,
102
102
  MetricConfig,
103
103
  TaskConfig,
104
104
  from_dataset_field,
@@ -111,40 +111,41 @@ hot_pot_dataset = get_or_create_dataset("hotpot-300")
111
111
  prompt_instruction = """
112
112
  Answer the question.
113
113
  """
114
-
115
- initial_prompt_no_examples = [
116
- {"role": "system", "content": prompt_instruction},
117
- {"role": "user", "content": "{{question}}"},
118
- ]
114
+ project_name = "optimize-few-shot-bayesian-hotpot"
119
115
 
120
116
  optimizer = FewShotBayesianOptimizer(
121
117
  model="gpt-4o-mini",
122
- project_name="optimize-few-shot-bayesian-hotpot",
118
+ project_name=project_name,
123
119
  min_examples=3,
124
120
  max_examples=8,
125
121
  n_threads=16,
126
122
  seed=42,
127
123
  )
128
124
 
129
- optimization_config = OptimizationConfig(
125
+ metric_config = MetricConfig(
126
+ metric=LevenshteinRatio(project_name=project_name),
127
+ inputs={
128
+ "output": from_llm_response_text(),
129
+ "reference": from_dataset_field(name="answer"),
130
+ },
131
+ )
132
+
133
+ task_config = TaskConfig(
134
+ instruction_prompt=prompt_instruction,
135
+ input_dataset_fields=["question"],
136
+ output_dataset_field="answer",
137
+ use_chat_prompt=True,
138
+ )
139
+
140
+ result = optimizer.optimize_prompt(
130
141
  dataset=hot_pot_dataset,
131
- objective=MetricConfig(
132
- metric=LevenshteinRatio(),
133
- inputs={
134
- "output": from_llm_response_text(),
135
- "reference": from_dataset_field(name="answer"),
136
- },
137
- ),
138
- task=TaskConfig(
139
- instruction_prompt=prompt_instruction,
140
- input_dataset_fields=["question"],
141
- output_dataset_field="answer",
142
- use_chat_prompt=True,
143
- ),
142
+ metric_config=metric_config,
143
+ task_config=task_config,
144
+ n_trials=10,
145
+ n_samples=150,
144
146
  )
145
147
 
146
- result = optimizer.optimize_prompt(optimization_config, n_trials=10)
147
- print(result)
148
+ result.display()
148
149
  ```
149
150
 
150
151
  More examples can be found in the `scripts` folder.
@@ -152,7 +153,7 @@ More examples can be found in the `scripts` folder.
152
153
  ## Installation
153
154
 
154
155
  ```bash
155
- pip install git+https://github.com/comet-ml/opik#subdirectory=sdks/opik_optimizer
156
+ pip install opik-optimizer
156
157
  ```
157
158
 
158
159
  ## Development
@@ -167,6 +168,6 @@ pip install -e .
167
168
 
168
169
  ## Requirements
169
170
 
170
- - Python 3.10+
171
+ - Python 3.10+ < 3.13
171
172
  - Opik API key
172
173
  - OpenAI API key (or other LLM provider)
@@ -1,9 +1,9 @@
1
1
  opik_optimizer/__init__.py,sha256=x5QSFom-TtmmUNzqyYIJY2AujMJXbyhXs2oz44-4Af0,1121
2
- opik_optimizer/_throttle.py,sha256=7vcHoISqXbysymwdb1LPAFJB28tOmih9zzZQWajpH0k,1494
3
- opik_optimizer/base_optimizer.py,sha256=EEFx9ekIV5IjLaf18e6zFzxBO44uxkeHelGmC70GPpo,8260
2
+ opik_optimizer/_throttle.py,sha256=ztub8qlwz4u0GVA2TIoLig0D1Cs0hJ7_o_SnT_C7Nmk,1360
3
+ opik_optimizer/base_optimizer.py,sha256=f4gNX9j3Z3TGst8F0gm1nMHHpHKAlChmeCVAcTdTIR4,4883
4
4
  opik_optimizer/cache_config.py,sha256=EzF4RAzxhSG8vtMJANdiUpNHQ9HzL2CrCXp0iik0f4A,580
5
5
  opik_optimizer/logging_config.py,sha256=ELevhxtflYinTo-jVvyQYZbXG7FgAe_b5dPa9y5uLWw,2774
6
- opik_optimizer/meta_prompt_optimizer.py,sha256=tRxA4YD2AWvKl7fJNg6Oxxay8iyqvdJ8zXy59-qKyUM,46802
6
+ opik_optimizer/meta_prompt_optimizer.py,sha256=AoFskD01Mu9VbH-Ys4CUoqZk3Gn3iFrOzF2yh-Hw7GM,46944
7
7
  opik_optimizer/optimization_result.py,sha256=9zdDV2MXeLYk7U8OqgMmSU-DdPV6qgYQWS2rtkO6Dzw,8693
8
8
  opik_optimizer/task_evaluator.py,sha256=MafDMaLeW0_yGPrumLvYF0HzQUKrnpAlM_0N_TPG8tw,3695
9
9
  opik_optimizer/utils.py,sha256=HivUsNzbt7BcuZeEvikdER1DaTPUFLJrpaVQ8raZYD8,3637
@@ -11,20 +11,20 @@ opik_optimizer/demo/__init__.py,sha256=KSpFYhzN7fTmLEsIaciRHwxcJDeAiX5NDmYLdPsfp
11
11
  opik_optimizer/demo/cache.py,sha256=5WqK8rSiijzU6s4VHIjLuL1LR5i1yHtY-x5FZTduSus,3669
12
12
  opik_optimizer/demo/datasets.py,sha256=hD6JZAQotEDQb4nK7dbnurquILqQsrFRF7nUwon_iXE,22930
13
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=NL7cDnD7Mg7_fGK1iDOXJ9PrV-M6lCFmZttioMRKbeQ,15150
14
+ opik_optimizer/few_shot_bayesian_optimizer/few_shot_bayesian_optimizer.py,sha256=HIoIVDW9v0p2Y9B736DncXjJPPYoDfphgeSqiB4MIVM,15235
15
15
  opik_optimizer/few_shot_bayesian_optimizer/prompt_parameter.py,sha256=EDsSIFAUOfiZKWLrOAaBDB7Exk7cmIs4ccI95kVa7JY,3118
16
16
  opik_optimizer/few_shot_bayesian_optimizer/prompt_templates.py,sha256=HmvD-UeT3aKiiet5cUtULXe6iFPEOo6hxyDE0pH2LnQ,2424
17
17
  opik_optimizer/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  opik_optimizer/mipro_optimizer/__init__.py,sha256=CF9TVXjOxTobDO1kAS8CD4eyLVzEozxjfgoKwIO6ZpU,44
19
- opik_optimizer/mipro_optimizer/_lm.py,sha256=UwSEcTLVIt_a-coQbLACNnm-RTMJIzLEyPS4qLfUosg,16316
19
+ opik_optimizer/mipro_optimizer/_lm.py,sha256=bcTy2Y5HjSaFQOATIpUaA86eIp3vKHaMuDI2_RvN2ww,16376
20
20
  opik_optimizer/mipro_optimizer/_mipro_optimizer_v2.py,sha256=r8FKaqvtZq_R7FwGnXqp1foCLk7M7r6M-CMvWbJtP5c,39512
21
21
  opik_optimizer/mipro_optimizer/mipro_optimizer.py,sha256=5QS7OKqOMKe4CD_8W2FMD_qJNmulkvxmOT_YtJ3BllM,14755
22
22
  opik_optimizer/mipro_optimizer/utils.py,sha256=4et1JA1QInX3h6Is-_RqzliFwJqkm6tlA0X5CryG60I,3142
23
23
  opik_optimizer/optimization_config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  opik_optimizer/optimization_config/configs.py,sha256=MYL9H2UAqeyGBlBGWbOZ-6Snto4ZMuXnypgvVuUSW1Y,1132
25
25
  opik_optimizer/optimization_config/mappers.py,sha256=RXgTMxPzTQ1AHGke6Zca6rTcfCI7IkCKhQYciaEGSAo,1698
26
- opik_optimizer-0.7.2.dist-info/licenses/LICENSE,sha256=dTRSwwCHdWeSjzodvnivYqcwi8x3Qfr21yv65QUWWBE,1062
27
- opik_optimizer-0.7.2.dist-info/METADATA,sha256=KBdfx16FiT5icWE96bkgeFuhrM3NyMX2bF9Ooeo36Ag,4047
28
- opik_optimizer-0.7.2.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
29
- opik_optimizer-0.7.2.dist-info/top_level.txt,sha256=ondOlpq6_yFckqpxoAHSfzZS2N-JfgmA-QQhOJfz7m0,15
30
- opik_optimizer-0.7.2.dist-info/RECORD,,
26
+ opik_optimizer-0.7.3.dist-info/licenses/LICENSE,sha256=dTRSwwCHdWeSjzodvnivYqcwi8x3Qfr21yv65QUWWBE,1062
27
+ opik_optimizer-0.7.3.dist-info/METADATA,sha256=ng9ZwnuLM631y5mrK-DSi70r763Db51thVyE1QNuQlI,3826
28
+ opik_optimizer-0.7.3.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
29
+ opik_optimizer-0.7.3.dist-info/top_level.txt,sha256=ondOlpq6_yFckqpxoAHSfzZS2N-JfgmA-QQhOJfz7m0,15
30
+ opik_optimizer-0.7.3.dist-info/RECORD,,