feat(model): implement OpenAI/Anthropic cloud backends + cloud-aware judge gate
The cloud branch of model-call.py was a stub (cloud_backend_not_implemented, failed even with a key set); casan-step.mjs gated the judge on a hard-coded Ollama ping. Wire up the real cloud path so a model can run without Ollama. - model-call.py: add call_openai() and call_anthropic() (raw urllib, no new dependency — matches the existing call_ollama). Endpoints hard-pinned to the SSRF allowlist; keys read from env, never logged. Anthropic sends no temperature/thinking (rejected as 400 on Opus 4.8/4.7; omitting thinking keeps the terse one-word classify/judge answer). main() routes by ollama:/openai:/anthropic: prefix; key-unset still fails closed honestly. provider-usage.jsonl cost_source is per-backend, keeping ollama's exact "ollama_local_real_tokens" tag that evidence/tests key on. - casan-step.mjs: ollamaAvailable() -> modelAvailable() — when CASAN_MODEL_PRIMARY is a cloud spec with its key set, the judge runs through the cloud path; otherwise it pings local Ollama as before. Default (unset CASAN_MODEL_PRIMARY) is unchanged. - CASAN_MASTER_RUNBOOK.md: update sections 0/1/4/7/8 — cloud is now implemented (not a stub); keep the honest "untested with a real key" + CA-cert caveats. Not verified against a live API key (none available); confirmed key-set makes a real HTTPS call and key-unset fails closed. Gates unchanged: security-gate PASS=11 FAIL=0, adversarial PASS=44 FAIL=0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c08d119381
commit
392f190b7e
@@ -19,7 +19,15 @@ try {
|
||||
/* standalone copy: keep silent */
|
||||
}
|
||||
|
||||
function ollamaAvailable() {
|
||||
// Is a model backend reachable for the judge gate? Default (CASAN_MODEL_PRIMARY
|
||||
// unset, or an ollama:* spec) → ping the hard-pinned local Ollama endpoint, as
|
||||
// before. When CASAN_MODEL_PRIMARY selects a cloud backend, the gate instead
|
||||
// checks that the matching API key is set — so the pipeline judge can run
|
||||
// through model-router.sh → model-call.py's cloud path without needing Ollama.
|
||||
function modelAvailable() {
|
||||
const spec = process.env.CASAN_MODEL_PRIMARY || 'ollama:ornith:9b';
|
||||
if (spec.startsWith('openai:')) return Boolean(process.env.OPENAI_API_KEY);
|
||||
if (spec.startsWith('anthropic:')) return Boolean(process.env.ANTHROPIC_API_KEY);
|
||||
try {
|
||||
const r = spawnSync('curl', ['-sS', '-m', '3', 'http://127.0.0.1:11434/api/tags'], { timeout: 5000 });
|
||||
return r.status === 0;
|
||||
@@ -29,9 +37,9 @@ function ollamaAvailable() {
|
||||
}
|
||||
|
||||
function judgeArtifact(filePath, criteria) {
|
||||
if (!ollamaAvailable()) {
|
||||
logDebug(`judge skipped (ollama_unavailable) artifact=${filePath}`);
|
||||
return { verdict: 'SKIP', note: 'ollama_unavailable' };
|
||||
if (!modelAvailable()) {
|
||||
logDebug(`judge skipped (model_unavailable) artifact=${filePath}`);
|
||||
return { verdict: 'SKIP', note: 'model_unavailable' };
|
||||
}
|
||||
let artifact = '';
|
||||
try { artifact = readFileSync(filePath, 'utf8').slice(0, 2000); } catch { return { verdict: 'SKIP', note: 'artifact_unreadable' }; }
|
||||
|
||||
Reference in New Issue
Block a user