mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
af4140efb9
Apply Phoenix Architecture principles to AQE — regenerate internals, conserve the interface. ADR-113 (evals are oracles, durable-first tests, mutation-score gate): - src/validation/oracle-eval.ts grades a generated test by running it against a reference impl + operator mutants (reuses qe-arena, ADR-104); rejects assertion-less output - oracle mode wired into scripts/run-skill-eval.ts (skips under the simulating runner; runs live) - qe-test-generation rewritten durable-first; its eval flipped to oracle - src/feedback/regenerability-gate.ts: mutation-score + regenerability gate (warn-default, opt-in block) - live-validated: Claude sonnet-4-6 clears the oracle 5/5; weak local-model output correctly fails ADR-114 (conservation layer, Phoenix essay 14): - src/validation/conservation-guard.ts + scripts/conservation-guard.ts guard CLI commands, output schemas, and dashboard API against breaking removals (deprecation registry exempts) - policy doc + baselines in verification/conservation/; wired into invariant-check.yml non-blocking LLM provider fixes: - OpenRouterProvider.type was 'openai' but registered under 'openrouter' -> ProviderManager failover never found it; fixed to 'openrouter' end-to-end - Gemini default model gemini-1.5-pro was retired -> gemini-2.5-flash; models/pricing refreshed from the live Gemini ListModels API - scripts/provider-health.ts (npm run providers:health) Cheaper-model eval lanes: - scripts/oracle-model-bench.ts + openrouter-models.ts; openai/gpt-oss-120b clears the oracle ~50-70x cheaper than Claude Sonnet; guide in docs/guides/cheaper-model-eval-lanes.md Verification: npm run build ok; 115 new+touched tests pass; 724 LLM tests pass; lint + parity clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
89 lines
4.1 KiB
TypeScript
89 lines
4.1 KiB
TypeScript
#!/usr/bin/env tsx
|
|
/**
|
|
* LLM provider health check (ADR-043 multi-provider). For each of AQE's 7 provider
|
|
* routers: report whether credentials are present and, if so, do a minimal live
|
|
* `generate` and report ok/latency. Loads .env; never prints key values.
|
|
*
|
|
* tsx scripts/provider-health.ts
|
|
*/
|
|
|
|
import { readFileSync, existsSync } from 'fs';
|
|
if (existsSync('.env')) {
|
|
for (const line of readFileSync('.env', 'utf8').split('\n')) {
|
|
const m = /^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)\s*$/.exec(line);
|
|
if (m && !process.env[m[1]]) process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
|
|
}
|
|
}
|
|
|
|
import { ClaudeProvider } from '../src/shared/llm/providers/claude.js';
|
|
import { OpenAIProvider } from '../src/shared/llm/providers/openai.js';
|
|
import { OllamaProvider } from '../src/shared/llm/providers/ollama.js';
|
|
import { OpenRouterProvider } from '../src/shared/llm/providers/openrouter.js';
|
|
import { GeminiProvider } from '../src/shared/llm/providers/gemini.js';
|
|
import type { LLMProvider } from '../src/shared/llm/interfaces.js';
|
|
|
|
const has = (...keys: string[]) => keys.some((k) => !!process.env[k]);
|
|
const PROMPT = 'Reply with exactly: OK';
|
|
|
|
interface Check { name: string; type: string; cred: boolean; result: string }
|
|
|
|
async function tryGen(p: LLMProvider): Promise<string> {
|
|
const t = Date.now();
|
|
try {
|
|
const res = await p.generate(PROMPT, { maxTokens: 16, temperature: 0, skipCache: true, timeoutMs: 20000 } as never);
|
|
const dt = ((Date.now() - t) / 1000).toFixed(1);
|
|
const content = (res.content ?? '').trim().replace(/\s+/g, ' ').slice(0, 30);
|
|
return `✓ WORKS (${dt}s, type=${res.provider}, "${content}")`;
|
|
} catch (e) {
|
|
return `✗ ERROR: ${(e instanceof Error ? e.message : String(e)).slice(0, 70)}`;
|
|
}
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const cfg = { maxRetries: 1, timeoutMs: 20000 } as never;
|
|
const checks: Check[] = [];
|
|
|
|
// Claude
|
|
{
|
|
const cred = has('ANTHROPIC_API_KEY');
|
|
const p = new ClaudeProvider({ model: 'claude-haiku-4-5-20251001', ...cfg } as never);
|
|
checks.push({ name: 'claude', type: 'claude', cred, result: cred ? await tryGen(p) : 'no ANTHROPIC_API_KEY — skipped' });
|
|
}
|
|
// OpenAI
|
|
{
|
|
const cred = has('OPENAI_API_KEY');
|
|
const p = new OpenAIProvider({ model: 'gpt-4o-mini', ...cfg } as never);
|
|
checks.push({ name: 'openai', type: 'openai', cred, result: cred ? await tryGen(p) : 'no OPENAI_API_KEY — skipped' });
|
|
}
|
|
// OpenRouter
|
|
{
|
|
const cred = has('OPENROUTER_API_KEY');
|
|
const p = new OpenRouterProvider({ model: 'mistralai/mistral-small-3.2-24b-instruct', ...cfg } as never);
|
|
checks.push({ name: 'openrouter', type: 'openrouter', cred, result: cred ? await tryGen(p) : 'no OPENROUTER_API_KEY — skipped' });
|
|
}
|
|
// Gemini
|
|
{
|
|
const cred = has('GEMINI_API_KEY', 'GOOGLE_AI_API_KEY', 'GOOGLE_API_KEY');
|
|
const p = new GeminiProvider({ ...cfg } as never); // uses provider default model
|
|
checks.push({ name: 'gemini', type: 'gemini', cred, result: cred ? await tryGen(p) : 'no GEMINI/GOOGLE_AI_API_KEY — skipped' });
|
|
}
|
|
// Ollama (local, no key)
|
|
{
|
|
const base = process.env.OLLAMA_BASE_URL ?? 'http://host.docker.internal:11434';
|
|
const p = new OllamaProvider({ model: 'qwen3:8b', baseUrl: base, ...cfg } as never);
|
|
checks.push({ name: 'ollama', type: 'ollama', cred: true, result: await tryGen(p) });
|
|
}
|
|
// Azure / Bedrock — config-heavy; report credential presence only
|
|
checks.push({ name: 'azure-openai', type: 'azure-openai', cred: has('AZURE_OPENAI_API_KEY'), result: has('AZURE_OPENAI_API_KEY') ? 'creds present (needs deployment config to test)' : 'no AZURE_OPENAI_* — skipped' });
|
|
checks.push({ name: 'bedrock', type: 'bedrock', cred: has('AWS_ACCESS_KEY_ID'), result: has('AWS_ACCESS_KEY_ID') ? 'creds present (needs region/model config to test)' : 'no AWS_* — skipped' });
|
|
|
|
console.log('\nAQE LLM provider health\n');
|
|
console.log('provider cred result');
|
|
for (const c of checks) {
|
|
console.log(`${c.name.padEnd(14)} ${(c.cred ? 'yes' : 'no').padEnd(5)} ${c.result}`);
|
|
}
|
|
process.exit(0);
|
|
}
|
|
|
|
main().catch((e) => { console.error('health error:', e instanceof Error ? e.message : String(e)); process.exit(1); });
|