@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 +349 -49
- package/dist/agents/index.d.mts +1 -22
- package/dist/evals/index.d.mts +145 -0
- package/dist/evals/index.mjs +419 -0
- package/dist/fevex-Dz6GBz9o.d.mts +32 -0
- package/dist/index-BzxHfyYV.d.mts +23 -0
- package/dist/index-CZGyhNIQ.d.mts +47 -0
- package/dist/index-C_Zpr13f.d.mts +124 -0
- package/dist/index-DGWOGXzc.d.mts +181 -0
- package/dist/index-QTkd6a7C.d.mts +20 -0
- package/dist/index-ojCY3lwm.d.mts +79 -0
- package/dist/index.d.mts +27 -15
- package/dist/index.mjs +1444 -197
- package/dist/model-stream-u2xMsEaf.mjs +251 -0
- package/dist/models/index.d.mts +24 -5
- package/dist/observability/index.d.mts +2 -0
- package/dist/observability/index.mjs +1 -0
- package/dist/policies/index.d.mts +2 -0
- package/dist/policies/index.mjs +6 -0
- package/dist/runtime/index.d.mts +2 -17
- package/dist/runtime/index.mjs +92 -1
- package/dist/testing/index.d.mts +7 -5
- package/dist/testing/index.mjs +172 -11
- package/dist/tools/index.d.mts +3 -31
- package/package.json +15 -3
- package/dist/index-D_RBJmVt.d.mts +0 -44
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
|
|
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
|
-
|
|
22
|
-
|
|
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: '
|
|
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: '
|
|
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: ['
|
|
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
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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.
|
|
173
|
-
`ToolSpec.inputSchema` and `
|
|
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
|
-
|
|
|
221
|
-
|
|
|
222
|
-
|
|
|
223
|
-
|
|
|
224
|
-
|
|
|
225
|
-
|
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
-
|
|
234
|
-
-
|
|
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
|
|
240
|
-
|
|
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
|
|
package/dist/agents/index.d.mts
CHANGED
|
@@ -1,23 +1,2 @@
|
|
|
1
|
-
import { t as
|
|
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 };
|