kiku-agent 1.3.0 → 1.3.2
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/README.md +7 -7
- package/package.json +4 -7
- package/server/agent/agentCore.js +2 -2
- package/server/agent/catalogEntityResolver.js +8 -0
- package/server/agent/cli.js +4 -0
- package/server/agent/eval/AGENTS.md +228 -0
- package/server/agent/eval/analyze.py +684 -0
- package/server/agent/eval/gates.json +15 -0
- package/server/agent/eval/modules/analysis/compare.js +198 -0
- package/server/agent/eval/modules/analysis/gates.js +72 -0
- package/server/agent/eval/modules/analysis/judge.js +70 -0
- package/server/agent/eval/modules/core/cli.js +453 -0
- package/server/agent/eval/modules/core/config.js +60 -0
- package/server/agent/eval/modules/core/store.js +236 -0
- package/server/agent/eval/modules/evaluation/cases.js +689 -0
- package/server/agent/eval/modules/evaluation/runner.js +298 -0
- package/server/agent/eval/modules/evaluation/scoring.js +351 -0
- package/server/agent/eval/modules/evaluation/worker.js +122 -0
- package/server/agent/eval/modules/reporting/benchmark.js +265 -0
- package/server/agent/eval/modules/reporting/benchmarkPng.js +487 -0
- package/server/agent/eval/modules/reporting/dashboard.js +1406 -0
- package/server/agent/eval/modules/server/evalApi.js +1099 -0
- package/server/agent/eval/modules/ui/evalUi.js +4796 -0
- package/server/agent/eval/modules/utils/smokes.js +119 -0
- package/server/agent/ink/App.jsx +348 -0
- package/server/agent/ink/Panel.jsx +54 -0
- package/server/agent/ink/PromptInput.jsx +321 -0
- package/server/agent/ink/TaskPlanPanel.jsx +88 -0
- package/server/agent/ink/ThinkingPanel.jsx +68 -0
- package/server/agent/ink/ToolResult.jsx +133 -0
- package/server/agent/ink/main.jsx +195 -0
- package/server/agent/ink/useEventBus.js +218 -0
- package/server/agent/memory/contextAllocator.js +214 -0
- package/server/agent/memory/hierarchicalStore.js +523 -0
- package/server/agent/memory/reflectionWorker.js +110 -0
- package/server/agent/prompts.js +1 -1
- package/server/agent/telemetry/hardwareTiers.js +332 -0
- package/server/agent/telemetry/runtimeWatchdog.js +66 -0
- package/server/agent/telemetry/techPowerUpClient.js +682 -0
- package/server/agent/telemetry/techpowerup-mcp.json +22 -0
- package/server/agent/tests/agentBubbleSystem.test.js +65 -0
- package/server/agent/tests/agentFollowUps.test.js +127 -0
- package/server/agent/tests/apiRouter.test.js +108 -0
- package/server/agent/tests/architectureFixes.test.js +81 -0
- package/server/agent/tests/architectureRefactoring.test.js +194 -0
- package/server/agent/tests/autoResearch.test.js +64 -0
- package/server/agent/tests/bilibiliEmbed.test.js +251 -0
- package/server/agent/tests/catalogSync.test.js +130 -0
- package/server/agent/tests/citations.test.js +180 -0
- package/server/agent/tests/clarificationAnalytics.test.js +219 -0
- package/server/agent/tests/clarifyingQuestions.test.js +594 -0
- package/server/agent/tests/cliAutomation.test.js +121 -0
- package/server/agent/tests/cloudProviders.test.js +234 -0
- package/server/agent/tests/commandInput.test.js +24 -0
- package/server/agent/tests/commands.test.js +417 -0
- package/server/agent/tests/contextIntelligence.test.js +162 -0
- package/server/agent/tests/conversationSafety.test.js +53 -0
- package/server/agent/tests/decisionAndSearchEnhancements.test.js +135 -0
- package/server/agent/tests/dramaAnswerQuality.test.js +73 -0
- package/server/agent/tests/dynamicCatalogIngestion.test.js +285 -0
- package/server/agent/tests/dynamicRoutingAndSearch.test.js +92 -0
- package/server/agent/tests/edgeCasesScenarios.test.js +49 -0
- package/server/agent/tests/entertainmentMetrics.test.js +126 -0
- package/server/agent/tests/envBootstrap.test.js +87 -0
- package/server/agent/tests/envCommand.test.js +215 -0
- package/server/agent/tests/errorHandling.test.js +80 -0
- package/server/agent/tests/errors.test.js +13 -0
- package/server/agent/tests/evalAnalyze.test.js +54 -0
- package/server/agent/tests/evalCommand.test.js +54 -0
- package/server/agent/tests/evalCorpus.test.js +126 -0
- package/server/agent/tests/evalHarness.test.js +62 -0
- package/server/agent/tests/evalJudgeContext.test.js +38 -0
- package/server/agent/tests/evalMetrics.test.js +312 -0
- package/server/agent/tests/evalStore.test.js +190 -0
- package/server/agent/tests/evalStudio.test.js +179 -0
- package/server/agent/tests/feedbackLearning.test.js +186 -0
- package/server/agent/tests/firstRun.test.js +44 -0
- package/server/agent/tests/gemma4eOptimization.test.js +95 -0
- package/server/agent/tests/hardwareTiers.test.js +206 -0
- package/server/agent/tests/hybridWebSearch.test.js +192 -0
- package/server/agent/tests/judgeService.test.js +251 -0
- package/server/agent/tests/lanDiscovery.test.js +319 -0
- package/server/agent/tests/mcpClient.test.js +201 -0
- package/server/agent/tests/modelManager.test.js +109 -0
- package/server/agent/tests/multilingualScenarios.test.js +69 -0
- package/server/agent/tests/newCommands.test.js +347 -0
- package/server/agent/tests/newCommandsTui.test.js +141 -0
- package/server/agent/tests/newTools.test.js +113 -0
- package/server/agent/tests/newsIntelligence.test.js +178 -0
- package/server/agent/tests/nextGenArchitecture.test.js +99 -0
- package/server/agent/tests/offlineInput.test.js +217 -0
- package/server/agent/tests/packagingGuard.test.js +57 -0
- package/server/agent/tests/presentation.test.js +61 -0
- package/server/agent/tests/providerPersistence.test.js +39 -0
- package/server/agent/tests/queryBenchmarks.test.js +83 -0
- package/server/agent/tests/queryDetails.test.js +126 -0
- package/server/agent/tests/realWorldValidation.test.js +391 -0
- package/server/agent/tests/resilienceAndConcurrency.test.js +117 -0
- package/server/agent/tests/runtimeWatchdog.test.js +32 -0
- package/server/agent/tests/sanitizer.test.js +37 -0
- package/server/agent/tests/scenariosData.js +378 -0
- package/server/agent/tests/schemaRouting.test.js +54 -0
- package/server/agent/tests/securityAuditRemediation.test.js +214 -0
- package/server/agent/tests/securityHardening.test.js +146 -0
- package/server/agent/tests/securitySanitizer.test.js +38 -0
- package/server/agent/tests/sessionAndCopy.test.js +232 -0
- package/server/agent/tests/sidebarControls.test.js +101 -0
- package/server/agent/tests/statefulMemory.test.js +270 -0
- package/server/agent/tests/subagent.test.js +216 -0
- package/server/agent/tests/subagentProgress.test.js +109 -0
- package/server/agent/tests/subagentTextFallback.test.js +115 -0
- package/server/agent/tests/suggestions.test.js +199 -0
- package/server/agent/tests/suggestionsTui.test.js +251 -0
- package/server/agent/tests/telemetryAndProfiles.test.js +179 -0
- package/server/agent/tests/terminalImage.test.js +80 -0
- package/server/agent/tests/terminalMarkdown.test.js +166 -0
- package/server/agent/tests/terminalText.test.js +133 -0
- package/server/agent/tests/textProtocolLoop.test.js +126 -0
- package/server/agent/tests/textToolParsing.test.js +139 -0
- package/server/agent/tests/theme.test.js +26 -0
- package/server/agent/tests/toolRegistryParity.test.js +53 -0
- package/server/agent/tests/toolRoutingRegressions.test.js +119 -0
- package/server/agent/tests/tools.test.js +259 -0
- package/server/agent/tests/unifiedEndpoints.test.js +152 -0
- package/server/agent/tests/v110Features.test.js +262 -0
- package/server/agent/tests/versionBump.test.js +69 -0
- package/server/agent/tests/vramEstimator.test.js +520 -0
- package/server/agent/tests/webChatInspection.test.js +300 -0
- package/server/agent/tests/webChatMemory.test.js +112 -0
- package/server/agent/tests/webChatSecurity.test.js +169 -0
- package/server/agent/tests/webExtract.test.js +261 -0
- package/server/agent/tests/webSources.test.js +247 -0
- package/server/agent/websearch/adapters/duckduckgo.js +97 -0
- package/server/agent/websearch/adapters/jina.js +77 -0
- package/server/agent/websearch/adapters/searxng.js +57 -0
- package/server/agent/websearch/core/canonical.js +89 -0
- package/server/agent/websearch/core/fusion.js +81 -0
- package/server/agent/websearch/core/types.js +29 -0
- package/server/agent/websearch/extract.js +605 -0
- package/server/agent/websearch/index.js +43 -0
- package/server/agent/websearch/orchestrator.js +266 -0
- package/server/agent/websearch/resilience/breaker.js +97 -0
- package/server/agent/websearch/resilience/cache.js +59 -0
- package/server/agent/websearch/resilience/limiter.js +93 -0
- package/server/agent/websearch/security/filter.js +36 -0
- package/server/agent/websearch/security/ssrf.js +289 -0
- package/server/agent/websearch/telemetry.js +71 -0
- package/server/apiRouter.js +7 -11
- package/server/eval/judgeRubrics.js +110 -0
- package/server/eval/judgeService.js +290 -0
- package/server/eval/runBenchmark.js +182 -0
- package/server/mcp/examples/librechat.example.yaml +16 -0
- package/server/mcp/tests/connection.test.js +156 -0
- package/server/routes/agent.js +55 -0
- package/server/supabase_schema.sql +63 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { ALLOWED_TOOLS } from '../core/config.js';
|
|
5
|
+
import { allCases, expandedCases } from './cases.js';
|
|
6
|
+
import {
|
|
7
|
+
scoreCoverage,
|
|
8
|
+
evaluateToolCorrectness,
|
|
9
|
+
detectTrajectoryLoops,
|
|
10
|
+
computeLatencyBreakdown,
|
|
11
|
+
checkGroundedness,
|
|
12
|
+
} from './scoring.js';
|
|
13
|
+
|
|
14
|
+
const isConnectionError = err => {
|
|
15
|
+
const msg = (err?.message || String(err)).toLowerCase();
|
|
16
|
+
return msg.includes('fetch failed') ||
|
|
17
|
+
msg.includes('econnrefused') ||
|
|
18
|
+
msg.includes('etimedout') ||
|
|
19
|
+
msg.includes('socket hang up') ||
|
|
20
|
+
msg.includes('no models loaded') ||
|
|
21
|
+
msg.includes('network') ||
|
|
22
|
+
msg.includes('connection');
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export async function runSingleCase(caseId, { model, baseUrl, timeoutMs, customQuestion }) {
|
|
26
|
+
const c = allCases.find(item => item.id === caseId) || (expandedCases && expandedCases.find(item => item.id === caseId));
|
|
27
|
+
if (!c) throw new Error('Unknown case');
|
|
28
|
+
|
|
29
|
+
process.env.KIKU_DATA_DIR = await fs.mkdtemp(path.join(os.tmpdir(), 'kiku-eval-'));
|
|
30
|
+
process.env.KIKU_AUTO_RESEARCH = 'off';
|
|
31
|
+
const [{ KikuAgent }, { AgentMemory }, { McpClientManager }, { ClarificationConfig, ClarificationAnalytics }] = await Promise.all([
|
|
32
|
+
import('../../../agentCore.js'), import('../../../memory.js'), import('../../../mcpClient.js'), import('../../../clarificationAnalytics.js'),
|
|
33
|
+
]);
|
|
34
|
+
const dir = process.env.KIKU_DATA_DIR;
|
|
35
|
+
const clarifyConfig = new ClarificationConfig({ storagePath: path.join(dir, 'clarify.json') });
|
|
36
|
+
clarifyConfig.config.enabled = false;
|
|
37
|
+
const agent = new KikuAgent({
|
|
38
|
+
provider: 'lmstudio', baseUrl, model, apiKey: 'lm-studio',
|
|
39
|
+
memory: new AgentMemory({ persistence: false }), sessionPersistence: false,
|
|
40
|
+
mcpManager: new McpClientManager({ autoLoad: false, configPaths: [] }),
|
|
41
|
+
clarifyConfig, clarifyAnalytics: new ClarificationAnalytics({ storagePath: path.join(dir, 'analytics.json') }),
|
|
42
|
+
allowedToolNames: ALLOWED_TOOLS,
|
|
43
|
+
});
|
|
44
|
+
const trace = [];
|
|
45
|
+
let steps = 0;
|
|
46
|
+
const start = performance.now();
|
|
47
|
+
let result;
|
|
48
|
+
try {
|
|
49
|
+
await agent.ready();
|
|
50
|
+
const query = customQuestion || c.question;
|
|
51
|
+
let answer;
|
|
52
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
53
|
+
try {
|
|
54
|
+
trace.length = 0;
|
|
55
|
+
steps = 0;
|
|
56
|
+
answer = await agent.run(query, {
|
|
57
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
58
|
+
onStepStart: step => {
|
|
59
|
+
steps = step;
|
|
60
|
+
console.log('EVAL_STEP=' + JSON.stringify({ event: 'step_start', step }));
|
|
61
|
+
},
|
|
62
|
+
onToolStart: (name, parameters) => {
|
|
63
|
+
trace.push({ event: 'start', name, parameters });
|
|
64
|
+
console.log('EVAL_STEP=' + JSON.stringify({ event: 'tool_start', name, parameters }));
|
|
65
|
+
},
|
|
66
|
+
onToolResult: (name, value, durationMs) => {
|
|
67
|
+
trace.push({ event: 'result', name, value, durationMs });
|
|
68
|
+
console.log('EVAL_STEP=' + JSON.stringify({ event: 'tool_result', name, durationMs }));
|
|
69
|
+
},
|
|
70
|
+
onNotice: message => trace.push({ event: 'notice', message }),
|
|
71
|
+
onError: error => trace.push({ event: 'error', message: error.message }),
|
|
72
|
+
});
|
|
73
|
+
break;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (attempt < 3 && isConnectionError(err)) {
|
|
76
|
+
console.log('EVAL_STEP=' + JSON.stringify({ event: 'retry', attempt, error: err.message }));
|
|
77
|
+
try {
|
|
78
|
+
const { wakeUpLmStudio } = await import('../../../lmStudioWakeup.js');
|
|
79
|
+
await wakeUpLmStudio({ baseUrl, model });
|
|
80
|
+
} catch {}
|
|
81
|
+
await new Promise(r => setTimeout(r, 2000 * attempt));
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
result = {
|
|
88
|
+
id: c.id,
|
|
89
|
+
category: c.category,
|
|
90
|
+
language: c.language,
|
|
91
|
+
question: query,
|
|
92
|
+
status: 'completed',
|
|
93
|
+
answer,
|
|
94
|
+
coverage: scoreCoverage(c, answer),
|
|
95
|
+
toolCorrectness: evaluateToolCorrectness(c, trace),
|
|
96
|
+
trajectoryHealth: detectTrajectoryLoops(trace),
|
|
97
|
+
groundedness: checkGroundedness(c, answer, trace),
|
|
98
|
+
metrics: agent.lastQueryMetrics,
|
|
99
|
+
telemetry: agent.lastTelemetry
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
result = { id: c.id, category: c.category, language: c.language, question: c.question, status: 'error', error: error.message };
|
|
103
|
+
} finally {
|
|
104
|
+
await agent.mcpManager.disconnectAll();
|
|
105
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
result.durationMs = Math.round(performance.now() - start);
|
|
108
|
+
result.dimension = c.dimension || null;
|
|
109
|
+
result.difficulty = c.difficulty || null;
|
|
110
|
+
result.judgeRequired = Boolean(c.judgeRequired);
|
|
111
|
+
result.steps = steps;
|
|
112
|
+
result.trace = trace;
|
|
113
|
+
result.latencyBreakdown = computeLatencyBreakdown(result);
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function runWorkerMain({ caseId, model, baseUrl, timeoutMs }) {
|
|
118
|
+
const result = await runSingleCase(caseId, { model, baseUrl, timeoutMs });
|
|
119
|
+
console.log('EVAL_RESULT=' + JSON.stringify(result));
|
|
120
|
+
process.exitCode = result.status === 'completed' ? 0 : 1;
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
export const BENCHMARK_TARGETS = {
|
|
5
|
+
passAt1: { min: 0.75, label: '>= 75.0%' },
|
|
6
|
+
trajectoryOptimality: { min: 0.8, label: '>= 0.80' },
|
|
7
|
+
toolF1: { min: 0.9, label: '>= 0.90' },
|
|
8
|
+
hallucinationRate: { max: 0.05, label: '<= 5.0%' },
|
|
9
|
+
costPerTask: { max: 0.04, label: '<= $0.04 / task' }
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const BENCHMARK_ROWS = [
|
|
13
|
+
{ dimension: 'Execution Capability', indicator: 'Task Success Rate (Pass@1)', benchmark: 'GAIA / SWE-bench', target: '>= 75.0%', key: 'passAt1', format: 'percent', iso: 'ISO/IEC 42001 9.2', nist: 'NIST AI RMF Measure 2.1' },
|
|
14
|
+
{ dimension: 'Trajectory Efficiency', indicator: 'Step Optimality Ratio', benchmark: 'AgentBench', target: '>= 0.80', key: 'trajectoryOptimality', format: 'ratio', iso: 'ISO/IEC 42001 9.3', nist: 'NIST AI RMF Measure 2.4' },
|
|
15
|
+
{ dimension: 'Tool Calling Precision', indicator: 'Tool Execution F1-Score', benchmark: 'ToolBench / ToolQA', target: '>= 0.90', key: 'toolF1', format: 'ratio', iso: 'ISO/IEC 42001 8.3', nist: 'NIST AI RMF Measure 2.2' },
|
|
16
|
+
{ dimension: 'Grounded Faithfulness', indicator: 'Hallucination Index (H_rate)', benchmark: 'TruthfulQA / RAG Triad', target: '<= 5.0%', key: 'hallucinationRate', format: 'percent', invert: true, iso: 'ISO/IEC 42001 9.4', nist: 'NIST AI RMF Govern 4.2' },
|
|
17
|
+
{ dimension: 'Operational Efficiency', indicator: 'Cost-to-Resolution Index (CRI)', benchmark: 'Custom Token/Sec SLA', target: '<= $0.04 / task', key: 'costPerTask', format: 'cost', invert: true, iso: 'ISO/IEC 42001 10.2', nist: 'NIST AI RMF Manage 1.3' }
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export const FAILURE_MODES = [
|
|
21
|
+
{ key: 'toolSchema', label: 'Tool Schema Error' },
|
|
22
|
+
{ key: 'contextOverflow', label: 'Context Overflow' },
|
|
23
|
+
{ key: 'replanning', label: 'Infinite Re-planning' },
|
|
24
|
+
{ key: 'permissionTimeout', label: 'Permission / Timeout' }
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const ROADMAP = [
|
|
28
|
+
{ phase: 'Phase 1', weeks: 'Weeks 1-3', title: 'Dynamic Trajectory Pruning & Error Recovery', actions: ['Cut re-planning loops when trajectory length exceeds 1.5 x L* with loop detection', 'Retry failed tool calls through a structured parser before resending the request'] },
|
|
29
|
+
{ phase: 'Phase 2', weeks: 'Weeks 4-6', title: 'Deterministic State Machine Integration', actions: ['Split core logic into a Plan to Act to Verify to Output finite state machine', 'Verify output against ground truth rules before responding to push H_rate below 2.0%'] },
|
|
30
|
+
{ phase: 'Phase 3', weeks: 'Weeks 7-8', title: 'Continuous Evals & Synthetic Regression Testing', actions: ['Run GAIA and SWE-bench aligned suites inside the CI pipeline', 'Generate synthetic edge cases covering safety and memory drift on every prompt update'] }
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export const PARETO_REFERENCES = [
|
|
34
|
+
{ label: 'Agent Model v2', successRate: 82.4, cost: 0.027, kind: 'candidate' },
|
|
35
|
+
{ label: 'Baseline v1', successRate: 68.0, cost: 0.045, kind: 'baseline' },
|
|
36
|
+
{ label: 'Legacy Script', successRate: 52.1, cost: 0.012, kind: 'legacy' }
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export const COST_MODEL = {
|
|
40
|
+
perOutputToken: 0.00001,
|
|
41
|
+
perToolCall: 0.002,
|
|
42
|
+
perSecond: 0.001
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export function binomial(n, k) {
|
|
46
|
+
const nn = Math.floor(Number(n));
|
|
47
|
+
const kk = Math.floor(Number(k));
|
|
48
|
+
if (!Number.isFinite(nn) || !Number.isFinite(kk) || nn < 0 || kk < 0 || kk > nn) return 0;
|
|
49
|
+
const kEff = Math.min(kk, nn - kk);
|
|
50
|
+
let result = 1;
|
|
51
|
+
for (let i = 1; i <= kEff; i++) {
|
|
52
|
+
result = result * (nn - kEff + i) / i;
|
|
53
|
+
}
|
|
54
|
+
return Math.round(result);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function passAtK(n, c, k = 1) {
|
|
58
|
+
const nn = Math.floor(Number(n));
|
|
59
|
+
const cc = Math.floor(Number(c));
|
|
60
|
+
const kk = Math.floor(Number(k));
|
|
61
|
+
if (!Number.isFinite(nn) || !Number.isFinite(cc) || !Number.isFinite(kk) || nn <= 0 || kk <= 0) return null;
|
|
62
|
+
if (cc <= 0) return 0;
|
|
63
|
+
if (cc >= nn) return 1;
|
|
64
|
+
if (kk > nn) return Number((cc / nn).toFixed(4));
|
|
65
|
+
const total = binomial(nn, kk);
|
|
66
|
+
const fail = binomial(nn - cc, kk);
|
|
67
|
+
if (!total) return null;
|
|
68
|
+
return Number((1 - fail / total).toFixed(4));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function trajectoryOptimality(lStar, l) {
|
|
72
|
+
const a = Number(lStar);
|
|
73
|
+
const b = Number(l);
|
|
74
|
+
if (!Number.isFinite(a) || !Number.isFinite(b) || a <= 0 || b <= 0) return null;
|
|
75
|
+
return Number((a / Math.max(b, a)).toFixed(4));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function faithfulnessScore(supported, total) {
|
|
79
|
+
const s = Number(supported);
|
|
80
|
+
const m = Number(total);
|
|
81
|
+
if (!Number.isFinite(s) || !Number.isFinite(m) || m <= 0 || s < 0) return null;
|
|
82
|
+
return Number((Math.min(s, m) / m).toFixed(4));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function toolF1(precision, recall) {
|
|
86
|
+
const p = Number(precision);
|
|
87
|
+
const r = Number(recall);
|
|
88
|
+
if (!Number.isFinite(p) || !Number.isFinite(r) || p < 0 || r < 0) return null;
|
|
89
|
+
if (p + r <= 0) return 0;
|
|
90
|
+
return Number(((2 * p * r) / (p + r)).toFixed(4));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function rowOptimalPath(row) {
|
|
94
|
+
const expected = Array.isArray(row?.toolCorrectness?.expected) ? row.toolCorrectness.expected.length : 0;
|
|
95
|
+
const groups = Number(row?.coverage?.total ?? 0);
|
|
96
|
+
const fallback = Number.isFinite(groups) && groups > 0 ? groups : 1;
|
|
97
|
+
const lStar = expected > 0 ? expected : fallback;
|
|
98
|
+
return Math.max(1, lStar);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function rowActualSteps(row) {
|
|
102
|
+
const trace = Array.isArray(row?.trace) ? row.trace : [];
|
|
103
|
+
const starts = trace.filter(t => t && t.event === 'start').length;
|
|
104
|
+
if (starts > 0) return starts;
|
|
105
|
+
if (row && row.status === 'completed') return 1;
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function estimateRowCost(row, model = COST_MODEL) {
|
|
110
|
+
const trace = Array.isArray(row?.trace) ? row.trace : [];
|
|
111
|
+
const toolCalls = trace.filter(t => t && t.event === 'start').length;
|
|
112
|
+
const seconds = Number.isFinite(row?.durationMs) ? Math.max(0, row.durationMs) / 1000 : 0;
|
|
113
|
+
let outputTokens = row?.latencyBreakdown?.outputTokens;
|
|
114
|
+
if (!Number.isFinite(outputTokens)) {
|
|
115
|
+
const answerLen = typeof row?.answer === 'string' ? row.answer.length : 0;
|
|
116
|
+
outputTokens = Math.round(answerLen / 4);
|
|
117
|
+
}
|
|
118
|
+
const cost = outputTokens * model.perOutputToken + toolCalls * model.perToolCall + seconds * model.perSecond;
|
|
119
|
+
return Number(cost.toFixed(5));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function failureModeOfRow(row) {
|
|
123
|
+
if (!row || typeof row !== 'object') return null;
|
|
124
|
+
const healthy = row.status === 'completed' && row.coverage?.allMatched === true && row.trajectoryHealth?.hasLoop !== true;
|
|
125
|
+
if (healthy) return null;
|
|
126
|
+
const trace = Array.isArray(row.trace) ? row.trace : [];
|
|
127
|
+
const blob = JSON.stringify({ error: row.error ?? null, trace: trace.slice(0, 40) }).toLowerCase();
|
|
128
|
+
const errText = String(row.error ?? '').toLowerCase();
|
|
129
|
+
const timeoutHit = errText.includes('timeout') || errText.includes('permission') || errText.includes('denied') || errText.includes('unauthorized') || errText.includes('abort') || blob.includes('timeout') && row.status === 'error';
|
|
130
|
+
if (timeoutHit) return 'permissionTimeout';
|
|
131
|
+
const lStar = rowOptimalPath(row);
|
|
132
|
+
const steps = rowActualSteps(row);
|
|
133
|
+
const loopCount = Number(row.trajectoryHealth?.loopCount ?? 0);
|
|
134
|
+
const thrashing = row.trajectoryHealth?.isThrashing === true;
|
|
135
|
+
if (thrashing || loopCount >= 2 || (steps > 0 && steps > 1.5 * lStar && steps >= 3)) return 'replanning';
|
|
136
|
+
const schemaHit = blob.includes('schema') || blob.includes('invalid') || blob.includes('validation') || blob.includes('parse') || trace.some(t => t && t.event === 'error');
|
|
137
|
+
const toolMiss = row.toolCorrectness && row.toolCorrectness.notApplicable !== true && (Number(row.toolCorrectness.precision ?? 1) < 0.5 || (row.toolCorrectness.missing ?? []).length > 0);
|
|
138
|
+
if (schemaHit || toolMiss) return 'toolSchema';
|
|
139
|
+
const answerLen = typeof row.answer === 'string' ? row.answer.length : 0;
|
|
140
|
+
const tooLong = steps > 6 || answerLen > 4000 || (Number(row.durationMs) > 60000);
|
|
141
|
+
if (tooLong) return 'contextOverflow';
|
|
142
|
+
if (row.status === 'error') return 'permissionTimeout';
|
|
143
|
+
if (row.coverage?.allMatched === false) return 'toolSchema';
|
|
144
|
+
return 'contextOverflow';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function meanFinite(values) {
|
|
148
|
+
const list = (Array.isArray(values) ? values : []).filter(v => Number.isFinite(v));
|
|
149
|
+
if (!list.length) return null;
|
|
150
|
+
return Number((list.reduce((a, b) => a + b, 0) / list.length).toFixed(4));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function round4(v) {
|
|
154
|
+
if (v === null || v === undefined) return null;
|
|
155
|
+
const n = Number(v);
|
|
156
|
+
if (!Number.isFinite(n)) return null;
|
|
157
|
+
return Number(n.toFixed(4));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function checkTarget(key, value) {
|
|
161
|
+
const rule = BENCHMARK_TARGETS[key];
|
|
162
|
+
if (!rule || value === null || value === undefined || !Number.isFinite(Number(value))) return null;
|
|
163
|
+
const v = Number(value);
|
|
164
|
+
if (rule.min !== undefined) return v >= rule.min;
|
|
165
|
+
if (rule.max !== undefined) return v <= rule.max;
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function computeBenchmarkSummary(rows = [], summary = {}) {
|
|
170
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
171
|
+
const completed = list.filter(r => r && r.status === 'completed');
|
|
172
|
+
const coverageRows = completed.filter(r => r.coverage && Number(r.coverage.total) > 0);
|
|
173
|
+
const passCount = coverageRows.filter(r => {
|
|
174
|
+
if (r.judgeResult && !r.judgeResult.judgeError) {
|
|
175
|
+
return r.judgeResult.score === 1.0 || r.judgeResult.verdict === 'PASS';
|
|
176
|
+
}
|
|
177
|
+
return r.coverage.allMatched === true;
|
|
178
|
+
}).length;
|
|
179
|
+
const passAt1 = coverageRows.length ? round4(passCount / coverageRows.length) : null;
|
|
180
|
+
const etas = completed.map(r => {
|
|
181
|
+
const steps = rowActualSteps(r);
|
|
182
|
+
if (!steps) return null;
|
|
183
|
+
return trajectoryOptimality(rowOptimalPath(r), steps);
|
|
184
|
+
}).filter(v => v !== null);
|
|
185
|
+
const trajectory = meanFinite(etas);
|
|
186
|
+
const f1s = completed.map(r => (r.toolCorrectness && r.toolCorrectness.notApplicable !== true && Number.isFinite(r.toolCorrectness.f1) ? r.toolCorrectness.f1 : null)).filter(v => v !== null);
|
|
187
|
+
const toolScore = meanFinite(f1s);
|
|
188
|
+
const supports = completed.map(r => {
|
|
189
|
+
if (!r.groundedness || r.groundedness.notApplicable === true) return null;
|
|
190
|
+
if (Number.isFinite(r.groundedness.supportRatio)) return r.groundedness.supportRatio;
|
|
191
|
+
return r.groundedness.isGrounded === true ? 1 : 0;
|
|
192
|
+
}).filter(v => v !== null);
|
|
193
|
+
const faith = meanFinite(supports);
|
|
194
|
+
const hallucinationRate = faith === null ? null : round4(1 - faith);
|
|
195
|
+
const costs = completed.map(r => estimateRowCost(r));
|
|
196
|
+
const costPerTask = costs.length ? round4(costs.reduce((a, b) => a + b, 0) / costs.length) : null;
|
|
197
|
+
const counts = { toolSchema: 0, contextOverflow: 0, replanning: 0, permissionTimeout: 0 };
|
|
198
|
+
let failures = 0;
|
|
199
|
+
for (const r of list) {
|
|
200
|
+
const mode = failureModeOfRow(r);
|
|
201
|
+
if (mode && counts[mode] !== undefined) {
|
|
202
|
+
counts[mode] += 1;
|
|
203
|
+
failures += 1;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const distribution = {};
|
|
207
|
+
for (const m of FAILURE_MODES) {
|
|
208
|
+
distribution[m.key] = {
|
|
209
|
+
label: m.label,
|
|
210
|
+
count: counts[m.key] ?? 0,
|
|
211
|
+
rate: failures ? round4((counts[m.key] ?? 0) / failures) : 0
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const livePoint = passAt1 !== null && costPerTask !== null ? { label: 'Live run', successRate: round4(passAt1 * 100), cost: costPerTask, kind: 'live' } : null;
|
|
215
|
+
const values = { passAt1, trajectoryOptimality: trajectory, toolF1: toolScore, hallucinationRate, costPerTask };
|
|
216
|
+
const status = {};
|
|
217
|
+
for (const k of Object.keys(values)) status[k] = checkTarget(k, values[k]);
|
|
218
|
+
return {
|
|
219
|
+
values,
|
|
220
|
+
status,
|
|
221
|
+
targets: BENCHMARK_TARGETS,
|
|
222
|
+
failureDistribution: distribution,
|
|
223
|
+
failureTotal: failures,
|
|
224
|
+
pareto: livePoint ? [...PARETO_REFERENCES, livePoint] : [...PARETO_REFERENCES],
|
|
225
|
+
livePoint,
|
|
226
|
+
denominators: {
|
|
227
|
+
completed: completed.length,
|
|
228
|
+
coverageEligible: coverageRows.length,
|
|
229
|
+
toolEligible: f1s.length,
|
|
230
|
+
groundedEligible: supports.length,
|
|
231
|
+
costSamples: costs.length
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function benchmarkGatesReport(rows = [], summary = {}) {
|
|
237
|
+
const bench = computeBenchmarkSummary(rows, summary);
|
|
238
|
+
const results = BENCHMARK_ROWS.map(r => {
|
|
239
|
+
const actual = bench.values[r.key] ?? null;
|
|
240
|
+
const rule = BENCHMARK_TARGETS[r.key] ?? {};
|
|
241
|
+
const pass = checkTarget(r.key, actual);
|
|
242
|
+
return { metric: r.key, dimension: r.dimension, threshold: rule, actual, pass };
|
|
243
|
+
});
|
|
244
|
+
return { passed: results.every(r => r.pass === true), results, benchmark: bench };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export async function writeBenchmarkSnapshot(rows, summary, outputPath) {
|
|
248
|
+
const bench = computeBenchmarkSummary(rows, summary);
|
|
249
|
+
const { default: fs } = await import('node:fs/promises');
|
|
250
|
+
const dir = path.dirname(outputPath);
|
|
251
|
+
await fs.mkdir(dir, { recursive: true });
|
|
252
|
+
await fs.writeFile(outputPath, JSON.stringify({ generatedAt: new Date().toISOString(), ...bench }, null, 2), 'utf8');
|
|
253
|
+
return outputPath;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
257
|
+
const mod = await import('../core/store.js');
|
|
258
|
+
const root = fileURLToPath(new URL('../../../../', import.meta.url));
|
|
259
|
+
const inPath = path.resolve(root, process.argv[2] || 'artifacts/eval/live.json');
|
|
260
|
+
const { default: fs } = await import('node:fs/promises');
|
|
261
|
+
const raw = await fs.readFile(inPath, 'utf8');
|
|
262
|
+
const report = JSON.parse(raw);
|
|
263
|
+
const bench = computeBenchmarkSummary(report.rows || [], report.summary || {});
|
|
264
|
+
console.log(JSON.stringify(bench, null, 2));
|
|
265
|
+
}
|