@formio/uag-sdk

中文文档
2.0.0发布于 2026/11/13MITTypeScriptCommonJS零依赖构建来源已验证近 30 天下载 2884

Build engine packages for the Form.io Universal Agent Gateway (UAG): the engine contract, defineEngine, settings and instance resolution, the engine loader, and a conformance check.

原始使用文档
文档目录8 节
  1. An engine package
  2. Generating text over documents
  3. Settings
  4. Instances
  5. Loading
  6. Errors
  7. Helpers
  8. Conformance

@formio/uag-sdk

Form.io UAG › Engines › this page

Build engine packages for the Form.io Universal Agent Gateway (UAG). This package contains:

  • the engine contract's types, and ENGINE_API_VERSION;
  • defineEngine, to declare an engine type and its settings;
  • EngineError, for messages that are safe to show to API callers;
  • the loader, which the UAG, uag-eval and the engine server use to find, configure and create engines;
  • shared helpers for model APIs with structured outputs;
  • a conformance check that runs from any test runner.

An engine package depends on this package only. It never needs @formio/uag.

npm install @formio/uag-sdk

The SDK requires Node.js 22 or later and has no dependencies. The full reference, with two complete example engines and their tests, is Engine SDK.

An engine package

Name the package @formio/uag-<type> (official engines) or uag-engine-<type> (anyone's). Export one engine type definition. It is a plugin of type "engine": name is the type name used in UAG_ENGINES, title (default: name) is shown to people, description is an optional summary for operators, and version defaults to your package's own version:

const { defineEngine, EngineError, ENGINE_API_VERSION, requiredSchema } = require('@formio/uag-sdk');

module.exports = defineEngine({
    type: 'engine',
    name: 'acme',
    title: 'Acme',
    kinds: ['agent'],
    apiVersion: ENGINE_API_VERSION,
    description: 'Acme models: structured extraction.',
    settings: {
        apiKey: { env: 'ACME_API_KEY', required: true, secret: true },
        model: { env: 'ACME_MODEL', default: 'acme-large-2026-09-01' },
        maxTokens: { env: 'ACME_MAX_TOKENS', type: 'number', default: 4000 },
    },
    create({ name, settings, options }) {
        const client = options.client || new AcmeClient({ apiKey: settings.apiKey });
        return {
            name,
            kind: 'agent',
            apiVersion: ENGINE_API_VERSION,
            model: settings.model,
            async extract(request) {
                request.signal?.throwIfAborted();
                const { schema, decode } = requiredSchema(request.schema);
                const answer = await client.complete({ schema, signal: request.signal /* ... */ });
                if (answer.quotaExceeded) throw new EngineError('Quota exceeded for this API key.');
                return { model: answer.model, values: decode(answer.json) };
            },
        };
    },
});

The engine must use the instance name it is given, and one of the kinds it declares. A decision engine has decide and maxOptions; an agent engine has extract, and sets toolUse: true when extract accepts read-only research tools. See contract.d.ts for every request and response.

A definition in the shape of the 2.0 release candidates, with the engine's name in type ({ type: 'acme', kinds, ... }), is refused with a message showing the shape above.

Generating text over documents

An agent engine may also implement generate(request), which document plugins call to write free text over uploaded documents, and declare accepts, the document kinds it reads natively (default ['text']; list 'pdf' only when your model reads PDFs as documents):

accepts: ['text', 'pdf'],
async generate({ instructions, system, attachments, format, maxTokens, signal, onProgress }) {
    // attachments: { kind: 'pdf', data: Buffer, label, pages? } or { kind: 'text', text, label, textFormat? }
    // Put the documents before the instructions; report progress as the answer streams.
    return { text, model, stopReason: 'end' /* or 'max_tokens' when cut off */, usage };
},

generate must honour signal, never log the documents, and report a truncated answer as stopReason: 'max_tokens' rather than failing.

create receives { name, settings, options, config, requireSetting }:

  • settings: the instance's value for each declared setting that has one.
  • options: the instance's options as given in configuration, including ones that are not settings. Accept test doubles here (options.client, as in the example above), so tests, and the conformance check below, never call the real API.
  • config: the host's configuration, read-only, for example routing.timeouts[name].
  • requireSetting(key): a setting's value, or the standard "needs its setting" error, for a setting that is required only sometimes.

create may be async.

Settings

Each setting is resolved from the first of these that is set:

  1. the instance's options in configuration;
  2. UAG_ENGINE_<NAME>_<SETTING>, from the instance name and the setting name (fast and apiKey give UAG_ENGINE_FAST_API_KEY);
  3. the variables the setting lists in env, in order;
  4. its default.
KeyMeaning
envThe type's own variables, as a name or a list.
requiredStartup fails without a value. The error names the instance, the type, the setting and every variable that sets it.
secretThe value is kept out of startup errors and capability discovery. Errors an engine throws while it runs are logged as they are, so never put a setting's value in one.
typestring (the default), number or boolean. Values from the environment are converted.
defaultUsed when nothing else is set.
descriptionDocumentation for operators.

Instances

Hosts enable engines as named instances of a type:

UAG_ENGINES=jev,fast:claude,deep:claude,x:@acme/uag-engine-x
  • A bare entry (jev) names an instance after its type.
  • <name>:<type> names it yourself, so one type can run as several instances with different settings.
  • A type starting with @ or uag-engine-, or containing /, is a package name. A bare package entry takes the package's type name.
  • Names use lowercase letters, digits and -, start with a letter, and are at most 63 characters. Anything else, including any path, is refused before anything is loaded.

In configuration, an entry is such a string, or { name, type, options }, where type may also be an engine type definition.

Loading

const { engineInstances } = require('@formio/uag-sdk');
const { enginePaths, loadEngines } = require('@formio/uag-sdk/loader');

const engines = await loadEngines(engineInstances(config, process.env), {
    types: { remote: RemoteEngine },        // built-in and embedded types, tried first
    paths: enginePaths(__dirname, process.env),
    env: process.env,
    config,
});

A type name is looked up in types, then as @formio/uag-<type>, then as uag-engine-<type>. Packages are looked up in the node_modules of the host's installation and of the directories above it (where Node finds the host's own dependencies), then in <dir>/node_modules of each absolute directory in UAG_ENGINE_PATH. Never in the working directory, NODE_PATH or Node's global folders, and a package whose entry point resolves outside its own directory is refused. Nothing is ever installed. Engine packages are loaded with require(): CommonJS, or ES modules on Node 22.12 or later. Each result is { engine, type, title, kinds, typeVersion, package, version } (typeVersion when the definition sets version; package and version when the type came from a package), which is what Formflow.engineRegistry takes.

planEngines(instances, options) takes the same arguments and does the same resolving and checking, but creates nothing: for each instance it returns the result's fields without engine, plus name, definition, args (what to pass to the definition's create) and failure(err), which words a failure to start as the loader does, with secret values left out. It is for a host that creates the engines itself. createEngine(definition, { name, options, env, config }) resolves the settings and creates one instance, as the UAG does.

Every definition is checked structurally, so a package built with another copy of this SDK, or with none, loads too. A package built for another engine contract version is refused, and the error names both versions.

Errors

Throw EngineError for a failure whose message the caller may see, such as "Quota exceeded for this API key." Any other error is reported to API callers only as "the engine failed (see the logs for request X)" (on an engine server, "The engine failed (see the engine server logs)."), because provider SDK errors can carry URLs and account details.

Helpers

  • requiredSchema(schema): the extraction schema with every field required, for strict structured outputs, and decode(answer) to map the answer back. An empty value stands for "not stated" and is left out. Numbers and yes/no fields travel as strings, keeping the field's title and description; a number written as an amount ($4,250) decodes to 4250, and anything else that is not a number is left out. A typed enum keeps its values, and an answer outside them is dropped.
  • parseJsonObject(text): the first JSON object in a text answer, or an EngineError.
  • toolResultText(result): a tool call's result as text for a model.

Conformance

const { checkEngine } = require('@formio/uag-sdk/conformance');

it('meets the engine contract', async () => {
    const report = await checkEngine(require('..'), { options: { client: fakeClient } });
    assert.deepStrictEqual(report.failures, []);
});

The check creates an instance and calls it. Pass options, such as a fake client, or the environment variables that let it answer. It checks that:

  • the definition and the engine have the contract's shape;
  • the engine uses the instance name and the model option;
  • extract returns only the fields it was asked about, or decide only the questions it was asked;
  • accepts, when set, lists only text and pdf; generate, when present, returns text, its model and a valid stopReason;
  • research stops at maxIterations, and reports every tool call it made;
  • an aborted signal stops the engine, including during research and generate.

A research round is the calls a model makes together; calls made one after another count as separate rounds.

License: MIT.

版本与发布记录

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

版本发布日期距今
2.0.0latest2026/11/132 天前
2.0.0-rc.11rc2026/11/105 天前
2.0.0-rc.102026/11/087 天前
2.0.0-rc.92026/11/079 天前
2.0.0-rc.82026/11/069 天前
2.0.0-rc.72026/11/0610 天前

关于 @formio/uag-sdk 的常见问题

@formio/uag-sdk 是什么?

Build engine packages for the Form.io Universal Agent Gateway (UAG): the engine contract, defineEngine, settings and instance resolution, the engine loader, and a conformance check.

如何安装 @formio/uag-sdk?

在项目目录运行 npm install @formio/uag-sdk,或使用 pnpm add / yarn add / bun add。

@formio/uag-sdk 支持 TypeScript 吗?

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

@formio/uag-sdk 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@formio/uag-sdk 需要什么版本的 Node.js?

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

@formio/uag-sdk 可以商用吗?

@formio/uag-sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@formio/uag-sdk 的下载量有多少?

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

@formio/uag-sdk 还在维护吗?

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