nexus-ai-pro 2.0.0 → 2.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/API_STABILITY.md +27 -0
- package/CHANGELOG.md +123 -1
- package/MIGRATING.md +1 -1
- package/README.md +8 -4
- package/dist/agent/create-agent.d.ts +9 -1
- package/dist/agent/create-agent.js +40 -1
- package/dist/cli/graph.d.ts +9 -0
- package/dist/cli/graph.js +47 -0
- package/dist/cli.js +9 -1
- package/dist/core/nexus.d.ts +6 -2
- package/dist/core/nexus.js +3 -1
- package/dist/evals/judge.d.ts +11 -0
- package/dist/evals/judge.js +33 -0
- package/dist/evaluate/index.d.ts +1 -0
- package/dist/evaluate/index.js +1 -0
- package/dist/evaluate/provenance.d.ts +22 -0
- package/dist/evaluate/provenance.js +10 -0
- package/dist/evaluate/run.js +20 -4
- package/dist/graph/durability.d.ts +27 -0
- package/dist/graph/durability.js +66 -0
- package/dist/graph/errors.d.ts +30 -4
- package/dist/graph/errors.js +18 -2
- package/dist/graph/event-stream.d.ts +75 -0
- package/dist/graph/event-stream.js +289 -0
- package/dist/graph/graph.d.ts +41 -3
- package/dist/graph/graph.js +270 -40
- package/dist/graph/index.d.ts +4 -2
- package/dist/graph/index.js +3 -1
- package/dist/graph/lint.d.ts +57 -0
- package/dist/graph/lint.js +141 -0
- package/dist/graph/run-control.d.ts +28 -0
- package/dist/graph/run-control.js +26 -0
- package/dist/operations/runner.js +13 -15
- package/dist/ops/health.d.ts +24 -0
- package/dist/ops/health.js +38 -10
- package/dist/ops/index.d.ts +1 -1
- package/dist/ops/index.js +1 -1
- package/dist/ops/rate-limit-adapters.d.ts +23 -0
- package/dist/ops/rate-limit-adapters.js +34 -0
- package/dist/ops/rate-limiter.d.ts +12 -2
- package/dist/ops/rate-limiter.js +29 -0
- package/dist/server/assistant.d.ts +40 -0
- package/dist/server/assistant.js +31 -0
- package/dist/server/runs.d.ts +4 -0
- package/dist/server/runs.js +21 -1
- package/dist/testing/record.d.ts +42 -0
- package/dist/testing/record.js +57 -5
- package/dist/tracing/index.d.ts +3 -3
- package/dist/tracing/index.js +2 -2
- package/dist/tracing/instrument.js +28 -0
- package/dist/tracing/otlp.d.ts +91 -0
- package/dist/tracing/otlp.js +124 -0
- package/dist/tracing/rollups.d.ts +100 -0
- package/dist/tracing/rollups.js +139 -0
- package/dist/tracing/stores.d.ts +17 -0
- package/dist/tracing/stores.js +23 -0
- package/dist/tracing/tracer.d.ts +50 -4
- package/dist/tracing/tracer.js +85 -6
- package/dist/types/config.d.ts +9 -0
- package/dist/types/evaluate.d.ts +37 -0
- package/dist/types/graph.d.ts +343 -8
- package/dist/types/graph.js +3 -1
- package/dist/types/operations.d.ts +16 -2
- package/dist/types/server.d.ts +9 -0
- package/dist/types/tracing.d.ts +11 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +1 -0
- package/dist-cjs/agent/create-agent.d.ts +9 -1
- package/dist-cjs/agent/create-agent.js +40 -1
- package/dist-cjs/cli/graph.d.ts +9 -0
- package/dist-cjs/cli/graph.js +50 -0
- package/dist-cjs/core/nexus.d.ts +6 -2
- package/dist-cjs/core/nexus.js +3 -1
- package/dist-cjs/evals/judge.d.ts +11 -0
- package/dist-cjs/evals/judge.js +33 -0
- package/dist-cjs/evaluate/index.d.ts +1 -0
- package/dist-cjs/evaluate/index.js +3 -1
- package/dist-cjs/evaluate/provenance.d.ts +22 -0
- package/dist-cjs/evaluate/provenance.js +14 -0
- package/dist-cjs/evaluate/run.js +20 -4
- package/dist-cjs/graph/durability.d.ts +27 -0
- package/dist-cjs/graph/durability.js +70 -0
- package/dist-cjs/graph/errors.d.ts +30 -4
- package/dist-cjs/graph/errors.js +20 -3
- package/dist-cjs/graph/event-stream.d.ts +75 -0
- package/dist-cjs/graph/event-stream.js +295 -0
- package/dist-cjs/graph/graph.d.ts +41 -3
- package/dist-cjs/graph/graph.js +269 -39
- package/dist-cjs/graph/index.d.ts +4 -2
- package/dist-cjs/graph/index.js +7 -1
- package/dist-cjs/graph/lint.d.ts +57 -0
- package/dist-cjs/graph/lint.js +144 -0
- package/dist-cjs/graph/run-control.d.ts +28 -0
- package/dist-cjs/graph/run-control.js +30 -0
- package/dist-cjs/operations/runner.js +13 -15
- package/dist-cjs/ops/health.d.ts +24 -0
- package/dist-cjs/ops/health.js +38 -10
- package/dist-cjs/ops/index.d.ts +1 -1
- package/dist-cjs/ops/rate-limit-adapters.d.ts +23 -0
- package/dist-cjs/ops/rate-limit-adapters.js +35 -1
- package/dist-cjs/ops/rate-limiter.d.ts +12 -2
- package/dist-cjs/ops/rate-limiter.js +30 -0
- package/dist-cjs/server/assistant.d.ts +40 -0
- package/dist-cjs/server/assistant.js +31 -0
- package/dist-cjs/server/runs.d.ts +4 -0
- package/dist-cjs/server/runs.js +21 -1
- package/dist-cjs/testing/record.d.ts +42 -0
- package/dist-cjs/testing/record.js +57 -5
- package/dist-cjs/tracing/index.d.ts +3 -3
- package/dist-cjs/tracing/index.js +4 -1
- package/dist-cjs/tracing/instrument.js +28 -0
- package/dist-cjs/tracing/otlp.d.ts +91 -0
- package/dist-cjs/tracing/otlp.js +129 -0
- package/dist-cjs/tracing/rollups.d.ts +100 -0
- package/dist-cjs/tracing/rollups.js +147 -0
- package/dist-cjs/tracing/stores.d.ts +17 -0
- package/dist-cjs/tracing/stores.js +24 -0
- package/dist-cjs/tracing/tracer.d.ts +50 -4
- package/dist-cjs/tracing/tracer.js +87 -6
- package/dist-cjs/types/config.d.ts +9 -0
- package/dist-cjs/types/evaluate.d.ts +37 -0
- package/dist-cjs/types/graph.d.ts +343 -8
- package/dist-cjs/types/graph.js +3 -1
- package/dist-cjs/types/operations.d.ts +16 -2
- package/dist-cjs/types/server.d.ts +9 -0
- package/dist-cjs/types/tracing.d.ts +11 -0
- package/dist-cjs/version.d.ts +5 -0
- package/dist-cjs/version.js +4 -0
- package/llms.txt +2 -2
- package/package.json +40 -1
package/API_STABILITY.md
CHANGED
|
@@ -41,6 +41,33 @@ Both are covered by this policy:
|
|
|
41
41
|
Deep imports into `dist`, `dist-cjs`, or `src` are not supported. Import only from `nexus-ai-pro` or
|
|
42
42
|
one of its explicit subpaths.
|
|
43
43
|
|
|
44
|
+
## 2.1 stage (2.1.0)
|
|
45
|
+
|
|
46
|
+
Everything 2.1 adds is stable from the start and additive: no default changes meaning.
|
|
47
|
+
|
|
48
|
+
- graph durability modes, `flush()`, `maxPendingWrites`, `onError` with `NodeFailure` and
|
|
49
|
+
`RecoveredFailure`, `nodeDefaults`, `NodeTimeout`, `context.heartbeat()`, `SendOptions`,
|
|
50
|
+
`RunControl` and `RunControlLike`, and `GraphDrainedError` (`nexus-ai-pro/graph`);
|
|
51
|
+
- `lintGraph()` and its finding types (`nexus-ai-pro/graph/lint`). New rules may be added in a minor
|
|
52
|
+
release; a rule's code and severity do not change within 2.x;
|
|
53
|
+
- `OperationContext.previousHeartbeat` and `OperationRecord.heartbeatDetails`;
|
|
54
|
+
- `AssistantRunContext.control`;
|
|
55
|
+
- the graph event stream: `events()`, `resumeEvents()`, `continueEvents()`, `GraphEventStream`, its
|
|
56
|
+
event and option types, `GraphStreamOverflowError`, `context.message()` and `context.tool()`, and
|
|
57
|
+
`subgraphEvents`. New projections may be added in a minor release;
|
|
58
|
+
- `TracerOptions.incremental` and `exporters`, `TraceExporter`, `traceparent` in and out, `flush()`,
|
|
59
|
+
`closeAbandonedRuns()`, `w3cTraceId()` and `w3cSpanId()` (`nexus-ai-pro/tracing`);
|
|
60
|
+
- `OtlpTraceExporter` and `runToOtlpSpan()` (`nexus-ai-pro/tracing/otlp`). Attributes may be added as
|
|
61
|
+
the GenAI conventions settle; the ones listed in the tracing guide stay;
|
|
62
|
+
- rollups (`nexus-ai-pro/tracing/rollups`), timed replay (`timing`, `pace`), evaluator provenance
|
|
63
|
+
(`withProvenance()`, `Experiment.evaluators` and `framework`, `LLMJudge.asEvaluator()`),
|
|
64
|
+
`createAgent({ streamTokens })`, and `graphAssistant(graph, { events })`;
|
|
65
|
+
- `rateLimit.algorithm` and `burst`, `RateLimitStore.gcra()`, `RateLimitDecision`, and `gcraDecide()`;
|
|
66
|
+
`health.observationTtlMs`, `ProviderHealthStatus`, a snapshot's `status` and `stale`, and
|
|
67
|
+
`checkProviders({ staleOnly })`.
|
|
68
|
+
|
|
69
|
+
The studio's `rollups` source follows the studio's experimental label.
|
|
70
|
+
|
|
44
71
|
## 2.0 stage (2.0.0)
|
|
45
72
|
|
|
46
73
|
2.0 removes what 1.x deprecated and promotes the surfaces that have settled.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,127 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.1.0] - 2026-10-04
|
|
8
|
+
|
|
9
|
+
Fault-tolerant graphs and crash-safe observability. A graph now chooses when its checkpoints are
|
|
10
|
+
written, recovers from a node whose retries ran out, tells a hung task from a slow one, and stops
|
|
11
|
+
cleanly when its worker has to go. Traces survive the process that wrote them and reach any
|
|
12
|
+
OpenTelemetry backend, every consumer of a run reads one event stream, and dashboards read hourly
|
|
13
|
+
totals instead of every trace. Nothing changes until it is opted into: the defaults keep their 2.0
|
|
14
|
+
meaning.
|
|
15
|
+
|
|
16
|
+
Two pieces planned for 2.3 come early: a rate limiter with no window edge to burst across, and
|
|
17
|
+
provider health that expires.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **Durability modes.** `compile({ durability })` takes `sync` (the default), `async`, or `exit`.
|
|
22
|
+
`async` writes checkpoints in the background, in order, while the next superstep runs, and waits
|
|
23
|
+
for them before the run returns, pauses, fails, or drains. `maxPendingWrites` (8) bounds the writes
|
|
24
|
+
in flight. `exit` writes only where the run stops. `flush()` waits for every run's writes.
|
|
25
|
+
- **Recovery after retries.** A node's `onError` receives a `NodeFailure` once its retries run out
|
|
26
|
+
and returns an update or a `Command`, such as a route to a refund node. The decision is
|
|
27
|
+
checkpointed, as `recovered` on the next checkpoint, before anything it routes to runs, so a crash
|
|
28
|
+
after it resumes into the same path. `compile({ nodeDefaults })` sets retries, timeouts, and
|
|
29
|
+
`onError` for every node.
|
|
30
|
+
- **Run and idle timeouts.** `timeout: { runMs, idleMs }` caps an attempt and the time between signs
|
|
31
|
+
of progress. `context.heartbeat()`, `emit()`, and `report()` refresh the idle timer.
|
|
32
|
+
`GraphNodeTimeoutError.kind` says which limit fired. A `Send` can override the timeout of the task
|
|
33
|
+
it creates, and the override is checkpointed with the task.
|
|
34
|
+
- **Cooperative drain.** `RunControl`, passed as `control` to a run, stops it after its current
|
|
35
|
+
superstep with its checkpoint written, ending in `GraphDrainedError`; `continue()` resumes it
|
|
36
|
+
anywhere. The agent server drains its graph runs this way when a replica drains with a queue, so
|
|
37
|
+
they are handed off at once instead of after the drain timeout.
|
|
38
|
+
- **`lintGraph()`** on the new `nexus-ai-pro/graph/lint`, and `nexus graph lint`: static cycles,
|
|
39
|
+
retried nodes that are not `idempotent` or have no timeout, deferred durability with side effects,
|
|
40
|
+
an in-process checkpointer under a deployment, unbounded concurrency, and unchecked routes. Nodes
|
|
41
|
+
can declare `idempotent: true`, and `describe()` reports durability, the checkpointer, and each
|
|
42
|
+
node's retries, timeouts, and recovery.
|
|
43
|
+
- **Progress that survives a retry.** An operation's `context.heartbeat(details)` stores `details` as
|
|
44
|
+
`heartbeatDetails`, and the next attempt reads them as `context.previousHeartbeat`.
|
|
45
|
+
- A `task_failed` graph event, and a semantics table in the graph guide that says what each
|
|
46
|
+
execution primitive repeats after a crash.
|
|
47
|
+
- **Crash-safe traces.** `new Tracer({ incremental: true })` writes each run when it starts and when it
|
|
48
|
+
ends, so a crashed run stays in the store with every run beneath it that finished, and a live run
|
|
49
|
+
can be watched. Sampling is decided at the root's start, and an unsampled trace is still kept whole
|
|
50
|
+
when tail sampling wants it. `closeAbandonedRuns()` closes what a dead process left running, as a
|
|
51
|
+
`RunAbandoned` error. `tracer.flush()` waits for writes and exports.
|
|
52
|
+
- **Traces across processes.** `handle.traceparent()` and `StartRunOptions.traceparent` carry a trace
|
|
53
|
+
through a W3C `traceparent` header: an operation's `traceContext`, a remote call, any OpenTelemetry
|
|
54
|
+
service. `w3cTraceId()` and `w3cSpanId()` give the ids every process derives alike.
|
|
55
|
+
- **OpenTelemetry export**, on the new `nexus-ai-pro/tracing/otlp`. `TracerOptions.exporters` takes any
|
|
56
|
+
`TraceExporter`, and `OtlpTraceExporter` sends runs to a collector as OTLP/HTTP JSON with no SDK:
|
|
57
|
+
model calls as `chat <model>` client spans with the GenAI semantic conventions, tools, agents, and
|
|
58
|
+
graph nodes with theirs, and Nexus-only facts under `nexus.*`. `runToOtlpSpan()` is the conversion.
|
|
59
|
+
- **One event stream.** `graph.events()`, `resumeEvents()`, and `continueEvents()` run a graph as
|
|
60
|
+
typed events with projections — `values`, `updates`, `messages`, `tools`, `tasks`, `checkpoints`,
|
|
61
|
+
`custom` — read together or one at a time through `messages()`, `tools()`, `values()`, and
|
|
62
|
+
`updates()`, with `result` for the outcome. Nodes stream model output with `context.message()`
|
|
63
|
+
and report tool calls with `context.tool()`. Subgraph events come through with a `namespace` when
|
|
64
|
+
asked, and `GraphRunOptions.subgraphEvents` does the same for `onEvent`.
|
|
65
|
+
- **Bounded readers.** Every reader of a stream has its own buffer (`maxBuffered`, 1,000), and the run
|
|
66
|
+
waits for the slowest at each superstep. Within a step, `overflow` coalesces model chunks and state
|
|
67
|
+
snapshots (the default), drops the oldest or newest, or fails with `GraphStreamOverflowError`.
|
|
68
|
+
`streamStats()` reports the peak and what was merged or dropped.
|
|
69
|
+
- **Agents stream.** `createAgent()` reports its model's output and every tool call on the stream,
|
|
70
|
+
token by token with `streamTokens` and a client with `stream()`. `traceGraph()` records the tool
|
|
71
|
+
calls as `tool` runs inside their node. `graphAssistant(graph, { events })` records chosen
|
|
72
|
+
projections on each server run.
|
|
73
|
+
- **Rollups**, on the new `nexus-ai-pro/tracing/rollups`: `rollupTraceStore()` adds every finished run
|
|
74
|
+
to hourly rows per kind, model, provider, and tenant, in `MemoryRollupStore` or any `RollupStore`,
|
|
75
|
+
with latency buckets for `rollupPercentile()` and `sumRollups()` to total them. The studio's costs
|
|
76
|
+
view reads them when given `rollups`.
|
|
77
|
+
- **Timed replay.** `recordingFetch({ timing: true })` keeps each chunk of a response and when it came,
|
|
78
|
+
and `replayFetch({ pace })` replays it at once, at the original pace, scaled, or released chunk by
|
|
79
|
+
chunk by a function.
|
|
80
|
+
- **GCRA rate limiting.** `rateLimit.algorithm: 'gcra'` spreads calls at `maxRequests` per `windowMs`
|
|
81
|
+
with at most `burst` at once, so a burst across a window edge can no longer pass twice the limit.
|
|
82
|
+
Both stores decide it through `gcra()`; `RedisRateLimitStore` runs it as one Lua script on Redis's
|
|
83
|
+
clock. `gcraDecide()` is the arithmetic, for a store of your own.
|
|
84
|
+
- **Health that expires.** `health.observationTtlMs` makes a provider with no recent calls `unknown`,
|
|
85
|
+
routed to as one never seen instead of kept down by an old failure or up by an old success.
|
|
86
|
+
Snapshots carry a `status` (`healthy`, `degraded`, `unhealthy`, `unknown`) and `stale`, and
|
|
87
|
+
`ai.checkProviders({ staleOnly: true })` refreshes the unknown ones.
|
|
88
|
+
- **Evaluator provenance.** An experiment records each evaluator's position, name, the score keys it
|
|
89
|
+
produced, and what `withProvenance()` declared — version, judge model, prompt version, temperature,
|
|
90
|
+
and rubric — and the framework version that ran it. `LLMJudge.asEvaluator()` brings the judge to
|
|
91
|
+
`evaluate()`, declaring its own provenance.
|
|
92
|
+
- A weekly `live-providers` workflow runs real conformance against every provider with a key in the
|
|
93
|
+
repository's secrets, DeepSeek included, and opens an issue when one breaks.
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
- **A manual operation heartbeat no longer strands the result.** `context.heartbeat()` wrote the
|
|
98
|
+
record behind the runner's back, so the runner's final write failed its sequence check and a
|
|
99
|
+
successful result was not saved. Heartbeats now go through the runner, one at a time.
|
|
100
|
+
- `continue()` on a finished thread left it marked `running` with an empty result. It now leaves it
|
|
101
|
+
completed and returns its state.
|
|
102
|
+
- A run started under a parent from another tracer, or another process, was never written: its trace
|
|
103
|
+
waited for a root that was not there. It is now written as its trace's root in this process.
|
|
104
|
+
|
|
105
|
+
### Proof
|
|
106
|
+
|
|
107
|
+
- A crash-injection suite kills a run before, during, and after every checkpoint write and inside
|
|
108
|
+
nodes, before and after their side effects, in `sync` and `async` mode, on the memory, operation
|
|
109
|
+
store, SQLite, Postgres, and Redis checkpointers. A fresh process always reaches the state of an
|
|
110
|
+
uninterrupted run.
|
|
111
|
+
- A saga test: a charge exhausts its retries, `onError` routes to a refund, the process dies during
|
|
112
|
+
the refund, and the resumed run refunds without charging again.
|
|
113
|
+
- A property test generates 40 graphs with branches, loops, `Send`, retries, and interrupts, and
|
|
114
|
+
replays each from every checkpoint of its history to the same state.
|
|
115
|
+
- The graph benchmark runs 50 supersteps against an 8 ms store: `async` finishes in about half the
|
|
116
|
+
time of `sync`, in the same state, and CI fails if it stops beating it by a quarter.
|
|
117
|
+
- A long agent's process dies at its fourteenth step: a second process reads every finished step from
|
|
118
|
+
the same JSONL store, and `closeAbandonedRuns()` closes the rest.
|
|
119
|
+
- A server and a worker each export to a local OTLP collector, and the collector receives one trace,
|
|
120
|
+
the worker's run under the server's, with the GenAI attributes on the model call.
|
|
121
|
+
- A reader a hundred times slower than a node that streams 100,000 chunks never holds more than its
|
|
122
|
+
buffer, under every overflow policy, and coalescing loses no text.
|
|
123
|
+
- The studio's costs view answers over a month of a million runs, held as rollups, in under 200 ms,
|
|
124
|
+
reading the traces once.
|
|
125
|
+
- In the two milliseconds around a window's edge, fixed windows pass 199 calls of a 100-call limit and
|
|
126
|
+
GCRA passes 100. A provider down on Monday and idle since is `unknown`, and routed to, on Wednesday.
|
|
127
|
+
|
|
7
128
|
## [2.0.0] - 2026-10-02
|
|
8
129
|
|
|
9
130
|
Consolidation. One lifecycle runs every call of every family, the root import holds the core client
|
|
@@ -1373,7 +1494,8 @@ shared policy vocabulary is in place for a future release that revisits this.
|
|
|
1373
1494
|
- URL fetching rejects unsafe private-network targets and limits response reads.
|
|
1374
1495
|
- CLI and audit findings no longer disclose detected secret values.
|
|
1375
1496
|
|
|
1376
|
-
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.
|
|
1497
|
+
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.1.0...HEAD
|
|
1498
|
+
[2.1.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.0.0...v2.1.0
|
|
1377
1499
|
[2.0.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.25.0...v2.0.0
|
|
1378
1500
|
[1.25.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.24.0...v1.25.0
|
|
1379
1501
|
[1.24.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.23.0...v1.24.0
|
package/MIGRATING.md
CHANGED
|
@@ -69,7 +69,7 @@ It cannot split a namespace import (`import * as nexus from 'nexus-ai-pro'`) or
|
|
|
69
69
|
so it lists them. Import each name you use from its subpath instead. Every subpath, and what it costs,
|
|
70
70
|
is in the [packaging guide](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md).
|
|
71
71
|
|
|
72
|
-
The root import now costs about
|
|
72
|
+
The root import now costs about 325 KB, down from 572 KB.
|
|
73
73
|
|
|
74
74
|
## Validators and Node types are optional
|
|
75
75
|
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ team can share.
|
|
|
8
8
|
Import only what you use. The root import is the client and nothing else, and every other capability
|
|
9
9
|
has its own entry point with a size budget CI enforces; [the packaging guide](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md)
|
|
10
10
|
publishes what each one costs. The package has no required dependencies: a graph-only application
|
|
11
|
-
loads
|
|
11
|
+
loads 73 KB, and installs nothing but this package.
|
|
12
12
|
|
|
13
13
|
- NPM: https://www.npmjs.com/package/nexus-ai-pro
|
|
14
14
|
- GitHub: https://github.com/mkhitar-abrahamyan/nexus-ai
|
|
@@ -243,12 +243,16 @@ Shipped so far:
|
|
|
243
243
|
a codemod that moves your imports;
|
|
244
244
|
- 2.0 consolidation: one lifecycle for every call of every family, a shared spend budget, a root
|
|
245
245
|
import that holds only the client, no required dependencies, and a model registry checked against
|
|
246
|
-
every provider
|
|
246
|
+
every provider;
|
|
247
|
+
- 2.1 fault tolerance and crash-safe observability: checkpoints written on your terms, recovery after
|
|
248
|
+
retries, idle timeouts and a clean drain, traces that survive a crash and reach any OpenTelemetry
|
|
249
|
+
backend, one event stream, and dashboard rollups.
|
|
247
250
|
|
|
248
251
|
Upgrading from 1.x: `npx nexus migrate src --write` moves your imports, and
|
|
249
252
|
[MIGRATING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/MIGRATING.md) covers the rest.
|
|
250
|
-
Next,
|
|
251
|
-
|
|
253
|
+
Next, 2.2 removes the scaling cliffs the guides document: versioned database migrations, indexed
|
|
254
|
+
queue dispatch, a circuit breaker that counts across workers, a routing index, and one tenant model. The [roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists the
|
|
255
|
+
releases after it.
|
|
252
256
|
|
|
253
257
|
## Before Production
|
|
254
258
|
|
|
@@ -2,7 +2,7 @@ import type { OperationLifecycleLike } from '../types/lifecycle.js';
|
|
|
2
2
|
import { type CompiledGraph } from '../graph/graph.js';
|
|
3
3
|
import type { GraphCheckpointer, NodeContext } from '../types/graph.js';
|
|
4
4
|
import type { CompletionRequest, Message, ToolDefinition } from '../types/messages.js';
|
|
5
|
-
import type { NexusResponse } from '../types/response.js';
|
|
5
|
+
import type { NexusResponse, StreamChunk } from '../types/response.js';
|
|
6
6
|
import type { Store } from '../types/store.js';
|
|
7
7
|
/**
|
|
8
8
|
* An agent as a graph.
|
|
@@ -17,6 +17,8 @@ import type { Store } from '../types/store.js';
|
|
|
17
17
|
export interface AgentModelClient {
|
|
18
18
|
/** Runs one completion. */
|
|
19
19
|
complete(request: CompletionRequest): Promise<NexusResponse>;
|
|
20
|
+
/** Streams one completion. Used when the agent is created with `streamTokens`. */
|
|
21
|
+
stream?(request: CompletionRequest): AsyncIterable<StreamChunk>;
|
|
20
22
|
}
|
|
21
23
|
/** A tool call the agent is about to make. */
|
|
22
24
|
export interface AgentToolCall {
|
|
@@ -108,6 +110,12 @@ export interface CreateAgentOptions {
|
|
|
108
110
|
* `ai.lifecycle`. Its model calls are operations of their own.
|
|
109
111
|
*/
|
|
110
112
|
lifecycle?: OperationLifecycleLike;
|
|
113
|
+
/**
|
|
114
|
+
* Streams the model's output token by token onto the run's `messages` events, through the client's
|
|
115
|
+
* `stream()`. Without it, or with a client that cannot stream, each answer arrives as one message
|
|
116
|
+
* event once the model returns. Middleware sees the assembled response either way.
|
|
117
|
+
*/
|
|
118
|
+
streamTokens?: boolean;
|
|
111
119
|
}
|
|
112
120
|
declare const agentChannels: () => {
|
|
113
121
|
messages: import("../types/graph.js").Channel<Message[]>;
|
|
@@ -37,7 +37,12 @@ export function createAgent(options) {
|
|
|
37
37
|
for (const item of middleware) {
|
|
38
38
|
request = (await item.beforeModel?.({ request, state, store: options.store })) ?? request;
|
|
39
39
|
}
|
|
40
|
-
let response =
|
|
40
|
+
let response = options.streamTokens && options.client.stream
|
|
41
|
+
? await streamed(options.client.stream(request), (chunk) => context.message(chunk))
|
|
42
|
+
: await options.client.complete(request);
|
|
43
|
+
if (!(options.streamTokens && options.client.stream) && response.content) {
|
|
44
|
+
context.message({ content: response.content });
|
|
45
|
+
}
|
|
41
46
|
for (const item of middleware) {
|
|
42
47
|
response = (await item.afterModel?.({ response, state, store: options.store })) ?? response;
|
|
43
48
|
}
|
|
@@ -88,9 +93,13 @@ export function createAgent(options) {
|
|
|
88
93
|
args = verdict.args;
|
|
89
94
|
}
|
|
90
95
|
const run = async () => executor.execute(description.name, args);
|
|
96
|
+
context.tool({ phase: 'start', id: call.id, name: description.name, args });
|
|
91
97
|
const result = await middleware.reduceRight((next, item) => item.wrapToolCall
|
|
92
98
|
? () => Promise.resolve(item.wrapToolCall?.({ ...description, args }, next))
|
|
93
99
|
: next, run)();
|
|
100
|
+
context.tool(result.ok
|
|
101
|
+
? { phase: 'result', id: call.id, name: description.name, result: result.result }
|
|
102
|
+
: { phase: 'error', id: call.id, name: description.name, error: result.error ?? 'The tool failed' });
|
|
94
103
|
return { messages: [toolMessage(call, result)] };
|
|
95
104
|
}, { ends: ['model'] })
|
|
96
105
|
.setEntry('model')
|
|
@@ -156,3 +165,33 @@ function parseArguments(raw) {
|
|
|
156
165
|
return { ok: false, error: `Tool arguments are not valid JSON (${reason}); the tool was not run` };
|
|
157
166
|
}
|
|
158
167
|
}
|
|
168
|
+
async function streamed(chunks, onChunk) {
|
|
169
|
+
let content = '';
|
|
170
|
+
const toolCalls = [];
|
|
171
|
+
let meta = {};
|
|
172
|
+
for await (const chunk of chunks) {
|
|
173
|
+
if (chunk.type === 'text' && chunk.content) {
|
|
174
|
+
content += chunk.content;
|
|
175
|
+
onChunk({ kind: 'text', content: chunk.content });
|
|
176
|
+
}
|
|
177
|
+
else if (chunk.type === 'reasoning' && chunk.content) {
|
|
178
|
+
onChunk({ kind: 'reasoning', content: chunk.content });
|
|
179
|
+
}
|
|
180
|
+
else if (chunk.type === 'tool_call' && chunk.toolCall) {
|
|
181
|
+
toolCalls.push(chunk.toolCall);
|
|
182
|
+
}
|
|
183
|
+
else if (chunk.type === 'done') {
|
|
184
|
+
meta = chunk.meta ?? meta;
|
|
185
|
+
}
|
|
186
|
+
else if (chunk.type === 'error') {
|
|
187
|
+
throw new Error(chunk.error ?? 'The model stream failed');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
content,
|
|
192
|
+
role: 'assistant',
|
|
193
|
+
...(toolCalls.length ? { toolCalls } : {}),
|
|
194
|
+
finishReason: toolCalls.length ? 'tool_calls' : 'stop',
|
|
195
|
+
meta: meta,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ParsedArgs } from './args.js';
|
|
2
|
+
/**
|
|
3
|
+
* `nexus graph lint <module>[#export]`.
|
|
4
|
+
*
|
|
5
|
+
* The module exports a compiled graph, or a description of one as `describe()` returns it, as the
|
|
6
|
+
* named export, as `graph`, or as its default. Exits 1 when a finding is an error, or with `--strict`
|
|
7
|
+
* when any finding is a warning too.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runGraphCommand(subcommand: string | undefined, args: ParsedArgs): Promise<void>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { lintGraph } from '../graph/lint.js';
|
|
2
|
+
import { CliUsageError, flagBool, isRecord, listFlag, loadModule, printTable, writeJson, } from './args.js';
|
|
3
|
+
export async function runGraphCommand(subcommand, args) {
|
|
4
|
+
if (subcommand !== 'lint') {
|
|
5
|
+
throw new CliUsageError(`Unknown graph command "${subcommand ?? ''}". Use lint.`);
|
|
6
|
+
}
|
|
7
|
+
const [target] = args.positionals;
|
|
8
|
+
if (!target)
|
|
9
|
+
throw new CliUsageError('nexus graph lint needs a module: nexus graph lint ./graph.mjs[#exportName].');
|
|
10
|
+
const [file, exportName] = target.split('#');
|
|
11
|
+
const loaded = await loadModule(file);
|
|
12
|
+
const graph = (exportName ? loaded[exportName] : (loaded.graph ?? loaded.default ?? loaded));
|
|
13
|
+
if (!isLintable(graph)) {
|
|
14
|
+
throw new CliUsageError(`${target} must export a compiled graph, or a graph description, ${exportName ? `as "${exportName}"` : 'as `graph` or its default'}.`);
|
|
15
|
+
}
|
|
16
|
+
const findings = lintGraph(graph, {
|
|
17
|
+
deployed: flagBool(args.flags, 'deployed'),
|
|
18
|
+
ignore: listFlag(args.flags, 'ignore'),
|
|
19
|
+
});
|
|
20
|
+
const failing = findings.filter((finding) => finding.severity === 'error' || (flagBool(args.flags, 'strict') && finding.severity === 'warning'));
|
|
21
|
+
if (flagBool(args.flags, 'json')) {
|
|
22
|
+
writeJson({ findings, failed: failing.length > 0 });
|
|
23
|
+
}
|
|
24
|
+
else if (findings.length === 0) {
|
|
25
|
+
console.log('No findings.');
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
printTable(findings.map((finding) => ({
|
|
29
|
+
severity: finding.severity,
|
|
30
|
+
code: finding.code,
|
|
31
|
+
node: finding.node ?? '',
|
|
32
|
+
message: finding.message,
|
|
33
|
+
})), ['severity', 'code', 'node', 'message']);
|
|
34
|
+
console.log('');
|
|
35
|
+
for (const finding of findings)
|
|
36
|
+
console.log(`${finding.code}: ${finding.fix}`);
|
|
37
|
+
}
|
|
38
|
+
if (failing.length > 0)
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
}
|
|
41
|
+
function isLintable(value) {
|
|
42
|
+
if (!isRecord(value))
|
|
43
|
+
return false;
|
|
44
|
+
if (typeof value.describe === 'function')
|
|
45
|
+
return true;
|
|
46
|
+
return Array.isArray(value.nodes) && Array.isArray(value.edges) && Array.isArray(value.dynamic);
|
|
47
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -54,6 +54,12 @@ async function main() {
|
|
|
54
54
|
await runMigrateCommand(parsed);
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
|
+
case 'graph': {
|
|
58
|
+
const [subcommand, ...args] = parsed.positionals;
|
|
59
|
+
const { runGraphCommand } = await import('./cli/graph.js');
|
|
60
|
+
await runGraphCommand(subcommand, { positionals: args, flags: parsed.flags });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
57
63
|
case 'deploy': {
|
|
58
64
|
const [subcommand, ...args] = parsed.positionals;
|
|
59
65
|
const { runDeployCommand } = await import('./cli/deploy.js');
|
|
@@ -402,6 +408,7 @@ Usage:
|
|
|
402
408
|
nexus traces export --store <runs.jsonl|store.mjs> [--out runs.jsonl] [same filters as list]
|
|
403
409
|
nexus db sql [--adapters operations,store,traces,evaluation,circuits,prompts] [--vector-dimensions 1536]
|
|
404
410
|
nexus migrate [paths...] [--write] [--check] [--json]
|
|
411
|
+
nexus graph lint <module>[#export] [--deployed] [--strict] [--ignore CODE,CODE] [--json]
|
|
405
412
|
nexus deploy status [assistant] --url <server> [--token t] [--json]
|
|
406
413
|
nexus deploy canary <assistant> <revision> <percent> --url <server> [--reason r] [--version n]
|
|
407
414
|
nexus deploy promote <assistant> <revision> --url <server> [--reason r] [--version n]
|
|
@@ -413,7 +420,8 @@ Evaluation modules for "eval run" export { target, dataset, evaluators, summary?
|
|
|
413
420
|
"eval gate" exits 1 when a metric got worse beyond noise, a new failure appeared, or the datasets differ.
|
|
414
421
|
Trace store modules export any TraceStore as { store }, such as a PostgresTraceStore over the application's own pool.
|
|
415
422
|
"deploy" reads NEXUS_SERVER_URL and NEXUS_SERVER_TOKEN when --url and --token are not given.
|
|
416
|
-
"migrate" moves 1.x root imports to their 2.0 subpaths and reports removed options; --check exits 1 while any remain
|
|
423
|
+
"migrate" moves 1.x root imports to their 2.0 subpaths and reports removed options; --check exits 1 while any remain.
|
|
424
|
+
"graph lint" reads a compiled graph's shape and exits 1 on an error finding, or on a warning with --strict.`);
|
|
417
425
|
}
|
|
418
426
|
main().catch((error) => {
|
|
419
427
|
const message = error instanceof Error ? error.message : String(error);
|
package/dist/core/nexus.d.ts
CHANGED
|
@@ -248,9 +248,13 @@ export declare class NexusAI {
|
|
|
248
248
|
/** Health of every provider seen so far, when health tracking is on. */
|
|
249
249
|
getProviderHealth(): import("../ops/health.js").ProviderHealthSnapshot[];
|
|
250
250
|
/**
|
|
251
|
-
* Calls each provider's health check and records the result.
|
|
251
|
+
* Calls each provider's health check and records the result. With `staleOnly`, only the providers
|
|
252
|
+
* whose health is `unknown` are checked: never called, or not since `health.observationTtlMs`. Run
|
|
253
|
+
* it on a schedule to keep an idle provider's health current.
|
|
252
254
|
*/
|
|
253
|
-
checkProviders(
|
|
255
|
+
checkProviders(options?: {
|
|
256
|
+
staleOnly?: boolean;
|
|
257
|
+
}): Promise<Array<{
|
|
254
258
|
providerName: string;
|
|
255
259
|
ok: boolean;
|
|
256
260
|
error?: string;
|
package/dist/core/nexus.js
CHANGED
|
@@ -547,9 +547,11 @@ export class NexusAI {
|
|
|
547
547
|
getProviderHealth() {
|
|
548
548
|
return this.health.snapshot();
|
|
549
549
|
}
|
|
550
|
-
async checkProviders() {
|
|
550
|
+
async checkProviders(options = {}) {
|
|
551
551
|
const results = [];
|
|
552
552
|
for (const [providerName, provider] of this.providers) {
|
|
553
|
+
if (options.staleOnly && !this.health.isUnknown(providerName))
|
|
554
|
+
continue;
|
|
553
555
|
const started = Date.now();
|
|
554
556
|
try {
|
|
555
557
|
const ok = await provider.healthCheck();
|
package/dist/evals/judge.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EvaluationContext, Evaluator } from '../types/evaluate.js';
|
|
1
2
|
import type { CompletionRequest } from '../types/messages.js';
|
|
2
3
|
import type { NexusResponse } from '../types/response.js';
|
|
3
4
|
import type { EvalCase, EvalJudge } from './runner.js';
|
|
@@ -75,6 +76,16 @@ export declare class LLMJudge<Response = NexusResponse> {
|
|
|
75
76
|
constructor(options: LLMJudgeOptions<Response>);
|
|
76
77
|
/** Judges one answer. */
|
|
77
78
|
evaluate(input: LLMJudgeInput): Promise<LLMJudgeResult>;
|
|
79
|
+
/**
|
|
80
|
+
* The judge as an evaluator for `evaluate()`, scoring each output under `key` (`judge` by
|
|
81
|
+
* default). `input` turns an evaluation context into what the judge sees; by default the output is
|
|
82
|
+
* the answer, the example's expected output the reference, and its inputs the question. The
|
|
83
|
+
* experiment records the judge's model, temperature, rubric, and a hash of its instructions.
|
|
84
|
+
*/
|
|
85
|
+
asEvaluator(options?: {
|
|
86
|
+
key?: string;
|
|
87
|
+
input?: (context: EvaluationContext) => LLMJudgeInput;
|
|
88
|
+
}): Evaluator;
|
|
78
89
|
/**
|
|
79
90
|
* Adapts the judge to an `EvalRunner` case, optionally with a mapper that picks what the judge
|
|
80
91
|
* sees.
|
package/dist/evals/judge.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { withProvenance } from '../evaluate/provenance.js';
|
|
1
3
|
const DEFAULT_SYSTEM_PROMPT = [
|
|
2
4
|
'You are a careful evaluation judge.',
|
|
3
5
|
'Return only JSON with these fields: score, passed, rationale, labels.',
|
|
@@ -43,6 +45,34 @@ export class LLMJudge {
|
|
|
43
45
|
modelUsed: modelOf(response),
|
|
44
46
|
});
|
|
45
47
|
}
|
|
48
|
+
asEvaluator(options = {}) {
|
|
49
|
+
const evaluator = async (context) => {
|
|
50
|
+
const input = options.input?.(context) ?? {
|
|
51
|
+
actual: text(context.output),
|
|
52
|
+
...(context.example.expected === undefined ? {} : { expected: text(context.example.expected) }),
|
|
53
|
+
query: text(context.example.inputs),
|
|
54
|
+
};
|
|
55
|
+
const verdict = await this.evaluate(input);
|
|
56
|
+
return {
|
|
57
|
+
key: options.key ?? 'judge',
|
|
58
|
+
score: verdict.score,
|
|
59
|
+
passed: verdict.passed,
|
|
60
|
+
...(verdict.rationale ? { comment: verdict.rationale } : {}),
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
return withProvenance(evaluator, {
|
|
64
|
+
name: options.key ?? 'judge',
|
|
65
|
+
judge: {
|
|
66
|
+
model: this.options.model,
|
|
67
|
+
promptVersion: createHash('sha256')
|
|
68
|
+
.update(this.options.systemPrompt ?? 'default')
|
|
69
|
+
.digest('hex')
|
|
70
|
+
.slice(0, 12),
|
|
71
|
+
temperature: this.options.temperature ?? 0,
|
|
72
|
+
},
|
|
73
|
+
...(this.options.rubric ? { rubric: this.options.rubric } : {}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
46
76
|
asEvalJudge(mapper) {
|
|
47
77
|
return async (response, testCase) => {
|
|
48
78
|
const input = mapper
|
|
@@ -150,3 +180,6 @@ function clamp(value, [min, max]) {
|
|
|
150
180
|
function midpoint([min, max]) {
|
|
151
181
|
return min + (max - min) / 2;
|
|
152
182
|
}
|
|
183
|
+
function text(value) {
|
|
184
|
+
return typeof value === 'string' ? value : JSON.stringify(value ?? '');
|
|
185
|
+
}
|
package/dist/evaluate/index.d.ts
CHANGED
|
@@ -5,3 +5,4 @@ export { AnnotationQueue, type AnnotationQueueOptions, evaluateOnline, type Onli
|
|
|
5
5
|
export { evaluate, type EvaluateOptions, type EvaluationTarget, stats, summarize } from './run.js';
|
|
6
6
|
export { type CachedOutput, type EvaluationCache, evaluationCacheKey, FileEvaluationCache, fingerprintOf, MemoryEvaluationCache, type MemoryEvaluationCacheOptions, } from './cache.js';
|
|
7
7
|
export type { Dataset, DatasetExample, DatasetStore, EvaluationContext, EvaluationScore, Evaluator, ExampleResult, Experiment, ExperimentStore, MetricSummary, SummaryEvaluator, } from '../types/evaluate.js';
|
|
8
|
+
export { withProvenance } from './provenance.js';
|
package/dist/evaluate/index.js
CHANGED
|
@@ -4,3 +4,4 @@ export { completed, contains, embeddingSimilarity, exactMatch, mustNotMatch, pai
|
|
|
4
4
|
export { AnnotationQueue, evaluateOnline, } from './review.js';
|
|
5
5
|
export { evaluate, stats, summarize } from './run.js';
|
|
6
6
|
export { evaluationCacheKey, FileEvaluationCache, fingerprintOf, MemoryEvaluationCache, } from './cache.js';
|
|
7
|
+
export { withProvenance } from './provenance.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Evaluator, EvaluatorProvenance } from '../types/evaluate.js';
|
|
2
|
+
/**
|
|
3
|
+
* Says where an evaluator's scores come from: its name and version, the judge model and prompt
|
|
4
|
+
* version behind it, and its rubric. `evaluate()` records it on the experiment, so a score of 0.87
|
|
5
|
+
* can be traced to exactly what produced it, and two experiments scored by different judges are not
|
|
6
|
+
* compared as if they were alike.
|
|
7
|
+
*
|
|
8
|
+
* Returns the same evaluator, marked; it runs exactly as before.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const grounded = withProvenance(groundedness, {
|
|
13
|
+
* name: 'groundedness',
|
|
14
|
+
* version: '3',
|
|
15
|
+
* judge: { model: 'claude-sonnet-5-5', promptVersion: 'grounded-v3', temperature: 0 },
|
|
16
|
+
* rubric: 'Every claim is supported by a cited passage.',
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function withProvenance<I = unknown, O = unknown>(evaluator: Evaluator<I, O>, provenance: Omit<EvaluatorProvenance, 'keys' | 'position'>): Evaluator<I, O>;
|
|
21
|
+
/** What an evaluator declared through `withProvenance()`, if anything. */
|
|
22
|
+
export declare function declaredProvenance(evaluator: unknown): Omit<EvaluatorProvenance, 'keys' | 'position'> | undefined;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const PROVENANCE = Symbol('nexus.evaluate.provenance');
|
|
2
|
+
export function withProvenance(evaluator, provenance) {
|
|
3
|
+
Object.defineProperty(evaluator, PROVENANCE, { value: provenance, configurable: true });
|
|
4
|
+
return evaluator;
|
|
5
|
+
}
|
|
6
|
+
export function declaredProvenance(evaluator) {
|
|
7
|
+
if (typeof evaluator !== 'function')
|
|
8
|
+
return undefined;
|
|
9
|
+
return evaluator[PROVENANCE];
|
|
10
|
+
}
|
package/dist/evaluate/run.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { NEXUS_VERSION } from '../version.js';
|
|
2
3
|
import { evaluationCacheKey } from './cache.js';
|
|
4
|
+
import { declaredProvenance } from './provenance.js';
|
|
3
5
|
export async function evaluate(target, dataset, evaluators, options = {}) {
|
|
4
6
|
if (dataset.examples.length === 0)
|
|
5
7
|
throw new RangeError(`Dataset "${dataset.name}" has no examples`);
|
|
@@ -14,8 +16,9 @@ export async function evaluate(target, dataset, evaluators, options = {}) {
|
|
|
14
16
|
example,
|
|
15
17
|
run,
|
|
16
18
|
})));
|
|
19
|
+
const produced = evaluators.map(() => new Set());
|
|
17
20
|
await runConcurrently(jobs, options.concurrency ?? 4, async ({ example, run }) => {
|
|
18
|
-
const result = await evaluateOne(target, example, run, evaluators, options);
|
|
21
|
+
const result = await evaluateOne(target, example, run, evaluators, options, produced);
|
|
19
22
|
results.push(result);
|
|
20
23
|
options.onResult?.(result);
|
|
21
24
|
}, options.signal);
|
|
@@ -35,6 +38,16 @@ export async function evaluate(target, dataset, evaluators, options = {}) {
|
|
|
35
38
|
results,
|
|
36
39
|
metrics: summarize(results),
|
|
37
40
|
summary,
|
|
41
|
+
evaluators: evaluators.map((evaluator, position) => {
|
|
42
|
+
const declared = declaredProvenance(evaluator);
|
|
43
|
+
return {
|
|
44
|
+
...declared,
|
|
45
|
+
position,
|
|
46
|
+
name: declared?.name ?? (evaluator.name || `evaluator-${position}`),
|
|
47
|
+
keys: [...(produced[position] ?? [])],
|
|
48
|
+
};
|
|
49
|
+
}),
|
|
50
|
+
framework: { name: 'nexus-ai-pro', version: NEXUS_VERSION },
|
|
38
51
|
...(options.metadata ? { metadata: options.metadata } : {}),
|
|
39
52
|
...(options.cache
|
|
40
53
|
? {
|
|
@@ -57,7 +70,7 @@ function repetitionsOf(example, setting) {
|
|
|
57
70
|
}
|
|
58
71
|
return count;
|
|
59
72
|
}
|
|
60
|
-
async function evaluateOne(target, example, run, evaluators, options) {
|
|
73
|
+
async function evaluateOne(target, example, run, evaluators, options, produced) {
|
|
61
74
|
const started = Date.now();
|
|
62
75
|
let output;
|
|
63
76
|
let error;
|
|
@@ -97,9 +110,12 @@ async function evaluateOne(target, example, run, evaluators, options) {
|
|
|
97
110
|
});
|
|
98
111
|
}
|
|
99
112
|
const scores = [];
|
|
100
|
-
for (const evaluator of evaluators) {
|
|
113
|
+
for (const [position, evaluator] of evaluators.entries()) {
|
|
101
114
|
try {
|
|
102
|
-
|
|
115
|
+
const made = normalizeScores(await evaluator(context), evaluator);
|
|
116
|
+
for (const score of made)
|
|
117
|
+
produced[position]?.add(score.key);
|
|
118
|
+
scores.push(...made);
|
|
103
119
|
}
|
|
104
120
|
catch (caught) {
|
|
105
121
|
scores.push({
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DurabilityMode } from '../types/graph.js';
|
|
2
|
+
/**
|
|
3
|
+
* Writes one run's checkpoints under a durability mode.
|
|
4
|
+
*
|
|
5
|
+
* `sync` writes each one before returning. `async` chains writes behind each other, so they land in
|
|
6
|
+
* order, and makes the run wait only when `maxPending` are already in flight. `exit` keeps only the
|
|
7
|
+
* latest and writes it when the run stops. A write that fails is kept and thrown from the next call,
|
|
8
|
+
* so a background failure still fails the run.
|
|
9
|
+
*/
|
|
10
|
+
export declare class CheckpointWriter<T> {
|
|
11
|
+
private readonly write;
|
|
12
|
+
private readonly mode;
|
|
13
|
+
private readonly maxPending;
|
|
14
|
+
private chain;
|
|
15
|
+
private readonly inFlight;
|
|
16
|
+
private failure;
|
|
17
|
+
private unwritten;
|
|
18
|
+
constructor(write: (checkpoint: T) => Promise<void>, mode: DurabilityMode, maxPending?: number);
|
|
19
|
+
/**
|
|
20
|
+
* Hands over a checkpoint. A `durable` one is persisted, with everything before it, before this
|
|
21
|
+
* resolves, whatever the mode: a pause, a failure, the end of a run, or a recovery decision.
|
|
22
|
+
*/
|
|
23
|
+
persist(checkpoint: T, durable?: boolean): Promise<void>;
|
|
24
|
+
/** Waits for every write handed over so far, and writes what `exit` mode was holding. */
|
|
25
|
+
flush(): Promise<void>;
|
|
26
|
+
private throwFailure;
|
|
27
|
+
}
|