@aid-framework/runtime

中文文档
0.1.98发布于 2026/11/03Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 1万

AID runtime for TypeScript: pinned-model gateway, prompt registry, tool dispatch, pipelines, structured output, eval gates and retrieval.

原始使用文档
文档目录12 节
  1. Install
  2. Quick start
  3. A pipeline
  4. What is in the package
  5. Model pinning and capability checks
  6. Decisions are not chat
  7. Evals gate on statistics, not equality
  8. Retrieval
  9. Limits, stated plainly
  10. API
  11. Related packages
  12. License

@aid-framework/runtime

The TypeScript runtime for AID Framework: the library a generated application calls at runtime. Pin a model, run a prompt, call a tool, trace it, stay inside a budget, and prove that quality did not regress.

Generated code is thin glue. This is where the behaviour lives — which is why it is a normal dependency of the generated app, not part of the compiler.

Install

npm install @aid-framework/runtime

Requires Node.js 24 or newer. Ships as ESM with TypeScript declarations. zod is used for structured output and nothing else.

Quick start

A gateway resolves aliases to pinned models, enforces budgets, records cost and opens a span per call:

import {
  Budget, ModelGateway, OpenAICompatibleProvider, PinnedModel,
} from '@aid-framework/runtime';

const gateway = new ModelGateway({
  providers: {
    'openai-compatible': new OpenAICompatibleProvider({ apiKey: process.env.OPENAI_API_KEY }),
  },
  aliases: {
    small: new PinnedModel({
      alias: 'small',
      spec: {
        name: 'gpt-4o-2024-08-06',
        provider: 'openai-compatible',
        contextWindow: 128_000,
        maxOutputTokens: 4_096,
        cost: { inputPer1kUsd: 0.0025, outputPer1kUsd: 0.01 },
      },
    }),
  },
  budget: new Budget({ maxUsd: 5, scope: 'nightly' }),
});

const result = await gateway.generate('small', { prompt: 'hello' });

result.text;
result.usage;
result.costUsd;
result.attempts;  // every alias tried, in order
result.fellBack;

An alias resolves to an exact, pinned model id. That is not cosmetic: a baseline recorded against gpt-4o means nothing if the alias floats to a later snapshot on the next run. An unknown alias therefore raises UnknownModelAliasError rather than falling through to a provider default.

A pipeline

Control flow is plain async TypeScript. There is no graph engine and no orchestration framework: a generated app is ordinary code that is easy to read, step through and test.

import { Pipeline, PipelineContext } from '@aid-framework/runtime';

const pipeline = new Pipeline([
  { name: 'load', run: (ctx) => ctx.withValues({ question: ctx.require('request').question }) },
  { name: 'answer', run: async (ctx) => ({ answer: await ask(ctx.require('question')) }) },
]);

const result = await pipeline.run(new PipelineContext({ request }));

PipelineContext is immutable — get / require / withValues — so a step cannot mutate what an earlier step observed, and a failed run's state stays inspectable. Each step runs in its own span; a step that throws aborts the run with the original error chained; StopPipeline is the supported early exit. A pipeline rejects an empty or duplicate-named step list at construction.

Composite steps — branch, parallel, mapStep, reduceStep — are provided as step builders, with bounded nesting (MAX_STEP_DEPTH).

What is in the package

AreaExportsNotes
TypesMessage, ModelSpec, PinnedModel, TokenUsage, CompletionRequest, CompletionResponse, CapabilityThe vocabulary everything else is typed against
ErrorsAidRuntimeError and 25 subclassesOne base, so an app catches framework refusals in one place
GatewayModelGateway, estimateUsagePinned aliases, capability negotiation, ordered fallback, budget preflight
ProvidersOpenAICompatibleProvider, FakeProvider, supportsOne HTTP adapter, one deterministic fake, plus the Provider protocol
DecisionsDecisionClient, DecisionQuestion, decisionKeyTypeSafe AI (Jev) typed decisions: choice, score, and noul answers
CostBudget, CostLedger, estimateCostUsdPre-call enforcement, per-call ledger entries, spend summaries
TracinggetTracer, llmSpan, recordUsage, recordCost, recordExceptionOTel GenAI semantic conventions; AID values namespaced aid.*
PromptsPromptRegistry, PromptTemplate, ExampleVersioned registry, typed rendering, few-shot assembly
ToolsTool, ToolExecutor, validateArgumentsSchema-validated dispatch over a documented JSON-Schema subset
Structured outputbuildAgent, parseStructured, runStructured, repairPromptParse/repair loop over zod schemas
PipelinesPipeline, PipelineContext, StopPipeline, branch, parallel, mapStep, reduceStepSequential reducer over an immutable context
ConcurrencyConcurrencyLimiter, holdsSlotBounded in-flight work, including inside composite steps
PolicyevaluatePolicy, Principal, POLICY_BUILTINSTool-auth expressions, fail-closed
Agent loopagents/*Native loop: transfer vs. delegate, budgets, degrade, memory, HITL checkpoint
EvalsDataset, EvalCase, runEvaluation, BaselineGate, EvalBaselineN-run sampling and the statistical gate
MetricsExactMatch, NormalizedContains, JsonSchemaValid, LlmJudgePlus defaultMetrics and metricNames
RetrievalRetriever, RetrievalGate, chunkDocument, openConnector, createStore, fuseConnectors, chunkers, embedders, stores, hybrid search, reranking, citations

Model pinning and capability checks

gateway.require(alias, capabilities) checks both sides — the model's declared capabilities and the adapter's ability to serialize them — and returns the pinned model. A model that supports tool calling behind an adapter that does not serialize tool definitions cannot tool-call, and CapabilityMismatchError names what is missing rather than failing later at the provider.

generate(alias, request, { fallbacks }) tries the alias, then each fallback in the order given. Fallbacks are ordered, not conditional, and result.attempts reports what was actually tried.

Decisions are not chat

DecisionClient calls TypeSafe AI's Jev (POST /v1/systemone), which takes a state plus typed questions and returns typed answers instead of prose. It is deliberately not in providers/: every provider there maps messages to text, and Jev has no text surface, so a pipeline cannot select it as the model that writes an answer.

import { DecisionClient, DecisionQuestion } from '@aid-framework/runtime';

const client = new DecisionClient();
const response = await client.decide({
  model: 'jev-1.14.0',
  state: { message },
  questions: {
    triage: new DecisionQuestion({
      type: 'choice',
      instructions: 'Which team should handle this',
      criteria: ['billing', 'technical'],
    }),
  },
});
response.answers.triage?.choice;

The key is TYPESAFE_API_KEY (Vercel's spelling, TYPESAFE_AI_API_KEY, is accepted too); TYPESAFE_BASE_URL overrides the host. Two rules matter more than they look: a noul answer carries noul and no confidence field at all, so a branch on confidence must refuse it rather than default to zero; and decide({ ..., reproducible: true }) refuses the moving aliases jev-latest and jev-preview, which is what eval scoring needs. Only 429/529 are retried, honouring retry-after.

Evals gate on statistics, not equality

A model is expected to vary between runs, so a metric comparing two runs for equality is either always red or measuring nothing. Non-determinism is the property the gate is built to tolerate:

import { BaselineGate, runEvaluation, ExactMatch, defaultMetrics } from '@aid-framework/runtime';

const run = await runEvaluation(dataset, runner, defaultMetrics(), { runs: 5 });
const verdict = new BaselineGate(thresholds).evaluate(run, baseline);

verdict.passed;
verdict.metrics; // per-metric z-score, allowance and outcome

runEvaluation samples the runner N times and reports per-run metric means, so one unlucky run does not decide the verdict. EvalBaseline.fromRun(run) stores per metric the sample count, mean and spread; the gate compares with a one-sided normal test. A metric passes by clearing minMean, by staying within maxRegression of the baseline, or by regressing only within sampling noise.

LlmJudge is implemented and never CI-required: a gate that depends on one non-deterministic call inside the gate you are trying to trust is the problem the design avoids.

The arithmetic matches the Python runtime exactly — the same z-critical table, the same baseline comparison, the same sampling semantics — because a gate whose statistics differed per language would let a TypeScript regression pass a Python baseline.

Retrieval

retrieval/* is IR-driven: bindingFromSpec(retriever, { embedding, store }) folds the IR's retrievers, embeddings and stores entries into one RetrieverBinding, which a Retriever then ingests and searches.

Chunk ids are content hashes, so re-ingesting the same input produces the same ids and a no-op diff. FakeDeterministicEmbedder hashes with SHAKE-128 and chunkIdFor with SHA-256, which is what lets a retrieval gate run in CI with no network and no key.

RetrievalMetric (RecallAtK, MeanReciprocalRank, CitationGrounding) is a separate protocol from Metric, because retrieval scores a retrieved set rather than a single string. Retrieval is sampled through RetrievalGate.sample(...) into a standard EvalRun and judged by the unchanged BaselineGate.

Limits, stated plainly

  • Streaming is declared, not implemented. Capability includes it so a spec can require it and a build-time gate can check it; calling ModelGateway.stream raises FeatureDeferredError. The transport is request/response only.
  • One HTTP provider and one fake. Breadth of vendor coverage is a later concern; a narrow, correct seam is worth more than a wide shallow one.
  • PgVectorStore needs a live vector extension and is not exercised by CI; the in-memory store carries the store tests, and both share the same VectorStore protocol and similarity semantics.

API

The full surface is the package's named exports; the table above is the map. The one deliberate asymmetry with the Python runtime is providers/fake.ts, which derives its accuracy draw from a SHA-256 hash of seed:index:prompt rather than CPython's Mersenne Twister, because string seeding there is a CPython implementation detail with no portable equivalent. The draw is still deterministic and still varies per call index, which is the property the gate depends on.

License

Apache-2.0. Code generated by AID Framework is not subject to this license.

版本与发布记录

@aid-framework/runtime 共发布 52 个版本,首次发布于 2026/10/31;过去 12 个月发布了 52 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.1.98latest2026/11/032 天前
0.1.972026/11/032 天前
0.1.962026/11/033 天前
0.1.952026/11/033 天前
0.1.942026/11/033 天前
0.1.932026/11/033 天前

关于 @aid-framework/runtime 的常见问题

@aid-framework/runtime 是什么?

AID runtime for TypeScript: pinned-model gateway, prompt registry, tool dispatch, pipelines, structured output, eval gates and retrieval.

如何安装 @aid-framework/runtime?

在项目目录运行 npm install @aid-framework/runtime,或使用 pnpm add / yarn add / bun add。

@aid-framework/runtime 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@aid-framework/runtime 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@aid-framework/runtime 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=24。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@aid-framework/runtime 可以商用吗?

@aid-framework/runtime 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@aid-framework/runtime 的下载量有多少?

根据 npm 官方下载统计,@aid-framework/runtime 在 2026/10/06 – 2026/11/04 这 30 天内被下载 10,061 次,日均约 335 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@aid-framework/runtime 还在维护吗?

最新版本 0.1.98 发布于 2026/11/03(2 天前);至今共发布 52 个版本,其中过去 12 个月发布了 52 个;首次发布于 2026/10/31。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。