@aid-framework/runtime 0.1.39 → 0.1.51
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 +171 -41
- package/dist/.tsbuildinfo +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,53 +1,183 @@
|
|
|
1
|
-
# `@aid-framework/runtime`
|
|
1
|
+
# `@aid-framework/runtime`
|
|
2
2
|
|
|
3
|
-
The TypeScript runtime for
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
The TypeScript runtime for [AID Framework](https://github.com/aid-framework/aid-framework): the library a
|
|
4
|
+
generated application calls at runtime. Pin a model, run a prompt, call a tool, trace it, stay inside a budget,
|
|
5
|
+
and prove that quality did not regress.
|
|
6
|
+
|
|
7
|
+
Generated code is thin glue. This is where the behaviour lives — which is why it is a normal dependency of the
|
|
8
|
+
generated app, not part of the compiler.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @aid-framework/runtime
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Requires Node.js 24 or newer. Ships as ESM with TypeScript declarations. `zod` is used for structured output and
|
|
17
|
+
nothing else.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
A gateway resolves aliases to pinned models, enforces budgets, records cost and opens a span per call:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import {
|
|
25
|
+
Budget, ModelGateway, OpenAICompatibleProvider, PinnedModel,
|
|
26
|
+
} from '@aid-framework/runtime';
|
|
27
|
+
|
|
28
|
+
const gateway = new ModelGateway({
|
|
29
|
+
providers: {
|
|
30
|
+
'openai-compatible': new OpenAICompatibleProvider({ apiKey: process.env.OPENAI_API_KEY }),
|
|
31
|
+
},
|
|
32
|
+
aliases: {
|
|
33
|
+
small: new PinnedModel({
|
|
34
|
+
alias: 'small',
|
|
35
|
+
spec: {
|
|
36
|
+
name: 'gpt-4o-2024-08-06',
|
|
37
|
+
provider: 'openai-compatible',
|
|
38
|
+
contextWindow: 128_000,
|
|
39
|
+
maxOutputTokens: 4_096,
|
|
40
|
+
cost: { inputPer1kUsd: 0.0025, outputPer1kUsd: 0.01 },
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
budget: new Budget({ maxUsd: 5, scope: 'nightly' }),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const result = await gateway.generate('small', { prompt: 'hello' });
|
|
48
|
+
|
|
49
|
+
result.text;
|
|
50
|
+
result.usage;
|
|
51
|
+
result.costUsd;
|
|
52
|
+
result.attempts; // every alias tried, in order
|
|
53
|
+
result.fellBack;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**An alias resolves to an exact, pinned model id.** That is not cosmetic: a baseline recorded against `gpt-4o`
|
|
57
|
+
means nothing if the alias floats to a later snapshot on the next run. An unknown alias therefore raises
|
|
58
|
+
`UnknownModelAliasError` rather than falling through to a provider default.
|
|
59
|
+
|
|
60
|
+
## A pipeline
|
|
61
|
+
|
|
62
|
+
Control flow is plain async TypeScript. There is no graph engine and no orchestration framework: a generated app
|
|
63
|
+
is ordinary code that is easy to read, step through and test.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { Pipeline, PipelineContext } from '@aid-framework/runtime';
|
|
67
|
+
|
|
68
|
+
const pipeline = new Pipeline([
|
|
69
|
+
{ name: 'load', run: (ctx) => ctx.withValues({ question: ctx.require('request').question }) },
|
|
70
|
+
{ name: 'answer', run: async (ctx) => ({ answer: await ask(ctx.require('question')) }) },
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
const result = await pipeline.run(new PipelineContext({ request }));
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`PipelineContext` is immutable — `get` / `require` / `withValues` — so a step cannot mutate what an earlier step
|
|
77
|
+
observed, and a failed run's state stays inspectable. Each step runs in its own span; a step that throws aborts
|
|
78
|
+
the run with the original error chained; `StopPipeline` is the supported early exit. A pipeline rejects an empty
|
|
79
|
+
or duplicate-named step list at construction.
|
|
80
|
+
|
|
81
|
+
Composite steps — `branch`, `parallel`, `mapStep`, `reduceStep` — are provided as step builders, with bounded
|
|
82
|
+
nesting (`MAX_STEP_DEPTH`).
|
|
7
83
|
|
|
8
84
|
## What is in the package
|
|
9
85
|
|
|
10
|
-
| Area |
|
|
86
|
+
| Area | Exports | Notes |
|
|
11
87
|
| --- | --- | --- |
|
|
12
|
-
| Types | `
|
|
13
|
-
| Errors | `
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
z-critical table, the same baseline comparison and the same N-run sampling
|
|
29
|
-
semantics. A gate whose statistics differed per language would let a TypeScript
|
|
30
|
-
regression pass a Python baseline.
|
|
88
|
+
| Types | `Message`, `ModelSpec`, `PinnedModel`, `TokenUsage`, `CompletionRequest`, `CompletionResponse`, `Capability` | The vocabulary everything else is typed against |
|
|
89
|
+
| Errors | `AidRuntimeError` and 25 subclasses | One base, so an app catches framework refusals in one place |
|
|
90
|
+
| Gateway | `ModelGateway`, `estimateUsage` | Pinned aliases, capability negotiation, ordered fallback, budget preflight |
|
|
91
|
+
| Providers | `OpenAICompatibleProvider`, `FakeProvider`, `supports` | One HTTP adapter, one deterministic fake, plus the `Provider` protocol |
|
|
92
|
+
| Cost | `Budget`, `CostLedger`, `estimateCostUsd` | Pre-call enforcement, per-call ledger entries, spend summaries |
|
|
93
|
+
| Tracing | `getTracer`, `llmSpan`, `recordUsage`, `recordCost`, `recordException` | OTel GenAI semantic conventions; AID values namespaced `aid.*` |
|
|
94
|
+
| Prompts | `PromptRegistry`, `PromptTemplate`, `Example` | Versioned registry, typed rendering, few-shot assembly |
|
|
95
|
+
| Tools | `Tool`, `ToolExecutor`, `validateArguments` | Schema-validated dispatch over a documented JSON-Schema subset |
|
|
96
|
+
| Structured output | `buildAgent`, `parseStructured`, `runStructured`, `repairPrompt` | Parse/repair loop over zod schemas |
|
|
97
|
+
| Pipelines | `Pipeline`, `PipelineContext`, `StopPipeline`, `branch`, `parallel`, `mapStep`, `reduceStep` | Sequential reducer over an immutable context |
|
|
98
|
+
| Concurrency | `ConcurrencyLimiter`, `holdsSlot` | Bounded in-flight work, including inside composite steps |
|
|
99
|
+
| Policy | `evaluatePolicy`, `Principal`, `POLICY_BUILTINS` | Tool-auth expressions, fail-closed |
|
|
100
|
+
| Agent loop | `agents/*` | Native loop: transfer vs. delegate, budgets, degrade, memory, HITL checkpoint |
|
|
101
|
+
| Evals | `Dataset`, `EvalCase`, `runEvaluation`, `BaselineGate`, `EvalBaseline` | N-run sampling and the statistical gate |
|
|
102
|
+
| Metrics | `ExactMatch`, `NormalizedContains`, `JsonSchemaValid`, `LlmJudge` | Plus `defaultMetrics` and `metricNames` |
|
|
103
|
+
| Retrieval | `Retriever`, `RetrievalGate`, `chunkDocument`, `openConnector`, `createStore`, `fuse` | Connectors, chunkers, embedders, stores, hybrid search, reranking, citations |
|
|
31
104
|
|
|
32
|
-
|
|
33
|
-
point of difference:
|
|
105
|
+
## Model pinning and capability checks
|
|
34
106
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
which is the property the gate depends on.
|
|
40
|
-
- `tools.ts` takes an explicit JSON-Schema subset instead of inferring a schema
|
|
41
|
-
from handler reflection, because JavaScript has no `inspect.signature`.
|
|
107
|
+
`gateway.require(alias, capabilities)` checks **both** sides — the model's declared capabilities and the
|
|
108
|
+
adapter's ability to serialize them — and returns the pinned model. A model that supports tool calling behind an
|
|
109
|
+
adapter that does not serialize tool definitions cannot tool-call, and `CapabilityMismatchError` names what is
|
|
110
|
+
missing rather than failing later at the provider.
|
|
42
111
|
|
|
43
|
-
|
|
112
|
+
`generate(alias, request, { fallbacks })` tries the alias, then each fallback **in the order given**. Fallbacks
|
|
113
|
+
are ordered, not conditional, and `result.attempts` reports what was actually tried.
|
|
44
114
|
|
|
45
|
-
|
|
46
|
-
SHA-256, so re-ingesting a corpus reproduces byte-identical ids and vectors.
|
|
115
|
+
## Evals gate on statistics, not equality
|
|
47
116
|
|
|
48
|
-
|
|
117
|
+
A model is *expected* to vary between runs, so a metric comparing two runs for equality is either always red or
|
|
118
|
+
measuring nothing. Non-determinism is the property the gate is built to tolerate:
|
|
49
119
|
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
|
|
120
|
+
```ts
|
|
121
|
+
import { BaselineGate, runEvaluation, ExactMatch, defaultMetrics } from '@aid-framework/runtime';
|
|
122
|
+
|
|
123
|
+
const run = await runEvaluation(dataset, runner, defaultMetrics(), { runs: 5 });
|
|
124
|
+
const verdict = new BaselineGate(thresholds).evaluate(run, baseline);
|
|
125
|
+
|
|
126
|
+
verdict.passed;
|
|
127
|
+
verdict.metrics; // per-metric z-score, allowance and outcome
|
|
53
128
|
```
|
|
129
|
+
|
|
130
|
+
`runEvaluation` samples the runner **N times** and reports per-run metric means, so one unlucky run does not
|
|
131
|
+
decide the verdict. `EvalBaseline.fromRun(run)` stores per metric the sample count, mean and spread; the gate
|
|
132
|
+
compares with a one-sided normal test. A metric passes by clearing `minMean`, by staying within `maxRegression`
|
|
133
|
+
of the baseline, or by regressing only within sampling noise.
|
|
134
|
+
|
|
135
|
+
`LlmJudge` is implemented and never CI-required: a gate that depends on one non-deterministic call inside the
|
|
136
|
+
gate you are trying to trust is the problem the design avoids.
|
|
137
|
+
|
|
138
|
+
The arithmetic matches the Python runtime exactly — the same z-critical table, the same baseline comparison, the
|
|
139
|
+
same sampling semantics — because a gate whose statistics differed per language would let a TypeScript
|
|
140
|
+
regression pass a Python baseline.
|
|
141
|
+
|
|
142
|
+
## Retrieval
|
|
143
|
+
|
|
144
|
+
`retrieval/*` is IR-driven: `bindingFromSpec(retriever, { embedding, store })` folds the IR's `retrievers`,
|
|
145
|
+
`embeddings` and `stores` entries into one `RetrieverBinding`, which a `Retriever` then ingests and searches.
|
|
146
|
+
|
|
147
|
+
Chunk ids are content hashes, so re-ingesting the same input produces the same ids and a no-op diff.
|
|
148
|
+
`FakeDeterministicEmbedder` hashes with SHAKE-128 and `chunkIdFor` with SHA-256, which is what lets a retrieval
|
|
149
|
+
gate run in CI with no network and no key.
|
|
150
|
+
|
|
151
|
+
`RetrievalMetric` (`RecallAtK`, `MeanReciprocalRank`, `CitationGrounding`) is a separate protocol from `Metric`,
|
|
152
|
+
because retrieval scores a *retrieved set* rather than a single string. Retrieval is sampled through
|
|
153
|
+
`RetrievalGate.sample(...)` into a standard `EvalRun` and judged by the unchanged `BaselineGate`.
|
|
154
|
+
|
|
155
|
+
## Limits, stated plainly
|
|
156
|
+
|
|
157
|
+
- **Streaming is declared, not implemented.** `Capability` includes it so a spec can require it and a build-time
|
|
158
|
+
gate can check it; calling `ModelGateway.stream` raises `FeatureDeferredError`. The transport is
|
|
159
|
+
request/response only.
|
|
160
|
+
- **One HTTP provider and one fake.** Breadth of vendor coverage is a later concern; a narrow, correct seam is
|
|
161
|
+
worth more than a wide shallow one.
|
|
162
|
+
- `PgVectorStore` needs a live `vector` extension and is not exercised by CI; the in-memory store carries the
|
|
163
|
+
store tests, and both share the same `VectorStore` protocol and `similarity` semantics.
|
|
164
|
+
|
|
165
|
+
## API
|
|
166
|
+
|
|
167
|
+
The full surface is the package's named exports; the table above is the map. The one deliberate asymmetry with
|
|
168
|
+
the Python runtime is `providers/fake.ts`, which derives its accuracy draw from a SHA-256 hash of
|
|
169
|
+
`seed:index:prompt` rather than CPython's Mersenne Twister, because string seeding there is a CPython
|
|
170
|
+
implementation detail with no portable equivalent. The draw is still deterministic and still varies per call
|
|
171
|
+
index, which is the property the gate depends on.
|
|
172
|
+
|
|
173
|
+
## Related packages
|
|
174
|
+
|
|
175
|
+
- [`aid-runtime-python`](https://pypi.org/project/aid-runtime-python/) — the same runtime for Python
|
|
176
|
+
- [`@aid-framework/generator-ts-vercel`](https://www.npmjs.com/package/@aid-framework/generator-ts-vercel) — the
|
|
177
|
+
generator that emits apps against this runtime
|
|
178
|
+
- [`@aid-framework/cli`](https://www.npmjs.com/package/@aid-framework/cli) — `aid`
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
[Apache-2.0](https://github.com/aid-framework/aid-framework/blob/main/LICENSE). Code generated by AID Framework is
|
|
183
|
+
not subject to this license.
|