Skip to content

Commit 3d39208

Browse files
authored
Merge pull request #3 from kamranjiwani-knox/main
feat: Vertex AI dual-mode judges + discriminated JudgeProviderConfig
2 parents a5bbd16 + b6c10a3 commit 3d39208

11 files changed

Lines changed: 862 additions & 153 deletions

File tree

README.md

Lines changed: 110 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
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

2934
1. **Load scenarios** from `.paddock/scenarios/` in the target project (YAML files organized by category)
3035
2. **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
3237
4. **Consensus**: median scores + majority vote → pass/fail/partial
3338
5. **If failing**: analyzer finds patterns, patcher generates code fixes, sandbox validates (type-check + build)
3439
6. **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)
5668
CLAUDE_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)
6072
GEMINI_API_KEY=AIza...
6173
OPENAI_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
64121
EVAL_REPO_ROOT=/path/to/agent-repo
65122
EVAL_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

Comments
 (0)