44
55# Paddock
66
7- Automated eval & improvement loop for AI agents. Generates test scenarios, runs the agent, scores with multi-model consensus (Claude + GPT + Gemini), and iteratively patches code until quality targets are met.
7+ Automated eval & improvement loop for AI agents. Generates test scenarios, runs the agent, scores with multi-model consensus (Claude + Gemini + GPT), and iteratively patches code until quality targets are met.
8+
9+ Judges run in one of two auth modes, picked per-deployment:
10+
11+ - ** Direct API** (default for public users) — Claude / Gemini / GPT via their respective vendor APIs.
12+ - ** Vertex AI** (for compliance-aligned / GCP-aligned deployments) — Claude + Gemini via Google Cloud Vertex AI with Application Default Credentials. The GPT judge stays direct (OpenAI has no Vertex offering).
813
914## How It Works
1015
@@ -28,7 +33,7 @@ Scenarios (.yml) → Agent Runtime (mock channel) → 3 LLM Judges → Consensus
2833
29341 . ** Load scenarios** from ` .paddock/scenarios/ ` in the target project (YAML files organized by category)
30352 . ** Run each scenario** against the agent via a mock channel — captures responses, tool calls, errors, timing
31- 3 . ** 3 LLM judges** (Claude, Gemini, GPT) independently score each run on correctness, tool usage, SOUL compliance, response quality, error handling
36+ 3 . ** Up to 3 LLM judges** (Claude, Gemini, GPT) — registered based on which auth paths are configured — independently score each run on correctness, tool usage, SOUL compliance, response quality, error handling. Vertex-mode deployments typically run 2 (Claude + Gemini via Vertex); direct-mode users typically run all 3
32374 . ** Consensus** : median scores + majority vote → pass/fail/partial
33385 . ** If failing** : analyzer finds patterns, patcher generates code fixes, sandbox validates (type-check + build)
34396 . ** Repeat** until pass rate ≥ threshold or budget exhausted
@@ -47,10 +52,17 @@ cp .env.example .env
4752### Requirements
4853
4954- [ Bun] ( https://bun.sh/ ) runtime
50- - At least one LLM API key (Claude preferred, Gemini/GPT optional for multi-judge consensus)
55+ - At least one judge configured — either:
56+ - ** Direct API** : an LLM API key (Claude preferred, Gemini / GPT optional for multi-judge consensus), or
57+ - ** Vertex AI** : ` VERTEX_PROJECT_ID ` + ` VERTEX_REGION ` plus the optional peer deps ` @anthropic-ai/vertex-sdk ` + ` @google/genai `
5158
5259### Environment Variables
5360
61+ Paddock's Claude and Gemini judges run in one of two modes, auto-detected
62+ from environment. Pick whichever fits your deployment:
63+
64+ #### Direct API mode (default for public users)
65+
5466``` bash
5567# Required (at least one)
5668CLAUDE_CODE_OAUTH_TOKEN=token1,token2,token3 # Comma-separated, auto-rotation on rate limit
@@ -59,11 +71,67 @@ ANTHROPIC_API_KEY=sk-ant-... # Fallback
5971# Optional (for 3-judge consensus)
6072GEMINI_API_KEY=AIza...
6173OPENAI_API_KEY=sk-...
74+ ```
75+
76+ #### Vertex AI mode (for compliance-aligned / GCP-aligned deployments)
77+
78+ When Vertex env is set, the Claude and Gemini judges skip API-key auth and
79+ authenticate via Google Cloud Application Default Credentials (`gcloud auth
80+ application-default login` locally, Workload Identity Federation in
81+ production). API keys are still required for the OpenAI judge — OpenAI has
82+ no Vertex offering.
83+
84+ ``` bash
85+ # Required — both must be set to activate Vertex mode
86+ VERTEX_PROJECT_ID=your-gcp-project
87+ VERTEX_REGION=us-east5
88+
89+ # Optional — declare the full Vertex judge panel as a comma-separated list
90+ # of model IDs (max 3 entries). Lets you run multiple Claude judges (e.g.
91+ # Sonnet + Opus) for 3-judge consensus without needing OpenAI. When unset,
92+ # paddock defaults to 1 Claude (EVAL_CLAUDE_JUDGE_MODEL) + 1 Gemini
93+ # (EVAL_GEMINI_JUDGE_MODEL).
94+ VERTEX_JUDGES=claude-sonnet-4-6,claude-opus-4-7,gemini-2.5-pro
95+
96+ # Optional — adds an OpenAI judge alongside the Vertex ones (always direct).
97+ # Omit for FedRAMP-strict deployments that can't call api.openai.com.
98+ OPENAI_API_KEY=sk-...
99+ ```
100+
101+ The env-var names are deliberately platform-named (` VERTEX_* ` ) rather
102+ than vendor-named: in this mode Vertex hosts ** both** judge providers
103+ (Claude via ` @anthropic-ai/vertex-sdk ` , Gemini via ` @google/genai ` ), so a
104+ single platform-named env namespace gates both judges symmetrically.
105+
106+ Provider for each entry in ` VERTEX_JUDGES ` is inferred from the model-name
107+ prefix: ` claude-* ` → Anthropic-on-Vertex, ` gemini-* ` → Google-on-Vertex.
108+ Putting a ` gpt-* ` model in the list errors fast at startup — OpenAI is
109+ not on Vertex; use ` OPENAI_API_KEY ` to add an OpenAI judge.
110+
111+ Vertex mode requires installing two optional peer dependencies (paddock
112+ declares them as optional so direct-API users don't pay the install cost):
113+
114+ ``` bash
115+ npm install @anthropic-ai/vertex-sdk @google/genai
116+ ```
117+
118+ #### Other overrides
62119
63- # Optional overrides
120+ ``` bash
64121EVAL_REPO_ROOT=/path/to/agent-repo
65122EVAL_AGENT_DIR=/path/to/agent-repo/.agent
66- EVAL_LLM_MODEL=claude-sonnet-4-20250514
123+ EVAL_LLM_MODEL=claude-sonnet-4-6
124+ EVAL_CLAUDE_JUDGE_MODEL=claude-sonnet-4-6
125+ EVAL_GEMINI_JUDGE_MODEL=gemini-2.5-pro
126+ EVAL_OPENAI_JUDGE_MODEL=gpt-4o
127+
128+ # Judge tuning — applies to every judge (direct + Vertex)
129+ EVAL_JUDGE_THINKING_BUDGET=8000 # Reasoning tokens per call. 0 disables
130+ # thinking on Claude/Gemini (OpenAI reasoning
131+ # models clamp to "medium").
132+ EVAL_JUDGE_MAX_TOKENS=16000 # Max output tokens per judge call. Generous
133+ # on purpose so thinking + final scored
134+ # output fit on hard scenarios.
67135```
68136
69137## Usage
@@ -259,26 +327,57 @@ bun run typecheck # Type-check
259327
260328### Adding a Judge Provider
261329
262- Create ` src/evaluator/providers/your-provider.ts ` implementing ` JudgeProvider ` :
330+ Three steps:
331+
332+ ** 1.** Create ` src/evaluator/providers/your-provider.ts ` implementing ` JudgeProvider ` :
263333
264334``` typescript
265- import type { JudgeProvider } from " ../../types"
335+ import type { JudgeProvider , JudgePrompt , TokenUsage } from " ../../types"
266336
267- export class YourProvider implements JudgeProvider {
337+ export class YourJudgeProvider implements JudgeProvider {
268338 name = " your-model"
269339 model: string
340+ usage: TokenUsage = { inputTokens: 0 , outputTokens: 0 , totalTokens: 0 }
270341
271342 constructor (apiKey : string , model = " your-model-id" ) {
272343 this .model = model
273344 }
274345
275- async complete(prompt : string ): Promise <string > {
276- // Call your LLM API, return raw text
346+ async complete(prompt : string | JudgePrompt ): Promise <string > {
347+ // Call your LLM API, return raw text. Populate this.usage along the way.
277348 }
278349}
279350```
280351
281- Register it in ` src/evaluator/providers/factory.ts ` .
352+ ** 2.** Add a variant to the ` JudgeProviderConfig ` discriminated union in ` src/types.ts ` :
353+
354+ ``` typescript
355+ export interface YourJudgeConfig {
356+ type: " your-provider"
357+ model: string
358+ apiKey: string
359+ }
360+
361+ export type JudgeProviderConfig =
362+ | ClaudeJudgeConfig
363+ | ClaudeVertexJudgeConfig
364+ | GeminiJudgeConfig
365+ | GeminiVertexJudgeConfig
366+ | OpenAIJudgeConfig
367+ | YourJudgeConfig // ← add here
368+ ` ` `
369+
370+ **3.** Add a ` case ` to the factory's ` switch ` in ` src /evaluator /providers /factory .ts ` :
371+
372+ ` ` ` typescript
373+ case " your-provider" :
374+ return new YourJudgeProvider (config .apiKey , config .model )
375+ ```
376+
377+ TypeScript's exhaustive ` never ` check in the factory's ` default ` branch will flag any new variant that lacks a case at build time.
378+
379+ To register the new judge from CLI / MCP based on env vars, mirror the
380+ ` buildJudgeConfigs ` pattern in ` src/cli.ts ` and ` src/mcp/server.ts ` .
282381
283382### Runtime Integration
284383
0 commit comments