@sbissoli/mcp-evals 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,SAAS,GACjB,YAAY,GACZ,YAAY,GACZ,QAAQ,GACR,SAAS,GACT,MAAM,GACN,SAAS,GACT,OAAO,CAAC;AAEZ,qBAAa,YAAa,SAAQ,KAAK;aAMnB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,SAAS;aACf,SAAS,EAAE,OAAO;IAPpC,iEAAiE;IACjE,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;gBAGpC,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,EACf,SAAS,EAAE,OAAO;CAKrC;AAED,4FAA4F;AAC5F,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAErD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAUtG;AAED,eAAO,MAAM,eAAe,OAAQ,CAAC;AACrC,eAAO,MAAM,cAAc,QAAS,CAAC;AACrC,eAAO,MAAM,WAAW,IAAI,CAAC;AAE7B;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,MAAM,CAK7E;AAED,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAI9E"}
package/dist/retry.js ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Núcleo puro de retry/classificação de erro do runner. Sem rede, sem I/O — os testes
3
+ * offline o exercitam diretamente.
4
+ *
5
+ * O runner manda cada fixture à Anthropic Messages API. Duas classes de falha precisam
6
+ * ser distinguidas, porque nenhuma delas é sinal de acurácia (uma fixture que nunca
7
+ * chegou ao modelo não foi "respondida errado"):
8
+ * - infra transitória (429 rate-limit, 529 overloaded, 5xx, rede) → vale retry;
9
+ * - infra fatal (401 auth, 400 "credit balance too low") → retry nunca ajuda; a rodada
10
+ * inteira deve falhar rápido com remédio claro em vez de martelar a API.
11
+ */
12
+ export class EvalApiError extends Error {
13
+ status;
14
+ kind;
15
+ retryable;
16
+ /** Dica Retry-After (segundos) vinda da API, quando presente. */
17
+ retryAfterSeconds;
18
+ constructor(message, status, kind, retryable) {
19
+ super(message);
20
+ this.status = status;
21
+ this.kind = kind;
22
+ this.retryable = retryable;
23
+ this.name = "EvalApiError";
24
+ }
25
+ }
26
+ /** Kinds fatais curto-circuitam a rodada inteira — toda fixture bateria na mesma parede. */
27
+ export function isFatalInfra(kind) {
28
+ return kind === "auth" || kind === "billing";
29
+ }
30
+ /**
31
+ * Classifica um erro HTTP da Anthropic API (status + corpo) em kind + retryable.
32
+ * Puro; o corpo só é inspecionado para distinguir um 400 de billing ("credit balance
33
+ * is too low") dos demais 400.
34
+ */
35
+ export function classifyApiError(status, body) {
36
+ const lower = (body || "").toLowerCase();
37
+ if (status === 429)
38
+ return { kind: "rate_limit", retryable: true };
39
+ if (status === 529)
40
+ return { kind: "overloaded", retryable: true };
41
+ if (status === 401 || status === 403)
42
+ return { kind: "auth", retryable: false };
43
+ if (status === 400 && (lower.includes("credit balance") || lower.includes("plans & billing"))) {
44
+ return { kind: "billing", retryable: false };
45
+ }
46
+ if (status >= 500)
47
+ return { kind: "server", retryable: true };
48
+ return { kind: "other", retryable: false };
49
+ }
50
+ export const BASE_BACKOFF_MS = 2_000;
51
+ export const MAX_BACKOFF_MS = 30_000;
52
+ export const MAX_RETRIES = 5;
53
+ /**
54
+ * Backoff base (ms) antes da tentativa de retry (0-based). Honra a dica Retry-After
55
+ * (segundos) da API quando presente — importante no 429, onde a janela de rate-limit
56
+ * precisa rolar antes da próxima tentativa. O chamador adiciona jitter; mantido puro
57
+ * para o teste unitário poder asserir valores exatos.
58
+ */
59
+ export function backoffMs(attempt, retryAfterSeconds) {
60
+ if (retryAfterSeconds !== undefined && Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0) {
61
+ return Math.min(Math.ceil(retryAfterSeconds * 1000), MAX_BACKOFF_MS);
62
+ }
63
+ return Math.min(BASE_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS);
64
+ }
65
+ /** Interpreta um header `Retry-After` (só a forma inteiro-segundos) em segundos. */
66
+ export function parseRetryAfter(headerValue) {
67
+ if (!headerValue)
68
+ return undefined;
69
+ const secs = parseInt(headerValue.trim(), 10);
70
+ return Number.isFinite(secs) && secs >= 0 ? secs : undefined;
71
+ }
72
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH,MAAM,OAAO,YAAa,SAAQ,KAAK;IAMnB;IACA;IACA;IAPlB,iEAAiE;IACjE,iBAAiB,CAAqB;IAEtC,YACE,OAAe,EACC,MAAc,EACd,IAAe,EACf,SAAkB;QAElC,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAW;QACf,cAAS,GAAT,SAAS,CAAS;QAGlC,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,4FAA4F;AAC5F,MAAM,UAAU,YAAY,CAAC,IAAe;IAC1C,OAAO,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,SAAS,CAAC;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,IAAY;IAC3D,MAAM,KAAK,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACzC,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACnE,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACnE,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAChF,IAAI,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,EAAE,CAAC;QAC9F,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAC/C,CAAC;IACD,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC9D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC;AACrC,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;AACrC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAE7B;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,iBAA0B;IACnE,IAAI,iBAAiB,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,IAAI,iBAAiB,GAAG,CAAC,EAAE,CAAC;QACnG,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,EAAE,cAAc,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,eAAe,GAAG,CAAC,IAAI,OAAO,EAAE,cAAc,CAAC,CAAC;AAClE,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,eAAe,CAAC,WAA0B;IACxD,IAAI,CAAC,WAAW;QAAE,OAAO,SAAS,CAAC;IACnC,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Runner — dirige o eval real de seleção de tool contra a Anthropic Messages API.
3
+ *
4
+ * Para cada fixture, envia o catálogo inteiro como `tools` da requisição, força uma
5
+ * chamada de tool com `tool_choice: {type: "any"}` e registra a tool escolhida. O scorer
6
+ * puro (score.ts) transforma as escolhas em acurácia top-1/top-k/por-área e na decisão
7
+ * de gate.
8
+ *
9
+ * Princípios (herdados do harness do senado — reexecução barata):
10
+ * - Sem dependência de SDK: `fetch` puro contra /v1/messages.
11
+ * - Gateado em ANTHROPIC_API_KEY: ausente → imprime instruções e devolve exit 0
12
+ * (nunca quebra CI, nunca exige rede).
13
+ * - Catálogo + fixtures são a fonte de verdade; o modelo só escolhe entre tools reais.
14
+ *
15
+ * Uso típico no `evals/run.ts` de um projeto:
16
+ *
17
+ * const { exitCode } = await runEval({ catalog, fixtures: FIXTURES, systemPrompt: "..." });
18
+ * process.exit(exitCode);
19
+ *
20
+ * Variáveis de ambiente (todas opcionais): EVAL_MODEL, EVAL_CONCURRENCY, EVAL_LIMIT.
21
+ */
22
+ import { type Catalog } from "./catalog.js";
23
+ import type { EvalFixture } from "./fixtures.js";
24
+ import { type GateOptions, type GateResult, type ScoreReport } from "./score.js";
25
+ import { type FixtureError } from "./report.js";
26
+ export interface EvalRunnerConfig {
27
+ catalog: Catalog;
28
+ fixtures: EvalFixture[];
29
+ /** System prompt de roteamento, na persona/idioma do servidor. */
30
+ systemPrompt: string;
31
+ /** Modelo padrão quando EVAL_MODEL não está definido. Padrão: claude-opus-4-8. */
32
+ defaultModel?: string;
33
+ /** Limiares/contexto do gate (toolCount é preenchido do catálogo se omitido). */
34
+ gate?: GateOptions;
35
+ /** Ambiente a consultar (padrão: process.env). */
36
+ env?: Record<string, string | undefined>;
37
+ /** Saída normal (padrão: console.log). */
38
+ log?: (line: string) => void;
39
+ /** Saída de erro/progresso de retry (padrão: console.error). */
40
+ logError?: (line: string) => void;
41
+ /** Implementação de fetch (padrão: fetch global) — injetável para teste offline. */
42
+ fetchImpl?: typeof fetch;
43
+ /** Implementação de sleep (padrão: setTimeout) — injetável para teste offline. */
44
+ sleepImpl?: (ms: number) => Promise<void>;
45
+ /** Máximo de retries por fixture em falha transitória. Padrão: MAX_RETRIES (5). */
46
+ maxRetries?: number;
47
+ }
48
+ export interface EvalRunResult {
49
+ /** 0 = completo (ou pulado sem API key); 2 = rodada incompleta (dropout de infra). */
50
+ exitCode: number;
51
+ /** True quando ANTHROPIC_API_KEY não estava definido e nada rodou. */
52
+ skipped: boolean;
53
+ report?: ScoreReport;
54
+ gate?: GateResult;
55
+ errors?: FixtureError[];
56
+ }
57
+ /**
58
+ * Roda o eval completo e imprime o relatório. Devolve o resultado com `exitCode`
59
+ * (0 completo/pulado, 2 rodada incompleta) — o `evals/run.ts` do projeto decide se
60
+ * chama `process.exit`.
61
+ */
62
+ export declare function runEval(config: EvalRunnerConfig): Promise<EvalRunResult>;
63
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAA+C,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAa,KAAK,WAAW,EAAE,KAAK,UAAU,EAAoC,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9H,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAc9D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,0CAA0C;IAC1C,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,oFAAoF;IACpF,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,kFAAkF;IAClF,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,mFAAmF;IACnF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,sFAAsF;IACtF,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;CACzB;AA0ID;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAmE9E"}
package/dist/runner.js ADDED
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Runner — dirige o eval real de seleção de tool contra a Anthropic Messages API.
3
+ *
4
+ * Para cada fixture, envia o catálogo inteiro como `tools` da requisição, força uma
5
+ * chamada de tool com `tool_choice: {type: "any"}` e registra a tool escolhida. O scorer
6
+ * puro (score.ts) transforma as escolhas em acurácia top-1/top-k/por-área e na decisão
7
+ * de gate.
8
+ *
9
+ * Princípios (herdados do harness do senado — reexecução barata):
10
+ * - Sem dependência de SDK: `fetch` puro contra /v1/messages.
11
+ * - Gateado em ANTHROPIC_API_KEY: ausente → imprime instruções e devolve exit 0
12
+ * (nunca quebra CI, nunca exige rede).
13
+ * - Catálogo + fixtures são a fonte de verdade; o modelo só escolhe entre tools reais.
14
+ *
15
+ * Uso típico no `evals/run.ts` de um projeto:
16
+ *
17
+ * const { exitCode } = await runEval({ catalog, fixtures: FIXTURES, systemPrompt: "..." });
18
+ * process.exit(exitCode);
19
+ *
20
+ * Variáveis de ambiente (todas opcionais): EVAL_MODEL, EVAL_CONCURRENCY, EVAL_LIMIT.
21
+ */
22
+ import { catalogAsAnthropicTools } from "./catalog.js";
23
+ import { scoreItem } from "./score.js";
24
+ import { formatReport } from "./report.js";
25
+ import { EvalApiError, classifyApiError, backoffMs, parseRetryAfter, isFatalInfra, MAX_RETRIES, } from "./retry.js";
26
+ const API_URL = "https://api.anthropic.com/v1/messages";
27
+ const API_VERSION = "2023-06-01";
28
+ const DEFAULT_MODEL = "claude-opus-4-8";
29
+ function defaultSleep(ms) {
30
+ return new Promise((resolve) => setTimeout(resolve, ms));
31
+ }
32
+ /** Uma tentativa HTTP. Lança EvalApiError tipado em qualquer não-2xx ou falha de rede. */
33
+ async function callOnce(fixture, apiKey, model, tools, systemPrompt, fetchImpl) {
34
+ const body = {
35
+ model,
36
+ max_tokens: 1024,
37
+ system: systemPrompt,
38
+ tools,
39
+ tool_choice: { type: "any" },
40
+ messages: [{ role: "user", content: fixture.query }],
41
+ };
42
+ let res;
43
+ try {
44
+ res = await fetchImpl(API_URL, {
45
+ method: "POST",
46
+ headers: {
47
+ "content-type": "application/json",
48
+ "x-api-key": apiKey,
49
+ "anthropic-version": API_VERSION,
50
+ },
51
+ body: JSON.stringify(body),
52
+ });
53
+ }
54
+ catch (e) {
55
+ // DNS/TCP/TLS/abort — sempre vale retry.
56
+ throw new EvalApiError(`erro de rede: ${e.message || "desconhecido"}`, 0, "network", true);
57
+ }
58
+ if (!res.ok) {
59
+ const text = await res.text().catch(() => "");
60
+ const { kind, retryable } = classifyApiError(res.status, text);
61
+ const err = new EvalApiError(`Anthropic API ${res.status}: ${text.slice(0, 200)}`, res.status, kind, retryable);
62
+ err.retryAfterSeconds = parseRetryAfter(res.headers.get("retry-after"));
63
+ throw err;
64
+ }
65
+ const data = (await res.json());
66
+ const picks = [];
67
+ for (const block of data.content ?? []) {
68
+ if (block.type === "tool_use" && typeof block.name === "string") {
69
+ picks.push(block.name);
70
+ }
71
+ }
72
+ return { id: fixture.id, predictedTools: picks };
73
+ }
74
+ /**
75
+ * Chama a API para uma fixture, com retry de falhas transitórias (429/529/5xx/rede) em
76
+ * backoff exponencial limitado + jitter, honrando Retry-After. Falhas fatais de infra
77
+ * (auth/billing) lançam imediatamente — retry só desperdiça tempo. O EvalApiError
78
+ * lançado carrega `kind` para o chamador distinguir "dropout de infra" de "tool errada".
79
+ */
80
+ async function predictOne(fixture, apiKey, model, tools, systemPrompt, fetchImpl, sleepImpl, maxRetries, logError) {
81
+ let lastError;
82
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
83
+ try {
84
+ return await callOnce(fixture, apiKey, model, tools, systemPrompt, fetchImpl);
85
+ }
86
+ catch (e) {
87
+ const err = e instanceof EvalApiError ? e : new EvalApiError(e.message, 0, "other", false);
88
+ lastError = err;
89
+ if (!err.retryable || attempt === maxRetries)
90
+ throw err;
91
+ const base = backoffMs(attempt, err.retryAfterSeconds);
92
+ const jitter = Math.floor(Math.random() * 500);
93
+ logError(` ↻ ${fixture.id}: ${err.kind} (HTTP ${err.status}); retry ${attempt + 1}/${maxRetries} em ${base + jitter}ms`);
94
+ await sleepImpl(base + jitter);
95
+ }
96
+ }
97
+ throw lastError ?? new EvalApiError(`falha desconhecida em ${fixture.id}`, 0, "other", false);
98
+ }
99
+ /** Mapeia `items` com concorrência limitada, preservando a ordem. */
100
+ async function mapWithConcurrency(items, limit, fn) {
101
+ const results = new Array(items.length);
102
+ let next = 0;
103
+ async function worker() {
104
+ while (true) {
105
+ const i = next++;
106
+ if (i >= items.length)
107
+ return;
108
+ results[i] = await fn(items[i], i);
109
+ }
110
+ }
111
+ const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
112
+ await Promise.all(workers);
113
+ return results;
114
+ }
115
+ function printSetupInstructions(config, log) {
116
+ const lines = [
117
+ "",
118
+ " ANTHROPIC_API_KEY não está definido — pulando a execução com modelo real.",
119
+ " Este runner faz a seleção de tool real via Anthropic Messages API.",
120
+ "",
121
+ " Para rodar de verdade:",
122
+ " ANTHROPIC_API_KEY=sk-ant-... npx tsx evals/run.ts",
123
+ "",
124
+ " Variáveis opcionais:",
125
+ ` EVAL_MODEL=... (padrão: ${config.defaultModel ?? DEFAULT_MODEL})`,
126
+ " EVAL_CONCURRENCY=4 (requisições paralelas)",
127
+ " EVAL_LIMIT=10 (rodar só as N primeiras fixtures)",
128
+ "",
129
+ ` Catálogo: ${config.catalog.tools.length} tools · Fixtures: ${config.fixtures.length} consultas`,
130
+ " O scorer e o catálogo são testados offline em `npm test`.",
131
+ "",
132
+ ];
133
+ log(lines.join("\n"));
134
+ }
135
+ /**
136
+ * Roda o eval completo e imprime o relatório. Devolve o resultado com `exitCode`
137
+ * (0 completo/pulado, 2 rodada incompleta) — o `evals/run.ts` do projeto decide se
138
+ * chama `process.exit`.
139
+ */
140
+ export async function runEval(config) {
141
+ const env = config.env ?? process.env;
142
+ const log = config.log ?? console.log;
143
+ const logError = config.logError ?? console.error;
144
+ const fetchImpl = config.fetchImpl ?? fetch;
145
+ const sleepImpl = config.sleepImpl ?? defaultSleep;
146
+ const maxRetries = config.maxRetries ?? MAX_RETRIES;
147
+ const apiKey = env.ANTHROPIC_API_KEY;
148
+ if (!apiKey) {
149
+ printSetupInstructions(config, log);
150
+ return { exitCode: 0, skipped: true };
151
+ }
152
+ const model = env.EVAL_MODEL || config.defaultModel || DEFAULT_MODEL;
153
+ const concurrency = Math.max(1, parseInt(env.EVAL_CONCURRENCY || "4", 10) || 4);
154
+ const limit = parseInt(env.EVAL_LIMIT || "", 10);
155
+ const fixtures = Number.isFinite(limit) && limit > 0 ? config.fixtures.slice(0, limit) : config.fixtures;
156
+ const tools = catalogAsAnthropicTools(config.catalog);
157
+ log(`Rodando ${fixtures.length} fixtures contra ${tools.length} tools (modelo ${model}, concorrência ${concurrency})...`);
158
+ // Depois de uma falha fatal de infra (auth/billing), toda fixture restante bateria na
159
+ // mesma parede — curto-circuita em vez de martelar a API com requisições condenadas.
160
+ let fatalAbort = null;
161
+ const predictions = [];
162
+ const errors = [];
163
+ await mapWithConcurrency(fixtures, concurrency, async (f) => {
164
+ if (fatalAbort) {
165
+ errors.push({ id: f.id, kind: fatalAbort.kind, status: fatalAbort.status, message: "abortado (falha fatal anterior)" });
166
+ return;
167
+ }
168
+ try {
169
+ predictions.push(await predictOne(f, apiKey, model, tools, config.systemPrompt, fetchImpl, sleepImpl, maxRetries, logError));
170
+ }
171
+ catch (e) {
172
+ const err = e instanceof EvalApiError ? e : new EvalApiError(e.message, 0, "other", false);
173
+ if (isFatalInfra(err.kind))
174
+ fatalAbort = err;
175
+ logError(` ! ${f.id}: [${err.kind}] ${err.message}`);
176
+ errors.push({ id: f.id, kind: err.kind, status: err.status, message: err.message });
177
+ }
178
+ });
179
+ const predById = new Map(predictions.map((p) => [p.id, p]));
180
+ const items = fixtures.map((f) => {
181
+ // A "área" da fixture é a área da primeira expectedTool (a resposta canônica).
182
+ const first = f.expectedTools[0];
183
+ const area = (first !== undefined ? config.catalog.areaByName.get(first) : undefined) ?? "desconhecida";
184
+ return scoreItem(f, predById.get(f.id), area);
185
+ });
186
+ const gateOptions = {
187
+ ...config.gate,
188
+ ...(config.gate?.toolCount === undefined ? { toolCount: config.catalog.tools.length } : {}),
189
+ };
190
+ const formatted = formatReport(items, errors, model, gateOptions);
191
+ for (const line of formatted.lines)
192
+ log(line);
193
+ return {
194
+ exitCode: formatted.exitCode,
195
+ skipped: false,
196
+ report: formatted.report,
197
+ gate: formatted.gate,
198
+ errors,
199
+ };
200
+ }
201
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Núcleo de scoring puro do eval de seleção de tool. Sem rede, sem modelo, sem I/O.
3
+ *
4
+ * O runner produz, por fixture, a lista ordenada de tools que o modelo chamaria (mais
5
+ * provável primeiro). Este módulo transforma essas predições + os `expectedTools` das
6
+ * fixtures em números de acurácia: top-1, top-k e por área. São estas funções que os
7
+ * testes offline exercitam com dados sintéticos de acurácia conhecida.
8
+ */
9
+ import type { EvalFixture } from "./fixtures.js";
10
+ /** Predição do modelo para uma fixture: nomes de tool em ordem de prioridade. */
11
+ export interface Prediction {
12
+ id: string;
13
+ /** Candidatas em ordem (mais provável primeiro). Vazio se o modelo não chamou tool. */
14
+ predictedTools: string[];
15
+ }
16
+ export interface ScoredItem {
17
+ id: string;
18
+ area: string;
19
+ expectedTools: string[];
20
+ predictedTools: string[];
21
+ /** True se a predição top-1 está em expectedTools. */
22
+ top1: boolean;
23
+ /** Rank (1-based) da primeira predição correta, ou null se nenhuma acertou. */
24
+ hitRank: number | null;
25
+ }
26
+ export interface AreaAccuracy {
27
+ area: string;
28
+ total: number;
29
+ top1Correct: number;
30
+ top1Accuracy: number;
31
+ }
32
+ export interface ScoreReport {
33
+ total: number;
34
+ /** Fixtures com ao menos uma predição (o modelo tentou chamar uma tool). */
35
+ attempted: number;
36
+ top1Correct: number;
37
+ top1Accuracy: number;
38
+ /** topKAccuracy[k] = fração cujo primeiro acerto tem rank <= k, para k em 1..maxK. */
39
+ topKAccuracy: Record<number, number>;
40
+ byArea: AreaAccuracy[];
41
+ items: ScoredItem[];
42
+ }
43
+ /** Pontua uma fixture contra sua predição. `area` vem do catálogo, via chamador. */
44
+ export declare function scoreItem(fixture: Pick<EvalFixture, "id" | "expectedTools">, prediction: Prediction | undefined, area: string): ScoredItem;
45
+ /**
46
+ * Agrega itens pontuados num relatório.
47
+ *
48
+ * @param maxK maior k para o cálculo de acurácia top-k (padrão 3).
49
+ */
50
+ export declare function aggregate(items: ScoredItem[], maxK?: number): ScoreReport;
51
+ /** Conveniência: pontua um lote inteiro dado fixtures, predições e lookup de área. */
52
+ export declare function scoreAll(fixtures: Pick<EvalFixture, "id" | "expectedTools">[], predictions: Prediction[], areaByExpectedTool: (fixture: Pick<EvalFixture, "id" | "expectedTools">) => string, maxK?: number): ScoreReport;
53
+ export type GateDecision = "remediar" | "despriorizar-refatoracao" | "zona-cinzenta";
54
+ export interface GateResult {
55
+ decision: GateDecision;
56
+ accuracy: number;
57
+ /** Recomendação legível em pt-BR. */
58
+ message: string;
59
+ }
60
+ export interface GateOptions {
61
+ /** Abaixo deste valor → `remediar`. Padrão: 0.85. */
62
+ remediationThreshold?: number;
63
+ /** Neste valor ou acima → `despriorizar-refatoracao`. Padrão: 0.9. */
64
+ deprioritizeThreshold?: number;
65
+ /**
66
+ * Tamanho do catálogo, se informado citado na mensagem de despriorização
67
+ * ("mesmo com N tools") — dá contexto ao número de acurácia.
68
+ */
69
+ toolCount?: number;
70
+ }
71
+ export declare const GATE_REMEDIATION_THRESHOLD = 0.85;
72
+ export declare const GATE_DEPRIORITIZE_THRESHOLD = 0.9;
73
+ /**
74
+ * Aplica o gate de seleção de tool a uma acurácia top-1:
75
+ * < remediationThreshold → abrir sessão de remediação (deferred loading / Code Mode /
76
+ * agrupamento por sessão).
77
+ * >= deprioritizeThreshold → despriorizar refatoração de catálogo (seguir só
78
+ * consolidando via enums).
79
+ * entre os dois → zona cinzenta (manter sob observação).
80
+ */
81
+ export declare function evaluateGate(top1Accuracy: number, options?: GateOptions): GateResult;
82
+ //# sourceMappingURL=score.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.d.ts","sourceRoot":"","sources":["../src/score.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,iFAAiF;AACjF,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,uFAAuF;IACvF,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,sDAAsD;IACtD,IAAI,EAAE,OAAO,CAAC;IACd,+EAA+E;IAC/E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,oFAAoF;AACpF,wBAAgB,SAAS,CACvB,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,eAAe,CAAC,EAClD,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,GACX,UAAU,CAmBZ;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,IAAI,SAAI,GAAG,WAAW,CAoCpE;AAED,sFAAsF;AACtF,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,eAAe,CAAC,EAAE,EACrD,WAAW,EAAE,UAAU,EAAE,EACzB,kBAAkB,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,eAAe,CAAC,KAAK,MAAM,EAClF,IAAI,SAAI,GACP,WAAW,CAIb;AAID,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,0BAA0B,GAAG,eAAe,CAAC;AAErF,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,YAAY,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,qDAAqD;IACrD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,sEAAsE;IACtE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAC/C,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAO/C;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,UAAU,CA8BxF"}
package/dist/score.js ADDED
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Núcleo de scoring puro do eval de seleção de tool. Sem rede, sem modelo, sem I/O.
3
+ *
4
+ * O runner produz, por fixture, a lista ordenada de tools que o modelo chamaria (mais
5
+ * provável primeiro). Este módulo transforma essas predições + os `expectedTools` das
6
+ * fixtures em números de acurácia: top-1, top-k e por área. São estas funções que os
7
+ * testes offline exercitam com dados sintéticos de acurácia conhecida.
8
+ */
9
+ /** Pontua uma fixture contra sua predição. `area` vem do catálogo, via chamador. */
10
+ export function scoreItem(fixture, prediction, area) {
11
+ const predicted = prediction?.predictedTools ?? [];
12
+ const expected = new Set(fixture.expectedTools);
13
+ let hitRank = null;
14
+ for (const [i, name] of predicted.entries()) {
15
+ if (expected.has(name)) {
16
+ hitRank = i + 1;
17
+ break;
18
+ }
19
+ }
20
+ const first = predicted[0];
21
+ return {
22
+ id: fixture.id,
23
+ area,
24
+ expectedTools: fixture.expectedTools,
25
+ predictedTools: predicted,
26
+ top1: first !== undefined && expected.has(first),
27
+ hitRank,
28
+ };
29
+ }
30
+ /**
31
+ * Agrega itens pontuados num relatório.
32
+ *
33
+ * @param maxK maior k para o cálculo de acurácia top-k (padrão 3).
34
+ */
35
+ export function aggregate(items, maxK = 3) {
36
+ const total = items.length;
37
+ const attempted = items.filter((it) => it.predictedTools.length > 0).length;
38
+ const top1Correct = items.filter((it) => it.top1).length;
39
+ const topKAccuracy = {};
40
+ for (let k = 1; k <= maxK; k++) {
41
+ const hits = items.filter((it) => it.hitRank !== null && it.hitRank <= k).length;
42
+ topKAccuracy[k] = total === 0 ? 0 : hits / total;
43
+ }
44
+ const areas = new Map();
45
+ for (const it of items) {
46
+ const a = areas.get(it.area) ?? { total: 0, correct: 0 };
47
+ a.total += 1;
48
+ if (it.top1)
49
+ a.correct += 1;
50
+ areas.set(it.area, a);
51
+ }
52
+ const byArea = [...areas.entries()]
53
+ .map(([area, v]) => ({
54
+ area,
55
+ total: v.total,
56
+ top1Correct: v.correct,
57
+ top1Accuracy: v.total === 0 ? 0 : v.correct / v.total,
58
+ }))
59
+ .sort((x, y) => x.top1Accuracy - y.top1Accuracy || x.area.localeCompare(y.area));
60
+ return {
61
+ total,
62
+ attempted,
63
+ top1Correct,
64
+ top1Accuracy: total === 0 ? 0 : top1Correct / total,
65
+ topKAccuracy,
66
+ byArea,
67
+ items,
68
+ };
69
+ }
70
+ /** Conveniência: pontua um lote inteiro dado fixtures, predições e lookup de área. */
71
+ export function scoreAll(fixtures, predictions, areaByExpectedTool, maxK = 3) {
72
+ const predById = new Map(predictions.map((p) => [p.id, p]));
73
+ const items = fixtures.map((f) => scoreItem(f, predById.get(f.id), areaByExpectedTool(f)));
74
+ return aggregate(items, maxK);
75
+ }
76
+ export const GATE_REMEDIATION_THRESHOLD = 0.85;
77
+ export const GATE_DEPRIORITIZE_THRESHOLD = 0.9;
78
+ /** Formata um limiar (fração) como percentual sem decimais espúrios: 0.85 → "85". */
79
+ function thresholdPct(fraction) {
80
+ return String(Math.round(fraction * 100 * 1e6) / 1e6);
81
+ }
82
+ /**
83
+ * Aplica o gate de seleção de tool a uma acurácia top-1:
84
+ * < remediationThreshold → abrir sessão de remediação (deferred loading / Code Mode /
85
+ * agrupamento por sessão).
86
+ * >= deprioritizeThreshold → despriorizar refatoração de catálogo (seguir só
87
+ * consolidando via enums).
88
+ * entre os dois → zona cinzenta (manter sob observação).
89
+ */
90
+ export function evaluateGate(top1Accuracy, options = {}) {
91
+ const { remediationThreshold = GATE_REMEDIATION_THRESHOLD, deprioritizeThreshold = GATE_DEPRIORITIZE_THRESHOLD, toolCount, } = options;
92
+ const pct = (top1Accuracy * 100).toFixed(1);
93
+ const remPct = thresholdPct(remediationThreshold);
94
+ const depPct = thresholdPct(deprioritizeThreshold);
95
+ if (top1Accuracy < remediationThreshold) {
96
+ return {
97
+ decision: "remediar",
98
+ accuracy: top1Accuracy,
99
+ message: `Acurácia top-1 = ${pct}% (< ${remPct}%). Recomendação: abrir SESSÃO DE REMEDIAÇÃO (deferred loading / Code Mode / agrupamento por sessão).`,
100
+ };
101
+ }
102
+ if (top1Accuracy >= deprioritizeThreshold) {
103
+ const context = toolCount !== undefined ? ` mesmo com ${toolCount} tools` : "";
104
+ return {
105
+ decision: "despriorizar-refatoracao",
106
+ accuracy: top1Accuracy,
107
+ message: `Acurácia top-1 = ${pct}% (>= ${depPct}%)${context}. Recomendação: DESPRIORIZAR refatoração de catálogo; seguir consolidando via enums.`,
108
+ };
109
+ }
110
+ return {
111
+ decision: "zona-cinzenta",
112
+ accuracy: top1Accuracy,
113
+ message: `Acurácia top-1 = ${pct}% (entre ${remPct}% e ${depPct}%). Zona cinzenta: manter sob observação; reavaliar após próxima mudança de tool/descrição.`,
114
+ };
115
+ }
116
+ //# sourceMappingURL=score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.js","sourceRoot":"","sources":["../src/score.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@sbissoli/mcp-evals",
3
+ "version": "0.1.0",
4
+ "description": "Harness de evals de seleção de tool para servidores MCP: extrator de catálogo (fake McpServer), validação de fixtures, scorer offline (top-1/top-k/por-área), gate de acurácia e runner via Anthropic Messages API.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.build.json",
20
+ "typecheck": "tsc --noEmit",
21
+ "test": "vitest run"
22
+ },
23
+ "keywords": [
24
+ "mcp",
25
+ "evals",
26
+ "tool-selection",
27
+ "evaluation",
28
+ "open-data"
29
+ ],
30
+ "author": "Sidney Bissoli <sbissoli76@gmail.com>",
31
+ "license": "MIT",
32
+ "peerDependencies": {
33
+ "zod": "^4.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "zod": "^4.0.0"
37
+ }
38
+ }