@fevex/core 0.1.0-alpha.0 → 0.1.0-alpha.1

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 CHANGED
@@ -6,7 +6,7 @@ TypeScript.
6
6
  Fevex gives you the small contracts needed to define agents, tools, model
7
7
  adapters, validation and tests without adopting a heavy runtime.
8
8
 
9
- > **Status:** early MVP. Fevex is usable for local, single-tool agent runs, but
9
+ > **Status:** early MVP. Fevex is usable for bounded local agent runs, but
10
10
  > its API may change before the first stable release.
11
11
 
12
12
  ## Why Fevex
@@ -17,21 +17,25 @@ adapters, validation and tests without adopting a heavy runtime.
17
17
  runtime boundary.
18
18
  - **Small runtime:** one package, ESM, Node.js 20+, no database or service
19
19
  required.
20
- - **Observable:** successful and failed runs emit structured events.
21
- - **Testable:** `fevex/testing` includes a deterministic model and shared model
22
- contract checks.
20
+ - **Observable:** successful and failed runs emit structured events and optional
21
+ local traces.
22
+ - **Testable:** `@fevex/core/testing` includes a deterministic model and shared
23
+ model contract checks; `@fevex/core/evals` adds local regression suites.
23
24
 
24
25
  ## Install
25
26
 
26
27
  ```bash
27
- npm install fevex
28
+ npm install @fevex/core
28
29
  ```
29
30
 
30
31
  For official model adapters:
31
32
 
32
33
  ```bash
33
- npm install fevex @fevex/openai
34
- npm install fevex @fevex/deepseek
34
+ npm install @fevex/core @fevex/openai
35
+ npm install @fevex/core @fevex/deepseek
36
+ npm install @fevex/core @fevex/sqlite
37
+ npm install @fevex/core @fevex/postgres
38
+ npm install @fevex/core @fevex/opentelemetry @opentelemetry/api
35
39
  ```
36
40
 
37
41
  Schemas are optional. The examples use Zod 4, but any Standard
@@ -46,8 +50,8 @@ npm install zod
46
50
  This example executes a complete model-tool-model cycle without network access:
47
51
 
48
52
  ```ts
49
- import { createFevex, defineAgent, defineTool } from 'fevex';
50
- import { fakeModel } from 'fevex/testing';
53
+ import { createFevex, defineAgent, defineTool } from '@fevex/core';
54
+ import { fakeModel } from '@fevex/core/testing';
51
55
  import { z } from 'zod';
52
56
 
53
57
  const accountInput = z.object({
@@ -60,7 +64,7 @@ const accountOutput = z.object({
60
64
  });
61
65
 
62
66
  const getAccount = defineTool({
63
- name: 'accounts.get',
67
+ name: 'accounts_get',
64
68
  description: 'Get an account by ID.',
65
69
  inputSchema: accountInput,
66
70
  outputSchema: accountOutput,
@@ -73,11 +77,11 @@ const model = fakeModel(
73
77
  {
74
78
  toolCalls: [{
75
79
  id: 'call-1',
76
- name: 'accounts.get',
80
+ name: 'accounts_get',
77
81
  input: { accountId: 42 },
78
82
  }],
79
83
  },
80
- { output: 'Account 42 is active.' },
84
+ { output: { answer: 'Account 42 is active.' } },
81
85
  );
82
86
 
83
87
  const app = createFevex({
@@ -85,17 +89,17 @@ const app = createFevex({
85
89
  agents: [defineAgent({
86
90
  name: 'support',
87
91
  instructions: 'Answer account questions clearly.',
88
- tools: ['accounts.get'],
89
- outputSchema: z.string(),
92
+ tools: ['accounts_get'],
93
+ outputSchema: z.object({ answer: z.string() }),
90
94
  })],
91
95
  tools: [getAccount],
92
96
  });
93
97
 
94
- const result = await app.runAgent<string, string>('support', {
98
+ const result = await app.runAgent<string, { answer: string }>('support', {
95
99
  input: 'What is the status of account 42?',
96
100
  });
97
101
 
98
- console.log(result.output);
102
+ console.log(result.output.answer);
99
103
  // Account 42 is active.
100
104
  ```
101
105
 
@@ -107,26 +111,39 @@ console.log(result.output);
107
111
  Real providers connect through the small `ModelGateway` contract:
108
112
 
109
113
  ```ts
110
- import type { ModelGateway } from 'fevex/models';
114
+ import type { ModelGateway } from '@fevex/core/models';
111
115
 
112
116
  export const model: ModelGateway = {
113
- async generate({ messages, tools, outputSchema, signal }) {
114
- const response = await yourProvider.generate({
115
- messages,
116
- tools,
117
- outputSchema,
118
- signal,
119
- });
120
-
121
- return {
122
- output: response.output,
123
- toolCalls: response.toolCalls,
124
- usage: response.usage,
125
- };
117
+ async *stream(input) {
118
+ for await (const event of yourProvider.stream(input)) {
119
+ if (event.type === 'text.delta') {
120
+ yield { type: 'output.delta', delta: event.text };
121
+ } else if (event.type === 'completed') {
122
+ yield {
123
+ type: 'completed',
124
+ result: {
125
+ output: event.output,
126
+ toolCalls: event.toolCalls,
127
+ usage: event.usage,
128
+ providerState: event.providerState,
129
+ },
130
+ };
131
+ }
132
+ }
126
133
  },
127
134
  };
128
135
  ```
129
136
 
137
+ The stream must emit visible text or JSON fragments as `output.delta` followed
138
+ by exactly one `completed` event. Fevex rejects missing or duplicate terminals,
139
+ post-terminal events and a final output that does not match the accumulated
140
+ deltas.
141
+
142
+ `providerState` is opaque, provider-specific continuation data. Fevex carries
143
+ it between model steps within one run and never adds it to agent messages,
144
+ events or `RunResult`. A direct `ModelGateway` consumer must pass it unchanged
145
+ to the next call; it is ephemeral and should not be inspected or persisted.
146
+
130
147
  Provider integrations remain outside the core package. The first official
131
148
  adapter is `@fevex/openai`, authored by Fevex over the native OpenAI HTTP API:
132
149
 
@@ -147,13 +164,35 @@ import { createDeepSeek } from '@fevex/deepseek';
147
164
 
148
165
  const model = createDeepSeek({
149
166
  apiKey: process.env.DEEPSEEK_API_KEY!,
150
- })('deepseek-chat');
167
+ schemaPolicy: 'best-effort',
168
+ })('deepseek-v4-flash');
151
169
  ```
152
170
 
153
171
  Applications can still implement `ModelGateway` directly as shown above.
154
172
 
173
+ Official adapters preserve ordinary model text exactly. JSON-looking text is
174
+ still a string unless `outputSchema` is present; structured output is then
175
+ parsed as JSON and validated locally by Fevex. Tool names must match
176
+ `[A-Za-z0-9_-]{1,64}`.
177
+
178
+ `modelOptions` remains a provider escape hatch, but Fevex-owned request fields
179
+ take precedence: model, messages, tools, tool choice, schema format,
180
+ parallelism and preventive token limits cannot be replaced. Compatible options
181
+ such as temperature, metadata and provider-only reasoning settings are kept.
182
+
155
183
  ## Validation
156
184
 
185
+ `createFevex` validates the complete composition before returning the app,
186
+ including JavaScript values that bypass TypeScript. Invalid registrations throw
187
+ `FevexConfigurationError` with a stable `code` such as `INVALID_TOOL`,
188
+ `DUPLICATE_AGENT` or `MISSING_MODEL`, while preserving a readable message.
189
+
190
+ The app captures a shallow snapshot of registered definitions, tool lists,
191
+ limits, model options and `onEvent`. Mutating those caller-owned containers
192
+ after `createFevex` does not reconfigure a running app. Model gateways, schemas,
193
+ tool functions and nested provider-option values remain cooperative runtime
194
+ references and are not frozen.
195
+
157
196
  Fevex accepts
158
197
  [`StandardSchemaV1`](https://github.com/standard-schema/standard-schema)
159
198
  validators for:
@@ -169,17 +208,30 @@ accepted by these fields.
169
208
 
170
209
  When a model adapter needs schemas, Fevex converts authoring schemas that also
171
210
  implement Standard JSON Schema into JSON Schema 2020-12 before calling
172
- `ModelGateway.generate`. Adapters receive transportable JSON Schema in
173
- `ToolSpec.inputSchema` and `ModelGenerateInput.outputSchema`; they never receive
211
+ `ModelGateway.stream`. Adapters receive transportable JSON Schema in
212
+ `ToolSpec.inputSchema` and `ModelInput.outputSchema`; they never receive
174
213
  the application's executable validators. If a run needs a schema at the model
175
214
  boundary and the schema cannot be converted, Fevex fails before invoking the
176
215
  model with `SCHEMA_NOT_TRANSPORTABLE`.
177
216
 
217
+ Transportable does not mean supported by every provider. Official adapters use
218
+ `schemaPolicy: "strict"` by default and validate their provider's documented
219
+ subset before making an HTTP request. Unsupported schemas fail with
220
+ `PROVIDER_SCHEMA_UNSUPPORTED`. Use `schemaPolicy: "best-effort"` explicitly
221
+ when local validation is sufficient and provider-level schema adherence is not
222
+ required; invalid model output can still fail locally and may require a retry
223
+ owned by the caller.
224
+
225
+ | Boundary | OpenAI strict | DeepSeek strict | Best effort |
226
+ | --- | --- | --- | --- |
227
+ | Tool input | Structured Outputs subset | Strict tools subset on `/beta` | Forwarded without provider guarantee |
228
+ | Final output | Structured Outputs subset | Unsupported | JSON instruction plus local validation |
229
+
178
230
  Use `testModelGateway` to verify a custom model adapter against the shared
179
231
  contract:
180
232
 
181
233
  ```ts
182
- import { testModelGateway } from 'fevex/testing';
234
+ import { testModelGateway } from '@fevex/core/testing';
183
235
 
184
236
  await testModelGateway(model);
185
237
  ```
@@ -204,41 +256,289 @@ The MVP emits:
204
256
 
205
257
  ```text
206
258
  run.started
259
+ model.started
260
+ model.output.delta
207
261
  model.completed
262
+ tool.started
208
263
  tool.completed
209
264
  tool.failed
210
265
  run.completed
211
266
  run.failed
267
+ run.cancelled
268
+ ```
269
+
270
+ `streamAgent` yields each persisted event as it happens. Use
271
+ `model.output.delta` to render output incrementally:
272
+
273
+ ```ts
274
+ for await (const event of app.streamAgent('support', { input: 'Hello' })) {
275
+ if (event.type === 'model.output.delta') {
276
+ process.stdout.write(event.payload.delta);
277
+ }
278
+ }
212
279
  ```
213
280
 
214
281
  Successful runs also return their events in `RunResult.events`.
282
+ Every event has a unique `id` and a run-local `sequence` starting at `1`.
283
+ `model.completed` includes the current step and accumulated usage.
284
+ Tool events include the current step, tool call ID and tool name.
285
+ `run.completed` includes the final JSON output and accumulated usage.
286
+
287
+ `onEvent` is a best-effort observer isolated from execution. Its exceptions do
288
+ not fail the run, and it continues receiving later events. Fevex does not await
289
+ a Promise returned by the observer, but absorbs its rejection to avoid an
290
+ unhandled rejection.
291
+
292
+ Consume the same execution incrementally with `streamAgent`:
293
+
294
+ ```ts
295
+ for await (const event of app.streamAgent('support', {
296
+ input: 'What is the status of account 42?',
297
+ })) {
298
+ console.log(event.type, event.payload);
299
+ }
300
+ ```
301
+
302
+ The stream emits `run.failed` before propagating the original error.
303
+ Cancellation emits `run.cancelled` with a safe `aborted` or `timeout` reason,
304
+ then propagates the original abort reason. `streamAgent` applies backpressure:
305
+ the run advances only while the consumer requests events, and abandoning the
306
+ iterable cancels and persists that local execution.
307
+
308
+ ## Sessions and consultable runs
309
+
310
+ Every run belongs to a session. Omit `sessionId` to create one, then pass the
311
+ returned ID to continue the conversation:
312
+
313
+ ```ts
314
+ const first = await app.runAgent('support', {
315
+ input: 'What is the status of account 42?',
316
+ });
317
+
318
+ const second = await app.runAgent('support', {
319
+ input: 'What should I do next?',
320
+ sessionId: first.sessionId,
321
+ });
322
+ ```
323
+
324
+ Only completed interactions enter session history. Failed and cancelled runs do
325
+ not affect the next prompt. Replace old history with an application-provided
326
+ summary when needed:
327
+
328
+ ```ts
329
+ await app.compactSession(first.sessionId, 'Account 42 is active.');
330
+ ```
331
+
332
+ Use `startAgent` when execution must continue without consuming a stream:
333
+
334
+ ```ts
335
+ const started = await app.startAgent('support', {
336
+ input: 'Check account 42.',
337
+ });
338
+
339
+ const run = await app.getRun(started.id);
340
+ const events = await app.listEvents(started.id);
341
+ const laterEvents = await app.listEvents(started.id, {
342
+ after: events.at(-1)?.id,
343
+ });
344
+
345
+ await app.cancelRun(started.id);
346
+ ```
347
+
348
+ `AgentRun`, `Session` and events are serializable snapshots. `listEvents` orders
349
+ by `sequence`; `after` accepts an event ID from the same run as a stable cursor.
350
+ Unknown cursors fail instead of silently replaying events.
351
+
352
+ `createFevex` uses `InMemoryRunStore` by default. Supply `runStore` to replace
353
+ it. The default survives only while the process lives, and one run at a time
354
+ may update a session.
355
+
356
+ ## Approvals and durable runs
357
+
358
+ Tools can declare risk, approval, idempotency, retries and credential names.
359
+ Approval pauses before credentials are resolved or the effect starts:
360
+
361
+ ```ts
362
+ const charge = defineTool({
363
+ name: 'charge',
364
+ risk: 'sensitive',
365
+ approval: 'required',
366
+ idempotency: 'keyed',
367
+ credentials: ['payments-api-key'],
368
+ retry: { maxAttempts: 3, backoffMs: 200, maxBackoffMs: 2_000 },
369
+ async execute(input, context) {
370
+ const apiKey = await context.getCredential('payments-api-key');
371
+ return chargeCustomer(input, {
372
+ apiKey,
373
+ idempotencyKey: context.idempotencyKey,
374
+ });
375
+ },
376
+ });
377
+ ```
378
+
379
+ `runAgent` throws `RunPausedError`, `streamAgent` emits `run.paused`, and
380
+ `startAgent` leaves the paused snapshot consultable. Continue an approval from
381
+ any runtime sharing the durable store:
382
+
383
+ ```ts
384
+ await app.resumeRun(runId, {
385
+ type: 'approval',
386
+ approvalId,
387
+ decision: 'approve',
388
+ actor: { id: 'reviewer-42' },
389
+ });
390
+ ```
391
+
392
+ Use `InMemoryRunStore` for ephemeral runs, `@fevex/sqlite` for local durability,
393
+ or `@fevex/postgres` across multiple processes:
394
+
395
+ ```ts
396
+ import { createSQLiteRunStore } from '@fevex/sqlite';
397
+
398
+ const runStore = createSQLiteRunStore({
399
+ filename: '.fevex/runs.db',
400
+ });
401
+ ```
402
+
403
+ SQLite creates and migrates its local file automatically. PostgreSQL migrations
404
+ remain explicit and are intended for server deployments.
405
+
406
+ Policies run before approval resolution, credentials and each effect or retry.
407
+ Credential values are not stored or sent to the model, and tool outputs that
408
+ contain a resolved value are rejected.
409
+
410
+ ## Limits
411
+
412
+ Agent runs default to 8 model steps and 16 sequential tool calls. Override
413
+ them on the agent:
414
+
415
+ ```ts
416
+ defineAgent({
417
+ name: 'support',
418
+ instructions: 'Answer account questions clearly.',
419
+ limits: {
420
+ maxSteps: 4,
421
+ maxToolCalls: 6,
422
+ maxInputTokens: 10_000,
423
+ maxOutputTokens: 2_000,
424
+ },
425
+ });
426
+ ```
427
+
428
+ Input token limits are cumulative run budgets checked from provider usage after
429
+ each model response. Output token limits are also sent to model gateways as the
430
+ remaining preventive cap, then verified from reported usage. A configured token
431
+ limit fails explicitly when the gateway does not report the corresponding usage
432
+ field. Set a token limit to `false` to leave it unlimited.
433
+
434
+ Cancellation is immediate from the caller's perspective. Models, tools and
435
+ schema validators still need to honor the supplied `AbortSignal` to stop their
436
+ underlying work. External systems must enforce the supplied idempotency key to
437
+ make keyed retries safe. Repeated tool call IDs are rejected within a run before
438
+ Fevex repeats an effect.
439
+
440
+ ## Observability
441
+
442
+ Add trace exporters to the application configuration; agent definitions stay
443
+ unchanged:
444
+
445
+ ```ts
446
+ import { createOpenTelemetryExporter } from '@fevex/opentelemetry';
447
+
448
+ const app = createFevex({
449
+ models,
450
+ agents,
451
+ tools,
452
+ observability: {
453
+ exporters: [createOpenTelemetryExporter()],
454
+ calculateCost({ usage }) {
455
+ return {
456
+ amount: (usage.totalTokens ?? 0) * 0.000_001,
457
+ currency: 'USD',
458
+ };
459
+ },
460
+ },
461
+ });
462
+
463
+ await app.runAgent('support', { input: 'Hello' });
464
+ await app.flushObservability();
465
+ ```
466
+
467
+ Traces contain only metadata by default. Output and safe error capture require
468
+ an explicit `content.include` policy; sensitive object keys are redacted before
469
+ an optional application redactor runs. Exporters run outside the run path: their
470
+ errors never change the run result, and `flushObservability()` reports pending
471
+ export failures. OpenTelemetry SDK setup, OTLP export and shutdown remain
472
+ application responsibilities.
473
+
474
+ ## Evaluations
475
+
476
+ `@fevex/core/evals` runs local datasets sequentially with fresh sessions:
477
+
478
+ ```ts
479
+ import {
480
+ exactOutputScorer,
481
+ runEvaluation,
482
+ toolSelectionScorer,
483
+ } from '@fevex/core/evals';
484
+
485
+ const report = await runEvaluation({
486
+ app,
487
+ suiteName: 'support-regression',
488
+ targetVersion: 'v2',
489
+ agentName: 'support',
490
+ dataset: {
491
+ name: 'support',
492
+ version: '1',
493
+ cases: [{
494
+ id: 'active-account',
495
+ input: 'Check account 42',
496
+ expected: {
497
+ output: { answer: 'Account 42 is active.' },
498
+ tools: ['accounts_get'],
499
+ },
500
+ }],
501
+ },
502
+ scorers: [exactOutputScorer(), toolSelectionScorer()],
503
+ });
504
+ ```
505
+
506
+ Built-in deterministic scorers cover exact output, ordered or unordered tool
507
+ selection, forbidden tools, max latency, max tokens and max cost. Use
508
+ `serializeEvalReport` for a canonical, versionable JSON baseline, `jsonReporter`
509
+ or `textReporter` with your own write callback, and `compareEvaluationReports`
510
+ to fail CI when cases disappear, passing scores fail or scores drop beyond the
511
+ configured tolerance.
215
512
 
216
513
  ## Public Exports
217
514
 
218
515
  | Import | Purpose |
219
516
  | --- | --- |
220
- | `fevex` | Common runtime, definitions and contracts |
221
- | `fevex/agents` | Agent definitions |
222
- | `fevex/models` | Provider-neutral model contracts |
223
- | `fevex/tools` | Tool definitions and execution context |
224
- | `fevex/runtime` | Run request and result contracts |
225
- | `fevex/testing` | Deterministic testing helpers |
226
-
227
- All subpaths ship in the same `fevex` package.
517
+ | `@fevex/core` | Common runtime, definitions and contracts |
518
+ | `@fevex/core/agents` | Agent definitions |
519
+ | `@fevex/core/models` | Provider-neutral model contracts |
520
+ | `@fevex/core/tools` | Tool definitions and execution context |
521
+ | `@fevex/core/runtime` | Runs, sessions and store contracts |
522
+ | `@fevex/core/policies` | Authorization policy contracts |
523
+ | `@fevex/core/observability` | Trace, redaction and cost contracts |
524
+ | `@fevex/core/evals` | Datasets, scorers, reporters and regressions |
525
+ | `@fevex/core/testing` | Deterministic testing helpers |
526
+
527
+ All subpaths ship in the same `@fevex/core` package.
228
528
 
229
529
  ## Current Scope
230
530
 
231
531
  The MVP intentionally supports:
232
532
 
233
- - one model response, or one model-tool-model cycle;
234
- - an in-memory runtime;
235
- - synchronous event observers;
533
+ - bounded multi-step model-tool loops with sequential tool execution;
534
+ - native provider streaming and durable, consultable runs;
535
+ - synchronous event observers and `AsyncIterable` event streaming;
536
+ - local traces, OpenTelemetry export and deterministic evaluation suites;
236
537
  - optional Standard Schema validation;
237
- - `AbortSignal` cancellation.
538
+ - immediate `AbortSignal` cancellation with cooperative underlying work.
238
539
 
239
- It does not yet include streaming, retries, persistence, approvals, a CLI or
240
- official provider adapters. `AgentDefinition.limits` is reserved and has no
241
- runtime effect yet.
540
+ It does not include a CLI, hosted observability service, model-judge evals or
541
+ human evaluation workflows.
242
542
 
243
543
  ## License
244
544
 
@@ -1,23 +1,2 @@
1
- import { t as ExactDefinition } from "../exact-definition-NHBBFlFR.mjs";
2
- import { ModelRef, ReasoningEffort } from "../models/index.mjs";
3
- import { StandardSchemaV1 } from "@standard-schema/spec";
4
- //#region src/agents/index.d.ts
5
- interface AgentLimits {
6
- maxSteps?: number;
7
- maxToolCalls?: number;
8
- maxInputTokens?: number | false;
9
- maxOutputTokens?: number | false;
10
- }
11
- interface AgentDefinition<TOutput = unknown> {
12
- name: string;
13
- instructions: string;
14
- model?: ModelRef;
15
- tools?: string[];
16
- reasoning?: ReasoningEffort;
17
- modelOptions?: Record<string, unknown>;
18
- outputSchema?: StandardSchemaV1<unknown, TOutput>;
19
- limits?: AgentLimits;
20
- }
21
- declare function defineAgent<T extends AgentDefinition>(agent: ExactDefinition<T, AgentDefinition>): T;
22
- //#endregion
1
+ import { n as AgentLimits, r as defineAgent, t as AgentDefinition } from "../index-BzxHfyYV.mjs";
23
2
  export { AgentDefinition, AgentLimits, defineAgent };
@@ -0,0 +1,145 @@
1
+ import { l as JsonValue, o as ExecutionContext, t as AgentEvent } from "../index-C_Zpr13f.mjs";
2
+ import { ModelMetadata, ModelUsage } from "../models/index.mjs";
3
+ import { l as TraceCost, t as CostCalculator } from "../index-ojCY3lwm.mjs";
4
+ import { t as Fevex } from "../fevex-Dz6GBz9o.mjs";
5
+ //#region src/evals/index.d.ts
6
+ interface EvalExpectation {
7
+ output?: JsonValue;
8
+ tools?: string[];
9
+ }
10
+ interface EvalCase<TInput = unknown> {
11
+ id: string;
12
+ input: TInput;
13
+ expected?: EvalExpectation;
14
+ context?: ExecutionContext;
15
+ }
16
+ interface EvalDataset<TInput = unknown> {
17
+ name: string;
18
+ version: string;
19
+ cases: readonly EvalCase<TInput>[];
20
+ }
21
+ interface EvalScorerContext<TInput = unknown> {
22
+ testCase: EvalCase<TInput>;
23
+ output?: JsonValue;
24
+ tools: readonly string[];
25
+ events: readonly AgentEvent[];
26
+ usage?: ModelUsage;
27
+ cost?: TraceCost;
28
+ latencyMs: number;
29
+ error?: string;
30
+ }
31
+ type EvalScorerResult = {
32
+ passed: boolean;
33
+ score: number;
34
+ details?: JsonValue;
35
+ } | {
36
+ skipped: true;
37
+ reason?: string;
38
+ };
39
+ interface EvalScorer<TInput = unknown> {
40
+ id: string;
41
+ score(context: EvalScorerContext<TInput>): EvalScorerResult | Promise<EvalScorerResult>;
42
+ }
43
+ type EvalScoreStatus = 'passed' | 'failed' | 'skipped' | 'error';
44
+ interface EvalScore {
45
+ scorerId: string;
46
+ status: EvalScoreStatus;
47
+ score?: number;
48
+ details?: JsonValue;
49
+ error?: string;
50
+ }
51
+ interface EvalCaseReport {
52
+ caseId: string;
53
+ status: 'completed' | 'failed';
54
+ output?: JsonValue;
55
+ tools: string[];
56
+ usage?: ModelUsage;
57
+ cost?: TraceCost;
58
+ latencyMs: number;
59
+ scores: EvalScore[];
60
+ error?: string;
61
+ }
62
+ interface EvalSummary {
63
+ caseCount: number;
64
+ completedCount: number;
65
+ failedCount: number;
66
+ passedScoreCount: number;
67
+ failedScoreCount: number;
68
+ skippedScoreCount: number;
69
+ errorScoreCount: number;
70
+ averageScore?: number;
71
+ usage?: ModelUsage;
72
+ cost?: TraceCost;
73
+ latencyMs: number;
74
+ }
75
+ interface EvalReport {
76
+ schemaVersion: 'fevex.eval.v1';
77
+ suiteName: string;
78
+ targetVersion: string;
79
+ agentName: string;
80
+ dataset: {
81
+ name: string;
82
+ version: string;
83
+ };
84
+ scorerIds: string[];
85
+ cases: EvalCaseReport[];
86
+ summary: EvalSummary;
87
+ }
88
+ interface EvalReporter {
89
+ report(report: Readonly<EvalReport>): void | Promise<void>;
90
+ }
91
+ interface EvalCostOptions {
92
+ calculate: CostCalculator;
93
+ modelRegistryName: string;
94
+ model?: ModelMetadata;
95
+ }
96
+ interface RunEvaluationOptions<TInput = unknown> {
97
+ app: Fevex;
98
+ suiteName: string;
99
+ targetVersion: string;
100
+ agentName: string;
101
+ dataset: EvalDataset<TInput>;
102
+ scorers: readonly EvalScorer<TInput>[];
103
+ reporters?: readonly EvalReporter[];
104
+ cost?: EvalCostOptions;
105
+ signal?: AbortSignal;
106
+ }
107
+ interface Regression {
108
+ kind: 'case_removed' | 'case_failed' | 'scorer_removed' | 'score_failed' | 'score_dropped' | 'latency_increased' | 'cost_increased';
109
+ caseId: string;
110
+ scorerId?: string;
111
+ message: string;
112
+ }
113
+ interface RegressionReport {
114
+ passed: boolean;
115
+ baselineTargetVersion: string;
116
+ currentTargetVersion: string;
117
+ regressions: Regression[];
118
+ }
119
+ interface RegressionOptions {
120
+ maxScoreDrop?: number;
121
+ maxLatencyIncreaseRatio?: number;
122
+ maxCostIncreaseRatio?: number;
123
+ }
124
+ declare function runEvaluation<TInput = unknown>(options: RunEvaluationOptions<TInput>): Promise<EvalReport>;
125
+ declare function exactOutputScorer(id?: string): EvalScorer;
126
+ declare function toolSelectionScorer(options?: {
127
+ id?: string;
128
+ ordered?: boolean;
129
+ }): EvalScorer;
130
+ declare function forbiddenToolsScorer(tools: readonly string[], id?: string): EvalScorer;
131
+ declare function maxLatencyScorer(maxMs: number, id?: string): EvalScorer;
132
+ declare function maxTokensScorer(limits: {
133
+ input?: number;
134
+ output?: number;
135
+ total?: number;
136
+ }, id?: string): EvalScorer;
137
+ declare function maxCostScorer(maxAmount: number, currency: string, id?: string): EvalScorer;
138
+ declare function serializeCanonical(value: unknown): string;
139
+ declare function serializeEvalReport(report: EvalReport): string;
140
+ declare function parseEvalReport(json: string): EvalReport;
141
+ declare function jsonReporter(write: (output: string) => void | Promise<void>): EvalReporter;
142
+ declare function textReporter(write: (output: string) => void | Promise<void>): EvalReporter;
143
+ declare function compareEvaluationReports(baseline: EvalReport, current: EvalReport, options?: RegressionOptions): RegressionReport;
144
+ //#endregion
145
+ export { EvalCase, EvalCaseReport, EvalCostOptions, EvalDataset, EvalExpectation, EvalReport, EvalReporter, EvalScore, EvalScoreStatus, EvalScorer, EvalScorerContext, EvalScorerResult, EvalSummary, Regression, RegressionOptions, RegressionReport, RunEvaluationOptions, compareEvaluationReports, exactOutputScorer, forbiddenToolsScorer, jsonReporter, maxCostScorer, maxLatencyScorer, maxTokensScorer, parseEvalReport, runEvaluation, serializeCanonical, serializeEvalReport, textReporter, toolSelectionScorer };