@dxv-systems/ai

中文文档
0.2.2发布于 2026/11/10MITTypeScriptCommonJS近 30 天下载 4065

App client for the DXV AI gateway: OpenAI and Vercel AI SDK providers, Kennisbank Ask, passages and access groups, and agent runs. Server only.

原始使用文档
文档目录13 节
  1. Install
  2. Environment
  3. Credentials
  4. Features
  5. OpenAI client
  6. Images
  7. Vercel AI SDK
  8. Ask
  9. Access groups
  10. Passages and read
  11. Agent runs
  12. Swapping the provider
  13. Errors

@dxv-systems/ai

App client for the DXV AI gateway (/api/ai/v1): a standard OpenAI client, a Vercel AI SDK provider, Kennisbank Ask and agent runs, all with the app's own credentials and a per-feature label.

Server only. The helpers use gateway credentials. Importing the package in a browser bundle is a bug: every factory throws AiConfigError when window exists. Call it from route handlers, server actions and jobs.

Install

npm install @dxv-systems/ai                    # ask() and runs
npm install openai                             # only for @dxv-systems/ai/openai
npm install @ai-sdk/openai-compatible ai       # only if you use the AI SDK subpath

openai and @ai-sdk/openai-compatible are optional peer dependencies: install the one you use. openai() lives in @dxv-systems/ai/openai and aiSdk() in @dxv-systems/ai/ai-sdk, separate entry points so a bundler never resolves a package you did not install. The main entry (ask, createRuns, the errors, isAiUnavailable) imports neither.

Environment

VariableMeaning
DXV_AI_GATEWAY_URLFull gateway base. Default https://platform.dxvagentic.com/api/ai/v1.
DXV_AI_GATEWAY_TOKENGateway token. When set, it is sent as the bearer.
VERCEL_OIDC_TOKENUsed when DXV_AI_GATEWAY_TOKEN is not set. Read through @vercel/oidc on every request (it refreshes; nothing is cached at import time).
AI_PROVIDERdxv (default), openai, anthropic or openrouter. Swaps inference only, see below.
AI_MODEL_LOW AI_MODEL_MEDIUM AI_MODEL_HIGH AI_MODEL_MAX (AI_MODEL_AUTO)Model id each dxv/* alias maps to under a non-dxv provider.
OPENAI_API_KEY OPENROUTER_API_KEY ANTHROPIC_API_KEYKey of the swapped-to provider.

Credentials

The package looks for a credential in this order on every request:

  1. DXV_AI_GATEWAY_TOKEN, when set.
  2. A Vercel OIDC token. On a Vercel deployment it is injected for you. Locally, run vercel env pull in the app: it writes VERCEL_OIDC_TOKEN to .env.local. The token is short lived; pull again when it expires.

With neither source the call throws AiConfigError naming these variables and vercel env pull.

Never put a provider key in the app unless you set AI_PROVIDER to swap away from DXV. With the default provider the gateway holds the keys, budgets and usage records; an app-held key bypasses all of it.

Features

Every factory takes { feature }, the label that appears in the gateway's usage and AI feature registry (X-DXV-Feature). It must match ^[A-Za-z0-9._:-]{1,100}$, otherwise the factory throws AiConfigError. Use a stable key per product feature (search.summary), never a per-user value.

OpenAI client

import { openai } from "@dxv-systems/ai/openai";

const client = openai({ feature: "support.reply" });
const res = await client.chat.completions.create({
  model: "dxv/medium", // dxv/low | dxv/medium | dxv/high | dxv/max | dxv/auto
  messages: [{ role: "user", content: "Summarise this ticket" }],
});

openai() returns a plain OpenAI instance from the openai package. The base URL, the per-request bearer and X-DXV-Feature are set for you; the API key is a placeholder the gateway ignores. Retries are the SDK's own (the gateway's x-should-retry stops retries on budget errors). Errors stay the SDK's errors (APIError, RateLimitError, ...).

openai({ feature, context }) also sends X-DXV-Context (at most 2000 characters, Latin-1, no control characters) for features with retrieval enabled.

Images

The gateway generates images. Use the SDK's own images.generate():

import { openai } from "@dxv-systems/ai/openai";

const res = await openai({ feature: "content.image" }).images.generate({
  model: "google/gemini-3.1-flash-image",
  prompt: "A red bicycle against a white wall",
  aspect_ratio: "4:5", // a gateway field, not in the SDK's types
} as never);
const image = Buffer.from(res.data![0].b64_json!, "base64");
  • model is an exact image model id. A dxv/* alias or a text model is a 400.
  • The gateway forwards prompt, n (1 to 10), resolution, aspect_ratio, size, quality, output_format, background, output_compression and input_references (1 to 8 source images for an edit, each { type: "image_url", image_url: { url } } with a data: or https:// URL). Other fields, such as the SDK's response_format, are dropped, not refused.
  • stream: true is a 400, and a request body over 4 MB is a 413.
  • With another AI_PROVIDER, the model id and the extra fields are that provider's.

Vercel AI SDK

import { generateText } from "ai";
import { aiSdk } from "@dxv-systems/ai/ai-sdk";

const dxv = aiSdk({ feature: "support.reply" });
const { text } = await generateText({ model: dxv.chatModel("dxv/high"), prompt: "..." });

Ask

import { ask } from "@dxv-systems/ai";

const r = await ask({ feature: "faq", question: "What are the opening hours?", endUser: session?.userId ?? null });
r.answer; r.sources; r.needsHuman; r.insufficientContext; r.reviewRequired;

endUser is required (string | null): it narrows the gateway's per-person limits. path optionally narrows the search: one folder or document, or a list of 1-10 searched as one set (for example the folders a user's role may read). Every entry must lie inside the feature's registered folders, else the gateway answers 400 (AiRequestError).

Access groups

When who may see a document is data in your app (roles set per document), map your roles onto access groups. The kennisbank never sees your users or roles, only these keys.

import { ask, setDocumentGroups, syncAccess } from "@dxv-systems/ai";

// After each deploy and nightly: the whole state. Groups and labels left out are removed.
await syncAccess({
  feature: "assistent",
  groups: roles.map((r) => ({ key: `role_${r.id}`, name: r.name })),
  documents: docs.map((d) => ({ path: d.kennisbankPath, groups: d.roleIds.map((id) => `role_${id}`) })),
});

// When an admin changes one document's roles.
await setDocumentGroups({ feature: "assistent", path: doc.kennisbankPath, groups: ["role_17"] });

// Ask for a user: only documents labelled with one of their groups are searched.
await ask({ feature: "assistent", question, groups: [`role_${user.roleId}`], endUser: user.id });

Group keys are your own stable ids (A-Za-z0-9._:-, 1-100 characters); renaming a role only changes name. A document without any group, or a group you never defined, gives nothing: send a group such as everyone to every user for documents all may see. Without groups, ask() searches every document in the feature's folders. Labelled paths must lie inside those folders. A preview has its own groups, apart from production, so sync there too.

Passages and read

For an app with its own model loop (tools), retrieve without an answer, scoped exactly like ask():

import { passages, read } from "@dxv-systems/ai";

const hits = await passages({ feature: "assistent", question, groups, limit: 8 }); // [{ id, path, version, heading, page, text }]
const page = await read({ feature: "assistent", path: hits[0].path, offset: 0, groups }); // { excerpt, offset, totalChars, truncated, ... }

read() with groups answers a document none of them may see as 404 (AiRequestError), the same as a missing one.

Agent runs

import { createRuns } from "@dxv-systems/ai";

const runs = createRuns({ feature: "intake" });
const { id, duplicate } = await runs.fireRun(agentUuid, { requestId: 12 }, { eventKey: "request:12" });
const state = await runs.pollRun(id, { deadlineMs: 120_000 }); // { id, status, createdAt, updatedAt, result, error? }
const many = await runs.pollMany([id1, id2], { deadlineMs: 120_000 }); // one shared deadline
  • fireRun(agentUuid, input, { eventKey?, replyTo?, timeoutMs = 15000 }): one POST, no retry, redirects never followed (a 3xx throws RunRefusedError, so the token cannot be sent elsewhere). 202 gives { id, duplicate: false }; 200 skipped: "duplicate" (same eventKey within 24 h) gives { id, duplicate: true }; any other 2xx shape throws RunRefusedError. eventKey uses the feature charset. A timeout throws RunNoAnswerError: the run may have started, so do not blindly re-send without an eventKey.
  • Reply-to fallback (explicit behaviour): if a request with replyTo is answered 400 or 404 (the gateway cannot place the reply, for example the parent run is unknown), it is re-sent once without replyTo and starts a fresh run. Nothing else is retried; a request without replyTo is never re-sent.
  • getRun(id): a 404 reads as { status: "failed", error: "not found" }.
  • pollRun(id, { deadlineMs, intervalMs = 3000 }): polls until completed, failed or cancelled. At the deadline it returns the last state (never throws for a deadline). Brief AiUnavailable / AiRateLimited blips during polling are tolerated; if no state was ever read, the last error is thrown.
  • pollMany(ids, { deadlineMs, intervalMs? }): all runs share one deadline.
  • RunStatus = "pending" | "implementing" | "completed" | "failed" | "cancelled".

Swapping the provider

AI_PROVIDER changes inference only (openai() and aiSdk()):

AI_PROVIDERBase URLKey
openaihttps://api.openai.com/v1OPENAI_API_KEY
openrouterhttps://openrouter.ai/api/v1OPENROUTER_API_KEY
anthropichttps://api.anthropic.com/v1/ (OpenAI-compatible endpoint)ANTHROPIC_API_KEY

dxv/* model ids are rewritten to the id in AI_MODEL_LOW|MEDIUM|HIGH|MAX (dxv/auto uses AI_MODEL_AUTO, else AI_MODEL_MEDIUM). An alias without a mapping is an AiConfigError (through openai() the SDK surfaces it as a 400 BadRequestError carrying the same message, so it is not retried); a model id that is not a dxv/* alias is passed through. No X-DXV-* headers are sent. ask(), passages(), read(), syncAccess(), setDocumentGroups() and createRuns() exist only on the DXV gateway and throw AiConfigError under another provider.

Errors

Exported classes, each with optional status and code. Messages never contain a token.

ErrorWhenWhat to do
AiUnavailable503, any 5xx, network failureThe degraded mode: show a fallback, retry later.
AiBudgetExceeded429 budget_exceeded (has retryAfterSeconds)The app's monthly cap is reached. Do not retry in a loop; degrade and tell DXV.
AiRateLimitedother 429 (has retryAfterSeconds)Back off for retryAfterSeconds.
AiRequestErrorother 4xx (gateway message and code)A bug or bad input; fix the call.
AiConfigErrorbad feature, missing credential, bad env, browser use, unmapped aliasFix the configuration.
RunNoAnswerErrorfireRun timed outThe run may exist. Re-send only with an eventKey.
RunRefusedErrorfireRun got a 3xx or an unexpected 2xxReport it; do not retry.

openai() throws the OpenAI SDK's own errors. Use isAiUnavailable(err) to treat this package's AiUnavailable and the SDK's 5xx and connection errors as one degraded mode:

try {
  await client.chat.completions.create(...);
} catch (err) {
  if (isAiUnavailable(err)) return fallback();
  throw err;
}

版本与发布记录

@dxv-systems/ai 共发布 7 个版本,首次发布于 2026/10/31;过去 12 个月发布了 7 个版本。

版本发布日期距今
0.2.2latest2026/11/103 天前
0.2.12026/11/103 天前
0.2.02026/10/3113 天前
0.1.22026/10/3113 天前
0.1.12026/10/3113 天前
0.1.02026/10/3113 天前

关于 @dxv-systems/ai 的常见问题

@dxv-systems/ai 是什么?

App client for the DXV AI gateway: OpenAI and Vercel AI SDK providers, Kennisbank Ask, passages and access groups, and agent runs. Server only.

如何安装 @dxv-systems/ai?

在项目目录运行 npm install @dxv-systems/ai,或使用 pnpm add / yarn add / bun add。

@dxv-systems/ai 支持 TypeScript 吗?

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

@dxv-systems/ai 支持 ESM 还是 CommonJS?

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

@dxv-systems/ai 需要什么版本的 Node.js?

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

@dxv-systems/ai 可以商用吗?

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

@dxv-systems/ai 的下载量有多少?

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

@dxv-systems/ai 还在维护吗?

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