@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.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # @sbissoli/mcp-evals
2
+
3
+ Harness de evals de **seleção de tool** para servidores MCP: mede a acurácia com que um
4
+ modelo escolhe a tool certa do catálogo, dada uma consulta realista na persona do
5
+ usuário-alvo do servidor. Generalização do harness do senado-br-mcp-cloudflare
6
+ (`evals/`), promovido a componente do portfólio na Fase 0 (Entregável 5).
7
+
8
+ O núcleo (extrator de catálogo + validação de fixtures + scorer + gate) roda **offline em
9
+ `npm test`**, sem rede e sem modelo — renomear/remover uma tool quebra o teste de
10
+ fixtures do projeto imediatamente, de graça. A rodada cara (modelo real via Anthropic
11
+ Messages API) só é necessária quando você quer o número de acurácia em si.
12
+
13
+ > **Custo**: o runner com modelo real cobra uso de API separado de qualquer assinatura
14
+ > Claude. Nunca rode `evals/run.ts` com `ANTHROPIC_API_KEY` sem decisão explícita.
15
+
16
+ ## Módulos
17
+
18
+ | Módulo | Papel |
19
+ |---|---|
20
+ | `catalog` | **Extrator de catálogo.** `CapturingServer` (fake McpServer) grava cada registro de tool ao rodar os `registerXTools` do servidor — sem rede, sem runtime de Worker. Captura as duas formas do portfólio: `server.tool(name, desc, shape, cb)` (senado) e `server.registerTool(name, {description, inputSchema}, cb)` (template Cloudflare / SDK v2). Converte zod → JSON-schema via `z.toJSONSchema` nativo (modo `io: "input"`: campo com `.default()` não é required). |
21
+ | `fixtures` | Contrato `EvalFixture` + `validateFixtures(fixtures, catalog, opts)`: contagem mínima/máxima, ids e queries únicos, `expectedTools` não-vazio e existente no catálogo, cobertura mínima de áreas. O teste de fixtures de um projeto vira `expect(validateFixtures(...)).toEqual([])`. |
22
+ | `score` | **Scorer puro.** top-1 / top-k / por-área + gate parametrizável (`evaluateGate`, limiares padrão 85%/90%). |
23
+ | `retry` | Classificação de erro da API (transitório × fatal) + backoff com Retry-After. Dropout de infra nunca é pontuado como escolha errada. |
24
+ | `report` | Formatação pura do relatório (linhas + exit code): cobertura, acurácias, erros por kind, gate — marcado PRELIMINAR quando a rodada é incompleta. |
25
+ | `runner` | `runEval(config)`: manda cada fixture + o catálogo inteiro à Messages API com `tool_choice: any`, com concorrência limitada, retry e curto-circuito em falha fatal (auth/billing). Sem `ANTHROPIC_API_KEY`, imprime instruções e devolve exit 0 — nunca quebra CI. `fetch` puro, sem SDK. |
26
+
27
+ ## Instanciando num projeto
28
+
29
+ ```
30
+ meu-servidor/
31
+ evals/
32
+ catalog.ts # GROUPS do projeto → buildCatalog(GROUPS)
33
+ fixtures/queries.ts
34
+ run.ts
35
+ tests/evals/
36
+ fixtures.test.ts
37
+ ```
38
+
39
+ `evals/catalog.ts` — a única parte específica do servidor é a lista de grupos:
40
+
41
+ ```ts
42
+ import { buildCatalog, type CatalogGroup } from "@sbissoli/mcp-evals";
43
+ import { registerFooTools } from "../src/tools/foo.js";
44
+
45
+ const GROUPS: CatalogGroup[] = [
46
+ { area: "foo", register: (s) => registerFooTools(s as never, BASE_URL) },
47
+ // ... um por grupo de src/server.ts — grupo faltando encolhe o eval em silêncio
48
+ ];
49
+
50
+ export const CATALOG = buildCatalog(GROUPS);
51
+ ```
52
+
53
+ `tests/evals/fixtures.test.ts` — o sinal offline de regressão:
54
+
55
+ ```ts
56
+ import { validateFixtures } from "@sbissoli/mcp-evals";
57
+ import { CATALOG } from "../../evals/catalog.js";
58
+ import { FIXTURES } from "../../evals/fixtures/queries.js";
59
+
60
+ it("fixtures válidas contra o catálogo vivo", () => {
61
+ expect(validateFixtures(FIXTURES, CATALOG, { minFixtures: 30, maxFixtures: 50, minAreas: 12 }))
62
+ .toEqual([]);
63
+ });
64
+ ```
65
+
66
+ `evals/run.ts` — a rodada com modelo real:
67
+
68
+ ```ts
69
+ import { runEval } from "@sbissoli/mcp-evals";
70
+ import { CATALOG } from "./catalog.js";
71
+ import { FIXTURES } from "./fixtures/queries.js";
72
+
73
+ const { exitCode } = await runEval({
74
+ catalog: CATALOG,
75
+ fixtures: FIXTURES,
76
+ systemPrompt: "Você é o roteador de ferramentas do MCP <nome>. ... apenas chame a ferramenta.",
77
+ });
78
+ process.exit(exitCode);
79
+ ```
80
+
81
+ Variáveis de ambiente do runner (opcionais): `EVAL_MODEL` (padrão `claude-opus-4-8`),
82
+ `EVAL_CONCURRENCY` (padrão 4), `EVAL_LIMIT` (smoke com as N primeiras fixtures).
83
+
84
+ ## Gate
85
+
86
+ A partir da acurácia **top-1** (`evaluateGate`, limiares configuráveis por servidor):
87
+
88
+ | Acurácia top-1 | Decisão | Recomendação |
89
+ |---|---|---|
90
+ | `< 85%` | `remediar` | Abrir sessão de remediação (deferred loading / Code Mode / agrupamento). |
91
+ | `85%–90%` | `zona-cinzenta` | Manter sob observação; reavaliar após a próxima mudança de tool/descrição. |
92
+ | `>= 90%` | `despriorizar-refatoracao` | Despriorizar refatoração de catálogo; seguir consolidando via enums. |
93
+
94
+ Exit codes do runner: `0` = rodada completa (gate autoritativo) ou pulada sem API key;
95
+ `2` = uma ou mais fixtures não chegaram ao modelo (gate PRELIMINAR — não usar para
96
+ decisão).
97
+
98
+ ## Migração do harness do senado (adoção na Fase 1)
99
+
100
+ A adoção troca imports sem mudar comportamento — as mensagens de gate são reproduzidas
101
+ byte-a-byte (teste de compatibilidade em `tests/score.test.ts`). Mapa:
102
+
103
+ | senado `evals/*` | `@sbissoli/mcp-evals` |
104
+ |---|---|
105
+ | `buildCatalog()` (sem args, memoizado, GROUPS embutido) | `buildCatalog(GROUPS)` — GROUPS fica no projeto; memoize no módulo do projeto (`export const CATALOG = buildCatalog(GROUPS)`) |
106
+ | `catalogToolNames()` / `catalogAreaByName()` | `CATALOG.toolNames` / `CATALOG.areaByName` |
107
+ | `catalogAsAnthropicTools()` | `catalogAsAnthropicTools(CATALOG)` |
108
+ | invariantes de `tests/evals/fixtures.test.ts` | `validateFixtures(FIXTURES, CATALOG, { minFixtures: 30, maxFixtures: 50, minAreas: 12 })` (os testes de contagem exata do catálogo e prefixo `senado_` permanecem no projeto) |
109
+ | `evaluateGate(acc)` (mensagem cita "67 tools") | `evaluateGate(acc, { toolCount: 67 })` — o runner preenche `toolCount` do catálogo automaticamente |
110
+ | `run.ts` inteiro (main/printReport/retry loop) | `runEval({ catalog, fixtures, systemPrompt })` — mesmo protocolo (`tool_choice: any`), mesmas env vars, mesmos exit codes |
111
+ | `retry.ts` / `score.ts` | idênticos (portados; `EvalApiError.retryAfterSeconds` virou campo declarado) |
112
+
113
+ Duas camadas de eval (ver FASE0_PROMPT_SESSAO.md): este pacote mede **seleção de tool**;
114
+ a camada de **completude de tarefa** (o modelo responde certo usando as tools?) é coberta
115
+ pelo `evaluation.py` do mcp-builder (assets em `fase0-insumos/mcp-builder-evaluation/`)
116
+ e/ou MCPJam Inspector — complementares, não substitutos.
117
+
118
+ ## Desenvolvimento
119
+
120
+ ```bash
121
+ npm run typecheck
122
+ npm test # 61 testes offline (catálogo, fixtures, scorer, retry, report, runner com fetch injetado)
123
+ npm run build
124
+ ```
125
+
126
+ Dependências: `zod` ^4 como peer (para `z.toJSONSchema` no extrator); nada mais.
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Extrator de catálogo — a fonte de verdade do harness.
3
+ *
4
+ * Nunca toca a rede. Um "fake McpServer" (`CapturingServer`) grava cada registro de tool
5
+ * feito pelos `registerXTools` do servidor, exatamente como o `server.ts` real os liga.
6
+ * O resultado é a lista viva de tools (nome, descrição, JSON-schema do input) contra a
7
+ * qual as fixtures são validadas — renomear/remover uma tool quebra o teste offline
8
+ * imediatamente, sem rede e sem modelo.
9
+ *
10
+ * Duas formas de registro são capturadas, cobrindo os dois padrões do portfólio:
11
+ * - `server.tool(name, description, shape, cb)` — forma clássica (shape zod cru);
12
+ * - `server.registerTool(name, { description, inputSchema }, cb)` — forma SDK v2
13
+ * (inputSchema pode ser um `z.object(...)` pronto ou um shape cru).
14
+ *
15
+ * Os callbacks das tools (que tocariam upstream/cache/bindings) são capturados mas nunca
16
+ * invocados — por isso nenhum runtime de Worker é necessário.
17
+ */
18
+ import { type ZodTypeAny } from "zod";
19
+ /** JSON-schema mínimo (subconjunto do draft 2020-12) descrevendo o input de uma tool. */
20
+ export interface JsonSchema {
21
+ type: "object";
22
+ properties: Record<string, unknown>;
23
+ required: string[];
24
+ additionalProperties: false;
25
+ }
26
+ export interface CatalogTool {
27
+ name: string;
28
+ description: string;
29
+ /** Área funcional grossa, herdada do grupo que registrou a tool. */
30
+ area: string;
31
+ /** Visão JSON-schema do shape zod de input (para a definição de tool do modelo). */
32
+ inputSchema: JsonSchema;
33
+ }
34
+ export type ZodShape = Record<string, ZodTypeAny>;
35
+ /** O que a captura aceita como "shape": shape cru, schema zod pronto, ou nada. */
36
+ export type CapturedShape = ZodShape | ZodTypeAny | undefined;
37
+ /**
38
+ * Fake McpServer que só grava registros de tool. Passe-o (com cast, ex.: `as never`)
39
+ * aos `registerXTools` do servidor real; nada além de `.tool`/`.registerTool` é chamado
40
+ * em tempo de registro nos servidores do portfólio.
41
+ */
42
+ export declare class CapturingServer {
43
+ readonly captured: {
44
+ name: string;
45
+ description: string;
46
+ shape: CapturedShape;
47
+ }[];
48
+ /** Forma clássica: `server.tool(name, description, shape, cb)`. */
49
+ tool(name: string, description: string, shape?: unknown, _cb?: unknown): void;
50
+ /** Forma SDK v2: `server.registerTool(name, { description, inputSchema }, cb)`. */
51
+ registerTool(name: string, config: {
52
+ title?: string;
53
+ description?: string;
54
+ inputSchema?: unknown;
55
+ }, _cb?: unknown): void;
56
+ }
57
+ /** Um grupo de tools do servidor: a área funcional + o registrar que a popula. */
58
+ export interface CatalogGroup {
59
+ area: string;
60
+ register: (server: CapturingServer) => void;
61
+ }
62
+ /** Catálogo extraído + índices derivados (nomes e área por nome). */
63
+ export interface Catalog {
64
+ tools: CatalogTool[];
65
+ /** Conjunto de nomes, para validação O(1) de fixtures. */
66
+ toolNames: Set<string>;
67
+ /** Nome da tool -> área grossa (para o relatório de acurácia por área). */
68
+ areaByName: Map<string, string>;
69
+ }
70
+ /** Converte o shape capturado (cru ou `z.object` pronto) em JSON-schema de input. */
71
+ export declare function shapeToJsonSchema(shape: CapturedShape): JsonSchema;
72
+ /** Monta o catálogo completo rodando o registrar de cada grupo. */
73
+ export declare function buildCatalog(groups: CatalogGroup[]): Catalog;
74
+ /** Definição de tool no formato do array `tools` da Anthropic Messages API. */
75
+ export interface AnthropicTool {
76
+ name: string;
77
+ description: string;
78
+ input_schema: JsonSchema;
79
+ }
80
+ /** Array `tools` da Anthropic Messages API construído a partir do catálogo. */
81
+ export declare function catalogAsAnthropicTools(catalog: Catalog): AnthropicTool[];
82
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAK,KAAK,UAAU,EAAoB,MAAM,KAAK,CAAC;AAE3D,yFAAyF;AACzF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oBAAoB,EAAE,KAAK,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,WAAW,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAElD,kFAAkF;AAClF,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAE9D;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,QAAQ,CAAC,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,CAAA;KAAE,EAAE,CAAM;IAEtF,mEAAmE;IACnE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,IAAI;IAI7E,mFAAmF;IACnF,YAAY,CACV,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,EACvE,GAAG,CAAC,EAAE,OAAO,GACZ,IAAI;CAOR;AAED,kFAAkF;AAClF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;CAC7C;AAED,qEAAqE;AACrE,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,0DAA0D;IAC1D,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,2EAA2E;IAC3E,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAiBD,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,UAAU,CAYlE;AAED,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAmB5D;AAED,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,UAAU,CAAC;CAC1B;AAED,+EAA+E;AAC/E,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,EAAE,CAMzE"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Extrator de catálogo — a fonte de verdade do harness.
3
+ *
4
+ * Nunca toca a rede. Um "fake McpServer" (`CapturingServer`) grava cada registro de tool
5
+ * feito pelos `registerXTools` do servidor, exatamente como o `server.ts` real os liga.
6
+ * O resultado é a lista viva de tools (nome, descrição, JSON-schema do input) contra a
7
+ * qual as fixtures são validadas — renomear/remover uma tool quebra o teste offline
8
+ * imediatamente, sem rede e sem modelo.
9
+ *
10
+ * Duas formas de registro são capturadas, cobrindo os dois padrões do portfólio:
11
+ * - `server.tool(name, description, shape, cb)` — forma clássica (shape zod cru);
12
+ * - `server.registerTool(name, { description, inputSchema }, cb)` — forma SDK v2
13
+ * (inputSchema pode ser um `z.object(...)` pronto ou um shape cru).
14
+ *
15
+ * Os callbacks das tools (que tocariam upstream/cache/bindings) são capturados mas nunca
16
+ * invocados — por isso nenhum runtime de Worker é necessário.
17
+ */
18
+ import { z } from "zod";
19
+ /**
20
+ * Fake McpServer que só grava registros de tool. Passe-o (com cast, ex.: `as never`)
21
+ * aos `registerXTools` do servidor real; nada além de `.tool`/`.registerTool` é chamado
22
+ * em tempo de registro nos servidores do portfólio.
23
+ */
24
+ export class CapturingServer {
25
+ captured = [];
26
+ /** Forma clássica: `server.tool(name, description, shape, cb)`. */
27
+ tool(name, description, shape, _cb) {
28
+ this.captured.push({ name, description, shape: shape });
29
+ }
30
+ /** Forma SDK v2: `server.registerTool(name, { description, inputSchema }, cb)`. */
31
+ registerTool(name, config, _cb) {
32
+ this.captured.push({
33
+ name,
34
+ description: config.description ?? "",
35
+ shape: config.inputSchema,
36
+ });
37
+ }
38
+ }
39
+ // ---------------------------------------------------------------------------
40
+ // Conversão zod -> JSON-schema.
41
+ //
42
+ // O zod 4 traz `z.toJSONSchema` nativo e estável entre versões — o harness não adiciona
43
+ // dependência nova nem alcança internals privados. Convertemos em modo `io: "input"`:
44
+ // um param é required sse não é `.optional()` nem `.default()` (campo com default é
45
+ // suprido pelo servidor, o modelo não precisa enviá-lo). O resultado é normalizado ao
46
+ // contrato JsonSchema deste harness: a saída nativa omite `additionalProperties` e
47
+ // descarta `required` quando vazio.
48
+ // ---------------------------------------------------------------------------
49
+ function isZodType(x) {
50
+ return !!x && typeof x.safeParse === "function";
51
+ }
52
+ /** Converte o shape capturado (cru ou `z.object` pronto) em JSON-schema de input. */
53
+ export function shapeToJsonSchema(shape) {
54
+ const objectSchema = isZodType(shape) ? shape : z.object((shape ?? {}));
55
+ const raw = z.toJSONSchema(objectSchema, { io: "input" });
56
+ return {
57
+ type: "object",
58
+ properties: raw.properties ?? {},
59
+ required: raw.required ?? [],
60
+ additionalProperties: false,
61
+ };
62
+ }
63
+ /** Monta o catálogo completo rodando o registrar de cada grupo. */
64
+ export function buildCatalog(groups) {
65
+ const tools = [];
66
+ for (const group of groups) {
67
+ const server = new CapturingServer();
68
+ group.register(server);
69
+ for (const t of server.captured) {
70
+ tools.push({
71
+ name: t.name,
72
+ description: t.description,
73
+ area: group.area,
74
+ inputSchema: shapeToJsonSchema(t.shape),
75
+ });
76
+ }
77
+ }
78
+ return {
79
+ tools,
80
+ toolNames: new Set(tools.map((t) => t.name)),
81
+ areaByName: new Map(tools.map((t) => [t.name, t.area])),
82
+ };
83
+ }
84
+ /** Array `tools` da Anthropic Messages API construído a partir do catálogo. */
85
+ export function catalogAsAnthropicTools(catalog) {
86
+ return catalog.tools.map((t) => ({
87
+ name: t.name,
88
+ description: t.description,
89
+ input_schema: t.inputSchema,
90
+ }));
91
+ }
92
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Contrato de fixtures + validação offline.
3
+ *
4
+ * Cada projeto do portfólio mantém seu próprio conjunto de fixtures (consultas realistas
5
+ * na persona do usuário-alvo, com as tools aceitáveis para o PRIMEIRO passo da resposta).
6
+ * `validateFixtures` codifica os invariantes que no senado viviam num teste ad-hoc:
7
+ * o teste de fixtures de cada projeto vira uma asserção única de lista vazia.
8
+ */
9
+ import type { Catalog } from "./catalog.js";
10
+ export interface EvalFixture {
11
+ id: string;
12
+ /** Consulta realista, no idioma e na persona do servidor. */
13
+ query: string;
14
+ /** Tools aceitáveis como primeiro passo. Predição correta = qualquer membro do conjunto. */
15
+ expectedTools: string[];
16
+ /** Por que essas tools (e não as vizinhas) são a resposta certa. */
17
+ note: string;
18
+ }
19
+ export interface FixtureValidationOptions {
20
+ /** Mínimo de fixtures no conjunto. Padrão: 10. */
21
+ minFixtures?: number;
22
+ /** Máximo de fixtures (evita conjunto inchado/caro). Padrão: sem teto. */
23
+ maxFixtures?: number;
24
+ /** Comprimento mínimo da query. Padrão: 10. */
25
+ minQueryLength?: number;
26
+ /** Exigir `note` não-vazia em toda fixture. Padrão: true. */
27
+ requireNote?: boolean;
28
+ /**
29
+ * Mínimo de áreas funcionais distintas cobertas (área da primeira expectedTool).
30
+ * Padrão: 0 (sem exigência).
31
+ */
32
+ minAreas?: number;
33
+ }
34
+ /**
35
+ * Valida um conjunto de fixtures contra o catálogo vivo. Retorna a lista de problemas
36
+ * encontrados (vazia = válido) — cada string é autoexplicativa para o log do teste.
37
+ * O invariante central: toda tool em `expectedTools` deve existir no catálogo, para que
38
+ * um rename de tool quebre o teste offline imediatamente.
39
+ */
40
+ export declare function validateFixtures(fixtures: EvalFixture[], catalog: Catalog, options?: FixtureValidationOptions): string[];
41
+ //# sourceMappingURL=fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,4FAA4F;IAC5F,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,wBAAwB;IACvC,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,wBAA6B,GACrC,MAAM,EAAE,CAuEV"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Contrato de fixtures + validação offline.
3
+ *
4
+ * Cada projeto do portfólio mantém seu próprio conjunto de fixtures (consultas realistas
5
+ * na persona do usuário-alvo, com as tools aceitáveis para o PRIMEIRO passo da resposta).
6
+ * `validateFixtures` codifica os invariantes que no senado viviam num teste ad-hoc:
7
+ * o teste de fixtures de cada projeto vira uma asserção única de lista vazia.
8
+ */
9
+ /**
10
+ * Valida um conjunto de fixtures contra o catálogo vivo. Retorna a lista de problemas
11
+ * encontrados (vazia = válido) — cada string é autoexplicativa para o log do teste.
12
+ * O invariante central: toda tool em `expectedTools` deve existir no catálogo, para que
13
+ * um rename de tool quebre o teste offline imediatamente.
14
+ */
15
+ export function validateFixtures(fixtures, catalog, options = {}) {
16
+ const { minFixtures = 10, maxFixtures, minQueryLength = 10, requireNote = true, minAreas = 0, } = options;
17
+ const problems = [];
18
+ if (fixtures.length < minFixtures) {
19
+ problems.push(`conjunto tem ${fixtures.length} fixtures (mínimo ${minFixtures})`);
20
+ }
21
+ if (maxFixtures !== undefined && fixtures.length > maxFixtures) {
22
+ problems.push(`conjunto tem ${fixtures.length} fixtures (máximo ${maxFixtures})`);
23
+ }
24
+ const seenIds = new Set();
25
+ const seenQueries = new Set();
26
+ for (const f of fixtures) {
27
+ if (!f.id) {
28
+ problems.push(`fixture sem id (query: ${JSON.stringify(f.query.slice(0, 40))})`);
29
+ }
30
+ else if (seenIds.has(f.id)) {
31
+ problems.push(`id duplicado: ${f.id}`);
32
+ }
33
+ else {
34
+ seenIds.add(f.id);
35
+ }
36
+ const normQuery = f.query.trim().toLowerCase();
37
+ if (seenQueries.has(normQuery)) {
38
+ problems.push(`[${f.id}] query duplicada`);
39
+ }
40
+ else {
41
+ seenQueries.add(normQuery);
42
+ }
43
+ if (f.query.trim().length < minQueryLength) {
44
+ problems.push(`[${f.id}] query curta demais (< ${minQueryLength} caracteres)`);
45
+ }
46
+ if (requireNote && (!f.note || f.note.trim().length === 0)) {
47
+ problems.push(`[${f.id}] note vazia`);
48
+ }
49
+ if (!Array.isArray(f.expectedTools) || f.expectedTools.length === 0) {
50
+ problems.push(`[${f.id}] expectedTools vazio`);
51
+ continue;
52
+ }
53
+ if (new Set(f.expectedTools).size !== f.expectedTools.length) {
54
+ problems.push(`[${f.id}] expectedTools com duplicata`);
55
+ }
56
+ for (const tool of f.expectedTools) {
57
+ if (!catalog.toolNames.has(tool)) {
58
+ problems.push(`[${f.id}] tool inexistente no catálogo: ${tool}`);
59
+ }
60
+ }
61
+ }
62
+ if (minAreas > 0) {
63
+ const areas = new Set();
64
+ for (const f of fixtures) {
65
+ const first = f.expectedTools[0];
66
+ if (first === undefined)
67
+ continue;
68
+ const area = catalog.areaByName.get(first);
69
+ if (area)
70
+ areas.add(area);
71
+ }
72
+ if (areas.size < minAreas) {
73
+ problems.push(`cobertura de áreas insuficiente: ${areas.size} distintas (mínimo ${minAreas})`);
74
+ }
75
+ }
76
+ return problems;
77
+ }
78
+ //# sourceMappingURL=fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.js","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export { CapturingServer, buildCatalog, catalogAsAnthropicTools, shapeToJsonSchema, type AnthropicTool, type CapturedShape, type Catalog, type CatalogGroup, type CatalogTool, type JsonSchema, type ZodShape, } from "./catalog.js";
2
+ export { validateFixtures, type EvalFixture, type FixtureValidationOptions, } from "./fixtures.js";
3
+ export { GATE_DEPRIORITIZE_THRESHOLD, GATE_REMEDIATION_THRESHOLD, aggregate, evaluateGate, scoreAll, scoreItem, type AreaAccuracy, type GateDecision, type GateOptions, type GateResult, type Prediction, type ScoreReport, type ScoredItem, } from "./score.js";
4
+ export { BASE_BACKOFF_MS, EvalApiError, MAX_BACKOFF_MS, MAX_RETRIES, backoffMs, classifyApiError, isFatalInfra, parseRetryAfter, type ErrorKind, } from "./retry.js";
5
+ export { formatReport, type FixtureError, type FormattedReport, } from "./report.js";
6
+ export { runEval, type EvalRunResult, type EvalRunnerConfig, } from "./runner.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,YAAY,EACZ,uBAAuB,EACvB,iBAAiB,EACjB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,QAAQ,GACd,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,gBAAgB,EAChB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAC9B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,EAC1B,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,YAAY,EACZ,cAAc,EACd,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,KAAK,SAAS,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,YAAY,EACZ,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,OAAO,EACP,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { CapturingServer, buildCatalog, catalogAsAnthropicTools, shapeToJsonSchema, } from "./catalog.js";
2
+ export { validateFixtures, } from "./fixtures.js";
3
+ export { GATE_DEPRIORITIZE_THRESHOLD, GATE_REMEDIATION_THRESHOLD, aggregate, evaluateGate, scoreAll, scoreItem, } from "./score.js";
4
+ export { BASE_BACKOFF_MS, EvalApiError, MAX_BACKOFF_MS, MAX_RETRIES, backoffMs, classifyApiError, isFatalInfra, parseRetryAfter, } from "./retry.js";
5
+ export { formatReport, } from "./report.js";
6
+ export { runEval, } from "./runner.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,YAAY,EACZ,uBAAuB,EACvB,iBAAiB,GAQlB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,gBAAgB,GAGjB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,EAC1B,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,SAAS,GAQV,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,YAAY,EACZ,cAAc,EACd,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,eAAe,GAEhB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,YAAY,GAGb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,OAAO,GAGR,MAAM,aAAa,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Formatação pura do relatório da rodada. Sem I/O — devolve linhas + código de saída,
3
+ * para que o teste offline possa asserir o comportamento (cobertura incompleta, dicas
4
+ * de remédio, gate PRELIMINAR) sem capturar console.
5
+ */
6
+ import { type GateOptions, type GateResult, type ScoredItem, type ScoreReport } from "./score.js";
7
+ import { type ErrorKind } from "./retry.js";
8
+ /** Fixture que nunca chegou ao modelo — dropout de infra, NÃO escolha errada de tool. */
9
+ export interface FixtureError {
10
+ id: string;
11
+ kind: ErrorKind;
12
+ status: number;
13
+ message: string;
14
+ }
15
+ export interface FormattedReport {
16
+ lines: string[];
17
+ /**
18
+ * 0 → toda fixture foi avaliada (a decisão de gate é autoritativa).
19
+ * 2 → uma ou mais fixtures nunca chegaram ao modelo (dropout de infra); o gate sobre
20
+ * o subconjunto avaliado aparece marcado PRELIMINAR — fixtures não tentadas não
21
+ * podem ser pontuadas silenciosamente como erro de seleção.
22
+ */
23
+ exitCode: 0 | 2;
24
+ /** Relatório agregado apenas sobre as fixtures que chegaram ao modelo. */
25
+ report: ScoreReport;
26
+ gate: GateResult;
27
+ }
28
+ /** Monta o relatório completo da rodada (cobertura, acurácias, erros, gate). */
29
+ export declare function formatReport(items: ScoredItem[], errors: FixtureError[], model: string, gateOptions?: GateOptions): FormattedReport;
30
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAA2B,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAC3H,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAE1D,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;IAChB,0EAA0E;IAC1E,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,UAAU,CAAC;CAClB;AAMD,gFAAgF;AAChF,wBAAgB,YAAY,CAC1B,KAAK,EAAE,UAAU,EAAE,EACnB,MAAM,EAAE,YAAY,EAAE,EACtB,KAAK,EAAE,MAAM,EACb,WAAW,GAAE,WAAgB,GAC5B,eAAe,CAwEjB"}
package/dist/report.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Formatação pura do relatório da rodada. Sem I/O — devolve linhas + código de saída,
3
+ * para que o teste offline possa asserir o comportamento (cobertura incompleta, dicas
4
+ * de remédio, gate PRELIMINAR) sem capturar console.
5
+ */
6
+ import { aggregate, evaluateGate } from "./score.js";
7
+ import { isFatalInfra } from "./retry.js";
8
+ function pct(n) {
9
+ return `${(n * 100).toFixed(1)}%`;
10
+ }
11
+ /** Monta o relatório completo da rodada (cobertura, acurácias, erros, gate). */
12
+ export function formatReport(items, errors, model, gateOptions = {}) {
13
+ // Só pontua as fixtures que de fato chegaram ao modelo — dropouts de infra ficam de
14
+ // fora, para que rate-limit/billing não se disfarce de acurácia ruim de seleção.
15
+ const erroredIds = new Set(errors.map((e) => e.id));
16
+ const evaluated = items.filter((it) => !erroredIds.has(it.id));
17
+ const report = aggregate(evaluated, 3);
18
+ const complete = errors.length === 0;
19
+ const lines = [];
20
+ lines.push("");
21
+ lines.push("=".repeat(64));
22
+ lines.push(` Eval de seleção de tool — modelo: ${model}`);
23
+ lines.push("=".repeat(64));
24
+ lines.push(` Cobertura: ${evaluated.length}/${items.length} fixtures avaliadas` +
25
+ (complete ? "" : ` · ${errors.length} não avaliadas (falha de infra)`));
26
+ lines.push(` Acurácia top-1: ${pct(report.top1Accuracy)} (${report.top1Correct}/${report.total} avaliadas)`);
27
+ lines.push(` Acurácia top-2: ${pct(report.topKAccuracy[2] ?? 0)}`);
28
+ lines.push(` Acurácia top-3: ${pct(report.topKAccuracy[3] ?? 0)}`);
29
+ lines.push("");
30
+ lines.push(" Por área (acurácia top-1 entre as avaliadas, pior → melhor):");
31
+ for (const a of report.byArea) {
32
+ lines.push(` ${a.area.padEnd(18)} ${pct(a.top1Accuracy).padStart(6)} (${a.top1Correct}/${a.total})`);
33
+ }
34
+ const misses = evaluated.filter((it) => !it.top1);
35
+ if (misses.length > 0) {
36
+ lines.push("");
37
+ lines.push(" Escolhas erradas (top-1 fora do esperado):");
38
+ for (const m of misses) {
39
+ const got = m.predictedTools[0] ?? "(nenhuma)";
40
+ lines.push(` [${m.id}] esperado ${JSON.stringify(m.expectedTools)} · obteve ${got}`);
41
+ }
42
+ }
43
+ if (errors.length > 0) {
44
+ const byKind = new Map();
45
+ for (const e of errors)
46
+ byKind.set(e.kind, (byKind.get(e.kind) ?? 0) + 1);
47
+ const kindSummary = [...byKind.entries()].map(([k, n]) => `${k}×${n}`).join(", ");
48
+ lines.push("");
49
+ lines.push(` ⚠ RODADA INCOMPLETA — ${errors.length} fixtures não avaliadas (${kindSummary}):`);
50
+ for (const e of errors) {
51
+ lines.push(` [${e.id}] ${e.kind} (HTTP ${e.status})`);
52
+ }
53
+ const fatal = errors.find((e) => isFatalInfra(e.kind));
54
+ lines.push("");
55
+ if (fatal?.kind === "billing") {
56
+ lines.push(" Causa fatal: saldo de créditos insuficiente. Reponha créditos em");
57
+ lines.push(" https://console.anthropic.com/settings/billing e rode de novo.");
58
+ }
59
+ else if (fatal) {
60
+ lines.push(" Causa fatal: chave rejeitada (auth). Verifique a ANTHROPIC_API_KEY e rode de novo.");
61
+ }
62
+ else {
63
+ lines.push(" Dica: rate-limit. Rode com EVAL_CONCURRENCY=1 e/ou um modelo menor (EVAL_MODEL).");
64
+ }
65
+ }
66
+ const gate = evaluateGate(report.top1Accuracy, gateOptions);
67
+ lines.push("");
68
+ lines.push(" Gate de seleção de tool:");
69
+ if (complete) {
70
+ lines.push(` decisão: ${gate.decision}`);
71
+ lines.push(` ${gate.message}`);
72
+ }
73
+ else {
74
+ lines.push(` decisão: PRELIMINAR/${gate.decision} (rodada incompleta — NÃO usar para decisão)`);
75
+ lines.push(` ${gate.message}`);
76
+ lines.push(" Complete a cobertura (100%) antes de tratar o gate como definitivo.");
77
+ }
78
+ lines.push("=".repeat(64));
79
+ lines.push("");
80
+ return { lines, exitCode: complete ? 0 : 2, report, gate };
81
+ }
82
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
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 type ErrorKind = "rate_limit" | "overloaded" | "server" | "network" | "auth" | "billing" | "other";
13
+ export declare class EvalApiError extends Error {
14
+ readonly status: number;
15
+ readonly kind: ErrorKind;
16
+ readonly retryable: boolean;
17
+ /** Dica Retry-After (segundos) vinda da API, quando presente. */
18
+ retryAfterSeconds: number | undefined;
19
+ constructor(message: string, status: number, kind: ErrorKind, retryable: boolean);
20
+ }
21
+ /** Kinds fatais curto-circuitam a rodada inteira — toda fixture bateria na mesma parede. */
22
+ export declare function isFatalInfra(kind: ErrorKind): boolean;
23
+ /**
24
+ * Classifica um erro HTTP da Anthropic API (status + corpo) em kind + retryable.
25
+ * Puro; o corpo só é inspecionado para distinguir um 400 de billing ("credit balance
26
+ * is too low") dos demais 400.
27
+ */
28
+ export declare function classifyApiError(status: number, body: string): {
29
+ kind: ErrorKind;
30
+ retryable: boolean;
31
+ };
32
+ export declare const BASE_BACKOFF_MS = 2000;
33
+ export declare const MAX_BACKOFF_MS = 30000;
34
+ export declare const MAX_RETRIES = 5;
35
+ /**
36
+ * Backoff base (ms) antes da tentativa de retry (0-based). Honra a dica Retry-After
37
+ * (segundos) da API quando presente — importante no 429, onde a janela de rate-limit
38
+ * precisa rolar antes da próxima tentativa. O chamador adiciona jitter; mantido puro
39
+ * para o teste unitário poder asserir valores exatos.
40
+ */
41
+ export declare function backoffMs(attempt: number, retryAfterSeconds?: number): number;
42
+ /** Interpreta um header `Retry-After` (só a forma inteiro-segundos) em segundos. */
43
+ export declare function parseRetryAfter(headerValue: string | null): number | undefined;
44
+ //# sourceMappingURL=retry.d.ts.map