recallnest 2.3.0 → 2.6.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/CHANGELOG.md +187 -0
- package/README.md +49 -38
- package/README_CN.md +46 -31
- package/assets/ui/app.js +1031 -0
- package/assets/ui/index.html +224 -0
- package/assets/ui/styles.css +781 -0
- package/config.json.example +4 -1
- package/docs/api-reference.md +1291 -0
- package/docs/architecture.md +168 -0
- package/docs/connector-spec.md +126 -0
- package/eval/cases-canary.json +58 -0
- package/eval/cases-scope-robustness.json +278 -0
- package/eval/cases.json +117 -20
- package/package.json +25 -4
- package/src/__tests__/access-tracker-novelty.test.ts +304 -0
- package/src/__tests__/activity-counter.test.ts +125 -0
- package/src/__tests__/adaptive-fidelity.test.ts +102 -0
- package/src/__tests__/admission-control.test.ts +206 -0
- package/src/__tests__/anchor-generator.test.ts +67 -0
- package/src/__tests__/api-server-security.test.ts +102 -0
- package/src/__tests__/auto-consolidation.test.ts +168 -0
- package/src/__tests__/auto-recall.test.ts +131 -0
- package/src/__tests__/babel-integration.test.ts +144 -0
- package/src/__tests__/batch-dedup.test.ts +65 -0
- package/src/__tests__/belief-history.test.ts +220 -0
- package/src/__tests__/canary-eval.test.ts +114 -0
- package/src/__tests__/capture-engine.test.ts +1328 -0
- package/src/__tests__/capture-heuristic.test.ts +171 -0
- package/src/__tests__/cascade-forget.test.ts +210 -0
- package/src/__tests__/category-min-scores.test.ts +116 -0
- package/src/__tests__/cc8-cc1-essential-context.test.ts +298 -0
- package/src/__tests__/circuit-breaker.test.ts +99 -0
- package/src/__tests__/cluster-consolidation.test.ts +945 -0
- package/src/__tests__/confidence-metadata.test.ts +125 -0
- package/src/__tests__/confidence-tracker.test.ts +261 -0
- package/src/__tests__/conflict-advisor.test.ts +206 -0
- package/src/__tests__/conflict-escalation.test.ts +149 -0
- package/src/__tests__/conflict-lifecycle.test.ts +83 -0
- package/src/__tests__/conflict-output.test.ts +85 -0
- package/src/__tests__/conflict-store.test.ts +418 -0
- package/src/__tests__/connector-v1.test.ts +181 -0
- package/src/__tests__/consolidation-engine.test.ts +430 -0
- package/src/__tests__/constructive-retrieval.test.ts +509 -0
- package/src/__tests__/context-collapse-renderer.test.ts +214 -0
- package/src/__tests__/context-composer-pins.test.ts +64 -0
- package/src/__tests__/context-composer-task-selection.test.ts +24 -0
- package/src/__tests__/context-reconstructor.test.ts +65 -0
- package/src/__tests__/context-renderer.test.ts +91 -0
- package/src/__tests__/continuity-eval.test.ts +275 -0
- package/src/__tests__/continuity-schema.test.ts +97 -0
- package/src/__tests__/contracts.test.ts +262 -0
- package/src/__tests__/conversation-importer.test.ts +692 -0
- package/src/__tests__/core-recall-eval.test.ts +235 -0
- package/src/__tests__/core-summary.test.ts +109 -0
- package/src/__tests__/data-checkup.test.ts +276 -0
- package/src/__tests__/decay-exemption.test.ts +129 -0
- package/src/__tests__/dedup-actions.test.ts +58 -0
- package/src/__tests__/dedup-guard.test.ts +39 -0
- package/src/__tests__/deterministic-id.test.ts +39 -0
- package/src/__tests__/distill-lock.test.ts +305 -0
- package/src/__tests__/distill-session-argshape.test.ts +79 -0
- package/src/__tests__/doctor.test.ts +319 -0
- package/src/__tests__/dream-pipeline.test.ts +780 -0
- package/src/__tests__/drill-down.test.ts +108 -0
- package/src/__tests__/embedder.test.ts +122 -0
- package/src/__tests__/emotion-decay.test.ts +46 -0
- package/src/__tests__/emotion-detector.test.ts +56 -0
- package/src/__tests__/emotion-phase1-integration.test.ts +153 -0
- package/src/__tests__/emotion-retrieval.test.ts +56 -0
- package/src/__tests__/entity-resolver.test.ts +58 -0
- package/src/__tests__/env-config.test.ts +151 -0
- package/src/__tests__/error-signature-boost.test.ts +87 -0
- package/src/__tests__/error-signature.test.ts +95 -0
- package/src/__tests__/error-taxonomy.test.ts +129 -0
- package/src/__tests__/ethics-layer.test.ts +476 -0
- package/src/__tests__/event-segmenter.test.ts +153 -0
- package/src/__tests__/feature-flags-integration.test.ts +54 -0
- package/src/__tests__/frequency-tracker.test.ts +113 -0
- package/src/__tests__/freshness.test.ts +186 -0
- package/src/__tests__/graph-export.test.ts +489 -0
- package/src/__tests__/hotness-blend.test.ts +283 -0
- package/src/__tests__/ingest-chunking.test.ts +44 -0
- package/src/__tests__/ingest.test.ts +360 -0
- package/src/__tests__/interference-detector.test.ts +203 -0
- package/src/__tests__/kg-extractor.test.ts +192 -0
- package/src/__tests__/kg-store.test.ts +300 -0
- package/src/__tests__/llm-client-pivot.test.ts +628 -0
- package/src/__tests__/llm-consolidation.test.ts +226 -0
- package/src/__tests__/maintenance-locks.test.ts +189 -0
- package/src/__tests__/mcp-efficiency.test.ts +288 -0
- package/src/__tests__/mcp-related-scopes.test.ts +151 -0
- package/src/__tests__/memory-boundaries.test.ts +282 -0
- package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +345 -0
- package/src/__tests__/memory-evolution.test.ts +271 -0
- package/src/__tests__/memory-health-rebalance.test.ts +144 -0
- package/src/__tests__/memory-lint.test.ts +421 -0
- package/src/__tests__/memory-output.test.ts +215 -0
- package/src/__tests__/memory-promotion.test.ts +315 -0
- package/src/__tests__/migrate-dedup-ids.test.ts +65 -0
- package/src/__tests__/multi-vector.test.ts +242 -0
- package/src/__tests__/narrative-architecture.test.ts +456 -0
- package/src/__tests__/noise-filter.test.ts +205 -0
- package/src/__tests__/noise-prototype-bank.test.ts +280 -0
- package/src/__tests__/obsidian-connector.test.ts +363 -0
- package/src/__tests__/omitted-reporting.test.ts +98 -0
- package/src/__tests__/pending-review.test.ts +77 -0
- package/src/__tests__/pii-redact.test.ts +333 -0
- package/src/__tests__/pivot-apply.test.ts +373 -0
- package/src/__tests__/pivot-distill-supervisor.test.ts +457 -0
- package/src/__tests__/pivot-distill.test.ts +1178 -0
- package/src/__tests__/post-retrieval-filter.test.ts +178 -0
- package/src/__tests__/ppr-traversal.test.ts +256 -0
- package/src/__tests__/prediction-engine.test.ts +349 -0
- package/src/__tests__/predictive-prospective-memory.test.ts +416 -0
- package/src/__tests__/preference-guard-smoke.test.ts +211 -0
- package/src/__tests__/preference-matcher.test.ts +282 -0
- package/src/__tests__/preference-slots.test.ts +205 -0
- package/src/__tests__/prospective-memory.test.ts +283 -0
- package/src/__tests__/pull-from-macbook-sync-only.test.ts +679 -0
- package/src/__tests__/query-entity-detector.test.ts +171 -0
- package/src/__tests__/query-expander-alias.test.ts +146 -0
- package/src/__tests__/query-expander.test.ts +34 -0
- package/src/__tests__/recall-gate.test.ts +266 -0
- package/src/__tests__/recall-governor.test.ts +227 -0
- package/src/__tests__/recall-mode.test.ts +64 -0
- package/src/__tests__/resolve-db-path.test.ts +130 -0
- package/src/__tests__/resolve-tier.test.ts +64 -0
- package/src/__tests__/result-synthesizer.test.ts +198 -0
- package/src/__tests__/resume-context.test.ts +4898 -0
- package/src/__tests__/retrieval-eval.test.ts +163 -0
- package/src/__tests__/retrieval-trace.test.ts +213 -0
- package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
- package/src/__tests__/retriever-lazy-init-dispatch.test.ts +123 -0
- package/src/__tests__/retroactive-boost.test.ts +223 -0
- package/src/__tests__/salience-scoring.test.ts +106 -0
- package/src/__tests__/scope-inventory.test.ts +200 -0
- package/src/__tests__/scope-policy.test.ts +69 -0
- package/src/__tests__/scope-suggester.test.ts +80 -0
- package/src/__tests__/session-checkpoint.test.ts +291 -0
- package/src/__tests__/session-distiller.test.ts +640 -0
- package/src/__tests__/session-gc.test.ts +215 -0
- package/src/__tests__/skill-engine.test.ts +552 -0
- package/src/__tests__/skill-promotion.test.ts +654 -0
- package/src/__tests__/skill-verifier.test.ts +122 -0
- package/src/__tests__/source-heartbeat.test.ts +250 -0
- package/src/__tests__/stable-context-length-contract.test.ts +62 -0
- package/src/__tests__/store-patch-metadata.test.ts +205 -0
- package/src/__tests__/store-read-consistency.test.ts +122 -0
- package/src/__tests__/store-schema-migration.test.ts +87 -0
- package/src/__tests__/store-scope-audit.test.ts +81 -0
- package/src/__tests__/store-upsert.test.ts +201 -0
- package/src/__tests__/supersede-chain.test.ts +154 -0
- package/src/__tests__/temporal-parser.test.ts +241 -0
- package/src/__tests__/temporal-validity.test.ts +160 -0
- package/src/__tests__/term-registry-terminal-roster.test.ts +127 -0
- package/src/__tests__/term-registry.test.ts +121 -0
- package/src/__tests__/tool-error-surfacing.test.ts +98 -0
- package/src/__tests__/tool-output-compressor.test.ts +160 -0
- package/src/__tests__/topic-tag.test.ts +113 -0
- package/src/__tests__/trusted-memory.test.ts +839 -0
- package/src/__tests__/ui-server-security.test.ts +93 -0
- package/src/__tests__/usage-tracker.test.ts +287 -0
- package/src/__tests__/version-manager.test.ts +213 -0
- package/src/__tests__/workflow-observation-skill-outcome.test.ts +352 -0
- package/src/__tests__/workflow-observation.test.ts +433 -0
- package/src/__tests__/write-verifier.test.ts +122 -0
- package/src/access-tracker.ts +90 -34
- package/src/activity-counter.ts +46 -18
- package/src/adaptive-retrieval.ts +11 -0
- package/src/aliases.ts +133 -0
- package/src/api-server.ts +36 -9
- package/src/audit-log.ts +4 -2
- package/src/auto-consolidation.ts +8 -3
- package/src/auto-gc.ts +85 -21
- package/src/auto-recall.ts +61 -1
- package/src/belief-history.ts +164 -0
- package/src/capture-engine.ts +142 -32
- package/src/cli.ts +268 -41
- package/src/confidence-tracker.ts +36 -18
- package/src/conflict-engine.ts +24 -3
- package/src/consolidation-engine.ts +255 -23
- package/src/context-composer-stable-selection.ts +7 -1
- package/src/context-composer.ts +8 -1
- package/src/data-checkup.ts +96 -7
- package/src/decay-engine.ts +59 -7
- package/src/distill-lock.ts +194 -15
- package/src/doctor.ts +13 -4
- package/src/dream-pipeline.ts +473 -41
- package/src/env-config.ts +118 -0
- package/src/error-signature.ts +153 -0
- package/src/error-taxonomy.ts +115 -0
- package/src/eval.ts +402 -14
- package/src/freshness.ts +192 -0
- package/src/http-request-policy.ts +148 -0
- package/src/idempotency.ts +8 -0
- package/src/ingest.ts +308 -12
- package/src/kg-extractor.ts +13 -3
- package/src/kg-store.ts +236 -44
- package/src/llm-client.ts +106 -11
- package/src/llm-consolidation.ts +2 -1
- package/src/mcp-server.ts +87 -1800
- package/src/mcp-tool-deps.ts +48 -0
- package/src/mcp-tools-advanced.ts +954 -0
- package/src/mcp-tools-core.ts +637 -0
- package/src/mcp-tools-governance.ts +475 -0
- package/src/memory-boundaries.ts +26 -3
- package/src/memory-evolution.ts +73 -1
- package/src/memory-health-rebalance.ts +4 -0
- package/src/memory-lint.ts +183 -10
- package/src/memory-output.ts +129 -4
- package/src/memory-promotion.ts +246 -0
- package/src/memory-schema.ts +25 -2
- package/src/memory-store-port.ts +46 -0
- package/src/multi-vector.ts +2 -1
- package/src/narrative-schema.ts +2 -1
- package/src/noise-filter.ts +57 -6
- package/src/noise-prototype-bank.ts +408 -0
- package/src/pii-detector.ts +207 -5
- package/src/pivot-apply.ts +399 -0
- package/src/preference-matcher.ts +37 -3
- package/src/query-expander.ts +90 -9
- package/src/recall-gate.ts +305 -0
- package/src/result-synthesizer.ts +2 -1
- package/src/retention-policy.ts +2 -1
- package/src/retrieval-profiles.ts +46 -4
- package/src/retriever.ts +354 -157
- package/src/retroactive-boost.ts +6 -2
- package/src/runtime-config.ts +78 -6
- package/src/scope-suggester.ts +113 -0
- package/src/session-distiller.ts +23 -2
- package/src/session-engine.ts +2 -1
- package/src/session-schema.ts +1 -0
- package/src/session-store.ts +5 -0
- package/src/skill-engine.ts +78 -0
- package/src/skill-promotion.ts +304 -48
- package/src/skill-schema.ts +16 -3
- package/src/skill-verifier.ts +152 -0
- package/src/store.ts +321 -57
- package/src/term-registry.ts +35 -0
- package/src/tool-output-compressor.ts +3 -1
- package/src/ui-server.ts +9 -1
- package/src/usage-tracker.ts +242 -0
- package/src/version-manager.ts +3 -2
- package/src/version.ts +19 -0
- package/src/workflow-observation-engine.ts +2 -1
- package/src/workflow-observation-schema.ts +2 -1
- package/src/workflow-observation-store.ts +6 -2
- package/.env +0 -4
- package/CLAUDE.md +0 -46
- package/config.json +0 -55
- package/daily-recall.sh +0 -110
- package/logs/daily-recall.error.log +0 -0
- package/logs/daily-recall.log +0 -60
- package/logs/ingest-2026-04-11.log +0 -110
- package/logs/ingest-2026-04-12.log +0 -90
- package/logs/ingest-2026-04-13.log +0 -87
- package/logs/launchagent-stderr.log +0 -0
- package/logs/launchagent-stdout.log +0 -0
package/src/eval.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { buildSessionCheckpointRecord, normalizeCheckpointScope } from "./sessio
|
|
|
8
8
|
import type { ResumeContextResponse, SessionCheckpointRecord } from "./session-schema.js";
|
|
9
9
|
import { composeResumeContext } from "./context-composer.js";
|
|
10
10
|
import { cleanText } from "./context-composer-text.js";
|
|
11
|
+
import { estimateTokens } from "./context-collapse-renderer.js";
|
|
12
|
+
import { TraceCollector } from "./retrieval-trace.js";
|
|
11
13
|
import { createComponents, loadConfig, loadDotEnv, type LocalMemoryConfig } from "./runtime-config.js";
|
|
12
14
|
import { logInfo } from "./stderr-log.js";
|
|
13
15
|
import { buildWorkflowObservationRecord } from "./workflow-observation-engine.js";
|
|
@@ -15,7 +17,7 @@ import type { WorkflowObservationInput } from "./workflow-observation-schema.js"
|
|
|
15
17
|
import { WorkflowObservationStore } from "./workflow-observation-store.js";
|
|
16
18
|
|
|
17
19
|
export type ProfileName = "default" | "writing" | "debug" | "fact-check";
|
|
18
|
-
export type EvalMode = "retrieval" | "continuity";
|
|
20
|
+
export type EvalMode = "retrieval" | "continuity" | "canary";
|
|
19
21
|
|
|
20
22
|
export interface RetrievalEvalCase {
|
|
21
23
|
name: string;
|
|
@@ -59,6 +61,73 @@ export interface ContinuityEvalCase {
|
|
|
59
61
|
notes?: string;
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
export interface CanaryEvalCase {
|
|
65
|
+
name: string;
|
|
66
|
+
query: string;
|
|
67
|
+
profile?: ProfileName;
|
|
68
|
+
scope?: string;
|
|
69
|
+
limit?: number;
|
|
70
|
+
/** A 类:该召回的 memory id(按 target id 算 rank) */
|
|
71
|
+
targets?: string[];
|
|
72
|
+
/** C 类:不该进 TopK 的干扰项 memory id */
|
|
73
|
+
hardNegatives?: string[];
|
|
74
|
+
/** 目标应进前几(默认 3) */
|
|
75
|
+
expectTopK?: number;
|
|
76
|
+
/** B 类新旧序:按此顺序排列,newer 在前,前者 rank 应小于后者 */
|
|
77
|
+
expectOrder?: string[];
|
|
78
|
+
/** D 类稳定偏好:召回文本命中任一要点即算对(不钉 id) */
|
|
79
|
+
expectContentAny?: string[];
|
|
80
|
+
/** 文本层禁词(沿用 retrieval 语义) */
|
|
81
|
+
forbid?: string[];
|
|
82
|
+
notes?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Per-case cost accounting for the retrieval pipeline — the X axis of the
|
|
87
|
+
* Pareto curve (see hippo-wiki [[帕累托效率曲线]]).
|
|
88
|
+
*
|
|
89
|
+
* Scope: self-comparison across runs (did a retrieval change make it slower or
|
|
90
|
+
* fatter?). NOT for cross-system comparison — `contextTokens` comes from an
|
|
91
|
+
* estimator, not a real tokenizer, and this suite is not LoCoMo, so the numbers
|
|
92
|
+
* are not commensurable with competitors' published token counts.
|
|
93
|
+
*/
|
|
94
|
+
export interface EvalCostMetrics {
|
|
95
|
+
/** End-to-end retrieve() wall time. */
|
|
96
|
+
totalMs: number;
|
|
97
|
+
/** Number of memories returned. */
|
|
98
|
+
resultCount: number;
|
|
99
|
+
/** Estimated tokens of returned memory text — the context a caller would spend. */
|
|
100
|
+
contextTokens: number;
|
|
101
|
+
/** Duration of the rerank stage, or null when the pipeline ran no rerank.
|
|
102
|
+
* Left as a raw fact rather than an inferred "api calls" count: a local
|
|
103
|
+
* lightweight rerank and a remote cross-encoder both produce a stage here. */
|
|
104
|
+
rerankMs: number | null;
|
|
105
|
+
/** Three slowest stages, for locating the bottleneck. */
|
|
106
|
+
slowestStages: Array<{ name: string; durationMs: number }>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface CanaryCaseReport {
|
|
110
|
+
mode: "canary";
|
|
111
|
+
name: string;
|
|
112
|
+
query: string;
|
|
113
|
+
profile: ProfileName;
|
|
114
|
+
score: number;
|
|
115
|
+
passed: boolean;
|
|
116
|
+
hitCount: number;
|
|
117
|
+
targetRanks: Array<{ id: string; rank: number | null }>;
|
|
118
|
+
top1Hit: boolean;
|
|
119
|
+
top3Hit: boolean;
|
|
120
|
+
forbiddenIdMatches: string[];
|
|
121
|
+
orderOk: boolean | null;
|
|
122
|
+
matchedContentAny: string[];
|
|
123
|
+
forbiddenMatches: string[];
|
|
124
|
+
topIds: string[];
|
|
125
|
+
topSnippet: string;
|
|
126
|
+
notes?: string;
|
|
127
|
+
/** Attached by the runner after scoring; absent when scored standalone. */
|
|
128
|
+
cost?: EvalCostMetrics;
|
|
129
|
+
}
|
|
130
|
+
|
|
62
131
|
export interface RetrievalCaseReport {
|
|
63
132
|
mode: "retrieval";
|
|
64
133
|
name: string;
|
|
@@ -74,6 +143,8 @@ export interface RetrievalCaseReport {
|
|
|
74
143
|
topScopes: string[];
|
|
75
144
|
topSnippet: string;
|
|
76
145
|
notes?: string;
|
|
146
|
+
/** Attached by the runner after scoring; absent when scored standalone. */
|
|
147
|
+
cost?: EvalCostMetrics;
|
|
77
148
|
}
|
|
78
149
|
|
|
79
150
|
export interface ContinuityCaseReport {
|
|
@@ -100,7 +171,7 @@ export interface ContinuityCaseReport {
|
|
|
100
171
|
notes?: string;
|
|
101
172
|
}
|
|
102
173
|
|
|
103
|
-
type EvalReport = RetrievalCaseReport | ContinuityCaseReport;
|
|
174
|
+
type EvalReport = RetrievalCaseReport | ContinuityCaseReport | CanaryCaseReport;
|
|
104
175
|
|
|
105
176
|
interface EvalArgs {
|
|
106
177
|
mode: EvalMode;
|
|
@@ -144,7 +215,7 @@ function parseArgs(args: string[]): EvalArgs {
|
|
|
144
215
|
const observationScopeIdx = args.indexOf("--observation-scope");
|
|
145
216
|
const observationSourceIdx = args.indexOf("--observation-source");
|
|
146
217
|
const modeRaw = modeIdx >= 0 ? args[modeIdx + 1] : "retrieval";
|
|
147
|
-
const mode = modeRaw === "continuity" ? "continuity" : "retrieval";
|
|
218
|
+
const mode: EvalMode = modeRaw === "continuity" ? "continuity" : modeRaw === "canary" ? "canary" : "retrieval";
|
|
148
219
|
|
|
149
220
|
return {
|
|
150
221
|
mode,
|
|
@@ -158,9 +229,9 @@ function parseArgs(args: string[]): EvalArgs {
|
|
|
158
229
|
}
|
|
159
230
|
|
|
160
231
|
function defaultCasesPath(mode: EvalMode): string {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
232
|
+
if (mode === "continuity") return resolve(metaDir(import.meta), "../eval/continuity/cases.json");
|
|
233
|
+
if (mode === "canary") return resolve(metaDir(import.meta), "../eval/cases-canary.json");
|
|
234
|
+
return resolve(metaDir(import.meta), "../eval/cases.json");
|
|
164
235
|
}
|
|
165
236
|
|
|
166
237
|
function loadCases<T>(mode: EvalMode, pathArg?: string): T[] {
|
|
@@ -176,8 +247,16 @@ function matchedTerms(terms: string[] | undefined, haystack: string): string[] {
|
|
|
176
247
|
return (terms || []).filter((term) => haystack.includes(term.toLowerCase()));
|
|
177
248
|
}
|
|
178
249
|
|
|
250
|
+
/**
|
|
251
|
+
* 命中比例 × 权重。
|
|
252
|
+
*
|
|
253
|
+
* 调用方必须先滤掉 case 没声明的期望、再对剩下的权重重新归一化——下面三个
|
|
254
|
+
* 评分器都是这么做的。空期望这里返回 0 而不是 weight:万一将来有第四个调用
|
|
255
|
+
* 点忘了滤,代价是少给分而不是白送分。三个现有调用点都已滤过,所以这个分支
|
|
256
|
+
* 当前走不到,纯属兜底。
|
|
257
|
+
*/
|
|
179
258
|
function scoreExpectation(expected: string[] | undefined, matched: string[], weight: number): number {
|
|
180
|
-
if (!expected || expected.length === 0) return
|
|
259
|
+
if (!expected || expected.length === 0) return 0;
|
|
181
260
|
return (matched.length / expected.length) * weight;
|
|
182
261
|
}
|
|
183
262
|
|
|
@@ -195,10 +274,23 @@ export function scoreRetrievalCase(
|
|
|
195
274
|
const matchedScopes = (evalCase.expectScopePrefixes || []).filter((scope) => scopes.some((item) => item.startsWith(scope)));
|
|
196
275
|
const forbiddenMatches = matchedTerms(evalCase.forbid, joined);
|
|
197
276
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
277
|
+
// 动态加权:只计 case 实际声明的维度,权重在这些维度间重新归一化——
|
|
278
|
+
// 和下面 canary、continuity 两个评分器已有的写法一致。
|
|
279
|
+
// 改之前未声明的维度按满权重计入,而 eval/cases.json 里 20 个 case 一个都没写
|
|
280
|
+
// expectAll,于是每个 case 白拿 0.3:expectAny 四个词命中一个再加 scope 前缀命中,
|
|
281
|
+
// 正好落在 0.7 及格线上。
|
|
282
|
+
const expectationScores = [
|
|
283
|
+
{ expected: evalCase.expectAny, matched: matchedAny, weight: 0.4 },
|
|
284
|
+
{ expected: evalCase.expectAll, matched: matchedAll, weight: 0.3 },
|
|
285
|
+
{ expected: evalCase.expectScopePrefixes, matched: matchedScopes, weight: 0.2 },
|
|
286
|
+
].filter((item) => (item.expected || []).length > 0);
|
|
287
|
+
|
|
288
|
+
const totalExpectedWeight = expectationScores.reduce((sum, item) => sum + item.weight, 0);
|
|
289
|
+
const normalizedExpectationScore = totalExpectedWeight > 0
|
|
290
|
+
? expectationScores.reduce((sum, item) => sum + scoreExpectation(item.expected, item.matched, item.weight), 0) / totalExpectedWeight
|
|
291
|
+
: 0;
|
|
292
|
+
|
|
293
|
+
let score = normalizedExpectationScore * 0.9;
|
|
202
294
|
if (results.length > 0) score += 0.1;
|
|
203
295
|
if (forbiddenMatches.length > 0) score -= 0.3;
|
|
204
296
|
score = Math.max(0, Math.min(1, score));
|
|
@@ -221,6 +313,109 @@ export function scoreRetrievalCase(
|
|
|
221
313
|
};
|
|
222
314
|
}
|
|
223
315
|
|
|
316
|
+
export function scoreCanaryCase(
|
|
317
|
+
evalCase: CanaryEvalCase,
|
|
318
|
+
results: Array<{ entry: { id: string; text: string; scope: string; metadata?: string } }>,
|
|
319
|
+
): CanaryCaseReport {
|
|
320
|
+
const profile = evalCase.profile || "default";
|
|
321
|
+
const ids = results.map((r) => r.entry.id);
|
|
322
|
+
const topK = evalCase.expectTopK ?? 3;
|
|
323
|
+
const limit = evalCase.limit ?? results.length;
|
|
324
|
+
const joined = results
|
|
325
|
+
.map((r) => `${r.entry.scope}\n${r.entry.text}\n${r.entry.metadata || ""}`)
|
|
326
|
+
.join("\n")
|
|
327
|
+
.toLowerCase();
|
|
328
|
+
|
|
329
|
+
// memory id 用前缀匹配:targets/expectOrder/hardNegatives 常写 8 位短前缀,
|
|
330
|
+
// 而召回结果的 entry.id 是完整 UUID(recallnest 惯例:≥8 hex 前缀即可定位)。
|
|
331
|
+
const idMatches = (fullId: string, target: string): boolean =>
|
|
332
|
+
fullId === target || fullId.startsWith(target);
|
|
333
|
+
const rankOf = (target: string): number | null => {
|
|
334
|
+
const idx = ids.findIndex((fullId) => idMatches(fullId, target));
|
|
335
|
+
return idx === -1 ? null : idx + 1;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// A 类:单目标命中
|
|
339
|
+
const targets = evalCase.targets || [];
|
|
340
|
+
const targetRanks = targets.map((id) => ({ id, rank: rankOf(id) }));
|
|
341
|
+
const top1Hit = targetRanks.some((t) => t.rank === 1);
|
|
342
|
+
const top3Hit = targetRanks.some((t) => t.rank !== null && t.rank <= Math.min(3, topK));
|
|
343
|
+
|
|
344
|
+
// C 类:干扰项进 TopK(limit 内)即违规(同样前缀匹配)
|
|
345
|
+
const forbiddenIdMatches = (evalCase.hardNegatives || []).filter((neg) => {
|
|
346
|
+
const rank = rankOf(neg);
|
|
347
|
+
return rank !== null && rank <= limit;
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// B 类:新旧序,前者须召回且 rank 小于后者
|
|
351
|
+
let orderOk: boolean | null = null;
|
|
352
|
+
const order = evalCase.expectOrder || [];
|
|
353
|
+
if (order.length >= 2) {
|
|
354
|
+
orderOk = true;
|
|
355
|
+
for (let i = 0; i < order.length - 1; i += 1) {
|
|
356
|
+
const a = rankOf(order[i]);
|
|
357
|
+
const b = rankOf(order[i + 1]);
|
|
358
|
+
if (a === null) {
|
|
359
|
+
orderOk = false;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
if (b !== null && a >= b) {
|
|
363
|
+
orderOk = false;
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// D 类:内容要点命中
|
|
370
|
+
const matchedContentAny = matchedTerms(evalCase.expectContentAny, joined);
|
|
371
|
+
|
|
372
|
+
// 文本禁词
|
|
373
|
+
const forbiddenMatches = matchedTerms(evalCase.forbid, joined);
|
|
374
|
+
|
|
375
|
+
// 动态加权:只计 case 实际声明的维度
|
|
376
|
+
const parts: Array<{ weight: number; value: number }> = [];
|
|
377
|
+
if (targets.length > 0) {
|
|
378
|
+
const ranks = targetRanks.map((t) => t.rank).filter((r): r is number => r !== null);
|
|
379
|
+
const bestRank = ranks.length > 0 ? Math.min(...ranks) : null;
|
|
380
|
+
const targetScore = bestRank === null ? 0 : bestRank === 1 ? 1 : bestRank <= 3 ? 0.7 : 0.4;
|
|
381
|
+
parts.push({ weight: 0.5, value: targetScore });
|
|
382
|
+
}
|
|
383
|
+
if (orderOk !== null) {
|
|
384
|
+
parts.push({ weight: 0.2, value: orderOk ? 1 : 0 });
|
|
385
|
+
}
|
|
386
|
+
const contentAny = evalCase.expectContentAny || [];
|
|
387
|
+
if (contentAny.length > 0) {
|
|
388
|
+
parts.push({ weight: 0.3, value: matchedContentAny.length / contentAny.length });
|
|
389
|
+
}
|
|
390
|
+
const totalWeight = parts.reduce((sum, p) => sum + p.weight, 0);
|
|
391
|
+
let score = totalWeight > 0
|
|
392
|
+
? parts.reduce((sum, p) => sum + p.weight * p.value, 0) / totalWeight
|
|
393
|
+
: (results.length > 0 ? 0.5 : 0);
|
|
394
|
+
if (forbiddenIdMatches.length > 0) score -= 0.3;
|
|
395
|
+
if (forbiddenMatches.length > 0) score -= 0.3;
|
|
396
|
+
score = Math.max(0, Math.min(1, score));
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
mode: "canary",
|
|
400
|
+
name: evalCase.name,
|
|
401
|
+
query: evalCase.query,
|
|
402
|
+
profile,
|
|
403
|
+
score,
|
|
404
|
+
passed: score >= 0.7 && forbiddenIdMatches.length === 0 && forbiddenMatches.length === 0,
|
|
405
|
+
hitCount: results.length,
|
|
406
|
+
targetRanks,
|
|
407
|
+
top1Hit,
|
|
408
|
+
top3Hit,
|
|
409
|
+
forbiddenIdMatches,
|
|
410
|
+
orderOk,
|
|
411
|
+
matchedContentAny,
|
|
412
|
+
forbiddenMatches,
|
|
413
|
+
topIds: ids.slice(0, 5),
|
|
414
|
+
topSnippet: results[0] ? clip(results[0].entry.text) : "-",
|
|
415
|
+
notes: evalCase.notes,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
224
419
|
function joinResumeSections(response: ResumeContextResponse): string {
|
|
225
420
|
return [
|
|
226
421
|
response.summary,
|
|
@@ -408,11 +603,12 @@ function markdownRetrievalReport(reports: RetrievalCaseReport[]): string {
|
|
|
408
603
|
`- Cases: ${reports.length}`,
|
|
409
604
|
`- Passed: ${passed}/${reports.length}`,
|
|
410
605
|
`- Average score: ${formatPercent(average)}`,
|
|
606
|
+
...costSummaryLines(summarizeCost(reports), reports.length),
|
|
411
607
|
"",
|
|
412
|
-
"| Case | Profile | Score | Pass | Hits | Top scopes |",
|
|
413
|
-
"
|
|
608
|
+
"| Case | Profile | Score | Pass | Hits | ms | ~tok | Top scopes |",
|
|
609
|
+
"|------|---------|-------|------|------|----|------|------------|",
|
|
414
610
|
...reports.map((item) =>
|
|
415
|
-
`| ${item.name} | ${item.profile} | ${(item.score * 100).toFixed(0)}% | ${item.passed ? "yes" : "no"} | ${item.hitCount} | ${item.topScopes.join(", ") || "-"} |`,
|
|
611
|
+
`| ${item.name} | ${item.profile} | ${(item.score * 100).toFixed(0)}% | ${item.passed ? "yes" : "no"} | ${item.hitCount} | ${item.cost?.totalMs ?? "-"} | ${item.cost?.contextTokens ?? "-"} | ${item.topScopes.join(", ") || "-"} |`,
|
|
416
612
|
),
|
|
417
613
|
"",
|
|
418
614
|
"## Case Notes",
|
|
@@ -431,6 +627,53 @@ function markdownRetrievalReport(reports: RetrievalCaseReport[]): string {
|
|
|
431
627
|
lines.push(`- Matched all: ${item.matchedAll.join(", ") || "-"}`);
|
|
432
628
|
lines.push(`- Matched scopes: ${item.matchedScopes.join(", ") || "-"}`);
|
|
433
629
|
lines.push(`- Forbidden matches: ${item.forbiddenMatches.join(", ") || "-"}`);
|
|
630
|
+
if (item.cost) lines.push(`- Cost: ${formatCost(item.cost)}`);
|
|
631
|
+
if (item.notes) lines.push(`- Notes: ${item.notes}`);
|
|
632
|
+
lines.push("");
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return lines.join("\n");
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function markdownCanaryReport(reports: CanaryCaseReport[]): string {
|
|
639
|
+
const { passed, average } = summarizeReports(reports);
|
|
640
|
+
const top1 = reports.filter((r) => r.top1Hit).length;
|
|
641
|
+
const top3 = reports.filter((r) => r.top3Hit).length;
|
|
642
|
+
|
|
643
|
+
const lines = [
|
|
644
|
+
"# RecallNest Canary Eval",
|
|
645
|
+
"",
|
|
646
|
+
`- Generated: ${new Date().toISOString()}`,
|
|
647
|
+
`- Cases: ${reports.length}`,
|
|
648
|
+
`- Passed: ${passed}/${reports.length}`,
|
|
649
|
+
`- Average score: ${formatPercent(average)}`,
|
|
650
|
+
`- Top1 hit: ${top1}/${reports.length}`,
|
|
651
|
+
`- Top3 hit: ${top3}/${reports.length}`,
|
|
652
|
+
...costSummaryLines(summarizeCost(reports), reports.length),
|
|
653
|
+
"",
|
|
654
|
+
"| Case | Profile | Score | Pass | Top1 | Top3 | Order | Forbid | ms | ~tok |",
|
|
655
|
+
"|------|---------|-------|------|------|------|-------|--------|----|------|",
|
|
656
|
+
...reports.map((item) =>
|
|
657
|
+
`| ${item.name} | ${item.profile} | ${(item.score * 100).toFixed(0)}% | ${item.passed ? "yes" : "no"} | ${item.top1Hit ? "yes" : "-"} | ${item.top3Hit ? "yes" : "-"} | ${item.orderOk === null ? "-" : item.orderOk ? "ok" : "bad"} | ${item.forbiddenIdMatches.length + item.forbiddenMatches.length || "-"} | ${item.cost?.totalMs ?? "-"} | ${item.cost?.contextTokens ?? "-"} |`,
|
|
658
|
+
),
|
|
659
|
+
"",
|
|
660
|
+
"## Case Notes",
|
|
661
|
+
"",
|
|
662
|
+
];
|
|
663
|
+
|
|
664
|
+
for (const item of reports) {
|
|
665
|
+
lines.push(`### ${item.name}`);
|
|
666
|
+
lines.push(`- Query: ${item.query}`);
|
|
667
|
+
lines.push(`- Score: ${(item.score * 100).toFixed(0)}% (${item.passed ? "pass" : "fail"})`);
|
|
668
|
+
lines.push(`- Target ranks: ${item.targetRanks.map((t) => `${t.id}=${t.rank ?? "miss"}`).join(", ") || "-"}`);
|
|
669
|
+
lines.push(`- Top1 / Top3: ${item.top1Hit ? "yes" : "no"} / ${item.top3Hit ? "yes" : "no"}`);
|
|
670
|
+
lines.push(`- Order ok: ${item.orderOk === null ? "n/a" : item.orderOk}`);
|
|
671
|
+
lines.push(`- Matched content: ${item.matchedContentAny.join(", ") || "-"}`);
|
|
672
|
+
lines.push(`- Forbidden id matches: ${item.forbiddenIdMatches.join(", ") || "-"}`);
|
|
673
|
+
lines.push(`- Forbidden term matches: ${item.forbiddenMatches.join(", ") || "-"}`);
|
|
674
|
+
lines.push(`- Top ids: ${item.topIds.join(", ") || "-"}`);
|
|
675
|
+
lines.push(`- Top snippet: ${item.topSnippet}`);
|
|
676
|
+
if (item.cost) lines.push(`- Cost: ${formatCost(item.cost)}`);
|
|
434
677
|
if (item.notes) lines.push(`- Notes: ${item.notes}`);
|
|
435
678
|
lines.push("");
|
|
436
679
|
}
|
|
@@ -485,6 +728,88 @@ function markdownContinuityReport(reports: ContinuityCaseReport[]): string {
|
|
|
485
728
|
return lines.join("\n");
|
|
486
729
|
}
|
|
487
730
|
|
|
731
|
+
/**
|
|
732
|
+
* Turn a finished TraceCollector plus the returned memories into cost metrics.
|
|
733
|
+
*
|
|
734
|
+
* The `mode` argument to finalize() only populates a display field on the trace
|
|
735
|
+
* object; this function consumes stages / totalMs only, so it is not inferred.
|
|
736
|
+
*/
|
|
737
|
+
function collectCostMetrics(
|
|
738
|
+
trace: TraceCollector,
|
|
739
|
+
query: string,
|
|
740
|
+
results: Array<{ entry: { text: string } }>,
|
|
741
|
+
): EvalCostMetrics {
|
|
742
|
+
const finalized = trace.finalize(query, "hybrid");
|
|
743
|
+
const rerankStage = finalized.stages.find((stage) => stage.name === "rerank");
|
|
744
|
+
const slowestStages = [...finalized.stages]
|
|
745
|
+
.sort((a, b) => b.durationMs - a.durationMs)
|
|
746
|
+
.slice(0, 3)
|
|
747
|
+
.map((stage) => ({ name: stage.name, durationMs: stage.durationMs }));
|
|
748
|
+
|
|
749
|
+
return {
|
|
750
|
+
totalMs: finalized.totalMs,
|
|
751
|
+
resultCount: results.length,
|
|
752
|
+
contextTokens: results.reduce((sum, item) => sum + estimateTokens(item.entry.text), 0),
|
|
753
|
+
rerankMs: rerankStage ? rerankStage.durationMs : null,
|
|
754
|
+
slowestStages,
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/** Aggregate cost across cases. Cases without cost data are skipped, and the
|
|
759
|
+
* count of those that did carry it is returned so the report can be honest
|
|
760
|
+
* about coverage rather than silently averaging over fewer cases. */
|
|
761
|
+
function summarizeCost(reports: Array<{ cost?: EvalCostMetrics }>): {
|
|
762
|
+
measured: number;
|
|
763
|
+
totalMs: number;
|
|
764
|
+
avgMs: number;
|
|
765
|
+
totalTokens: number;
|
|
766
|
+
avgTokens: number;
|
|
767
|
+
} | null {
|
|
768
|
+
const withCost = reports.map((item) => item.cost).filter((cost): cost is EvalCostMetrics => Boolean(cost));
|
|
769
|
+
if (withCost.length === 0) return null;
|
|
770
|
+
|
|
771
|
+
const totalMs = withCost.reduce((sum, cost) => sum + cost.totalMs, 0);
|
|
772
|
+
const totalTokens = withCost.reduce((sum, cost) => sum + cost.contextTokens, 0);
|
|
773
|
+
return {
|
|
774
|
+
measured: withCost.length,
|
|
775
|
+
totalMs,
|
|
776
|
+
avgMs: Math.round(totalMs / withCost.length),
|
|
777
|
+
totalTokens,
|
|
778
|
+
avgTokens: Math.round(totalTokens / withCost.length),
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/** Aggregate cost lines for a report header. Returns [] when no case carried
|
|
783
|
+
* cost data, so reports produced before this instrumentation still render. */
|
|
784
|
+
function costSummaryLines(
|
|
785
|
+
summary: ReturnType<typeof summarizeCost>,
|
|
786
|
+
caseCount: number,
|
|
787
|
+
): string[] {
|
|
788
|
+
if (!summary) return [];
|
|
789
|
+
const coverage = summary.measured === caseCount
|
|
790
|
+
? ""
|
|
791
|
+
: ` (measured on ${summary.measured}/${caseCount} cases)`;
|
|
792
|
+
return [
|
|
793
|
+
`- Retrieval latency: ${summary.avgMs} ms avg, ${summary.totalMs} ms total${coverage}`,
|
|
794
|
+
`- Context tokens: ~${summary.avgTokens} avg, ~${summary.totalTokens} total`
|
|
795
|
+
+ ` (estimated, not tokenizer-measured — compare across runs, not against other systems)`,
|
|
796
|
+
];
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/** One-line per-case cost breakdown. */
|
|
800
|
+
function formatCost(cost: EvalCostMetrics): string {
|
|
801
|
+
const parts = [
|
|
802
|
+
`${cost.totalMs}ms`,
|
|
803
|
+
`~${cost.contextTokens} tok`,
|
|
804
|
+
`${cost.resultCount} results`,
|
|
805
|
+
cost.rerankMs === null ? "no rerank stage" : `rerank ${cost.rerankMs}ms`,
|
|
806
|
+
];
|
|
807
|
+
const slowest = cost.slowestStages
|
|
808
|
+
.map((stage) => `${stage.name} ${stage.durationMs}ms`)
|
|
809
|
+
.join(", ");
|
|
810
|
+
return `${parts.join(" / ")}${slowest ? ` | slowest: ${slowest}` : ""}`;
|
|
811
|
+
}
|
|
812
|
+
|
|
488
813
|
export async function runRetrievalEval(
|
|
489
814
|
cases: RetrievalEvalCase[],
|
|
490
815
|
deps: {
|
|
@@ -502,13 +827,16 @@ export async function runRetrievalEval(
|
|
|
502
827
|
const profileName = evalCase.profile || "default";
|
|
503
828
|
const { retriever, accessTracker } = createEvalComponentsForCase(profileName);
|
|
504
829
|
try {
|
|
830
|
+
const trace = new TraceCollector();
|
|
505
831
|
const results = await retriever.retrieve({
|
|
506
832
|
query: evalCase.query,
|
|
507
833
|
limit: evalCase.limit || 5,
|
|
508
834
|
scopeFilter: evalCase.scope ? [evalCase.scope] : undefined,
|
|
509
835
|
source: "auto-recall",
|
|
836
|
+
trace,
|
|
510
837
|
});
|
|
511
838
|
const report = scoreRetrievalCase(evalCase, results);
|
|
839
|
+
report.cost = collectCostMetrics(trace, evalCase.query, results);
|
|
512
840
|
reports.push(report);
|
|
513
841
|
if (cases.length > 1) {
|
|
514
842
|
logInfo(
|
|
@@ -523,6 +851,47 @@ export async function runRetrievalEval(
|
|
|
523
851
|
return reports;
|
|
524
852
|
}
|
|
525
853
|
|
|
854
|
+
export async function runCanaryEval(
|
|
855
|
+
cases: CanaryEvalCase[],
|
|
856
|
+
deps: {
|
|
857
|
+
createEvalComponents?: EvalComponentFactory;
|
|
858
|
+
} = {},
|
|
859
|
+
): Promise<CanaryCaseReport[]> {
|
|
860
|
+
const config = deps.createEvalComponents ? null : loadConfig();
|
|
861
|
+
const createEvalComponentsForCase = deps.createEvalComponents || createFreshEvalComponentsFactory(config!);
|
|
862
|
+
const reports: CanaryCaseReport[] = [];
|
|
863
|
+
|
|
864
|
+
for (const [index, evalCase] of cases.entries()) {
|
|
865
|
+
if (cases.length > 1) {
|
|
866
|
+
logInfo(`[INFO] canary-eval ${index + 1}/${cases.length}: ${evalCase.name}`);
|
|
867
|
+
}
|
|
868
|
+
const profileName = evalCase.profile || "default";
|
|
869
|
+
const { retriever, accessTracker } = createEvalComponentsForCase(profileName);
|
|
870
|
+
try {
|
|
871
|
+
const trace = new TraceCollector();
|
|
872
|
+
const results = await retriever.retrieve({
|
|
873
|
+
query: evalCase.query,
|
|
874
|
+
limit: evalCase.limit || 8,
|
|
875
|
+
scopeFilter: evalCase.scope ? [evalCase.scope] : undefined,
|
|
876
|
+
source: "auto-recall",
|
|
877
|
+
trace,
|
|
878
|
+
});
|
|
879
|
+
const report = scoreCanaryCase(evalCase, results);
|
|
880
|
+
report.cost = collectCostMetrics(trace, evalCase.query, results);
|
|
881
|
+
reports.push(report);
|
|
882
|
+
if (cases.length > 1) {
|
|
883
|
+
logInfo(
|
|
884
|
+
`[INFO] canary-eval ${index + 1}/${cases.length} done: ${evalCase.name} ${formatPercent(report.score)} ${report.passed ? "pass" : "fail"}`,
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
} finally {
|
|
888
|
+
accessTracker.destroy();
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
return reports;
|
|
893
|
+
}
|
|
894
|
+
|
|
526
895
|
export async function runContinuityEval(
|
|
527
896
|
cases: ContinuityEvalCase[],
|
|
528
897
|
options: { recordObservations?: boolean; observationScope?: string; observationSource?: string } = {},
|
|
@@ -610,6 +979,25 @@ async function main() {
|
|
|
610
979
|
return;
|
|
611
980
|
}
|
|
612
981
|
|
|
982
|
+
if (args.mode === "canary") {
|
|
983
|
+
const cases = loadCases<CanaryEvalCase>("canary", args.casesPath);
|
|
984
|
+
const reports = await runCanaryEval(cases);
|
|
985
|
+
if (args.jsonMode) {
|
|
986
|
+
const payload = JSON.stringify({
|
|
987
|
+
mode: "canary",
|
|
988
|
+
generatedAt: new Date().toISOString(),
|
|
989
|
+
reports,
|
|
990
|
+
}, null, 2);
|
|
991
|
+
writeOutput(args.outputPath, payload);
|
|
992
|
+
console.log(payload);
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
const output = markdownCanaryReport(reports);
|
|
996
|
+
writeOutput(args.outputPath, output);
|
|
997
|
+
console.log(output);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
613
1001
|
const cases = loadCases<RetrievalEvalCase>("retrieval", args.casesPath);
|
|
614
1002
|
const reports = await runRetrievalEval(cases);
|
|
615
1003
|
if (args.jsonMode) {
|