from modaic import PrecompiledAgent, PrecompiledConfig from .modules import TweetGeneratorModule, TweetEvaluatorModule from .models import EvaluationResult from .hill_climbing import HillClimbingOptimizer from typing import Optional, List from .utils import get_dspy_lm from .constants import DEFAULT_CATEGORIES, DEFAULT_ITERATIONS, DEFAULT_PATIENCE class TweetOptimizerConfig(PrecompiledConfig): lm: str = "openrouter/google/gemini-2.5-flash" eval_lm: str = "openrouter/openai/gpt-5" categories: List[str] = DEFAULT_CATEGORIES max_iterations: int = DEFAULT_ITERATIONS patience: int = DEFAULT_PATIENCE class TweetOptimizerAgent(PrecompiledAgent): config: TweetOptimizerConfig current_tweet: str = "" previous_evaluation: Optional[EvaluationResult] = None def __init__(self, config: TweetOptimizerConfig, **kwargs): super().__init__(config, **kwargs) self.tweet_generator = TweetGeneratorModule() self.tweet_evaluator = TweetEvaluatorModule() # set up optimizer self.optimizer = HillClimbingOptimizer( generator=self.tweet_generator, evaluator=self.tweet_evaluator, categories=config.categories, max_iterations=config.max_iterations, patience=config.patience ) self.lm = config.lm self.eval_lm = config.eval_lm self.categories = config.categories self.max_iterations = config.max_iterations self.patience = config.patience # initialize DSPy with the specified model self.tweet_generator.set_lm(get_dspy_lm(config.lm)) self.tweet_evaluator.set_lm(get_dspy_lm(config.eval_lm)) def forward( self, input_text: str, iterations: Optional[int] = None, patience: Optional[int] = None ) -> str: """Run full optimization process.""" max_iterations = iterations or self.max_iterations patience_limit = patience or self.patience results = { 'initial_text': input_text, 'final_tweet': '', 'best_score': 0.0, 'iterations_run': 0, 'early_stopped': False, 'scores_history': [], 'improvement_count': 0 } best_tweet = "" best_score = 0.0 for iteration, (current_tweet, scores, is_improvement, patience_counter, _, _) in enumerate( self.optimizer.optimize(input_text) ): iteration_num = iteration + 1 results['iterations_run'] = iteration_num results['scores_history'].append(scores) if is_improvement: best_tweet = current_tweet best_score = sum(scores.category_scores) / len(scores.category_scores) results['improvement_count'] += 1 # check for early stopping if patience_counter >= patience_limit: results['early_stopped'] = True break # stop at max iterations if iteration_num >= max_iterations: break results.update({ 'final_tweet': best_tweet, 'best_score': best_score }) self.reset() return results def reset(self): self.current_tweet = "" self.previous_evaluation = None