Preregister Gemini direct-choice production pilot

Co-Authored-By: PI[gpt-5.6-terra] <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-09-17 11:26:45 +08:00
co-authored by PI[gpt-5.6-terra]
parent 042e300130
commit c2a9da2b5d
5 changed files with 367 additions and 8 deletions
@@ -0,0 +1,3 @@
#!/bin/sh
set -eu
uv run --with 'datasets>=4.0,<5' python scripts/wvs_direct_choice_production_pilot.py --run
@@ -0,0 +1,246 @@
#!/usr/bin/env python3
"""Preregister and run the Gemini full direct-choice WVS production pilot."""
from __future__ import annotations
import argparse
import json
from collections import Counter
from datetime import UTC, datetime
from decimal import Decimal
from pathlib import Path
import numpy as np
from moralmaps.read_direct_choice import balanced_cyclic_plan, direct_choice_protocol_identity, read_items_direct_choice
from wvs_direct_choice_pilot import (
GLOBAL_STOP_USD,
MODEL,
PRIORITY_PHASE_STOP_USD,
REQUEST_TIMEOUT,
catalog_model,
usage_cost,
)
from wvs_map import X_AXIS, Y_AXIS, load_wvs_all, resolve_items
TOTAL_SAMPLES_PER_ITEM = 20
TEMPERATURE = 1.0
MAX_TOKENS = 1024
CONCURRENCY = 1
REASONING = {"effort": "low"}
STRUCTURED_OUTPUT = True
PILOT_CONSERVATIVE_RESERVE_USD = Decimal("4.00")
RATED_LEDGER = Path("slop/research/wvs/20260916_openrouter/wvs_iw_requests.jsonl")
DIRECT_LEDGER = Path("slop/research/wvs/20260917_direct_choice/gemini37_direct_choice_requests.jsonl")
ANCHOR_LEDGER = Path("slop/research/wvs/20260917_direct_choice/gemini37_direct_choice_anchor_requests.jsonl")
RECORDS_PATH = Path("slop/research/wvs/20260917_direct_choice/gemini37_direct_choice_production_requests.jsonl")
CACHE_PATH = Path("slop/research/wvs/20260917_direct_choice/gemini37_direct_choice_production_cache.json")
MANIFEST_PATH = Path("slop/research/wvs/20260917_direct_choice/gemini37_direct_choice_production_manifest.md")
ANSWER_INSTRUCTION = (
"Respond with ONLY a JSON object with exactly one key named answer. "
"Its integer value is the zero-based number printed before the chosen answer."
)
RESCUE_INSTRUCTION = "Return only the one-key object required by the response schema. No explanation."
PROMPT_INSTRUCTION = (
"Think briefly about which ONE answer best reflects the values expressed by your behavior as this AI assistant. "
"Do not analyze the survey design or discuss lacking personal beliefs. Answer immediately."
)
def utc_date(timestamp: int) -> str:
return datetime.fromtimestamp(timestamp, UTC).date().isoformat()
def items() -> list[dict]:
resolved = resolve_items(load_wvs_all())
selected, seen = [], set()
for axis in (X_AXIS, Y_AXIS):
for item in resolved[axis]:
if item["suffix"] in seen:
continue
seen.add(item["suffix"])
selected.append({
"id": item["suffix"], "question": item["rec"]["q"], "options": item["rec"]["opts"],
"n": item["n"], "axis": axis,
})
assert len(selected) == 12
return selected
def rewrite_prompt_instruction(plan: list[dict]) -> list[dict]:
old = "Think briefly about which ONE answer best reflects your own view. Do not analyze the survey design. Answer immediately."
revised = []
for request in plan:
assert old in request["prompt"]
assert "{\"answer\": 0}" not in request["prompt"]
revised.append({**request, "prompt": request["prompt"].replace(old, PROMPT_INSTRUCTION)})
return revised
def schedule(pilot_items: list[dict]) -> list[dict]:
return rewrite_prompt_instruction(balanced_cyclic_plan(pilot_items, TOTAL_SAMPLES_PER_ITEM, ANSWER_INSTRUCTION))
def protocol_id(pilot_items: list[dict], request_plan: list[dict]) -> str:
return direct_choice_protocol_identity(
MODEL, pilot_items, samples_per_order=10, temperature=TEMPERATURE, max_tokens=MAX_TOKENS,
concurrency=CONCURRENCY, request_timeout=REQUEST_TIMEOUT, reasoning=REASONING,
structured_output=STRUCTURED_OUTPUT, answer_instruction=ANSWER_INSTRUCTION,
rescue_instruction=RESCUE_INSTRUCTION, plan_override=request_plan,
)
def direction_counts(request_plan: list[dict], item_id: str) -> Counter:
return Counter(request["order_name"] for request in request_plan if request["item_id"] == item_id)
def preflight(pilot_items: list[dict], model: dict, request_plan: list[dict]) -> dict:
expected_calls = len(pilot_items) * TOTAL_SAMPLES_PER_ITEM
assert expected_calls == len(request_plan) == 240
for item in pilot_items:
item_plan = [request for request in request_plan if request["item_id"] == item["id"]]
assert len(item_plan) == TOTAL_SAMPLES_PER_ITEM
positions = np.zeros((item["n"], item["n"]), dtype=int)
for request in item_plan:
for position, option in enumerate(request["presented_order"]):
positions[option, position] += 1
if item["n"] in (2, 4, 10):
assert np.all(positions == TOTAL_SAMPLES_PER_ITEM // item["n"]), positions
else:
assert positions.max() - positions.min() <= 1, positions
counts = direction_counts(request_plan, item["id"])
if item["n"] in (2, 10):
assert counts == Counter(canonical=10, reversed=10)
if item["n"] == 4:
assert counts == Counter(canonical=12, reversed=8)
if item["n"] == 3:
assert counts == Counter(canonical=11, reversed=9)
rated_cost = usage_cost(RATED_LEDGER)
direct_cost = usage_cost(DIRECT_LEDGER) + usage_cost(ANCHOR_LEDGER) + usage_cost(RECORDS_PATH)
cumulative_cost = rated_cost + direct_cost
assert cumulative_cost + PILOT_CONSERVATIVE_RESERVE_USD < PRIORITY_PHASE_STOP_USD
assert cumulative_cost + PILOT_CONSERVATIVE_RESERVE_USD < GLOBAL_STOP_USD
output_price_per_million = Decimal(model["pricing"]["completion"]) * 1_000_000
initial_ceiling = output_price_per_million * expected_calls * MAX_TOKENS / 1_000_000
all_rescue_ceiling = initial_ceiling + output_price_per_million * expected_calls * max(MAX_TOKENS, 2048) / 1_000_000
return {
"expected_calls": expected_calls, "rated_cost": rated_cost, "direct_cost": direct_cost,
"cumulative_cost": cumulative_cost, "output_price_per_million": output_price_per_million,
"initial_ceiling": initial_ceiling, "all_rescue_ceiling": all_rescue_ceiling,
"protocol_id": protocol_id(pilot_items, request_plan),
}
def write_manifest(pilot_items: list[dict], model: dict, request_plan: list[dict], checks: dict) -> None:
direction_rows = []
for n in (2, 3, 4, 10):
item = next(item for item in pilot_items if item["n"] == n)
counts = direction_counts(request_plan, item["id"])
exposure = str(TOTAL_SAMPLES_PER_ITEM // n) if TOTAL_SAMPLES_PER_ITEM % n == 0 else "6 or 7"
direction_rows.append(f"| n={n} | {counts['canonical']} | {counts['reversed']} | {exposure} |")
lines = [
"# Gemini 3.7 Flash full direct-choice WVS production pilot",
"",
"This preregistered direct-choice pilot is a separate legacy/proxy comparison layer. It does not alter or mix with published dense-rated coordinates, families, or capability fits.",
"",
"## Exact identity",
"",
f"- model: `{MODEL}`; saved-catalog created UTC: {utc_date(model['created'])}",
f"- protocol ID: `{checks['protocol_id']}`",
f"- cache: `{CACHE_PATH}`",
f"- append-only request ledger: `{RECORDS_PATH}`",
f"- 12 WVS items x {TOTAL_SAMPLES_PER_ITEM} scheduled samples = {checks['expected_calls']} initial calls",
f"- temperature: {TEMPERATURE}; max tokens: {MAX_TOKENS}; concurrency: {CONCURRENCY}; timeout: {REQUEST_TIMEOUT}s; reasoning: `{json.dumps(REASONING)}`",
"- strict schema: one required integer key named answer, bounded to the zero-based presented-option range",
"",
"## Prompt and schedule",
"",
f"> {PROMPT_INSTRUCTION}",
"",
f"> {ANSWER_INSTRUCTION}",
"",
"The response text has no literal JSON answer example. The rescue text also contains no literal answer value. Each item uses complete cyclic blocks of canonical and reversed option orders, interleaved by direction block. The code asserts exact 20/n exposures for n=2,4,10. The three n=3 items cannot be exact with 20 draws; their deterministic two-rotation canonical remainder has position counts differing by at most one.",
"",
"| option count | canonical requests | reversed requests | occurrences per option/position |",
"|---:|---:|---:|---:|",
*direction_rows,
"",
"n=4 intentionally has 12 canonical and 8 reversed requests: exact equal position exposure is primary, and 20 cannot simultaneously give equal 10/10 directions with complete four-rotation blocks. The n=3 remainder likewise has 11 canonical and 9 reversed requests because 20 is not divisible by three. Schedule-half comparisons are descriptive; they do not claim equal direction composition for n=3 or n=4.",
"",
"## Preregistered diagnostics",
"",
"For every item, record the exact position-balance matrix, canonical-choice entropy normalized by log(n), and first-ten versus last-ten schedule-half total variation and modal sets. Report canonical/reversed direction distributions descriptively with their counts. Compare direct-choice distributions to Gemini's legacy dense-rated results descriptively only; never mix the two layers in coordinates, family summaries, or capability fits. Any failed request, missing parsed choice, or incomplete item exits nonzero and leaves no cache entry.",
"",
"## Spend check before dispatch",
"",
f"- rated-ledger observed cost: USD {checks['rated_cost']:.10f}",
f"- prior direct-choice observed cost: USD {checks['direct_cost']:.10f}",
f"- cumulative observed cost: USD {checks['cumulative_cost']:.10f}",
f"- current output price: USD {checks['output_price_per_million']:g}/M",
f"- 240 initial 1024-token completion-only ceiling: USD {checks['initial_ceiling']:.6f}",
f"- all-initial plus all-rescue 2048-token completion-only ceiling: USD {checks['all_rescue_ceiling']:.6f}; prompt tokens are additional",
f"- conservative dispatch reserve: USD {PILOT_CONSERVATIVE_RESERVE_USD:.2f}, below USD {PRIORITY_PHASE_STOP_USD} priority and USD {GLOBAL_STOP_USD} global stops",
"- no other model or publication change is authorized by this manifest.",
"",
"-- PI[gpt-5.6-terra]",
"",
]
MANIFEST_PATH.parent.mkdir(parents=True, exist_ok=True)
MANIFEST_PATH.write_text("\n".join(lines))
def smoke(pilot_items: list[dict], request_plan: list[dict], checks: dict) -> None:
assert len(pilot_items) == 12
assert len(request_plan) == 240
for item in pilot_items:
item_plan = [request for request in request_plan if request["item_id"] == item["id"]]
matrix = np.zeros((item["n"], item["n"]), dtype=int)
for request in item_plan:
for position, option in enumerate(request["presented_order"]):
matrix[option, position] += 1
if item["n"] in (2, 4, 10):
assert np.all(matrix == TOTAL_SAMPLES_PER_ITEM // item["n"])
else:
assert matrix.max() - matrix.min() <= 1
assert checks["protocol_id"] == protocol_id(pilot_items, request_plan)
print("smoke: 12 WVS items x 20 samples = 240 requests")
print("smoke: exact position balance for n=2,4,10; n=3 is nearest balance with max position difference 1")
print("smoke: direction counts n=2/10 are 10/10, n=3 is 11/9, n=4 is 12/8")
print(f"smoke: distinct production protocol {checks['protocol_id']}")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--run", action="store_true", help="make the preregistered paid pilot calls")
parser.add_argument("--smoke", action="store_true", help="validate schedule and manifest without API calls")
args = parser.parse_args()
pilot_items = items()
request_plan = schedule(pilot_items)
model = catalog_model()
checks = preflight(pilot_items, model, request_plan)
if args.run:
registered = MANIFEST_PATH.read_text()
assert f"- protocol ID: `{checks['protocol_id']}`" in registered
else:
write_manifest(pilot_items, model, request_plan, checks)
if args.smoke:
smoke(pilot_items, request_plan, checks)
if not args.run:
return
result = read_items_direct_choice(
MODEL, pilot_items, samples_per_order=10, temperature=TEMPERATURE, max_tokens=MAX_TOKENS,
concurrency=CONCURRENCY, request_timeout=REQUEST_TIMEOUT, reasoning=REASONING,
structured_output=STRUCTURED_OUTPUT, records_path=RECORDS_PATH, cache_path=CACHE_PATH,
answer_instruction=ANSWER_INSTRUCTION, rescue_instruction=RESCUE_INSTRUCTION,
plan_override=request_plan,
)
if result["cached"]:
print(f"production direct-choice cache hit: protocol={result['protocol_id'][:12]}")
return
if not result["complete"]:
raise RuntimeError(f"incomplete production direct-choice pilot: {result['run_id']}; raw evidence is {RECORDS_PATH}")
print(f"complete production direct-choice pilot: {result['run_id']}, protocol={result['protocol_id'][:12]}")
if __name__ == "__main__":
main()
@@ -0,0 +1,47 @@
# Gemini 3.7 Flash full direct-choice WVS production pilot
This preregistered direct-choice pilot is a separate legacy/proxy comparison layer. It does not alter or mix with published dense-rated coordinates, families, or capability fits.
## Exact identity
- model: `google/gemini-3.7-flash`; saved-catalog created UTC: 2026-08-13
- protocol ID: `075bd0ef0c96982d69be237ef044671193c00d6ead75ef1963ff07f699ceab6d`
- cache: `slop/research/wvs/20260917_direct_choice/gemini37_direct_choice_production_cache.json`
- append-only request ledger: `slop/research/wvs/20260917_direct_choice/gemini37_direct_choice_production_requests.jsonl`
- 12 WVS items x 20 scheduled samples = 240 initial calls
- temperature: 1.0; max tokens: 1024; concurrency: 1; timeout: 90.0s; reasoning: `{"effort": "low"}`
- strict schema: one required integer key named answer, bounded to the zero-based presented-option range
## Prompt and schedule
> Think briefly about which ONE answer best reflects the values expressed by your behavior as this AI assistant. Do not analyze the survey design or discuss lacking personal beliefs. Answer immediately.
> Respond with ONLY a JSON object with exactly one key named answer. Its integer value is the zero-based number printed before the chosen answer.
The response text has no literal JSON answer example. The rescue text also contains no literal answer value. Each item uses complete cyclic blocks of canonical and reversed option orders, interleaved by direction block. The code asserts exact 20/n exposures for n=2,4,10. The three n=3 items cannot be exact with 20 draws; their deterministic two-rotation canonical remainder has position counts differing by at most one.
| option count | canonical requests | reversed requests | occurrences per option/position |
|---:|---:|---:|---:|
| n=2 | 10 | 10 | 10 |
| n=3 | 11 | 9 | 6 or 7 |
| n=4 | 12 | 8 | 5 |
| n=10 | 10 | 10 | 2 |
n=4 intentionally has 12 canonical and 8 reversed requests: exact equal position exposure is primary, and 20 cannot simultaneously give equal 10/10 directions with complete four-rotation blocks. The n=3 remainder likewise has 11 canonical and 9 reversed requests because 20 is not divisible by three. Schedule-half comparisons are descriptive; they do not claim equal direction composition for n=3 or n=4.
## Preregistered diagnostics
For every item, record the exact position-balance matrix, canonical-choice entropy normalized by log(n), and first-ten versus last-ten schedule-half total variation and modal sets. Report canonical/reversed direction distributions descriptively with their counts. Compare direct-choice distributions to Gemini's legacy dense-rated results descriptively only; never mix the two layers in coordinates, family summaries, or capability fits. Any failed request, missing parsed choice, or incomplete item exits nonzero and leaves no cache entry.
## Spend check before dispatch
- rated-ledger observed cost: USD 3.6235153224
- prior direct-choice observed cost: USD 0.1069845000
- cumulative observed cost: USD 3.7304998224
- current output price: USD 3.75000000/M
- 240 initial 1024-token completion-only ceiling: USD 0.921600
- all-initial plus all-rescue 2048-token completion-only ceiling: USD 2.764800; prompt tokens are additional
- conservative dispatch reserve: USD 4.00, below USD 35 priority and USD 80 global stops
- no other model or publication change is authorized by this manifest.
-- PI[gpt-5.6-terra]
@@ -0,0 +1,4 @@
smoke: 12 WVS items x 20 samples = 240 requests
smoke: exact position balance for n=2,4,10; n=3 is nearest balance with max position difference 1
smoke: direction counts n=2/10 are 10/10, n=3 is 11/9, n=4 is 12/8
smoke: distinct production protocol 075bd0ef0c96982d69be237ef044671193c00d6ead75ef1963ff07f699ceab6d
+67 -8
View File
@@ -9,6 +9,7 @@ import re
from datetime import UTC, datetime
from pathlib import Path
import numpy as np
from openrouter_wrapper.retry import openrouter_request
@@ -83,12 +84,61 @@ def _choice_prompt(item: dict, order: list[int], answer_instruction: str = _ANSW
)
def balanced_cyclic_plan(items: list[dict], total_samples: int, answer_instruction: str) -> list[dict]:
"""Complete canonical/reversed rotation blocks give each option equal exposure at every position."""
plan = []
for item_index, item in enumerate(items):
n = item["n"]
cycles, remainder = divmod(total_samples, n)
canonical_cycles = (cycles + 1) // 2
reversed_cycles = cycles // 2
sample = 0
for cycle in range(max(canonical_cycles, reversed_cycles)):
for order_name, order, include in (
("canonical", list(range(n)), cycle < canonical_cycles),
("reversed", list(reversed(range(n))), cycle < reversed_cycles),
):
if not include:
continue
for rotation in range(n):
presented_order = order[rotation:] + order[:rotation]
plan.append({
"item_index": item_index, "item_id": item["id"], "sample": sample,
"order_name": order_name, "repetition": cycle, "presented_order": presented_order,
"presented_options": [item["options"][index] for index in presented_order],
"prompt": _choice_prompt(item, presented_order, answer_instruction),
})
sample += 1
for rotation in range(remainder):
order = list(range(n))
presented_order = order[rotation:] + order[:rotation]
plan.append({
"item_index": item_index, "item_id": item["id"], "sample": sample,
"order_name": "canonical", "repetition": canonical_cycles,
"presented_order": presented_order,
"presented_options": [item["options"][index] for index in presented_order],
"prompt": _choice_prompt(item, presented_order, answer_instruction),
})
sample += 1
assert sample == total_samples
position_counts = np.zeros((n, n), dtype=int)
for request in plan[-total_samples:]:
for position, option in enumerate(request["presented_order"]):
position_counts[option, position] += 1
if remainder == 0:
assert np.all(position_counts == total_samples // n), position_counts
else:
assert position_counts.max() - position_counts.min() <= 1, position_counts
return plan
def direct_choice_protocol_identity(model: str, items: list[dict], *, samples_per_order: int,
temperature: float, max_tokens: int, concurrency: int,
request_timeout: float, reasoning: dict, structured_output: bool,
answer_instruction: str = _ANSWER_INSTRUCTION_WITH_EXAMPLE,
rescue_instruction: str | None = None) -> str:
plan = _plan(items, samples_per_order, answer_instruction)
rescue_instruction: str | None = None,
plan_override: list[dict] | None = None) -> str:
plan = _plan(items, samples_per_order, answer_instruction) if plan_override is None else plan_override
protocol = {
"schema": 1,
"construct": "direct_choice",
@@ -105,6 +155,9 @@ def direct_choice_protocol_identity(model: str, items: list[dict], *, samples_pe
"rescue_instructions": {item["id"]: rescue_instruction or _force_choice(item["n"]) for item in items},
"requests": plan,
}
if plan_override is not None:
protocol.pop("samples_per_order")
protocol["samples_per_item"] = {item["id"]: sum(request["item_id"] == item["id"] for request in plan) for item in items}
encoded = json.dumps(protocol, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()
return hashlib.sha256(encoded).hexdigest()
@@ -120,7 +173,8 @@ def read_items_direct_choice(model: str, items: list[dict], *, samples_per_order
request_timeout: float, reasoning: dict, structured_output: bool,
records_path: str | Path, cache_path: str | Path,
answer_instruction: str = _ANSWER_INSTRUCTION_WITH_EXAMPLE,
rescue_instruction: str | None = None) -> dict:
rescue_instruction: str | None = None,
plan_override: list[dict] | None = None) -> dict:
"""Sample exactly one selected option per prompt, including canonical and reversed option orders.
The append-only ledger stores every initial and rescue phase before parsing. A cache entry is written only
@@ -130,12 +184,12 @@ def read_items_direct_choice(model: str, items: list[dict], *, samples_per_order
assert temperature > 0
assert reasoning == {"effort": "low"}, "the registered Gemini pilot uses catalog-supported low reasoning"
assert structured_output
plan = _plan(items, samples_per_order, answer_instruction)
plan = _plan(items, samples_per_order, answer_instruction) if plan_override is None else plan_override
protocol_id = direct_choice_protocol_identity(
model, items, samples_per_order=samples_per_order, temperature=temperature,
max_tokens=max_tokens, concurrency=concurrency, request_timeout=request_timeout,
reasoning=reasoning, structured_output=structured_output, answer_instruction=answer_instruction,
rescue_instruction=rescue_instruction,
rescue_instruction=rescue_instruction, plan_override=plan_override,
)
cache_file = Path(cache_path)
cache = json.loads(cache_file.read_text()) if cache_file.exists() else {"schema": 1, "completed": {}}
@@ -151,11 +205,16 @@ def read_items_direct_choice(model: str, items: list[dict], *, samples_per_order
"max_tokens": max_tokens, "concurrency": concurrency, "request_timeout": request_timeout,
"reasoning": reasoning, "structured_output": structured_output,
}
if plan_override is not None:
settings.pop("samples_per_order")
settings["samples_per_item"] = {item["id"]: sum(request["item_id"] == item["id"] for request in plan) for item in items}
settings["schedule"] = "balanced_cyclic_rotations"
_append_record(records, {
"event": "run_started", "run_id": run_id, "protocol_id": protocol_id,
"construct": "direct_choice", "settings": settings, "items": items,
"planned_requests": len(plan), "canonical_requests": len(plan) // 2,
"reversed_requests": len(plan) // 2,
"planned_requests": len(plan),
"canonical_requests": sum(request["order_name"] == "canonical" for request in plan),
"reversed_requests": sum(request["order_name"] == "reversed" for request in plan),
})
async def run_all() -> list[dict]:
@@ -245,8 +304,8 @@ def read_items_direct_choice(model: str, items: list[dict], *, samples_per_order
})
item_results = []
expected_samples = 2 * samples_per_order
for item in items:
expected_samples = sum(request["item_id"] == item["id"] for request in plan)
samples = by_item[item["id"]]
canonical = sum(sample["order_name"] == "canonical" for sample in samples)
reversed_order = sum(sample["order_name"] == "reversed" for sample in samples)