@atlanai/sdk

中文文档
0.9.0发布于 2026/11/11Apache-2.0TypeScriptESM + CJS近 30 天下载 1980

Atlan Agent Gateway management SDK

原始使用文档
文档目录12 节
  1. Install
  2. Quickstart
  3. Run an eval
  4. Scorer return values
  5. Run part of a dataset
  6. Control an eval lifecycle directly
  7. Tracing
  8. Claude Agent SDK
  9. LiteLLM deployment id
  10. Errors
  11. Runtimes
  12. LiteLLM deployment id on Bun, Deno and Workers

@atlanai/sdk

TypeScript/JavaScript SDK for the Atlan Agent Gateway: manage agents, skills, sessions, and workspaces, and optionally trace what your agents do.

Install

npm install @atlanai/sdk

Quickstart

import { AtlanClient } from "@atlanai/sdk";

const client = new AtlanClient({
  gatewayOrigin: "https://<your-gateway-host>",
  bearerToken: "<your-api-token>",
});

const agent = await client.agents.create({
  name: "support-triage",
  workspaceId: "workspace_01example",
});

const page = await client.agents.list({ limit: 10 });
console.log(`${page.items.length} agents`);

Methods read as client.<resource>.<action> — client.agents.get(agentId), client.sessions.messages.create(sessionId, {...}), and so on. Every public Agent Gateway operation is reachable this way; nothing requires reaching into a generated client directly.

Generated APIs are also typed by service and tag, including their middleware:

const releases = client.raw.registry.apis.releases.withMiddleware({
  post: async ({ response }) => response,
});
const page = await releases.registryListReleases({ limit: 10 });

These accessors preserve generated request/response types. Dynamic string service lookups remain available for code that discovers services at runtime.

Requests make one attempt by default. Applications can configure opt-in retry middleware with bounded attempts, jitter, cancellation and Retry-After support. See request errors for the common typed error and its retry metadata.

timeoutMs bounds each management operation, including token lookup, retries, and success/error body decoding. Pass a signal through generated API request overrides to cancel a call. Raw methods retain the deadline while their body is open: consume the response or cancel response.raw.body when finished.

Pass a default workspace once instead of repeating it on every call:

const client = new AtlanClient({ gatewayOrigin, bearerToken, workspace: "workspace_01example" });

The default also supplies required workspace query parameters on resource methods, for both positional arguments and explicit options objects. An explicit workspace value wins; without a default, required workspace parameters remain required at runtime.

Run an eval

Eval() executes the task and scorers, creates the Registry experiment, emits one trace per case, uploads trace-linked results, and finalizes the run. Registry derives the durable score summary during that finalization:

import { Eval } from "@atlanai/sdk";

const result = await Eval("Anthropic Evaluation", {
  data: () => [
    { input: "What is 2+2?", expected: "4" },
    { input: "What is the capital of France?", expected: "Paris" },
  ],
  task: async (input) => callModel(input),
  scores: [{
    name: "accuracy",
    scorer: ({ output, expected }) => output === expected ? 1 : 0,
  }],
}, { subjectKind: "agent", subjectId: "agent_01example" });

console.log(result.experimentId);

subjectKind and subjectId link the run to the agent it tested, which is how the Evals UI finds it; the agent ID (agent_...) is the id that client.agents.list() returns. A run with neither is still saved, but lists under no agent, and Eval() warns when it creates one.

Set ATLAN_API_KEY and ATLAN_WORKSPACE_ID. Replace inline data with dataset: "dataset_..." or one exact Registry dataset name. The runner refuses to proceed when tracing is disabled because trace-less eval rows break the evidence chain. After flushing, it reads every case trace back through the experiment filter before it uploads results; a missing trace marks the experiment failed. A Registry dataset case passes the value at the dataset's inputKey (default question) to the task and unwraps a single-key { value: ... } expected value. A run that produced no usable score at all is finalized and then refused; pass requireScores: false if an empty score set is expected.

Scorer return values

A scorer returns one of these. The scorer's own name is the metric name unless the value carries one.

() => 0.8                                        // a number (true counts as 1, false as 0)
() => null                                       // abstain: recorded, but not averaged
() => ({ score: 1, name: "exact", metadata: { why: "match" } })
() => [{ score: 1, name: "a" }, { score: 0, name: "b" }]   // a list of { score, ... } objects

A list holds { score, name?, metadata? } objects, not bare numbers. A plain object needs a score key.

By default a scorer that throws marks its whole case as errored. Pass { scorerErrors: "score" } in the options to keep the failure to that scorer: its metric is recorded as null. The message is stored on the case result (and under sourceMetadata.atlan_eval.scorer_errors on the persisted row), and the case stays successful unless no scorer produced a number. Only an exception thrown by the scorer counts: an unsupported return value, a duplicate or colliding score name, or a failed registration still errors the case.

Run part of a dataset

recordIds, where and limit run a subset of a Registry dataset. The run stays linked to the dataset, so each result keeps its datasetRecordId and the run can be compared with a full run of the same dataset on the records both ran. They apply in that order to the experiment's frozen snapshot, and a resume must pass the same selectors. where must be a synchronous function that returns a bool.

await Eval("Support smoke", {
  dataset: "support-daily-tasks",
  task,
  scores,
}, {
  where: (item) => (item.tags ?? []).includes("billing"),
  limit: 10,
});

Control an eval lifecycle directly

Resolve an existing dataset by artifact ID or exact name, then create the Registry experiment before another runner emits any traces:

import { createContextManifest, startExperiment } from "@atlanai/sdk";
import { propagateAttributes } from "@atlanai/sdk/tracing";

const contextManifest = await createContextManifest([{
  kind: "file",
  name: "CLAUDE.md",
  version: "git:0123456789abcdef0123456789abcdef01234567",
  digest: `sha256:${"0".repeat(64)}`,
}]);

const run = await startExperiment(
  client,
  "conversational-studio-daily", // exact name, or dataset_... ID
  { name: "candidate-run", config: { model: "example-model" } },
  { contextManifest },
);

await propagateAttributes(run.traceOptions, () => existingRunner());

run.experiment is the generated create response and run.id is its experimentId. The helper pins the resolved dataset version and context manifest in the immutable experiment config. Use it when another harness owns execution, result upload, and finalization.

Tracing

import { initLogger, wrapTraced } from "@atlanai/sdk/tracing";

const logger = initLogger({ projectName: "support-agent" });
const handleRequest = wrapTraced(async (input: string) => callModel(input));

await logger.traced(async (span) => {
  const output = await handleRequest("hello");
  span.log({ input: "hello", output });
}, { name: "handle-request", type: "task" });

await logger.flush();

Tracing has its own API key and lifecycle. It does not reuse the management client's transport. It ships as a separate subpath (@atlanai/sdk/tracing) so importing it doesn't pull OpenTelemetry into a management-only bundle. npm still installs the OpenTelemetry dependencies with the package because npm cannot make dependencies conditional on an imported subpath; splitting tracing into a second package would trade that install size for a second version and release lifecycle.

Vercel AI SDK v3-v6 uses its native OpenTelemetry path:

import * as ai from "ai";
import { initLogger, wrapAISDK } from "@atlanai/sdk/tracing";

initLogger({ projectName: "support-agent" });
const { generateText, streamText } = wrapAISDK(ai);

See evals and tracing for framework setup, context manifests, serverless flushing, and the tested integration matrix.

Claude Agent SDK

The Claude Agent SDK is wrapped in code rather than named in instrument: the package is ESM-only, so there is nothing for init() to patch.

import * as sdk from "@anthropic-ai/claude-agent-sdk";
import { getClient, init, wrapClaudeAgentSDK } from "@atlanai/sdk/tracing";

init({ serviceName: "support-agent" });
const { query } = wrapClaudeAgentSDK(sdk); // same exports as the SDK, traced

for await (const message of query({ prompt: "hello" })) {
  // unchanged
}
await getClient().flush();

Each query() call becomes one invoke_agent run with a chat span per model turn and an execute_tool span per tool call. Naming claude-agent-sdk in instrument prints this hint once and reports it manual in init(...).instrumentation.

LiteLLM deployment id

A LiteLLM proxy names the deployment that served a call in the x-litellm-model-id response header. After init() or initLogger(), the SDK records that value as atlan.model.ref on the call's LLM span, and x-litellm-call-id as atlan.model.call_id, so Atlan can price the call by deployment. No client code changes. The Atlan model gateway is read the same way: x-gateway-model-deployment (the LiteLLM deployment it billed), or else x-gateway-model-id (its model row), becomes atlan.model.ref, and x-gateway-model-name replaces gen_ai.response.model when the span is exported. The gateway's x-gateway-model-price (the price it settled the call at) becomes atlan.model.price, so the trace costs the call as Model Gateway usage does. atlan.model.gateway.type names the router (atlan-model-gateway or litellm).

It is on by default. On Node the SDK subscribes to undici's diagnostics channels and patches nothing, so clients built before init() are covered too. Only the built-in fetch (undici) is observed: clients on node-fetch, axios or node:http need litellmFetch() or recordModelRef(). Bun, Deno and Cloudflare Workers work differently; see Runtimes. Only 2xx responses are read, response bodies never are, and a value the span already carries is kept. The value needs an LLM span to land on: an llm span of your own, a provider instrumentor's span, or the AI SDK's doGenerate / doStream span.

Turn it off with init({ captureModelRef: false }) or ATLAN_CAPTURE_MODEL_REF=false. Off means the hook is not installed; it does not remove a hook that an earlier init() in the same process installed.

For a call the hook cannot see, pass litellmFetch() as the client's fetch. It wraps globalThis.fetch (or the fetch you pass) for that client only:

import OpenAI from "openai";
import { litellmFetch } from "@atlanai/sdk/tracing";

const openai = new OpenAI({ baseURL: litellmUrl, fetch: litellmFetch() });

Or record the value yourself inside an LLM span. recordModelRef writes to the active LLM span unless you pass span, returns true when it wrote anything, and never throws:

import {
  LITELLM_CALL_ID_HEADER,
  LITELLM_MODEL_ID_HEADER,
  recordModelRef,
} from "@atlanai/sdk/tracing";

if (response.ok) {
  recordModelRef(response.headers.get(LITELLM_MODEL_ID_HEADER), {
    callId: response.headers.get(LITELLM_CALL_ID_HEADER),
  });
}

A call with no LLM span at all has nowhere to record.

Errors

Every non-2xx response rejects with AtlanAPIError, with .status, .code, and (where the gateway includes one) .traceId. A failure before any response is also normalized as AtlanAPIError with status === 0:

import { AtlanAPIError } from "@atlanai/sdk";

try {
  await client.agents.get("agent_does_not_exist");
} catch (error) {
  if (error instanceof AtlanAPIError) {
    console.log(error.status, error.code);
  }
}

Runtimes

Node ≥20. The tracing subpath is written to also run in non-Node runtimes like Cloudflare Workers — pass configuration to init() explicitly there rather than relying on environment variables, since Workers has no ambient environment.

LiteLLM deployment id on Bun, Deno and Workers

Bun, Deno and Cloudflare Workers never publish undici's diagnostics events, so there init() wraps globalThis.fetch instead. The openai and @anthropic-ai/sdk clients keep the fetch they find when they are constructed, so a client built before init() is missed silently. Call init() before any client is built. On a runtime it does not recognise, the SDK installs nothing.

Bun, with a preload:

bun --preload ./atlan-init.ts ./app.ts

Deno, with the init module as the first import:

import "./atlan-init.ts"; // calls init() before anything below runs
import OpenAI from "openai";

Workers, with clients built in the handler after init():

import OpenAI from "openai";
import { init } from "@atlanai/sdk/tracing";

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    init({ serviceName: "edge-agent", apiKey: env.ATLAN_API_KEY, workspaceId: env.ATLAN_WORKSPACE_ID });
    const openai = new OpenAI({ apiKey: env.LITELLM_API_KEY, baseURL: env.LITELLM_URL });
    // ...
  },
};

When a client has to exist before init(), give it litellmFetch() instead. Workers without AsyncLocalStorage and Vercel Edge have not been tested.

版本与发布记录

@atlanai/sdk 共发布 23 个版本,首次发布于 2026/09/03;过去 12 个月发布了 23 个版本。

版本发布日期距今
0.9.0latest2026/11/113 天前
0.9.0-beta.1beta2026/11/0212 天前
0.8.22026/10/2223 天前
0.8.02026/10/2025 天前
0.8.0-beta.22026/10/2025 天前
0.6.02026/10/1530 天前

关于 @atlanai/sdk 的常见问题

@atlanai/sdk 是什么?

Atlan Agent Gateway management SDK

如何安装 @atlanai/sdk?

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

@atlanai/sdk 支持 TypeScript 吗?

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

@atlanai/sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@atlanai/sdk 需要什么版本的 Node.js?

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

@atlanai/sdk 可以商用吗?

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

@atlanai/sdk 的下载量有多少?

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

@atlanai/sdk 还在维护吗?

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