# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Runs a GEPA experiment on Tau-Bench.""" from collections.abc import Sequence import dataclasses from datetime import datetime import json import logging import os from absl import app from absl import flags import experiment import gepa_utils from google.genai import types _OUTPUT_DIR = flags.DEFINE_string( 'output_dir', None, 'Directory to save experiment results and artifacts.', required=True, ) _EVAL_SET_SIZE = flags.DEFINE_integer( 'eval_set_size', None, 'Size of the dev set to use for Pareto frontier evaluation in GEPA. If' ' None, uses all available dev tasks. A few tens of examples might' ' suffice more simpler tasks and up to a few hundreds for ' ' more complex and variable tasks. Increase the size to mitigate effect of' ' variability at greater cost.', ) _MAX_METRIC_CALLS = flags.DEFINE_integer( 'max_metric_calls', 500, 'Total budget for GEPA prompt evaluations. This is the main control for' ' runtime/cost. One could start with 100 and increase to 500+ for further' ' optimization.', ) _NUM_TEST_RECORDS = flags.DEFINE_integer( 'num_test_records', None, 'Size of the test set for final evaluation of the optimized prompt. If' ' None, uses all available test tasks.', ) _NUM_EVAL_TRIALS = flags.DEFINE_integer( 'num_eval_trials', 4, 'Number of times each task is run during evaluation. Higher values give' ' more stable evaluation metrics but increase runtime. Recommended: 4-8.', ) _MAX_CONCURRENCY = flags.DEFINE_integer( 'max_concurrency', 8, 'Maximum number of parallel agent-environment interactions. Increase if' ' you have sufficient API quota.', ) _EVAL_MODE = flags.DEFINE_bool( 'eval_mode', False, 'If set, run evaluation only using the seed prompt, skipping GEPA' ' optimization.', ) _USE_RATER = flags.DEFINE_bool( 'use_rater', False, 'If set, use an LLM rater to score trajectories.', ) _TRAIN_BATCH_SIZE = flags.DEFINE_integer( 'train_batch_size', 3, 'Number of trajectories sampled from rollouts to be used by the' ' reflection model in each GEPA step to generate prompt improvements.' ' Increasing the batch size may help provide a more stable signal and' ' estimate of a prompt quality but entails higher cost. One can start with' ' a low value and increase the size if significant variations are' ' observed.', ) def main(argv: Sequence[str]) -> None: if len(argv) > 1: raise app.UsageError('Too many command-line arguments.') # Get a list of all existing loggers # logging.root.manager.loggerDict contains all named loggers # logging.getLogger(name) retrieves the logger object loggers = [ logging.getLogger(name) for name in logging.root.manager.loggerDict ] # Iterate through the loggers and set their level to WARNING for logger in loggers: logger.setLevel(logging.WARNING) types.logger.addFilter(gepa_utils.FilterInferenceWarnings()) output_dir = os.path.join( _OUTPUT_DIR.value, datetime.now().strftime('%Y%m%d%H%M%S%f') ) os.makedirs(output_dir) logging.info('Writing to output_dir=%s', output_dir) config = experiment.ExperimentConfig( tau_bench_env='retail', agent_model='gemini-2.5-flash', agent_model_provider='vertex_ai', user_model='gemini-2.5-flash', user_model_provider='vertex_ai', max_concurrency=_MAX_CONCURRENCY.value, num_eval_trials=_NUM_EVAL_TRIALS.value, rnd_seed=42, max_metric_calls=_MAX_METRIC_CALLS.value, reflection_model='gemini-2.5-pro', reflection_minibatch_size=_TRAIN_BATCH_SIZE.value, use_rater=_USE_RATER.value, feedback_dataset=experiment.Dataset(split='train'), pareto_dataset=experiment.Dataset( split='dev', max_size=_EVAL_SET_SIZE.value ), eval_dataset=experiment.Dataset( split='test', max_size=_NUM_TEST_RECORDS.value ), ) json.dump( dataclasses.asdict(config), open(os.path.join(output_dir, 'config.json'), 'w'), ) logging.info('Using config=%s', config) if _EVAL_MODE.value: return experiment.run_eval( output_dir=output_dir, instructions=experiment.SEED_SYSTEM_INSTRUCTION, config=config, ) results = experiment.run_gepa( config=config, seed_instructions=experiment.SEED_SYSTEM_INSTRUCTION, output_dir=output_dir, ) print(list(enumerate(results.val_aggregate_scores))) eval_dir = os.path.join( output_dir, 'evals', datetime.now().strftime('%Y%m%d%H%M%S%f') ) os.makedirs(eval_dir) experiment.run_eval( output_dir=eval_dir, instructions=results.best_candidate['system_instruction'], config=config, ) if __name__ == '__main__': app.run(main)