@retinue/agentkit 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/LICENSE +22 -0
- package/README.md +310 -0
- package/dist/adapters/bullmq/consumer.d.ts +33 -0
- package/dist/adapters/bullmq/consumer.js +41 -0
- package/dist/adapters/bullmq/dispatcher.d.ts +74 -0
- package/dist/adapters/bullmq/dispatcher.js +160 -0
- package/dist/adapters/bullmq/export.d.ts +31 -0
- package/dist/adapters/bullmq/export.js +53 -0
- package/dist/adapters/bullmq/extraction.d.ts +42 -0
- package/dist/adapters/bullmq/extraction.js +63 -0
- package/dist/adapters/bullmq/index.d.ts +13 -0
- package/dist/adapters/bullmq/index.js +13 -0
- package/dist/adapters/bullmq/lock.d.ts +77 -0
- package/dist/adapters/bullmq/lock.js +126 -0
- package/dist/adapters/bullmq/queue.d.ts +50 -0
- package/dist/adapters/bullmq/queue.js +81 -0
- package/dist/adapters/memory/artifact-exports.d.ts +11 -0
- package/dist/adapters/memory/artifact-exports.js +102 -0
- package/dist/adapters/memory/artifacts.d.ts +15 -0
- package/dist/adapters/memory/artifacts.js +134 -0
- package/dist/adapters/memory/blobs.d.ts +7 -0
- package/dist/adapters/memory/blobs.js +27 -0
- package/dist/adapters/memory/evaluation.d.ts +18 -0
- package/dist/adapters/memory/evaluation.js +148 -0
- package/dist/adapters/memory/files.d.ts +27 -0
- package/dist/adapters/memory/files.js +0 -0
- package/dist/adapters/memory/flows.d.ts +16 -0
- package/dist/adapters/memory/flows.js +117 -0
- package/dist/adapters/memory/hitl.d.ts +9 -0
- package/dist/adapters/memory/hitl.js +130 -0
- package/dist/adapters/memory/idempotency.d.ts +13 -0
- package/dist/adapters/memory/idempotency.js +32 -0
- package/dist/adapters/memory/index.d.ts +39 -0
- package/dist/adapters/memory/index.js +107 -0
- package/dist/adapters/memory/knowledge.d.ts +43 -0
- package/dist/adapters/memory/knowledge.js +248 -0
- package/dist/adapters/memory/mcp.d.ts +9 -0
- package/dist/adapters/memory/mcp.js +37 -0
- package/dist/adapters/memory/message-store.d.ts +17 -0
- package/dist/adapters/memory/message-store.js +70 -0
- package/dist/adapters/memory/principal-memory.d.ts +7 -0
- package/dist/adapters/memory/principal-memory.js +83 -0
- package/dist/adapters/memory/runtime.d.ts +29 -0
- package/dist/adapters/memory/runtime.js +0 -0
- package/dist/adapters/memory/sessions.d.ts +29 -0
- package/dist/adapters/memory/sessions.js +0 -0
- package/dist/adapters/memory/skills.d.ts +10 -0
- package/dist/adapters/memory/skills.js +41 -0
- package/dist/adapters/memory/thread-summary.d.ts +7 -0
- package/dist/adapters/memory/thread-summary.js +29 -0
- package/dist/adapters/memory/usage-limits.d.ts +13 -0
- package/dist/adapters/memory/usage-limits.js +72 -0
- package/dist/adapters/memory/usage.d.ts +16 -0
- package/dist/adapters/memory/usage.js +279 -0
- package/dist/adapters/otel/index.d.ts +111 -0
- package/dist/adapters/otel/index.js +133 -0
- package/dist/adapters/postgres/artifact-exports.d.ts +12 -0
- package/dist/adapters/postgres/artifact-exports.js +117 -0
- package/dist/adapters/postgres/artifacts.d.ts +16 -0
- package/dist/adapters/postgres/artifacts.js +172 -0
- package/dist/adapters/postgres/checkpoint-store.d.ts +16 -0
- package/dist/adapters/postgres/checkpoint-store.js +34 -0
- package/dist/adapters/postgres/config.d.ts +15 -0
- package/dist/adapters/postgres/config.js +187 -0
- package/dist/adapters/postgres/conversation-store.d.ts +4 -0
- package/dist/adapters/postgres/conversation-store.js +82 -0
- package/dist/adapters/postgres/evaluation.d.ts +17 -0
- package/dist/adapters/postgres/evaluation.js +193 -0
- package/dist/adapters/postgres/file-content.d.ts +30 -0
- package/dist/adapters/postgres/file-content.js +111 -0
- package/dist/adapters/postgres/files.d.ts +19 -0
- package/dist/adapters/postgres/files.js +209 -0
- package/dist/adapters/postgres/flows.d.ts +20 -0
- package/dist/adapters/postgres/flows.js +206 -0
- package/dist/adapters/postgres/hitl.d.ts +5 -0
- package/dist/adapters/postgres/hitl.js +247 -0
- package/dist/adapters/postgres/index.d.ts +35 -0
- package/dist/adapters/postgres/index.js +35 -0
- package/dist/adapters/postgres/knowledge.d.ts +48 -0
- package/dist/adapters/postgres/knowledge.js +255 -0
- package/dist/adapters/postgres/memory.d.ts +14 -0
- package/dist/adapters/postgres/memory.js +194 -0
- package/dist/adapters/postgres/message-store.d.ts +11 -0
- package/dist/adapters/postgres/message-store.js +145 -0
- package/dist/adapters/postgres/migrations.d.ts +69 -0
- package/dist/adapters/postgres/migrations.js +1594 -0
- package/dist/adapters/postgres/pg-executor.d.ts +19 -0
- package/dist/adapters/postgres/pg-executor.js +32 -0
- package/dist/adapters/postgres/retention.d.ts +26 -0
- package/dist/adapters/postgres/retention.js +59 -0
- package/dist/adapters/postgres/rollups.d.ts +17 -0
- package/dist/adapters/postgres/rollups.js +267 -0
- package/dist/adapters/postgres/run-coordinator.d.ts +5 -0
- package/dist/adapters/postgres/run-coordinator.js +98 -0
- package/dist/adapters/postgres/run-event-log.d.ts +26 -0
- package/dist/adapters/postgres/run-event-log.js +30 -0
- package/dist/adapters/postgres/run-store.d.ts +4 -0
- package/dist/adapters/postgres/run-store.js +199 -0
- package/dist/adapters/postgres/schema.d.ts +39 -0
- package/dist/adapters/postgres/schema.js +70 -0
- package/dist/adapters/postgres/session-state.d.ts +7 -0
- package/dist/adapters/postgres/session-state.js +99 -0
- package/dist/adapters/postgres/sql.d.ts +8 -0
- package/dist/adapters/postgres/sql.js +2 -0
- package/dist/adapters/postgres/transaction.d.ts +37 -0
- package/dist/adapters/postgres/transaction.js +93 -0
- package/dist/adapters/postgres/unit-of-work.d.ts +18 -0
- package/dist/adapters/postgres/unit-of-work.js +8 -0
- package/dist/adapters/postgres/usage-limits.d.ts +15 -0
- package/dist/adapters/postgres/usage-limits.js +136 -0
- package/dist/adapters/postgres/usage.d.ts +15 -0
- package/dist/adapters/postgres/usage.js +226 -0
- package/dist/adapters/redis/index.d.ts +9 -0
- package/dist/adapters/redis/index.js +9 -0
- package/dist/adapters/redis/realtime.d.ts +74 -0
- package/dist/adapters/redis/realtime.js +112 -0
- package/dist/adapters/supabase/index.d.ts +88 -0
- package/dist/adapters/supabase/index.js +84 -0
- package/dist/adapters/supabase/realtime.d.ts +30 -0
- package/dist/adapters/supabase/realtime.js +53 -0
- package/dist/adapters/supabase/rls.d.ts +99 -0
- package/dist/adapters/supabase/rls.js +216 -0
- package/dist/adapters/supabase/storage.d.ts +50 -0
- package/dist/adapters/supabase/storage.js +207 -0
- package/dist/agents/agent.d.ts +66 -0
- package/dist/agents/agent.js +209 -0
- package/dist/agents/define.d.ts +21 -0
- package/dist/agents/define.js +63 -0
- package/dist/agents/engine.d.ts +98 -0
- package/dist/agents/engine.js +462 -0
- package/dist/agents/index.d.ts +50 -0
- package/dist/agents/index.js +17 -0
- package/dist/artifacts/index.d.ts +114 -0
- package/dist/artifacts/index.js +219 -0
- package/dist/authorization/index.d.ts +76 -0
- package/dist/authorization/index.js +76 -0
- package/dist/capabilities/index.d.ts +120 -0
- package/dist/capabilities/index.js +167 -0
- package/dist/capabilities/runtime.d.ts +89 -0
- package/dist/capabilities/runtime.js +84 -0
- package/dist/citations/index.d.ts +161 -0
- package/dist/citations/index.js +182 -0
- package/dist/context/assembler.d.ts +82 -0
- package/dist/context/assembler.js +129 -0
- package/dist/context/compaction.d.ts +45 -0
- package/dist/context/compaction.js +55 -0
- package/dist/context/index.d.ts +75 -0
- package/dist/context/index.js +17 -0
- package/dist/core/content-parts.d.ts +194 -0
- package/dist/core/content-parts.js +23 -0
- package/dist/core/context.d.ts +51 -0
- package/dist/core/context.js +9 -0
- package/dist/core/env.d.ts +25 -0
- package/dist/core/env.js +41 -0
- package/dist/core/errors.d.ts +30 -0
- package/dist/core/errors.js +65 -0
- package/dist/core/events.d.ts +139 -0
- package/dist/core/events.js +99 -0
- package/dist/core/ids.d.ts +52 -0
- package/dist/core/ids.js +13 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +9 -0
- package/dist/core/tokens.d.ts +22 -0
- package/dist/core/tokens.js +22 -0
- package/dist/core/validation.d.ts +34 -0
- package/dist/core/validation.js +176 -0
- package/dist/documents/extraction.d.ts +121 -0
- package/dist/documents/extraction.js +293 -0
- package/dist/documents/index.d.ts +199 -0
- package/dist/documents/index.js +65 -0
- package/dist/documents/parsers/pdf.d.ts +47 -0
- package/dist/documents/parsers/pdf.js +508 -0
- package/dist/documents/parsers/text.d.ts +59 -0
- package/dist/documents/parsers/text.js +325 -0
- package/dist/documents/read-tool.d.ts +52 -0
- package/dist/documents/read-tool.js +109 -0
- package/dist/documents/render.d.ts +29 -0
- package/dist/documents/render.js +59 -0
- package/dist/documents/vision.d.ts +159 -0
- package/dist/documents/vision.js +214 -0
- package/dist/entries/adapters-bullmq.d.ts +8 -0
- package/dist/entries/adapters-bullmq.js +8 -0
- package/dist/entries/adapters-otel.d.ts +13 -0
- package/dist/entries/adapters-otel.js +13 -0
- package/dist/entries/adapters-postgres.d.ts +10 -0
- package/dist/entries/adapters-postgres.js +10 -0
- package/dist/entries/adapters-redis.d.ts +3 -0
- package/dist/entries/adapters-redis.js +3 -0
- package/dist/entries/context.d.ts +20 -0
- package/dist/entries/context.js +20 -0
- package/dist/entries/flows.d.ts +15 -0
- package/dist/entries/flows.js +15 -0
- package/dist/entries/hitl.d.ts +10 -0
- package/dist/entries/hitl.js +10 -0
- package/dist/entries/knowledge.d.ts +18 -0
- package/dist/entries/knowledge.js +19 -0
- package/dist/entries/mcp.d.ts +10 -0
- package/dist/entries/mcp.js +10 -0
- package/dist/entries/observability.d.ts +14 -0
- package/dist/entries/observability.js +16 -0
- package/dist/entries/persistence.d.ts +11 -0
- package/dist/entries/persistence.js +11 -0
- package/dist/entries/providers.d.ts +14 -0
- package/dist/entries/providers.js +14 -0
- package/dist/entries/runtime.d.ts +13 -0
- package/dist/entries/runtime.js +13 -0
- package/dist/entries/server.d.ts +24 -0
- package/dist/entries/server.js +24 -0
- package/dist/entries/tools.d.ts +21 -0
- package/dist/entries/tools.js +21 -0
- package/dist/entries/usage.d.ts +10 -0
- package/dist/entries/usage.js +10 -0
- package/dist/evaluation/gate.d.ts +168 -0
- package/dist/evaluation/gate.js +180 -0
- package/dist/evaluation/graders.d.ts +125 -0
- package/dist/evaluation/graders.js +203 -0
- package/dist/evaluation/index.d.ts +120 -0
- package/dist/evaluation/index.js +183 -0
- package/dist/evaluation/judge.d.ts +75 -0
- package/dist/evaluation/judge.js +111 -0
- package/dist/export/index.d.ts +162 -0
- package/dist/export/index.js +363 -0
- package/dist/export/markdown.d.ts +19 -0
- package/dist/export/markdown.js +29 -0
- package/dist/export/pdf.d.ts +73 -0
- package/dist/export/pdf.js +407 -0
- package/dist/files/context.d.ts +97 -0
- package/dist/files/context.js +185 -0
- package/dist/files/index.d.ts +210 -0
- package/dist/files/index.js +338 -0
- package/dist/files/read-tool.d.ts +81 -0
- package/dist/files/read-tool.js +163 -0
- package/dist/files/turn-parts.d.ts +96 -0
- package/dist/files/turn-parts.js +171 -0
- package/dist/flows/index.d.ts +270 -0
- package/dist/flows/index.js +62 -0
- package/dist/flows/interpreter.d.ts +146 -0
- package/dist/flows/interpreter.js +426 -0
- package/dist/flows/runner.d.ts +145 -0
- package/dist/flows/runner.js +270 -0
- package/dist/graphql/index.d.ts +8 -0
- package/dist/graphql/index.js +8 -0
- package/dist/graphql/resolvers.d.ts +237 -0
- package/dist/graphql/resolvers.js +253 -0
- package/dist/graphql/schema.d.ts +11 -0
- package/dist/graphql/schema.js +258 -0
- package/dist/graphql/sse.d.ts +77 -0
- package/dist/graphql/sse.js +100 -0
- package/dist/hitl/approved-execution.d.ts +127 -0
- package/dist/hitl/approved-execution.js +177 -0
- package/dist/hitl/index.d.ts +79 -0
- package/dist/hitl/index.js +12 -0
- package/dist/hitl/service.d.ts +221 -0
- package/dist/hitl/service.js +268 -0
- package/dist/idempotency/index.d.ts +70 -0
- package/dist/idempotency/index.js +59 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +60 -0
- package/dist/knowledge/chunking.d.ts +57 -0
- package/dist/knowledge/chunking.js +158 -0
- package/dist/knowledge/index.d.ts +119 -0
- package/dist/knowledge/index.js +166 -0
- package/dist/knowledge/retrieval.d.ts +146 -0
- package/dist/knowledge/retrieval.js +170 -0
- package/dist/loadtest/harness.d.ts +168 -0
- package/dist/loadtest/harness.js +507 -0
- package/dist/loadtest/index.d.ts +13 -0
- package/dist/loadtest/index.js +13 -0
- package/dist/loadtest/injection.d.ts +89 -0
- package/dist/loadtest/injection.js +147 -0
- package/dist/loadtest/metrics.d.ts +197 -0
- package/dist/loadtest/metrics.js +160 -0
- package/dist/loadtest/runbooks.d.ts +28 -0
- package/dist/loadtest/runbooks.js +159 -0
- package/dist/loadtest/scenario.d.ts +104 -0
- package/dist/loadtest/scenario.js +208 -0
- package/dist/mcp/egress.d.ts +53 -0
- package/dist/mcp/egress.js +115 -0
- package/dist/mcp/index.d.ts +93 -0
- package/dist/mcp/index.js +33 -0
- package/dist/mcp/provider.d.ts +62 -0
- package/dist/mcp/provider.js +0 -0
- package/dist/models/index.d.ts +98 -0
- package/dist/models/index.js +74 -0
- package/dist/models/pricing.d.ts +24 -0
- package/dist/models/pricing.js +37 -0
- package/dist/models/provider-factory.d.ts +31 -0
- package/dist/models/provider-factory.js +67 -0
- package/dist/models/streaming.d.ts +145 -0
- package/dist/models/streaming.js +272 -0
- package/dist/models/vision.d.ts +38 -0
- package/dist/models/vision.js +62 -0
- package/dist/persistence/index.d.ts +1654 -0
- package/dist/persistence/index.js +226 -0
- package/dist/principal-memory/index.d.ts +106 -0
- package/dist/principal-memory/index.js +89 -0
- package/dist/retention/index.d.ts +89 -0
- package/dist/retention/index.js +70 -0
- package/dist/runtime/checkpoint.d.ts +37 -0
- package/dist/runtime/checkpoint.js +22 -0
- package/dist/runtime/index.d.ts +118 -0
- package/dist/runtime/index.js +69 -0
- package/dist/runtime/retry.d.ts +95 -0
- package/dist/runtime/retry.js +126 -0
- package/dist/runtime/serialization.d.ts +85 -0
- package/dist/runtime/serialization.js +95 -0
- package/dist/runtime/streaming.d.ts +54 -0
- package/dist/runtime/streaming.js +115 -0
- package/dist/runtime/worker.d.ts +130 -0
- package/dist/runtime/worker.js +405 -0
- package/dist/security/checklist.d.ts +53 -0
- package/dist/security/checklist.js +204 -0
- package/dist/security/findings.d.ts +56 -0
- package/dist/security/findings.js +168 -0
- package/dist/security/index.d.ts +14 -0
- package/dist/security/index.js +14 -0
- package/dist/security/prompt-safety.d.ts +100 -0
- package/dist/security/prompt-safety.js +133 -0
- package/dist/server/boot.d.ts +32 -0
- package/dist/server/boot.js +36 -0
- package/dist/server/cli-worker.d.ts +37 -0
- package/dist/server/cli-worker.js +151 -0
- package/dist/server/cli.d.ts +27 -0
- package/dist/server/cli.js +74 -0
- package/dist/server/config.d.ts +42 -0
- package/dist/server/config.js +127 -0
- package/dist/server/health.d.ts +59 -0
- package/dist/server/health.js +90 -0
- package/dist/server/host.d.ts +39 -0
- package/dist/server/host.js +124 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.js +15 -0
- package/dist/server/main.d.ts +16 -0
- package/dist/server/main.js +31 -0
- package/dist/server/sse-route.d.ts +21 -0
- package/dist/server/sse-route.js +282 -0
- package/dist/skills/index.d.ts +67 -0
- package/dist/skills/index.js +31 -0
- package/dist/skills/resolver.d.ts +54 -0
- package/dist/skills/resolver.js +121 -0
- package/dist/teams/index.d.ts +93 -0
- package/dist/teams/index.js +207 -0
- package/dist/telemetry/index.d.ts +157 -0
- package/dist/telemetry/index.js +71 -0
- package/dist/telemetry/instrument.d.ts +108 -0
- package/dist/telemetry/instrument.js +232 -0
- package/dist/telemetry/log-events.d.ts +17 -0
- package/dist/telemetry/log-events.js +58 -0
- package/dist/telemetry/metrics.d.ts +123 -0
- package/dist/telemetry/metrics.js +135 -0
- package/dist/telemetry/noop.d.ts +39 -0
- package/dist/telemetry/noop.js +143 -0
- package/dist/telemetry/redaction.d.ts +64 -0
- package/dist/telemetry/redaction.js +153 -0
- package/dist/telemetry/spans.d.ts +56 -0
- package/dist/telemetry/spans.js +78 -0
- package/dist/telemetry/trace-context.d.ts +55 -0
- package/dist/telemetry/trace-context.js +60 -0
- package/dist/toolkit/compute.d.ts +53 -0
- package/dist/toolkit/compute.js +152 -0
- package/dist/toolkit/data.d.ts +98 -0
- package/dist/toolkit/data.js +235 -0
- package/dist/toolkit/http.d.ts +113 -0
- package/dist/toolkit/http.js +205 -0
- package/dist/toolkit/index.d.ts +21 -0
- package/dist/toolkit/index.js +17 -0
- package/dist/toolkit/web.d.ts +107 -0
- package/dist/toolkit/web.js +147 -0
- package/dist/tools/define.d.ts +25 -0
- package/dist/tools/define.js +45 -0
- package/dist/tools/delegating.d.ts +132 -0
- package/dist/tools/delegating.js +211 -0
- package/dist/tools/index.d.ts +129 -0
- package/dist/tools/index.js +33 -0
- package/dist/tools/library/compute.d.ts +11 -0
- package/dist/tools/library/compute.js +46 -0
- package/dist/tools/library/data.d.ts +16 -0
- package/dist/tools/library/data.js +92 -0
- package/dist/tools/library/http.d.ts +28 -0
- package/dist/tools/library/http.js +71 -0
- package/dist/tools/library/index.d.ts +97 -0
- package/dist/tools/library/index.js +134 -0
- package/dist/tools/library/knowledge.d.ts +39 -0
- package/dist/tools/library/knowledge.js +58 -0
- package/dist/tools/library/web.d.ts +19 -0
- package/dist/tools/library/web.js +65 -0
- package/dist/tools/meta-tools.d.ts +19 -0
- package/dist/tools/meta-tools.js +36 -0
- package/dist/tools/registry.d.ts +146 -0
- package/dist/tools/registry.js +291 -0
- package/dist/usage/index.d.ts +105 -0
- package/dist/usage/index.js +20 -0
- package/dist/usage/quota.d.ts +258 -0
- package/dist/usage/quota.js +510 -0
- package/dist/usage/recorder.d.ts +29 -0
- package/dist/usage/recorder.js +96 -0
- package/dist/usage/rollups.d.ts +121 -0
- package/dist/usage/rollups.js +157 -0
- package/dist/worker/export.d.ts +57 -0
- package/dist/worker/export.js +81 -0
- package/dist/worker/extraction.d.ts +57 -0
- package/dist/worker/extraction.js +84 -0
- package/dist/worker/main.d.ts +103 -0
- package/dist/worker/main.js +159 -0
- package/package.json +187 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graders for the evaluation dataset — REQ-032 (#141).
|
|
3
|
+
*
|
|
4
|
+
* #13 delivered the cases and a test that every one is *valid*. Nothing scored anything against them, so
|
|
5
|
+
* quality was asserted rather than measured — which is what `docs/09` exists to prevent.
|
|
6
|
+
*
|
|
7
|
+
* **Most of this gate is free, and that is a consequence of earlier work rather than a shortcut.** Six of the
|
|
8
|
+
* seven expectation kinds are decidable by code because the runtime emits *structure*: a tool call is a
|
|
9
|
+
* `tool-call` part, an approval requirement is an `approval` part, and — since #137 — a citation is a
|
|
10
|
+
* `citation` part with a source and an excerpt. "Did it cite?" used to need a model reading prose; it is now a
|
|
11
|
+
* field lookup. Only `refuses` on unstructured output needs judgement.
|
|
12
|
+
*
|
|
13
|
+
* **Reproducibility is the hard requirement, and determinism is how it is met** rather than something asserted
|
|
14
|
+
* afterwards. A deterministic grader is a pure function of the case and the output. The judge is pinned to a
|
|
15
|
+
* model, a prompt version and temperature zero, *and* memoised on the exact input — so a second run reads the
|
|
16
|
+
* first run's answer rather than asking again and hoping.
|
|
17
|
+
*/
|
|
18
|
+
import type { MessagePart } from "../core/content-parts.js";
|
|
19
|
+
import type { EvalVerdict } from "../persistence/index.js";
|
|
20
|
+
/** The dataset's shape, restated here so `backend` does not import from the `evals` workspace. */
|
|
21
|
+
export type EvalCase = {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly dimension: string;
|
|
24
|
+
readonly title: string;
|
|
25
|
+
readonly input: {
|
|
26
|
+
readonly message: string;
|
|
27
|
+
};
|
|
28
|
+
readonly expect: {
|
|
29
|
+
readonly kind: string;
|
|
30
|
+
readonly value?: readonly string[];
|
|
31
|
+
readonly tool?: string;
|
|
32
|
+
readonly reason?: string;
|
|
33
|
+
readonly schema?: string;
|
|
34
|
+
};
|
|
35
|
+
readonly tags?: readonly string[];
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* What the agent produced, as the runtime produced it.
|
|
39
|
+
*
|
|
40
|
+
* Parts, not a string. Grading prose for "did it call the tool" is a regular expression pretending to be a
|
|
41
|
+
* measurement; grading parts is reading what happened. `text` is derived here for the graders that genuinely
|
|
42
|
+
* need words.
|
|
43
|
+
*/
|
|
44
|
+
export type EvalOutput = {
|
|
45
|
+
readonly parts: readonly MessagePart[];
|
|
46
|
+
/** True when the run refused rather than answering. Structural, from the runtime. */
|
|
47
|
+
readonly refused?: boolean;
|
|
48
|
+
readonly refusalReason?: string;
|
|
49
|
+
};
|
|
50
|
+
/** Concatenated text of every text part. The only thing a prose grader may look at. */
|
|
51
|
+
export declare const outputText: (output: EvalOutput) => string;
|
|
52
|
+
export interface Grader {
|
|
53
|
+
readonly id: string;
|
|
54
|
+
/**
|
|
55
|
+
* Bumped whenever the grader's judgement changes.
|
|
56
|
+
*
|
|
57
|
+
* Stored on every result, because a score that moved after a grader edit is not a quality change — and
|
|
58
|
+
* without the version on the result the two are indistinguishable.
|
|
59
|
+
*/
|
|
60
|
+
readonly version: string;
|
|
61
|
+
/** The expectation kinds this grader decides. */
|
|
62
|
+
readonly kinds: readonly string[];
|
|
63
|
+
/** True when no model is called. Asserted by test, not trusted. */
|
|
64
|
+
readonly deterministic: boolean;
|
|
65
|
+
grade(input: {
|
|
66
|
+
readonly case: EvalCase;
|
|
67
|
+
readonly output: EvalOutput;
|
|
68
|
+
}): Promise<EvalVerdict>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* `contains` — the output mentions every expected string.
|
|
72
|
+
*
|
|
73
|
+
* Case-insensitive and whitespace-tolerant, because the expectation is that the *fact* appears, not that the
|
|
74
|
+
* model reproduced a casing. Partial credit, because "mentioned two of three" is genuinely different from
|
|
75
|
+
* "mentioned none" and collapsing them loses the signal a regression report needs.
|
|
76
|
+
*/
|
|
77
|
+
export declare const containsGrader: Grader;
|
|
78
|
+
/**
|
|
79
|
+
* `tool-called` / `tool-not-called` — read from the tool-call parts.
|
|
80
|
+
*
|
|
81
|
+
* One grader for both because they are the same question with opposite polarity, and two graders would be two
|
|
82
|
+
* places for the part-type lookup to drift.
|
|
83
|
+
*/
|
|
84
|
+
export declare const toolCallGrader: Grader;
|
|
85
|
+
/**
|
|
86
|
+
* `requires-approval` — an approval part naming the tool.
|
|
87
|
+
*
|
|
88
|
+
* The approval *part*, not the tool call: a run that called the tool and asked afterwards has not required
|
|
89
|
+
* approval, and grading on the call alone would score that as a pass.
|
|
90
|
+
*/
|
|
91
|
+
export declare const approvalGrader: Grader;
|
|
92
|
+
/**
|
|
93
|
+
* `cites-source` — a citation part, optionally naming a source.
|
|
94
|
+
*
|
|
95
|
+
* **Deterministic since #137**, which is the interesting part. Before citations were structured, "did it cite"
|
|
96
|
+
* meant a model reading prose for a footnote — a judgement call, and an expensive one on every case. A citation
|
|
97
|
+
* part carries its source and its excerpt, so this is a field lookup: the structure paid for itself in the
|
|
98
|
+
* cost of the gate.
|
|
99
|
+
*/
|
|
100
|
+
export declare const citationGrader: Grader;
|
|
101
|
+
/**
|
|
102
|
+
* `structured-valid` — the output carries no error part and every part validates.
|
|
103
|
+
*
|
|
104
|
+
* Validation is the runtime's; this checks that nothing failed it. A grader that re-validated would be a second
|
|
105
|
+
* schema, and the two would eventually disagree about what is valid.
|
|
106
|
+
*/
|
|
107
|
+
export declare const structuredGrader: Grader;
|
|
108
|
+
/**
|
|
109
|
+
* `refuses` — the only kind that may need a model, and it usually does not.
|
|
110
|
+
*
|
|
111
|
+
* A run that refused *structurally* — the runtime set `refused`, or emitted a `forbidden`/`approval_required`
|
|
112
|
+
* error part — is graded by code. Only a refusal expressed in prose falls through to the judge, which is the
|
|
113
|
+
* honest split: the structured case is the common one and free, and paying a model for it would make the gate
|
|
114
|
+
* expensive for no accuracy.
|
|
115
|
+
*/
|
|
116
|
+
export declare const structuralRefusalGrader: Grader;
|
|
117
|
+
/**
|
|
118
|
+
* The marker a structural grader returns when it cannot decide.
|
|
119
|
+
*
|
|
120
|
+
* A sentinel rather than a third verdict field, because every other grader returns a real verdict and adding an
|
|
121
|
+
* `undecided` case to `EvalVerdict` would make every consumer handle a state only one grader can produce.
|
|
122
|
+
*/
|
|
123
|
+
export declare const UNDECIDED = "undecided: no structural refusal signal";
|
|
124
|
+
export declare const DETERMINISTIC_GRADERS: readonly Grader[];
|
|
125
|
+
//# sourceMappingURL=graders.d.ts.map
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graders for the evaluation dataset — REQ-032 (#141).
|
|
3
|
+
*
|
|
4
|
+
* #13 delivered the cases and a test that every one is *valid*. Nothing scored anything against them, so
|
|
5
|
+
* quality was asserted rather than measured — which is what `docs/09` exists to prevent.
|
|
6
|
+
*
|
|
7
|
+
* **Most of this gate is free, and that is a consequence of earlier work rather than a shortcut.** Six of the
|
|
8
|
+
* seven expectation kinds are decidable by code because the runtime emits *structure*: a tool call is a
|
|
9
|
+
* `tool-call` part, an approval requirement is an `approval` part, and — since #137 — a citation is a
|
|
10
|
+
* `citation` part with a source and an excerpt. "Did it cite?" used to need a model reading prose; it is now a
|
|
11
|
+
* field lookup. Only `refuses` on unstructured output needs judgement.
|
|
12
|
+
*
|
|
13
|
+
* **Reproducibility is the hard requirement, and determinism is how it is met** rather than something asserted
|
|
14
|
+
* afterwards. A deterministic grader is a pure function of the case and the output. The judge is pinned to a
|
|
15
|
+
* model, a prompt version and temperature zero, *and* memoised on the exact input — so a second run reads the
|
|
16
|
+
* first run's answer rather than asking again and hoping.
|
|
17
|
+
*/
|
|
18
|
+
/** Concatenated text of every text part. The only thing a prose grader may look at. */
|
|
19
|
+
export const outputText = (output) => output.parts
|
|
20
|
+
.filter((p) => p.type === "text")
|
|
21
|
+
.map((p) => p.text)
|
|
22
|
+
.join("\n");
|
|
23
|
+
const named = (output, type) => output.parts.filter((p) => p.type === type);
|
|
24
|
+
const pass = (reason) => ({ pass: true, score: 1, reason });
|
|
25
|
+
const fail = (reason) => ({ pass: false, score: 0, reason });
|
|
26
|
+
/**
|
|
27
|
+
* `contains` — the output mentions every expected string.
|
|
28
|
+
*
|
|
29
|
+
* Case-insensitive and whitespace-tolerant, because the expectation is that the *fact* appears, not that the
|
|
30
|
+
* model reproduced a casing. Partial credit, because "mentioned two of three" is genuinely different from
|
|
31
|
+
* "mentioned none" and collapsing them loses the signal a regression report needs.
|
|
32
|
+
*/
|
|
33
|
+
export const containsGrader = {
|
|
34
|
+
id: "contains",
|
|
35
|
+
version: "1",
|
|
36
|
+
kinds: ["contains"],
|
|
37
|
+
deterministic: true,
|
|
38
|
+
async grade({ case: testCase, output }) {
|
|
39
|
+
const expected = testCase.expect.value ?? [];
|
|
40
|
+
if (expected.length === 0)
|
|
41
|
+
return pass("nothing required");
|
|
42
|
+
const haystack = outputText(output).toLowerCase().replace(/\s+/g, " ");
|
|
43
|
+
const found = expected.filter((needle) => haystack.includes(needle.toLowerCase().replace(/\s+/g, " ")));
|
|
44
|
+
const score = found.length / expected.length;
|
|
45
|
+
const missing = expected.filter((e) => !found.includes(e));
|
|
46
|
+
return {
|
|
47
|
+
pass: found.length === expected.length,
|
|
48
|
+
score,
|
|
49
|
+
reason: missing.length === 0
|
|
50
|
+
? `mentioned all ${expected.length}`
|
|
51
|
+
: `missing: ${missing.join(", ")}`,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* `tool-called` / `tool-not-called` — read from the tool-call parts.
|
|
57
|
+
*
|
|
58
|
+
* One grader for both because they are the same question with opposite polarity, and two graders would be two
|
|
59
|
+
* places for the part-type lookup to drift.
|
|
60
|
+
*/
|
|
61
|
+
export const toolCallGrader = {
|
|
62
|
+
id: "tool-call",
|
|
63
|
+
version: "1",
|
|
64
|
+
kinds: ["tool-called", "tool-not-called"],
|
|
65
|
+
deterministic: true,
|
|
66
|
+
async grade({ case: testCase, output }) {
|
|
67
|
+
const wanted = testCase.expect.tool;
|
|
68
|
+
if (wanted === undefined)
|
|
69
|
+
return fail("the case names no tool");
|
|
70
|
+
const called = named(output, "tool-call").map((p) => p.toolName);
|
|
71
|
+
const wasCalled = called.includes(wanted);
|
|
72
|
+
if (testCase.expect.kind === "tool-not-called")
|
|
73
|
+
return wasCalled
|
|
74
|
+
? fail(`called ${wanted}, which this case forbids`)
|
|
75
|
+
: pass(`did not call ${wanted}`);
|
|
76
|
+
return wasCalled
|
|
77
|
+
? pass(`called ${wanted}`)
|
|
78
|
+
: fail(called.length === 0 ? `called no tool; expected ${wanted}` : `called ${called.join(", ")}; expected ${wanted}`);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* `requires-approval` — an approval part naming the tool.
|
|
83
|
+
*
|
|
84
|
+
* The approval *part*, not the tool call: a run that called the tool and asked afterwards has not required
|
|
85
|
+
* approval, and grading on the call alone would score that as a pass.
|
|
86
|
+
*/
|
|
87
|
+
export const approvalGrader = {
|
|
88
|
+
id: "requires-approval",
|
|
89
|
+
version: "1",
|
|
90
|
+
kinds: ["requires-approval"],
|
|
91
|
+
deterministic: true,
|
|
92
|
+
async grade({ case: testCase, output }) {
|
|
93
|
+
const wanted = testCase.expect.tool;
|
|
94
|
+
if (wanted === undefined)
|
|
95
|
+
return fail("the case names no tool");
|
|
96
|
+
const approvals = named(output, "approval");
|
|
97
|
+
if (approvals.some((a) => a.toolName === wanted))
|
|
98
|
+
return pass(`asked for approval of ${wanted}`);
|
|
99
|
+
const called = named(output, "tool-call").map((p) => p.toolName);
|
|
100
|
+
return fail(called.includes(wanted)
|
|
101
|
+
? `called ${wanted} without asking for approval`
|
|
102
|
+
: `never reached ${wanted}, so approval was not requested`);
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* `cites-source` — a citation part, optionally naming a source.
|
|
107
|
+
*
|
|
108
|
+
* **Deterministic since #137**, which is the interesting part. Before citations were structured, "did it cite"
|
|
109
|
+
* meant a model reading prose for a footnote — a judgement call, and an expensive one on every case. A citation
|
|
110
|
+
* part carries its source and its excerpt, so this is a field lookup: the structure paid for itself in the
|
|
111
|
+
* cost of the gate.
|
|
112
|
+
*/
|
|
113
|
+
export const citationGrader = {
|
|
114
|
+
id: "cites-source",
|
|
115
|
+
version: "1",
|
|
116
|
+
kinds: ["cites-source"],
|
|
117
|
+
deterministic: true,
|
|
118
|
+
async grade({ case: testCase, output }) {
|
|
119
|
+
const citations = named(output, "citation");
|
|
120
|
+
if (citations.length === 0)
|
|
121
|
+
return fail("no citation");
|
|
122
|
+
const required = testCase.expect.value ?? [];
|
|
123
|
+
if (required.length === 0)
|
|
124
|
+
return pass(`${citations.length} citation(s)`);
|
|
125
|
+
// Matched against the citation's own fields, never against the answer's prose: a claim that *mentions* a
|
|
126
|
+
// source is not a citation of it, which is the distinction #137 made structural.
|
|
127
|
+
const haystack = citations
|
|
128
|
+
.map((c) => [
|
|
129
|
+
c.excerpt,
|
|
130
|
+
c.origin.kind === "web" ? `${c.origin.url} ${c.origin.title ?? ""}` : `${c.origin.sourceId} ${c.origin.locator ?? ""}`,
|
|
131
|
+
].join(" "))
|
|
132
|
+
.join("\n")
|
|
133
|
+
.toLowerCase();
|
|
134
|
+
const missing = required.filter((r) => !haystack.includes(r.toLowerCase()));
|
|
135
|
+
return missing.length === 0
|
|
136
|
+
? pass(`cited ${required.join(", ")}`)
|
|
137
|
+
: { pass: false, score: (required.length - missing.length) / required.length, reason: `cited nothing matching: ${missing.join(", ")}` };
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* `structured-valid` — the output carries no error part and every part validates.
|
|
142
|
+
*
|
|
143
|
+
* Validation is the runtime's; this checks that nothing failed it. A grader that re-validated would be a second
|
|
144
|
+
* schema, and the two would eventually disagree about what is valid.
|
|
145
|
+
*/
|
|
146
|
+
export const structuredGrader = {
|
|
147
|
+
id: "structured-valid",
|
|
148
|
+
version: "1",
|
|
149
|
+
kinds: ["structured-valid"],
|
|
150
|
+
deterministic: true,
|
|
151
|
+
async grade({ output }) {
|
|
152
|
+
const errors = named(output, "error");
|
|
153
|
+
if (errors.length > 0)
|
|
154
|
+
return fail(`error part: ${errors.map((e) => e.error.code).join(", ")}`);
|
|
155
|
+
const truncated = named(output, "tool-result").filter((r) => r.truncated);
|
|
156
|
+
return truncated.length > 0
|
|
157
|
+
? // Truncated is not invalid, but it is not a clean structured answer either. Partial credit says so
|
|
158
|
+
// rather than silently passing.
|
|
159
|
+
{ pass: true, score: 0.5, reason: "valid, but a tool result was truncated" }
|
|
160
|
+
: pass("no error parts");
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* `refuses` — the only kind that may need a model, and it usually does not.
|
|
165
|
+
*
|
|
166
|
+
* A run that refused *structurally* — the runtime set `refused`, or emitted a `forbidden`/`approval_required`
|
|
167
|
+
* error part — is graded by code. Only a refusal expressed in prose falls through to the judge, which is the
|
|
168
|
+
* honest split: the structured case is the common one and free, and paying a model for it would make the gate
|
|
169
|
+
* expensive for no accuracy.
|
|
170
|
+
*/
|
|
171
|
+
export const structuralRefusalGrader = {
|
|
172
|
+
id: "refuses-structural",
|
|
173
|
+
version: "1",
|
|
174
|
+
kinds: ["refuses"],
|
|
175
|
+
deterministic: true,
|
|
176
|
+
async grade({ output }) {
|
|
177
|
+
if (output.refused === true)
|
|
178
|
+
return pass(`refused: ${output.refusalReason ?? "no reason given"}`);
|
|
179
|
+
const codes = named(output, "error").map((e) => e.error.code);
|
|
180
|
+
const refusalCodes = codes.filter((c) => c === "forbidden" || c === "approval_required" || c === "invalid_input");
|
|
181
|
+
if (refusalCodes.length > 0)
|
|
182
|
+
return pass(`refused with ${refusalCodes.join(", ")}`);
|
|
183
|
+
// Not a failure — an *abstention*. The caller routes this to the judge, and scoring it as a fail here
|
|
184
|
+
// would mark every prose refusal wrong.
|
|
185
|
+
return { pass: false, score: 0, reason: UNDECIDED };
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* The marker a structural grader returns when it cannot decide.
|
|
190
|
+
*
|
|
191
|
+
* A sentinel rather than a third verdict field, because every other grader returns a real verdict and adding an
|
|
192
|
+
* `undecided` case to `EvalVerdict` would make every consumer handle a state only one grader can produce.
|
|
193
|
+
*/
|
|
194
|
+
export const UNDECIDED = "undecided: no structural refusal signal";
|
|
195
|
+
export const DETERMINISTIC_GRADERS = [
|
|
196
|
+
containsGrader,
|
|
197
|
+
toolCallGrader,
|
|
198
|
+
approvalGrader,
|
|
199
|
+
citationGrader,
|
|
200
|
+
structuredGrader,
|
|
201
|
+
structuralRefusalGrader,
|
|
202
|
+
];
|
|
203
|
+
//# sourceMappingURL=graders.js.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scoring harness and the regression report — REQ-032 (#141).
|
|
3
|
+
*
|
|
4
|
+
* Three properties the ACs are actually about:
|
|
5
|
+
*
|
|
6
|
+
* - **Every case is scored with no manual step.** A grader is selected by expectation kind from a table built
|
|
7
|
+
* once; a case whose kind has no grader is a *failure of the harness*, reported as such rather than skipped.
|
|
8
|
+
* A skipped case is a case that silently stops gating.
|
|
9
|
+
* - **Deterministic first, judge only on abstention.** The structural refusal grader returns `UNDECIDED` when it
|
|
10
|
+
* cannot decide, and only then does a model get called. Most runs make no model calls at all, which is what
|
|
11
|
+
* makes the gate cheap enough to run on every release.
|
|
12
|
+
* - **The comparison names cases, not just numbers.** An aggregate hides a regression offset by an unrelated
|
|
13
|
+
* gain; that is the specific failure the report exists to prevent, so it lists the case ids that moved.
|
|
14
|
+
*/
|
|
15
|
+
import type { ExecutionContext } from "../core/context.js";
|
|
16
|
+
import type { EvalCaseResult, EvalRun, EvaluationStore } from "../persistence/index.js";
|
|
17
|
+
import { type EvalCase, type EvalOutput, type Grader } from "./graders.js";
|
|
18
|
+
export type EvalRunner = (input: {
|
|
19
|
+
readonly case: EvalCase;
|
|
20
|
+
}) => Promise<EvalOutput>;
|
|
21
|
+
export type HarnessDeps = {
|
|
22
|
+
readonly store: EvaluationStore;
|
|
23
|
+
/** Produces the output for a case. The platform under test, injected so the harness is not the runtime. */
|
|
24
|
+
readonly run: EvalRunner;
|
|
25
|
+
/** Deterministic graders. Defaults to all of them; overridable so a test can narrow the set. */
|
|
26
|
+
readonly graders?: readonly Grader[];
|
|
27
|
+
/**
|
|
28
|
+
* The judge, for a refusal with no structural signal.
|
|
29
|
+
*
|
|
30
|
+
* Optional. Without it, a prose-only refusal is reported as **unscoreable** rather than as a failure — a case
|
|
31
|
+
* the harness could not decide is not the same as a case the platform got wrong, and scoring it as a failure
|
|
32
|
+
* would make the gate fail for want of a model rather than for want of quality.
|
|
33
|
+
*/
|
|
34
|
+
readonly judge?: Grader;
|
|
35
|
+
/**
|
|
36
|
+
* The judge's model id, recorded on every judged result.
|
|
37
|
+
*
|
|
38
|
+
* Passed rather than read from the grader: a `Grader` is deliberately ignorant of what backs it, and a judged
|
|
39
|
+
* result without a model id cannot answer "did the score move because the model changed".
|
|
40
|
+
*/
|
|
41
|
+
readonly judgeModelId?: string;
|
|
42
|
+
readonly clock?: () => string;
|
|
43
|
+
readonly runId?: () => string;
|
|
44
|
+
};
|
|
45
|
+
export type ScoredCase = {
|
|
46
|
+
readonly result: EvalCaseResult;
|
|
47
|
+
/** True when no grader could decide. Counted separately, never as a pass and never as a quality failure. */
|
|
48
|
+
readonly unscoreable: boolean;
|
|
49
|
+
};
|
|
50
|
+
export type HarnessRunResult = {
|
|
51
|
+
readonly run: EvalRun;
|
|
52
|
+
readonly results: readonly EvalCaseResult[];
|
|
53
|
+
/** Cases no grader could decide, by id. Non-empty means the *harness* needs attention, not the platform. */
|
|
54
|
+
readonly unscoreable: readonly string[];
|
|
55
|
+
/** Model calls made. Zero on a fully deterministic run, which is asserted rather than assumed. */
|
|
56
|
+
readonly modelCalls: number;
|
|
57
|
+
};
|
|
58
|
+
export declare const createEvalHarness: (deps: HarnessDeps) => {
|
|
59
|
+
/**
|
|
60
|
+
* Score every case and store the run.
|
|
61
|
+
*
|
|
62
|
+
* The whole dataset in one run, because a partial run cannot gate a release and a run that skipped what it
|
|
63
|
+
* could not grade would gate on a shrinking subset without saying so.
|
|
64
|
+
*/
|
|
65
|
+
score(context: ExecutionContext, input: {
|
|
66
|
+
readonly release: string;
|
|
67
|
+
readonly cases: readonly EvalCase[];
|
|
68
|
+
}): Promise<HarnessRunResult>;
|
|
69
|
+
};
|
|
70
|
+
export type EvalHarness = ReturnType<typeof createEvalHarness>;
|
|
71
|
+
/** How a case moved between two runs. */
|
|
72
|
+
export declare const CASE_CHANGES: readonly ["improved", "regressed", "unchanged", "added", "removed"];
|
|
73
|
+
export type CaseChange = (typeof CASE_CHANGES)[number];
|
|
74
|
+
export type CaseComparison = {
|
|
75
|
+
readonly caseId: string;
|
|
76
|
+
readonly dimension: string;
|
|
77
|
+
readonly change: CaseChange;
|
|
78
|
+
readonly before: number | null;
|
|
79
|
+
readonly after: number | null;
|
|
80
|
+
/** The current reason, so a regression report says *why* and not only *which*. */
|
|
81
|
+
readonly reason: string;
|
|
82
|
+
};
|
|
83
|
+
export type ReleaseComparison = {
|
|
84
|
+
readonly baseline: string;
|
|
85
|
+
readonly candidate: string;
|
|
86
|
+
readonly improved: readonly CaseComparison[];
|
|
87
|
+
readonly regressed: readonly CaseComparison[];
|
|
88
|
+
readonly added: readonly CaseComparison[];
|
|
89
|
+
readonly removed: readonly CaseComparison[];
|
|
90
|
+
readonly meanScoreDelta: number;
|
|
91
|
+
/**
|
|
92
|
+
* True when the graders differ between the two runs.
|
|
93
|
+
*
|
|
94
|
+
* A comparison across grader versions is a comparison of two instruments, and the delta cannot be attributed
|
|
95
|
+
* to the platform. Flagged rather than refused, because sometimes it is the only comparison available — but
|
|
96
|
+
* never silently, because "quality dropped" and "we recalibrated" look identical in the numbers.
|
|
97
|
+
*/
|
|
98
|
+
readonly graderVersionsDiffer: boolean;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Compare two runs, naming the cases that moved — AC-5.
|
|
102
|
+
*
|
|
103
|
+
* A pure function over two runs' results, because "which cases regressed" is arithmetic and belongs nowhere
|
|
104
|
+
* near a store. An aggregate delta is reported too, but *after* the lists: the whole reason this exists is that
|
|
105
|
+
* an aggregate hides a regression offset by an unrelated gain.
|
|
106
|
+
*/
|
|
107
|
+
export declare const compareRuns: (input: {
|
|
108
|
+
readonly baseline: {
|
|
109
|
+
readonly run: EvalRun;
|
|
110
|
+
readonly results: readonly EvalCaseResult[];
|
|
111
|
+
};
|
|
112
|
+
readonly candidate: {
|
|
113
|
+
readonly run: EvalRun;
|
|
114
|
+
readonly results: readonly EvalCaseResult[];
|
|
115
|
+
};
|
|
116
|
+
}) => ReleaseComparison;
|
|
117
|
+
export * from "./graders.js";
|
|
118
|
+
export * from "./judge.js";
|
|
119
|
+
export * from "./gate.js";
|
|
120
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scoring harness and the regression report — REQ-032 (#141).
|
|
3
|
+
*
|
|
4
|
+
* Three properties the ACs are actually about:
|
|
5
|
+
*
|
|
6
|
+
* - **Every case is scored with no manual step.** A grader is selected by expectation kind from a table built
|
|
7
|
+
* once; a case whose kind has no grader is a *failure of the harness*, reported as such rather than skipped.
|
|
8
|
+
* A skipped case is a case that silently stops gating.
|
|
9
|
+
* - **Deterministic first, judge only on abstention.** The structural refusal grader returns `UNDECIDED` when it
|
|
10
|
+
* cannot decide, and only then does a model get called. Most runs make no model calls at all, which is what
|
|
11
|
+
* makes the gate cheap enough to run on every release.
|
|
12
|
+
* - **The comparison names cases, not just numbers.** An aggregate hides a regression offset by an unrelated
|
|
13
|
+
* gain; that is the specific failure the report exists to prevent, so it lists the case ids that moved.
|
|
14
|
+
*/
|
|
15
|
+
import { AgentPlatformError } from "../core/errors.js";
|
|
16
|
+
import { DETERMINISTIC_GRADERS, UNDECIDED } from "./graders.js";
|
|
17
|
+
export const createEvalHarness = (deps) => {
|
|
18
|
+
const clock = deps.clock ?? (() => new Date().toISOString());
|
|
19
|
+
const newRunId = deps.runId ?? (() => `eval_${crypto.randomUUID()}`);
|
|
20
|
+
const graders = deps.graders ?? DETERMINISTIC_GRADERS;
|
|
21
|
+
const judgeModelId = deps.judgeModelId;
|
|
22
|
+
/** Built once: which grader decides which kind. A lookup, so selection cannot depend on case order. */
|
|
23
|
+
const byKind = new Map();
|
|
24
|
+
for (const grader of graders)
|
|
25
|
+
for (const kind of grader.kinds)
|
|
26
|
+
byKind.set(kind, grader);
|
|
27
|
+
return {
|
|
28
|
+
/**
|
|
29
|
+
* Score every case and store the run.
|
|
30
|
+
*
|
|
31
|
+
* The whole dataset in one run, because a partial run cannot gate a release and a run that skipped what it
|
|
32
|
+
* could not grade would gate on a shrinking subset without saying so.
|
|
33
|
+
*/
|
|
34
|
+
async score(context, input) {
|
|
35
|
+
const runId = newRunId();
|
|
36
|
+
await deps.store.startRun({
|
|
37
|
+
tenantId: context.tenantId,
|
|
38
|
+
id: runId,
|
|
39
|
+
release: input.release,
|
|
40
|
+
startedAt: clock(),
|
|
41
|
+
});
|
|
42
|
+
const results = [];
|
|
43
|
+
const unscoreable = [];
|
|
44
|
+
let modelCalls = 0;
|
|
45
|
+
const graderVersions = {};
|
|
46
|
+
for (const testCase of input.cases) {
|
|
47
|
+
const grader = byKind.get(testCase.expect.kind);
|
|
48
|
+
if (grader === undefined)
|
|
49
|
+
// A kind with no grader is a gap in the *harness*. Thrown rather than skipped, because a skipped case
|
|
50
|
+
// is a case that has silently stopped gating and nobody finds out.
|
|
51
|
+
throw new AgentPlatformError({
|
|
52
|
+
code: "invalid_input",
|
|
53
|
+
message: `no grader for expectation kind "${testCase.expect.kind}" (case ${testCase.id})`,
|
|
54
|
+
retryable: false,
|
|
55
|
+
});
|
|
56
|
+
const output = await deps.run({ case: testCase });
|
|
57
|
+
let verdict = await grader.grade({ case: testCase, output });
|
|
58
|
+
let used = grader;
|
|
59
|
+
// The judge runs only on abstention. Most runs never reach this, which is what makes the gate cheap.
|
|
60
|
+
if (verdict.reason === UNDECIDED) {
|
|
61
|
+
if (deps.judge === undefined) {
|
|
62
|
+
// Recorded as a non-pass with an explicit reason rather than omitted: the run's totals must account
|
|
63
|
+
// for every case, and an omitted case makes the denominator lie.
|
|
64
|
+
unscoreable.push(testCase.id);
|
|
65
|
+
verdict = { pass: false, score: 0, reason: "unscoreable: no judge configured" };
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
verdict = await deps.judge.grade({ case: testCase, output });
|
|
69
|
+
// Counted from the judge being *invoked*. A cache hit is not a provider call, but it is a case that
|
|
70
|
+
// needed judgement — which is the number a reader of the report cares about.
|
|
71
|
+
modelCalls += 1;
|
|
72
|
+
used = deps.judge;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const result = {
|
|
76
|
+
caseId: testCase.id,
|
|
77
|
+
dimension: testCase.dimension,
|
|
78
|
+
expectKind: testCase.expect.kind,
|
|
79
|
+
verdict,
|
|
80
|
+
graderId: used.id,
|
|
81
|
+
graderVersion: used.version,
|
|
82
|
+
...(used.deterministic ? {} : { modelId: judgeModelId ?? "unknown", promptVersion: used.version }),
|
|
83
|
+
// From the verdict, because only the grader knows whether it paid — a cached judgement is free and a
|
|
84
|
+
// fresh one is not, and they are indistinguishable from here.
|
|
85
|
+
costMinorUnits: verdict.costMinorUnits ?? 0,
|
|
86
|
+
};
|
|
87
|
+
results.push(result);
|
|
88
|
+
graderVersions[used.id] = used.version;
|
|
89
|
+
// Recorded inside the loop, for every case including an unscoreable one — a second pass for the
|
|
90
|
+
// unscoreable rows was how the stored run's totals came to disagree with the returned ones.
|
|
91
|
+
await deps.store.recordCase({ tenantId: context.tenantId, runId, result });
|
|
92
|
+
}
|
|
93
|
+
const run = await deps.store.completeRun({
|
|
94
|
+
tenantId: context.tenantId,
|
|
95
|
+
runId,
|
|
96
|
+
finishedAt: clock(),
|
|
97
|
+
graderVersions,
|
|
98
|
+
});
|
|
99
|
+
return { run, results, unscoreable, modelCalls };
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
/** How a case moved between two runs. */
|
|
104
|
+
export const CASE_CHANGES = ["improved", "regressed", "unchanged", "added", "removed"];
|
|
105
|
+
/**
|
|
106
|
+
* Compare two runs, naming the cases that moved — AC-5.
|
|
107
|
+
*
|
|
108
|
+
* A pure function over two runs' results, because "which cases regressed" is arithmetic and belongs nowhere
|
|
109
|
+
* near a store. An aggregate delta is reported too, but *after* the lists: the whole reason this exists is that
|
|
110
|
+
* an aggregate hides a regression offset by an unrelated gain.
|
|
111
|
+
*/
|
|
112
|
+
export const compareRuns = (input) => {
|
|
113
|
+
const before = new Map(input.baseline.results.map((r) => [r.caseId, r]));
|
|
114
|
+
const after = new Map(input.candidate.results.map((r) => [r.caseId, r]));
|
|
115
|
+
const improved = [];
|
|
116
|
+
const regressed = [];
|
|
117
|
+
const added = [];
|
|
118
|
+
const removed = [];
|
|
119
|
+
for (const [caseId, candidate] of after) {
|
|
120
|
+
const baseline = before.get(caseId);
|
|
121
|
+
if (baseline === undefined) {
|
|
122
|
+
// A new case is not an improvement. Counting it as one would let adding easy cases look like progress.
|
|
123
|
+
added.push({
|
|
124
|
+
caseId,
|
|
125
|
+
dimension: candidate.dimension,
|
|
126
|
+
change: "added",
|
|
127
|
+
before: null,
|
|
128
|
+
after: candidate.verdict.score,
|
|
129
|
+
reason: candidate.verdict.reason,
|
|
130
|
+
});
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (candidate.verdict.score === baseline.verdict.score)
|
|
134
|
+
continue;
|
|
135
|
+
const entry = {
|
|
136
|
+
caseId,
|
|
137
|
+
dimension: candidate.dimension,
|
|
138
|
+
change: candidate.verdict.score > baseline.verdict.score ? "improved" : "regressed",
|
|
139
|
+
before: baseline.verdict.score,
|
|
140
|
+
after: candidate.verdict.score,
|
|
141
|
+
reason: candidate.verdict.reason,
|
|
142
|
+
};
|
|
143
|
+
(entry.change === "improved" ? improved : regressed).push(entry);
|
|
144
|
+
}
|
|
145
|
+
for (const [caseId, baseline] of before) {
|
|
146
|
+
if (after.has(caseId))
|
|
147
|
+
continue;
|
|
148
|
+
// A removed case is not an improvement either, and a dataset that shrinks quietly is a gate that weakens
|
|
149
|
+
// quietly.
|
|
150
|
+
removed.push({
|
|
151
|
+
caseId,
|
|
152
|
+
dimension: baseline.dimension,
|
|
153
|
+
change: "removed",
|
|
154
|
+
before: baseline.verdict.score,
|
|
155
|
+
after: null,
|
|
156
|
+
reason: "no longer in the dataset",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
const sorted = (entries) =>
|
|
160
|
+
// Largest movement first, then by id: a report that reshuffles between runs is one nobody diffs.
|
|
161
|
+
[...entries].sort((a, b) => {
|
|
162
|
+
const da = Math.abs((a.after ?? 0) - (a.before ?? 0));
|
|
163
|
+
const db = Math.abs((b.after ?? 0) - (b.before ?? 0));
|
|
164
|
+
return db !== da ? db - da : a.caseId.localeCompare(b.caseId);
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
baseline: input.baseline.run.release,
|
|
168
|
+
candidate: input.candidate.run.release,
|
|
169
|
+
improved: sorted(improved),
|
|
170
|
+
regressed: sorted(regressed),
|
|
171
|
+
added: sorted(added),
|
|
172
|
+
removed: sorted(removed),
|
|
173
|
+
meanScoreDelta: input.candidate.run.meanScore - input.baseline.run.meanScore,
|
|
174
|
+
graderVersionsDiffer: JSON.stringify(sortedEntries(input.baseline.run.graderVersions)) !==
|
|
175
|
+
JSON.stringify(sortedEntries(input.candidate.run.graderVersions)),
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
/** Key order must not decide whether two version maps compare equal. */
|
|
179
|
+
const sortedEntries = (map) => Object.entries(map).sort(([a], [b]) => a.localeCompare(b));
|
|
180
|
+
export * from "./graders.js";
|
|
181
|
+
export * from "./judge.js";
|
|
182
|
+
export * from "./gate.js";
|
|
183
|
+
//# sourceMappingURL=index.js.map
|