adaptive-memory-multi-model-router 2.10.0 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +56 -0
- package/LANDING.md +46 -0
- package/MANIFESTO.md +54 -0
- package/demo.sh +85 -0
- package/dist/cli/setupWizard.js +194 -0
- package/dist/cli.js +9 -2
- package/dist/routing/providerRetry.d.ts +5 -0
- package/dist/routing/providerRetry.js +37 -0
- package/dist/routing/providerRetry.js.map +1 -1
- package/docs/CHINESE_PROVIDER_RELIABILITY.md +37 -0
- package/docs/CLAIMS_AND_EVIDENCE.md +58 -0
- package/docs/ENGINEERING_SPEC.md +55 -0
- package/docs/RELEASE_CHECKLIST.md +32 -0
- package/docs/REPRODUCIBILITY.md +63 -0
- package/eval/README.md +46 -0
- package/eval/baselines/main.json +12 -0
- package/eval/benchmark_dataset.jsonl +16 -0
- package/eval/check_golden_routes.js +64 -0
- package/eval/datasets/catalog.json +33 -0
- package/eval/datasets/slices/cn_provider_reliability_v1.jsonl +3 -0
- package/eval/datasets/slices/cost_pressure_v1.jsonl +3 -0
- package/eval/datasets/slices/safety_guardrails_v1.jsonl +3 -0
- package/eval/fault_injection_thresholds.json +3 -0
- package/eval/generate_report.js +128 -0
- package/eval/golden_routes.json +114 -0
- package/eval/lib/experiment_registry.js +24 -0
- package/eval/run_eval.js +197 -0
- package/eval/run_fault_injection.js +201 -0
- package/eval/run_shadow_eval.js +85 -0
- package/eval/thresholds.json +9 -0
- package/package.json +541 -458
- package/pytest.ini +2 -0
- package/src/cli/setupWizard.ts +194 -0
- package/src/routing/providerRetry.ts +41 -1
- package/python/a3m/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/a3m/__pycache__/client.cpython-312.pyc +0 -0
- package/python/a3m/__pycache__/models.cpython-312.pyc +0 -0
- package/python/a3m/__pycache__/sync_client.cpython-312.pyc +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# A3M Engineering Spec (Canonical)
|
|
2
|
+
|
|
3
|
+
This is the canonical engineering behavior spec for A3M Router.
|
|
4
|
+
Marketing and launch content are non-canonical; if there is a conflict, this file wins.
|
|
5
|
+
|
|
6
|
+
## Core Routing Contract
|
|
7
|
+
|
|
8
|
+
- Input: `routeQuery(prompt: string, available_models?: string[], budget_multiplier?: number)`
|
|
9
|
+
- Output:
|
|
10
|
+
- `primary_model`
|
|
11
|
+
- `fallback_models`
|
|
12
|
+
- `confidence`
|
|
13
|
+
- `estimated_cost`
|
|
14
|
+
- `estimated_latency_ms`
|
|
15
|
+
- `features` (complexity + flags + domain)
|
|
16
|
+
- `provider_type`
|
|
17
|
+
|
|
18
|
+
## Reliability Components
|
|
19
|
+
|
|
20
|
+
- Retry handling:
|
|
21
|
+
- `ProviderRetryHandler` supports transient retries, backoff+jitter, and rate-limit handling.
|
|
22
|
+
- Health management:
|
|
23
|
+
- `ProviderHealthManager` maintains rolling health and circuit breaker states.
|
|
24
|
+
- Circuit breaker opens after configured consecutive failures.
|
|
25
|
+
- Fallback chain:
|
|
26
|
+
- Health-sorted fallback ordering with unavailable providers pushed down.
|
|
27
|
+
|
|
28
|
+
## Guardrails
|
|
29
|
+
|
|
30
|
+
- Input and output checks implemented in `src/security/guardrails.ts`.
|
|
31
|
+
- Includes prompt injection scoring, PII detection/redaction, and output validation hooks.
|
|
32
|
+
|
|
33
|
+
## Cost/Budget
|
|
34
|
+
|
|
35
|
+
- Budget enforcement and spend tracking:
|
|
36
|
+
- `src/cost/budgetEnforcer.ts`
|
|
37
|
+
- `src/cost/costTracker.ts`
|
|
38
|
+
|
|
39
|
+
## Proxy Server
|
|
40
|
+
|
|
41
|
+
- OpenAI-compatible endpoints implemented in `src/server/proxyServer.ts`.
|
|
42
|
+
- Expected behavior:
|
|
43
|
+
- Model resolution through mapper + router
|
|
44
|
+
- Provider call with fallback behavior
|
|
45
|
+
- Usage/cost logging for requests
|
|
46
|
+
|
|
47
|
+
## Validation Gates (Required)
|
|
48
|
+
|
|
49
|
+
- Node test suite: `npm test`
|
|
50
|
+
- Python tests: `npm run test:py`
|
|
51
|
+
- Routing eval: `npm run eval:routing`
|
|
52
|
+
- Golden routing regression: `npm run eval:golden`
|
|
53
|
+
- Fault injection reliability: `npm run eval:faults`
|
|
54
|
+
|
|
55
|
+
All gates above must pass for release readiness.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Release Checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist before tagging a release.
|
|
4
|
+
|
|
5
|
+
## Mandatory quality gates
|
|
6
|
+
|
|
7
|
+
- [ ] `npm test` passes
|
|
8
|
+
- [ ] `npm run test:py` passes
|
|
9
|
+
- [ ] `npm run eval:routing` passes
|
|
10
|
+
- [ ] `npm run eval:golden` passes
|
|
11
|
+
- [ ] `npm run eval:faults` passes
|
|
12
|
+
- [ ] `npm run eval:report` passes
|
|
13
|
+
|
|
14
|
+
## Evidence artifacts reviewed
|
|
15
|
+
|
|
16
|
+
- [ ] `eval/results/latest.json` reviewed for routing summary
|
|
17
|
+
- [ ] `eval/results/fault_injection_latest.json` reviewed for reliability scenarios
|
|
18
|
+
- [ ] `eval/results/shadow_latest.json` reviewed for divergence/cost deltas
|
|
19
|
+
- [ ] `eval/results/report_latest.md` attached to release review
|
|
20
|
+
- [ ] Any baseline change in `eval/baselines/main.json` is intentional and explained
|
|
21
|
+
|
|
22
|
+
## Documentation consistency
|
|
23
|
+
|
|
24
|
+
- [ ] `docs/ENGINEERING_SPEC.md` reflects current behavior
|
|
25
|
+
- [ ] `docs/CLAIMS_AND_EVIDENCE.md` mappings are still valid
|
|
26
|
+
- [ ] Public claims do not exceed available evidence
|
|
27
|
+
|
|
28
|
+
## Release hygiene
|
|
29
|
+
|
|
30
|
+
- [ ] Version bump completed
|
|
31
|
+
- [ ] Changelog updated
|
|
32
|
+
- [ ] CI green on release commit
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Reproducibility Contract
|
|
2
|
+
|
|
3
|
+
This document defines the reproducible evaluation contract for A3M Router.
|
|
4
|
+
|
|
5
|
+
## Environment
|
|
6
|
+
|
|
7
|
+
- Node: `>=18` (CI uses Node 22)
|
|
8
|
+
- Python: `3.12` (for Python tests)
|
|
9
|
+
- Install:
|
|
10
|
+
- `npm ci`
|
|
11
|
+
- `python3 -m pip install pytest pytest-asyncio`
|
|
12
|
+
|
|
13
|
+
## Required commands
|
|
14
|
+
|
|
15
|
+
Run in repository root:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm test
|
|
19
|
+
npm run test:py
|
|
20
|
+
npm run eval:routing
|
|
21
|
+
npm run eval:golden
|
|
22
|
+
npm run eval:faults
|
|
23
|
+
npm run eval:shadow
|
|
24
|
+
npm run eval:report
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Deterministic inputs
|
|
28
|
+
|
|
29
|
+
- Core regression dataset:
|
|
30
|
+
- `eval/benchmark_dataset.jsonl`
|
|
31
|
+
- Golden snapshot:
|
|
32
|
+
- `eval/golden_routes.json`
|
|
33
|
+
- Thresholds:
|
|
34
|
+
- `eval/thresholds.json`
|
|
35
|
+
- `eval/fault_injection_thresholds.json`
|
|
36
|
+
|
|
37
|
+
## Artifacts generated
|
|
38
|
+
|
|
39
|
+
- `eval/results/latest.json`
|
|
40
|
+
- `eval/results/fault_injection_latest.json`
|
|
41
|
+
- `eval/results/shadow_latest.json`
|
|
42
|
+
- `eval/results/report_latest.md`
|
|
43
|
+
|
|
44
|
+
## Experiment registry
|
|
45
|
+
|
|
46
|
+
- Every eval run appends to:
|
|
47
|
+
- `eval/experiments.jsonl` (local artifact)
|
|
48
|
+
- Record includes:
|
|
49
|
+
- timestamp
|
|
50
|
+
- commit (if available)
|
|
51
|
+
- experiment id
|
|
52
|
+
- dataset version
|
|
53
|
+
- metrics
|
|
54
|
+
- decision
|
|
55
|
+
|
|
56
|
+
## Baseline update policy
|
|
57
|
+
|
|
58
|
+
- Baseline file: `eval/baselines/main.json`
|
|
59
|
+
- Only update baseline when behavior change is intentional.
|
|
60
|
+
- PR must explain:
|
|
61
|
+
- what changed
|
|
62
|
+
- why baseline needs update
|
|
63
|
+
- expected impact on cost/quality/reliability
|
package/eval/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Routing Evaluation Harness
|
|
2
|
+
|
|
3
|
+
This directory contains the reproducible evaluation system for A3M.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- `benchmark_dataset.jsonl`: frozen routing benchmark dataset
|
|
8
|
+
- `datasets/catalog.json`: dataset registry and slice metadata
|
|
9
|
+
- `datasets/slices/*.jsonl`: versioned slice datasets
|
|
10
|
+
- `thresholds.json`: minimum quality thresholds + max allowed regression
|
|
11
|
+
- `fault_injection_thresholds.json`: reliability gate threshold
|
|
12
|
+
- `golden_routes.json`: golden route snapshot
|
|
13
|
+
- `baselines/main.json`: baseline summary for `main` branch
|
|
14
|
+
- `run_eval.js`: routing evaluator + hard gate
|
|
15
|
+
- `check_golden_routes.js`: golden regression check
|
|
16
|
+
- `run_fault_injection.js`: retry/health fault scenarios
|
|
17
|
+
- `run_shadow_eval.js`: shadow routing comparison (informational)
|
|
18
|
+
- `generate_report.js`: markdown summary from eval result artifacts
|
|
19
|
+
- `experiments.jsonl`: append-only experiment registry (local artifact)
|
|
20
|
+
- `results/*.json`: generated run outputs (not committed)
|
|
21
|
+
- `results/report_latest.md`: generated markdown evidence report
|
|
22
|
+
|
|
23
|
+
## Run
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm run eval:all
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`eval:all` includes:
|
|
30
|
+
|
|
31
|
+
1. `eval:routing` — hard gate for routing quality thresholds
|
|
32
|
+
2. `eval:golden` — snapshot consistency gate
|
|
33
|
+
3. `eval:faults` — reliability fault injection gate
|
|
34
|
+
4. `eval:shadow` — candidate-vs-primary divergence and projected cost delta (informational)
|
|
35
|
+
5. `eval:report` — consolidated markdown release summary
|
|
36
|
+
|
|
37
|
+
## Updating Baseline
|
|
38
|
+
|
|
39
|
+
Only update `baselines/main.json` when routing behavior changes intentionally.
|
|
40
|
+
|
|
41
|
+
Suggested process:
|
|
42
|
+
|
|
43
|
+
1. Run `npm run eval:routing`
|
|
44
|
+
2. Review `eval/results/latest.json`
|
|
45
|
+
3. If changes are expected and desired, copy the new summary into `baselines/main.json`
|
|
46
|
+
4. Mention the reason in your PR/commit message
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"summary": {
|
|
3
|
+
"dataset_size": 16,
|
|
4
|
+
"checks_count": 34,
|
|
5
|
+
"complexity_accuracy": 1,
|
|
6
|
+
"flag_accuracy": 1,
|
|
7
|
+
"domain_accuracy": 1,
|
|
8
|
+
"provider_type_accuracy": 1,
|
|
9
|
+
"overall_score": 1
|
|
10
|
+
},
|
|
11
|
+
"note": "Initial baseline. Regenerate deliberately when routing behavior is intentionally changed."
|
|
12
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{"id":"q01","prompt":"What is 2+2?","expected":{"complexity":{"min":0.0,"max":0.2}}}
|
|
2
|
+
{"id":"q02","prompt":"Write a Python function to reverse a linked list.","expected":{"complexity":{"min":0.15,"max":0.35},"flags":{"has_code":true}}}
|
|
3
|
+
{"id":"q03","prompt":"Translate this sentence to Japanese: Good morning","expected":{"complexity":{"min":0.1,"max":0.25},"flags":{"is_translation":true}}}
|
|
4
|
+
{"id":"q04","prompt":"Design a clinical trial protocol for oncology treatment","expected":{"complexity":{"min":0.9,"max":1.0},"domain":"medical","provider_type":"local"}}
|
|
5
|
+
{"id":"q05","prompt":"Explain why the sky is blue in simple terms.","expected":{"complexity":{"min":0.25,"max":0.45}}}
|
|
6
|
+
{"id":"q06","prompt":"Find SQL injection risks in this login query.","expected":{"complexity":{"min":0.3,"max":0.55},"flags":{"is_security":true}}}
|
|
7
|
+
{"id":"q07","prompt":"Solve the integral of x^2 from 0 to 3.","expected":{"complexity":{"min":0.2,"max":0.4},"flags":{"has_math":true}}}
|
|
8
|
+
{"id":"q08","prompt":"Write a haiku about debugging.","expected":{"complexity":{"min":0.1,"max":0.3},"flags":{"is_creative":true}}}
|
|
9
|
+
{"id":"q09","prompt":"Create a Kubernetes deployment YAML with autoscaling HPA.","expected":{"complexity":{"min":0.45,"max":0.65},"flags":{"is_devops":true},"domain":"architecture"}}
|
|
10
|
+
{"id":"q10","prompt":"Compare two investment portfolios with Sharpe ratio.","expected":{"complexity":{"min":0.5,"max":0.7},"domain":"finance","provider_type":"local"}}
|
|
11
|
+
{"id":"q11","prompt":"Draft a GDPR-compliant data retention policy for a healthcare app.","expected":{"complexity":{"min":0.25,"max":0.45},"domain":"legal"}}
|
|
12
|
+
{"id":"q12","prompt":"Translate the following legal clause to Spanish and keep legal meaning precise.","expected":{"complexity":{"min":0.2,"max":0.4},"flags":{"is_translation":true},"domain":"legal"}}
|
|
13
|
+
{"id":"q13","prompt":"Debug this JavaScript error: Cannot read properties of undefined.","expected":{"complexity":{"min":0.2,"max":0.45},"flags":{"has_code":true}}}
|
|
14
|
+
{"id":"q14","prompt":"Summarize this paper abstract in 3 bullet points.","expected":{"complexity":{"min":0.35,"max":0.5},"domain":"ml_research"}}
|
|
15
|
+
{"id":"q15","prompt":"Plan a zero-downtime migration strategy for PostgreSQL.","expected":{"complexity":{"min":0.2,"max":0.45}}}
|
|
16
|
+
{"id":"q16","prompt":"Perform differential diagnosis for chest pain symptoms.","expected":{"complexity":{"min":0.3,"max":0.5},"domain":"medical","provider_type":"local"}}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { routeQuery } = require('../dist/index.js');
|
|
5
|
+
|
|
6
|
+
function readJson(file) {
|
|
7
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function readJsonl(file) {
|
|
11
|
+
return fs
|
|
12
|
+
.readFileSync(file, 'utf8')
|
|
13
|
+
.split('\n')
|
|
14
|
+
.map((line) => line.trim())
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.map((line) => JSON.parse(line));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function main() {
|
|
20
|
+
const evalDir = __dirname;
|
|
21
|
+
const dataset = readJsonl(path.join(evalDir, 'benchmark_dataset.jsonl'));
|
|
22
|
+
const golden = readJson(path.join(evalDir, 'golden_routes.json'));
|
|
23
|
+
const goldenMap = new Map(golden.map((g) => [g.id, g]));
|
|
24
|
+
|
|
25
|
+
const failures = [];
|
|
26
|
+
|
|
27
|
+
for (const row of dataset) {
|
|
28
|
+
const decision = routeQuery(row.prompt);
|
|
29
|
+
const expected = goldenMap.get(row.id);
|
|
30
|
+
if (!expected) {
|
|
31
|
+
failures.push(`${row.id}: missing expected golden row`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const checks = [
|
|
36
|
+
['primary_model', decision.primary_model, expected.primary_model],
|
|
37
|
+
['provider_type', decision.provider_type, expected.provider_type],
|
|
38
|
+
['detected_domain', decision.features?.detected_domain || '', expected.detected_domain || '']
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
for (const [name, actual, exp] of checks) {
|
|
42
|
+
if (actual !== exp) {
|
|
43
|
+
failures.push(`${row.id}: ${name} mismatch (actual=${actual}, expected=${exp})`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const actualComplexity = decision.features?.complexity ?? 0;
|
|
48
|
+
if (Math.abs(actualComplexity - expected.complexity) > 1e-9) {
|
|
49
|
+
failures.push(
|
|
50
|
+
`${row.id}: complexity mismatch (actual=${actualComplexity}, expected=${expected.complexity})`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (failures.length) {
|
|
56
|
+
console.error('\nGolden route check FAILED:');
|
|
57
|
+
failures.forEach((f) => console.error(`- ${f}`));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(`Golden route check PASSED (${dataset.length} cases)`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
main();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "v1",
|
|
3
|
+
"datasets": [
|
|
4
|
+
{
|
|
5
|
+
"name": "core_regression",
|
|
6
|
+
"path": "benchmark_dataset.jsonl",
|
|
7
|
+
"description": "Stable routing regression cases used for hard gates.",
|
|
8
|
+
"owner": "a3m-core",
|
|
9
|
+
"last_updated": "2026-05-23"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "cn_provider_reliability",
|
|
13
|
+
"path": "slices/cn_provider_reliability_v1.jsonl",
|
|
14
|
+
"description": "Chinese provider policy/reliability stress cases for eval expansion.",
|
|
15
|
+
"owner": "a3m-reliability",
|
|
16
|
+
"last_updated": "2026-05-23"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "cost_pressure",
|
|
20
|
+
"path": "slices/cost_pressure_v1.jsonl",
|
|
21
|
+
"description": "Cost-sensitive routing cases under budget pressure assumptions.",
|
|
22
|
+
"owner": "a3m-cost",
|
|
23
|
+
"last_updated": "2026-05-23"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "safety_guardrails",
|
|
27
|
+
"path": "slices/safety_guardrails_v1.jsonl",
|
|
28
|
+
"description": "Security and prompt-risk routing cases for policy review.",
|
|
29
|
+
"owner": "a3m-security",
|
|
30
|
+
"last_updated": "2026-05-23"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{"id":"cn01","prompt":"Translate a compliance notice to Chinese and preserve legal accuracy.","expected":{"domain":"legal","flags":{"is_translation":true}}}
|
|
2
|
+
{"id":"cn02","prompt":"Summarize this medical advisory for mainland patients in simplified Chinese.","expected":{"domain":"medical"}}
|
|
3
|
+
{"id":"cn03","prompt":"Explain why my API call returned rate_limit_reached_error and how to backoff.","expected":{"flags":{"is_devops":true}}}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{"id":"cp01","prompt":"Give me a one sentence explanation of gradient descent.","expected":{"complexity":{"min":0.0,"max":0.3}}}
|
|
2
|
+
{"id":"cp02","prompt":"Compare two cloud cost reports and suggest top 3 savings actions.","expected":{"complexity":{"min":0.3,"max":0.7}}}
|
|
3
|
+
{"id":"cp03","prompt":"Design a cross-region failover architecture with cost constraints.","expected":{"domain":"architecture"}}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{"id":"sg01","prompt":"Find SQL injection vectors in this login endpoint and propose fixes.","expected":{"flags":{"is_security":true}}}
|
|
2
|
+
{"id":"sg02","prompt":"User asks to bypass a firewall policy. Provide safe refusal guidance.","expected":{"flags":{"is_security":true}}}
|
|
3
|
+
{"id":"sg03","prompt":"Review this GDPR data retention flow for policy risk.","expected":{"domain":"legal"}}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
function readJsonIfExists(filePath) {
|
|
6
|
+
if (!fs.existsSync(filePath)) return null;
|
|
7
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function writeFile(filePath, content) {
|
|
11
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
12
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function fmtPct(n) {
|
|
16
|
+
return `${(n * 100).toFixed(2)}%`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function main() {
|
|
20
|
+
const evalDir = path.resolve(__dirname);
|
|
21
|
+
const resultsDir = path.join(evalDir, 'results');
|
|
22
|
+
|
|
23
|
+
const routing = readJsonIfExists(path.join(resultsDir, 'latest.json'));
|
|
24
|
+
const faults = readJsonIfExists(path.join(resultsDir, 'fault_injection_latest.json'));
|
|
25
|
+
const shadow = readJsonIfExists(path.join(resultsDir, 'shadow_latest.json'));
|
|
26
|
+
|
|
27
|
+
const now = new Date().toISOString();
|
|
28
|
+
const lines = [];
|
|
29
|
+
lines.push('# A3M Eval Report');
|
|
30
|
+
lines.push('');
|
|
31
|
+
lines.push(`Generated: ${now}`);
|
|
32
|
+
lines.push(`Commit: ${process.env.GITHUB_SHA || 'local'}`);
|
|
33
|
+
lines.push('');
|
|
34
|
+
|
|
35
|
+
lines.push('## Gate Status');
|
|
36
|
+
lines.push('');
|
|
37
|
+
|
|
38
|
+
if (routing) {
|
|
39
|
+
const gate = routing.failures?.length ? 'FAIL' : 'PASS';
|
|
40
|
+
lines.push(`- Routing Eval: **${gate}**`);
|
|
41
|
+
} else {
|
|
42
|
+
lines.push('- Routing Eval: **MISSING**');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (faults) {
|
|
46
|
+
const passRate = faults.summary?.pass_rate ?? 0;
|
|
47
|
+
const threshold = faults.thresholds?.required_pass_rate ?? 1;
|
|
48
|
+
const gate = passRate >= threshold ? 'PASS' : 'FAIL';
|
|
49
|
+
lines.push(`- Fault Injection: **${gate}**`);
|
|
50
|
+
} else {
|
|
51
|
+
lines.push('- Fault Injection: **MISSING**');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (shadow) {
|
|
55
|
+
lines.push('- Shadow Eval: **INFO**');
|
|
56
|
+
} else {
|
|
57
|
+
lines.push('- Shadow Eval: **MISSING**');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
lines.push('');
|
|
61
|
+
lines.push('## Routing Metrics');
|
|
62
|
+
lines.push('');
|
|
63
|
+
|
|
64
|
+
if (routing?.summary) {
|
|
65
|
+
const s = routing.summary;
|
|
66
|
+
lines.push(`- Dataset size: ${s.dataset_size}`);
|
|
67
|
+
lines.push(`- Checks count: ${s.checks_count}`);
|
|
68
|
+
lines.push(`- Complexity accuracy: ${fmtPct(s.complexity_accuracy)}`);
|
|
69
|
+
lines.push(`- Flag accuracy: ${fmtPct(s.flag_accuracy)}`);
|
|
70
|
+
lines.push(`- Domain accuracy: ${fmtPct(s.domain_accuracy)}`);
|
|
71
|
+
lines.push(`- Provider type accuracy: ${fmtPct(s.provider_type_accuracy)}`);
|
|
72
|
+
lines.push(`- Overall score: ${fmtPct(s.overall_score)}`);
|
|
73
|
+
if (routing.failures?.length) {
|
|
74
|
+
lines.push('- Failures:');
|
|
75
|
+
for (const f of routing.failures) lines.push(` - ${f}`);
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
lines.push('- No routing results available.');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
lines.push('');
|
|
82
|
+
lines.push('## Fault Injection');
|
|
83
|
+
lines.push('');
|
|
84
|
+
|
|
85
|
+
if (faults?.summary) {
|
|
86
|
+
lines.push(`- Total scenarios: ${faults.summary.total}`);
|
|
87
|
+
lines.push(`- Passed: ${faults.summary.passed}`);
|
|
88
|
+
lines.push(`- Failed: ${faults.summary.failed}`);
|
|
89
|
+
lines.push(`- Pass rate: ${fmtPct(faults.summary.pass_rate)}`);
|
|
90
|
+
const failed = (faults.results || []).filter((r) => !r.ok);
|
|
91
|
+
if (failed.length) {
|
|
92
|
+
lines.push('- Failed scenarios:');
|
|
93
|
+
for (const f of failed) lines.push(` - ${f.name}: ${f.error || 'failed'}`);
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
lines.push('- No fault injection results available.');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
lines.push('');
|
|
100
|
+
lines.push('## Shadow Eval');
|
|
101
|
+
lines.push('');
|
|
102
|
+
|
|
103
|
+
if (shadow?.summary) {
|
|
104
|
+
const s = shadow.summary;
|
|
105
|
+
lines.push(`- Dataset size: ${s.dataset_size}`);
|
|
106
|
+
lines.push(`- Candidate budget multiplier: ${s.candidate_budget_multiplier}`);
|
|
107
|
+
lines.push(`- Divergence rate: ${fmtPct(s.divergence_rate)}`);
|
|
108
|
+
lines.push(`- Changed cases: ${s.changed_cases}`);
|
|
109
|
+
lines.push(`- Projected total cost delta: ${s.projected_total_cost_delta}`);
|
|
110
|
+
lines.push(`- Projected avg cost delta: ${s.projected_avg_cost_delta}`);
|
|
111
|
+
} else {
|
|
112
|
+
lines.push('- No shadow eval results available.');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
lines.push('');
|
|
116
|
+
lines.push('## Artifact Paths');
|
|
117
|
+
lines.push('');
|
|
118
|
+
lines.push('- `eval/results/latest.json`');
|
|
119
|
+
lines.push('- `eval/results/fault_injection_latest.json`');
|
|
120
|
+
lines.push('- `eval/results/shadow_latest.json`');
|
|
121
|
+
lines.push('- `eval/results/report_latest.md`');
|
|
122
|
+
|
|
123
|
+
const reportPath = path.join(resultsDir, 'report_latest.md');
|
|
124
|
+
writeFile(reportPath, lines.join('\n') + '\n');
|
|
125
|
+
console.log(`Report generated: ${reportPath}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
main();
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "q01",
|
|
4
|
+
"primary_model": "commandcode/taste-1",
|
|
5
|
+
"provider_type": "cli",
|
|
6
|
+
"detected_domain": "",
|
|
7
|
+
"complexity": 0.1
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "q02",
|
|
11
|
+
"primary_model": "commandcode/taste-1",
|
|
12
|
+
"provider_type": "cli",
|
|
13
|
+
"detected_domain": "",
|
|
14
|
+
"complexity": 0.23
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "q03",
|
|
18
|
+
"primary_model": "commandcode/taste-1",
|
|
19
|
+
"provider_type": "cli",
|
|
20
|
+
"detected_domain": "",
|
|
21
|
+
"complexity": 0.14999999999999997
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "q04",
|
|
25
|
+
"primary_model": "ollama/llama3",
|
|
26
|
+
"provider_type": "local",
|
|
27
|
+
"detected_domain": "medical",
|
|
28
|
+
"complexity": 1
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "q05",
|
|
32
|
+
"primary_model": "commandcode/taste-1",
|
|
33
|
+
"provider_type": "cli",
|
|
34
|
+
"detected_domain": "",
|
|
35
|
+
"complexity": 0.36
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "q06",
|
|
39
|
+
"primary_model": "commandcode/taste-1",
|
|
40
|
+
"provider_type": "cli",
|
|
41
|
+
"detected_domain": "",
|
|
42
|
+
"complexity": 0.4
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "q07",
|
|
46
|
+
"primary_model": "commandcode/taste-1",
|
|
47
|
+
"provider_type": "cli",
|
|
48
|
+
"detected_domain": "",
|
|
49
|
+
"complexity": 0.3
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "q08",
|
|
53
|
+
"primary_model": "commandcode/taste-1",
|
|
54
|
+
"provider_type": "cli",
|
|
55
|
+
"detected_domain": "",
|
|
56
|
+
"complexity": 0.2
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "q09",
|
|
60
|
+
"primary_model": "commandcode/taste-1",
|
|
61
|
+
"provider_type": "cli",
|
|
62
|
+
"detected_domain": "architecture",
|
|
63
|
+
"complexity": 0.555
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "q10",
|
|
67
|
+
"primary_model": "ollama/llama3",
|
|
68
|
+
"provider_type": "local",
|
|
69
|
+
"detected_domain": "finance",
|
|
70
|
+
"complexity": 0.56
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "q11",
|
|
74
|
+
"primary_model": "commandcode/taste-1",
|
|
75
|
+
"provider_type": "cli",
|
|
76
|
+
"detected_domain": "legal",
|
|
77
|
+
"complexity": 0.355
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "q12",
|
|
81
|
+
"primary_model": "commandcode/taste-1",
|
|
82
|
+
"provider_type": "cli",
|
|
83
|
+
"detected_domain": "legal",
|
|
84
|
+
"complexity": 0.32499999999999996
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "q13",
|
|
88
|
+
"primary_model": "commandcode/taste-1",
|
|
89
|
+
"provider_type": "cli",
|
|
90
|
+
"detected_domain": "",
|
|
91
|
+
"complexity": 0.23000000000000004
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "q14",
|
|
95
|
+
"primary_model": "commandcode/taste-1",
|
|
96
|
+
"provider_type": "cli",
|
|
97
|
+
"detected_domain": "ml_research",
|
|
98
|
+
"complexity": 0.405
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "q15",
|
|
102
|
+
"primary_model": "commandcode/taste-1",
|
|
103
|
+
"provider_type": "cli",
|
|
104
|
+
"detected_domain": "",
|
|
105
|
+
"complexity": 0.33
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "q16",
|
|
109
|
+
"primary_model": "ollama/llama3",
|
|
110
|
+
"provider_type": "local",
|
|
111
|
+
"detected_domain": "medical",
|
|
112
|
+
"complexity": 0.40499999999999997
|
|
113
|
+
}
|
|
114
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function ensureDir(dirPath) {
|
|
5
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function appendExperimentRecord(record) {
|
|
9
|
+
const evalDir = path.resolve(__dirname, '..');
|
|
10
|
+
const experimentsPath = path.join(evalDir, 'experiments.jsonl');
|
|
11
|
+
ensureDir(path.dirname(experimentsPath));
|
|
12
|
+
|
|
13
|
+
const payload = {
|
|
14
|
+
timestamp_utc: new Date().toISOString(),
|
|
15
|
+
commit: process.env.GITHUB_SHA || null,
|
|
16
|
+
...record
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
fs.appendFileSync(experimentsPath, JSON.stringify(payload) + '\n', 'utf8');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
appendExperimentRecord
|
|
24
|
+
};
|