Skip to content

Commit 3b1add6

Browse files
committed
Run the behaviour check through the shim
The scripted behaviour check and the live phase-judge test posted to Google's URL by hand, so neither could follow CODETRIAL_GEMINI_REST_BASE the way the report does. Both now build the report's URL, so pointed at scripts/gemini-shim.py they send what production sends, tools included, and the judge test prints each judgment it applies. The played candidates keep BEHAVIOR_LOCAL_BASE for talking to an OpenAI-compatible server directly. Against gemma-4-12b with --thinking off the check takes about four minutes, and what it fails on is the model's behaviour, chiefly a hint request answered without log_hint, which is what the results collected in #257 compare.
1 parent 78b7aa1 commit 3b1add6

4 files changed

Lines changed: 39 additions & 17 deletions

File tree

‎README.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,14 @@ Gemini. Any base other than Google's gets longer report deadlines, 45 seconds
317317
a call and 250 in all instead of 20 and 125, since a 12B model on one 16 GB GPU
318318
takes 14 to 32 seconds per report.
319319

320+
The shim carries function calls too, so the interviewer behaviour check in
321+
[docs/development.md](docs/development.md#checks-outside-the-gate) runs against
322+
the same base. With Gemma 4, start it with `--thinking off`. That check names no
323+
thinking budget, and with thinking on gemma-4-12b sometimes repeated itself to
324+
the output limit or put its reasoning in the reply. With it off the check
325+
passed 18 of 21 problem runs across seven runs, about 24 seconds a run against
326+
240; each miss was a second hint request answered without calling `log_hint`.
327+
320328
## Recording
321329

322330
Recording is off by default. Enabling it requires a separate LiveKit project,

‎docs/development.md‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -238,11 +238,11 @@ reads them.
238238
## Checks outside the gate
239239

240240
Whether the interviewer actually follows the live prompt, rather than whether
241-
the prompt says the right things, needs a Gemini key. The check scripts a
242-
candidate through three problems against a text model given the same
243-
instructions, greeting and tools, and fails on a named source, a volunteered
244-
limit, an unanswered size question, or a hint that goes past the rung it was
245-
served:
241+
the prompt says the right things, needs a Gemini key or a local model. The
242+
check scripts a candidate through three problems against a text model given
243+
the same instructions, greeting and tools, and fails on a named source, a
244+
volunteered limit, an unanswered size question, or a hint that goes past the
245+
rung it was served:
246246

247247
```bash
248248
scripts/interview-behavior-check.sh
@@ -252,6 +252,19 @@ BEHAVIOR_PROBLEMS=3sum,lru-cache scripts/interview-behavior-check.sh
252252
A free key allows fifteen requests a minute, so the check waits out rate
253253
limits; three problems take about two minutes.
254254

255+
Against a local model, point it at `scripts/gemini-shim.py` the way the report
256+
is pointed, so the check sends what production sends through the shim the
257+
report uses. The key is still read but goes no further than the shim, which
258+
ignores it, so any non-empty value does when the config file has none:
259+
260+
```bash
261+
CODETRIAL_GEMINI_REST_BASE=http://127.0.0.1:8090 GOOGLE_API_KEY=local \
262+
scripts/interview-behavior-check.sh
263+
```
264+
265+
`BEHAVIOR_LOCAL_BASE` instead talks to an OpenAI-compatible server directly,
266+
without the shim; it is what the played candidates use for their own side.
267+
255268
The end-to-end browser check additionally needs Playwright and Chromium:
256269

257270
```bash

‎src/gemini.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2065,7 +2065,7 @@ fn tool_ack_pcm(
20652065
/// No `?key=` here on purpose. A `reqwest` error Displays the URL it was built
20662066
/// from, and this call's errors reach the candidate's browser in the report
20672067
/// failure note, so the credential travels in a header instead.
2068-
fn gemini_generate_content_url(model: &str) -> String {
2068+
pub fn gemini_generate_content_url(model: &str) -> String {
20692069
format!(
20702070
"{}/v1beta/models/{}:generateContent",
20712071
rest_base(),

‎tests/interview_behavior.rs‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,14 @@ fn the_rules_catch_a_named_source_and_a_volunteered_limit() {
240240
}
241241

242242
/// Who plays the interviewer. Gemini is the model production runs; a local
243-
/// OpenAI-compatible server (llama.cpp's `llama-server`) plays it for free,
244-
/// which makes it a fair stand-in for comparing candidates against one
245-
/// another and a poor one for judging Gemini.
243+
/// model plays it for free, which makes it a fair stand-in for comparing
244+
/// candidates against one another and a poor one for judging Gemini.
245+
///
246+
/// A local model is reached one of two ways. `Gemini` follows
247+
/// `CODETRIAL_GEMINI_REST_BASE` the way the report does, so pointed at
248+
/// `scripts/gemini-shim.py` it exercises the same request shape and the same
249+
/// shim the report uses. `Local` skips both and talks to an OpenAI-compatible
250+
/// server directly, translating here.
246251
#[derive(Clone)]
247252
enum Backend {
248253
Gemini { key: String, model: String },
@@ -396,10 +401,7 @@ impl Conversation {
396401
for _ in 0..8 {
397402
let response: Value = self
398403
.client
399-
.post(format!(
400-
"https://generativelanguage.googleapis.com/v1beta/models/{}:generateContent",
401-
model
402-
))
404+
.post(codetrial::gemini::gemini_generate_content_url(model))
403405
.header("x-goog-api-key", key)
404406
.json(&json!({
405407
"systemInstruction": { "parts": [{ "text": self.instructions }] },
@@ -1617,9 +1619,7 @@ async fn judge_phases(state: &mut RuntimeState, problem: &'static Problem) -> Ve
16171619
.filter(|model| !model.is_empty())
16181620
.unwrap_or_else(|| codetrial::config::DEFAULT_GEMINI_REPORT_MODEL.to_string());
16191621
let response: Value = reqwest::Client::new()
1620-
.post(format!(
1621-
"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent"
1622-
))
1622+
.post(codetrial::gemini::gemini_generate_content_url(&model))
16231623
.header("x-goog-api-key", gemini_key())
16241624
.json(&json!({
16251625
"systemInstruction": { "parts": [{ "text": phase_judge_system_instruction() }] },
@@ -1632,13 +1632,14 @@ async fn judge_phases(state: &mut RuntimeState, problem: &'static Problem) -> Ve
16321632
}))
16331633
.send()
16341634
.await
1635-
.expect("Gemini is reachable")
1635+
.expect("the report model is reachable")
16361636
.json()
16371637
.await
16381638
.expect("Gemini answers JSON");
16391639
let text = response["candidates"][0]["content"]["parts"][0]["text"]
16401640
.as_str()
16411641
.unwrap_or_else(|| panic!("no judgment: {response}"));
1642+
println!("judgment: {text}");
16421643
apply_phase_judgment(state, text);
16431644
framework_progress(state)
16441645
}

0 commit comments

Comments
 (0)