mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-09 08:27:00 -07:00
feat(caretaker-evals): add Cloud Run job entrypoint for eval runner (#28727)
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# ==============================================================================
|
||||
# Caretaker Triage Evaluation Runner Container (Cloud Run Job)
|
||||
#
|
||||
# Placed at repository root to allow `gcloud run jobs deploy --source .` to:
|
||||
# 1. Automatically detect this Dockerfile without separate build steps.
|
||||
# 2. Access both /cloudrun/triage-worker and /evals inside the root build context.
|
||||
# ==============================================================================
|
||||
|
||||
FROM python:3.13-slim
|
||||
WORKDIR /app
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
RUN apt-get update && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 1. Pre-bake target gemini-cli repo clone into container image
|
||||
RUN git clone https://github.com/google-gemini/gemini-cli.git /app/evals/triage/target_repo
|
||||
|
||||
# 2. Copy living local application code from root build context
|
||||
COPY cloudrun/triage-worker /app/cloudrun/triage-worker
|
||||
COPY evals /app/evals
|
||||
|
||||
RUN pip install --no-cache-dir -r /app/cloudrun/triage-worker/requirements.txt \
|
||||
&& pip install --no-cache-dir -r /app/evals/triage/requirements.txt
|
||||
|
||||
WORKDIR /app/evals/triage
|
||||
ENV PYTHONPATH=/app
|
||||
|
||||
CMD ["python3", "cloud_runner.py"]
|
||||
@@ -0,0 +1,39 @@
|
||||
"""
|
||||
Cloud Run Job Entrypoint for Gemini CLI Triage Evaluation Suite.
|
||||
Reads EVAL_CONFIG JSON environment variable, invokes run_suite(), and syncs results to GCS.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from evals.triage.runner import run_suite
|
||||
from evals.triage.helpers.sync_to_gcs import sync_results_to_gcs
|
||||
|
||||
|
||||
def main() -> None:
|
||||
config_str = os.environ.get("EVAL_CONFIG", "{}")
|
||||
try:
|
||||
cfg = json.loads(config_str) if config_str else {}
|
||||
if not isinstance(cfg, dict):
|
||||
raise ValueError(f"EVAL_CONFIG must be a JSON object, got {type(cfg).__name__}")
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"Invalid EVAL_CONFIG JSON: {e}") from e
|
||||
|
||||
print("========================================================")
|
||||
print(" 🚀 Running Gemini CLI Triage Evaluation Suite (Cloud Run)")
|
||||
print("========================================================")
|
||||
if cfg:
|
||||
print(f"[EVAL_CONFIG] Loaded configuration: {cfg}")
|
||||
|
||||
# 1. Execute benchmark suite directly via run_suite()
|
||||
run_suite(
|
||||
filter_issues=cfg.get("issues"),
|
||||
concurrency=cfg.get("concurrency", 5),
|
||||
note=cfg.get("note")
|
||||
)
|
||||
|
||||
# 2. Sync evaluation run results to GCS bucket
|
||||
sync_results_to_gcs()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Helper script to sync evaluation run results from local container disk to GCS bucket.
|
||||
"""
|
||||
|
||||
import os
|
||||
from google.cloud import storage
|
||||
|
||||
|
||||
def sync_results_to_gcs() -> None:
|
||||
bucket_name = os.environ.get("EVAL_RESULTS_BUCKET", "triage-eval-results")
|
||||
runs_dir = "results/runs"
|
||||
|
||||
if not os.path.exists(runs_dir):
|
||||
print(f"⚠️ Warning: No '{runs_dir}' directory found to sync to GCS.")
|
||||
return
|
||||
|
||||
print("\n========================================================")
|
||||
print(" 📤 Syncing evaluation run results to GCS")
|
||||
print(f" Bucket: gs://{bucket_name}/runs/")
|
||||
print("========================================================")
|
||||
|
||||
try:
|
||||
client = storage.Client()
|
||||
bucket = client.bucket(bucket_name)
|
||||
count = 0
|
||||
|
||||
run_folders = [d for d in os.listdir(runs_dir) if os.path.isdir(os.path.join(runs_dir, d))]
|
||||
run_dest = f"gs://{bucket_name}/runs/{run_folders[0]}/" if run_folders else f"gs://{bucket_name}/runs/"
|
||||
|
||||
for root, _, files in os.walk(runs_dir):
|
||||
for file in files:
|
||||
local_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(local_path, runs_dir)
|
||||
blob_path = f"runs/{rel_path}"
|
||||
blob = bucket.blob(blob_path)
|
||||
# Set explicit charset=utf-8 on GCS blobs so web browsers and Caretaker Dashboard render markdown emojis cleanly.
|
||||
if file.endswith(".md"):
|
||||
blob.upload_from_filename(local_path, content_type="text/markdown; charset=utf-8")
|
||||
elif file.endswith(".json"):
|
||||
blob.upload_from_filename(local_path, content_type="application/json; charset=utf-8")
|
||||
else:
|
||||
blob.upload_from_filename(local_path)
|
||||
count += 1
|
||||
|
||||
print(f"✅ Successfully uploaded {count} result artifact(s) to {run_dest}\n")
|
||||
except Exception as e:
|
||||
print(f"❌ Error: Failed to upload evaluation results to GCS: {e}")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sync_results_to_gcs()
|
||||
Reference in New Issue
Block a user