> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coze.cn/llms.txt
> Use this file to discover all available pages before exploring further.

本文指导你如何安装并使用扣子罗盘 Node.js SDK 进行数据上报。
# 准备工作 {#2ce1050e}
## 环境准备 {#13378398}
扣子罗盘 Node SDK [@cozeloop/ai](https://www.npmjs.com/package/@cozeloop/ai) 适用于 Node.js 18 及以上版本。安装之前，可执行以下命令确认你的 Node.js 版本。
> 推荐使用 [fnm](https://github.com/Schniz/fnm) 管理 Node.js 版本。

```Shell
# 查看 Node 版本   
node -v
```

## SDK 授权 {#508d6131}
在使用扣子罗盘 Node SDK 前，确保你已经完成了授权。详情请参考[SDK 鉴权](/cozeloop/authentication-for-sdk)。
# 安装扣子罗盘 Node.js SDK {#b3feb4a0}
执行以下命令安装扣子罗盘 Node.js SDK。
```Shell
npm install @cozeloop/ai
# or
pnpm install @cozeloop/ai
```

默认直接安装最新版本的 SDK。如果你想使用历史版本，单击[此处](https://github.com/coze-dev/cozeloop-go/blob/main/CHANGLOG.md)查看历史版本记录和 README。
# 初始化扣子罗盘 ApiClient {#29c31330}
初始化扣子罗盘 ApiClient 之后，才可以访问 SDK 提供的 API。
初始化时推荐通过环境变量动态获取访问密钥，以免硬编码引发数据安全风险。 设置完成环境变量后，你可以直接访问 SDK API 实现相应功能。
以下示例代码展示了如何使用扣子罗盘 ApiClient，并请求某个 API。
```TypeScript
const apiClient = new ApiClient({
  /**
   * Api baseURL, default value is:
   * * process.env.COZELOOP_API_BASE_URL
   * * https://api.coze.cn
   */
  baseURL: 'https://api.coze.cn',
  /**
   * Personal Access Token (PAT) or OAuth2.0 token, or a function to get token.
   * use process.env.COZELOOP_API_TOKEN when unprovided
   */
  token: '',
  /** Custom axios instance */
  axiosInstance: axios,
  /**
   * Partial [axios request-config](https://github.com/axios/axios?tab=readme-ov-file#request-config), excludes url, method, baeURL, data and responseType.
   */
  axiosOptions: {},
  /** Custom headers */
  headers: {},
});

const resp = await apiClient.post('/v1/api_url', {/** data */});
```

扣子罗盘 SDK 的主要能力组件如 Tracer、PromptHub 等都依赖 ApiClient实例或 ApiClient 配置参数。
# 示例代码 {#c736ee81}
> 示例代码：https://github.com/coze-dev/cozeloop-js/tree/main/examples/cozeloop-ai-node/src

## Trace 上报 {#947a9e15}
### Tracer 初始化 {#b358cfd6}
> 完整示例：https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/index.ts

参考以下代码初始化全局 Tracer 实例。（请尽早，比如在应用启动时。）
```TypeScript
// initialize tracer globally
cozeLoopTracer.initialize({
  /** workspace id, use process.env.COZELOOP_WORKSPACE_ID when unprovided */
  workspaceId: 'your_workspace_id',
  apiClient: {
    baseURL: 'https://api.coze.cn',
    token: 'your_api_token',
  },
  /** Allow ultra long text report */
  ultraLargeReport: true,
});    
```

### Root Span上报 {#fe321f34}
> 完整示例：https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/root.ts

建议将一次完整的用户请求串联成一个 Trace。在服务入口处，上报一个 Root Span，并在此注入相关的业务 ID 便于 Trace 定位和统计分析。
```TypeScript
import { cozeLoopTracer } from '@cozeloop/ai';

export async function runRoot() {
  // We recommend concatenating a complete user request into a trace,
  // so the recommended approach is to report a root span at the entrance of the entire execution
  await cozeLoopTracer.traceable(
    async () => {
      // execute your method
      const result = await doSomething();

      return result;
    },
    {
      name: 'TestRootSpan',
      type: 'RootSpanType',
      // you can set your own baggage fields (eg. userId),
      // these fields will be automatically passed through and set in all sub-spans
      baggages: {
        user_id: 'uid-123',
        message_id: 'msg-123',
        thread_id: 'thread-123',
        custom_id: 'custom-123',
      },
    },
  );
}
```

### 自定义 Span上报 {#02474a19}
> 完整示例：https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/basic.ts

扣子罗盘支持在任何关键节点进行自定义 span 上报，并自由设置关键信息和自定义 tag。每个`traceable`所包装的[span](https://opentelemetry.io/docs/concepts/signals/traces/#spans)实例将透传给内部业务方法，使用者可以在方法中消费。
```TypeScript
import { cozeLoopTracer } from '@cozeloop/ai';

export async function runCustom() {
  // Wrap any function to make it traceable
  await cozeLoopTracer.traceable(
    async parentSpan => {
      // Manually set input
      cozeLoopTracer.setInput(parentSpan, 'xxx');

      // Invoke any function, if it throws error, error will be caught and automatically set span as error
      const result = await doSomething();

      // Or, you can manually set error
      cozeLoopTracer.setError(parentSpan, 'custom error message');

      // You can also trace nested span, the parent-child relationship of span will be automatically concatenated
      await cozeLoopTracer.traceable(
        async childSpan => {
          // Set custom tags
          childSpan.setAttribute('custom-tag', 'xxx');

          await doSomething();
        },
        {
          name: 'TestCustomChildSpan',
          type: 'MyCustomType',
        },
      );

      // Automatically set return value as output
      return result;
    },
    {
      name: 'TestCustomParentSpan',
      type: 'MyCustomType',
    },
  );
}
```

### Model Span上报 {#a3ddf11c}
> 完整示例：https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/llm.ts

模型调用往往是最关键的节点，可将 span type 设置为 `SpanKind.Model`，并上报 Model的相关信息，在上报 input 和 output 时建议使用和遵循 SDK 提供的 `LoopTraceLLMCallInput` 和 `LoopTraceLLMCallOutput` 类型，以便在平台获得更好的观测体验。
```TypeScript
import { SpanKind } from '@cozeloop/ai';
import {
  COZELOOP_TRACE_BUSINESS_TAGS,
  cozeLoopTracer,
  type LoopTraceLLMCallInput,
} from '@cozeloop/ai';

export async function runModel() {
  // Wrap model invoke function to make it traceable
  await cozeLoopTracer.traceable(
    async span => {
      const input: LoopTraceLLMCallInput = {
        messages: [
          {
            role: 'user',
            content: 'hi',
          },
        ],
        tools: [
          {
            type: 'function',
            function: {
              name: 'test-tool',
              description: 'tool-description',
              parameters: {
                type: 'object',
                properties: {
                  name: { type: 'string' },
                },
              },
            },
          },
        ],
        tool_choice: {
          type: 'auto',
          function: {
            name: 'test-tool',
          },
        },
      };

      // Manually set input, if the input satisfies the LoopTraceLLMCallInput structure,
      // the results will be better displayed in the CozeLoop platform
      cozeLoopTracer.setInput(span, input);

      // Invoke/stream model
      const result = await fakeLLMCall();

      // Set model related tags
      cozeLoopTracer.setTags(span, {
        [COZELOOP_TRACE_BUSINESS_TAGS.MODEL_NAME]: 'custom-model',
        [COZELOOP_TRACE_BUSINESS_TAGS.MODEL_PROVIDER]: 'cozeloop',
        [COZELOOP_TRACE_BUSINESS_TAGS.CALL_OPTIONS]: {
          temperature: 0.5,
          max_tokens: 1000,
        },
        [COZELOOP_TRACE_BUSINESS_TAGS.INPUT_TOKENS]: 100,
        [COZELOOP_TRACE_BUSINESS_TAGS.OUTPUT_TOKENS]: 200,
        [COZELOOP_TRACE_BUSINESS_TAGS.TOKENS]: 300,
        // If you use streaming return, record the microsecond timestamp returned by the first Token,
        // and the SDK will automatically calculate the time spent on the first Token
        [COZELOOP_TRACE_BUSINESS_TAGS.START_TIME_FIRST_RESP]: 1741305600123456,
      });

      // Manually set output, if the output satisfies the LoopTraceLLMCallOutput structure,
      // the results will be better displayed in the CozeLoop platform
      cozeLoopTracer.setOutput(span, result);

      return result.choices;
    },
    {
      name: 'TestModelSpan',
      type: SpanKind.Model,
    },
  );
}
```

### 超大文本上报 {#09d01ef2}
> 完整示例：https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/large-text.ts

在 Tracer 初始化时，如果将 `ultraLargeReport` 设置为 `true`，则启用超大文本上报。当 Input / Output 内容超过 **1 MB** 时，将通过文件形式进行上报，并可在平台上查看完整内容；如果未启用该选项，超出大小限制的内容将会被丢弃。
```TypeScript
import {
  cozeLoopTracer,
  SpanKind,
  type LoopTraceLLMCallInput,
} from '@cozeloop/ai';

function generateLargeString(sizeInMB: number) {
  const repeats = sizeInMB * 1024 * 1024;
  return 'x'.repeat(repeats);
}

// You can enable the reporting of ultra-long texts by setting
// ultraLargeReport to true when you initialize tracer
export async function runLargeText() {
  await cozeLoopTracer.traceable(
    async span => {
      // Reporting of ultra-long texts will only take effect when the
      // input / output satisfies the LoopTraceLLMCallInput / LoopTraceLLMCallOutput structure
      const input: LoopTraceLLMCallInput = {
        messages: [
          {
            role: 'user',
            content: generateLargeString(2),
          },
        ],
      };

      // Manually set input
      cozeLoopTracer.setInput(span, input);

      // execute your method
      const result = await doSomething();

      return result;
    },
    {
      name: 'TestLargeTextSpan',
      type: SpanKind.Model,
    },
  );
}
```

### 多模态上报 {#283e5741}
> 完整示例：https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/multi-modality.ts

Model Span 的 Input / Output 可能包含图片或文件。当 Input / Output 遵循 SDK 提供的 `LoopTraceLLMCallInput` 和 `LoopTraceLLMCallOutput` 类型时，多模态资源将被自动处理和上报。图片和文件应使用标准的 MDN 格式或公网可访问的 URL
```TypeScript
import {
  cozeLoopTracer,
  SpanKind,
  type LoopTraceLLMCallInput,
} from '@cozeloop/ai';

export async function runMultiModality() {
  await cozeLoopTracer.traceable(
    async span => {
      // Reporting of multi modality will only take effect when the
      // input / output satisfies the LoopTraceLLMCallInput / LoopTraceLLMCallOutput structure
      const input: LoopTraceLLMCallInput = {
        messages: [
          {
            role: 'user',
            content: '',
            parts: [
              // current support base64 encoded image and file
              {
                type: 'image_url',
                image_url: {
                  url: 'data:image/png;base64,XXX',
                },
              },
              {
                type: 'file_url',
                file_url: {
                  name: 'test.txt',
                  url: 'data:text/plain;base64,XXX',
                },
              },
            ],
          },
        ],
      };

      // Manually set input
      cozeLoopTracer.setInput(span, input);

      // execute your method
      const result = await doSomething();

      return result;
    },
    {
      name: 'TestMultiModalitySpan',
      type: SpanKind.Model,
    },
  );
}
```

### 跨服务上报 {#39814584}
> 完整示例：https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/transfer-between-services.ts


1. 当需要得到当前上下文，并**提供**给下游服务时：
* 【CozeLoop SDK 协议上下文】你可以通过`injectWithCozeLoopHeaders`来将上下文信息注入headers：

```TypeScript
import { context, injectWithCozeLoopHeaders } from '@cozeloop/ai';

const headers: Record<string, string> = {};

injectWithCozeLoopHeaders(context.active(), headers);
```


* 【标准[W3C协议](https://www.w3.org/TR/trace-context/)上下文】你可以通过`propagation.inject`来将上下文信息注入headers：

```TypeScript
import { propagation } from '@opentelemetry/api';
import { context } from '@cozeloop/ai';

const headers: Record<string, string> = {};

propagation.inject(context.active(), headers);
```


2. 当需要**接收**来自上游服务的上下文时：
* 【CozeLoop SDK 协议上下文】你可以使用`extractWithCozeLoopHeaders`并传入带有 CozeLoop SDK 协议的 headers 来得到 Context：

```TypeScript
import { context, extractWithCozeLoopHeaders, cozeLoopTracer } from '@cozeloop/ai';

const extractedContext = extractWithCozeLoopHeaders(
  context.active(),
  headers,
);

context.with(extractedContext, () =>
  cozeLoopTracer.traceable(
    () => {
      doSomething();
    },
    {
      name: 'XXX',
      type: 'XXX',
    },
  ),
);
```


* 【标准[W3C协议](https://www.w3.org/TR/trace-context/)上下文】你可以使用`propagation.extract`并传入带有标准W3C协议的 headers 来得到 Context：

```TypeScript
import { propagation } from '@opentelemetry/api';
import { context, extractWithCozeLoopHeaders, cozeLoopTracer } from '@cozeloop/ai';

const extractedContext = propagation.extract(context.active(), headers);

context.with(extractedContext, () =>
  cozeLoopTracer.traceable(
    () => {
      doSomething();
    },
    {
      name: 'XXX',
      type: 'XXX',
    },
  ),
);
```

完整示例如下：
```TypeScript
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
import axios, { type AxiosRequestConfig } from 'axios';
import { ROOT_CONTEXT } from '@opentelemetry/api';
import {
  cozeLoopTracer,
  injectWithCozeLoopHeaders,
  context,
  extractWithCozeLoopHeaders,
} from '@cozeloop/ai';

import { doSomething } from './utils';

function setupMockServer() {
  const mockServer = setupServer(
    http.post(/\/mock\/service\/api\/endpoint/, async req => {
      // Simulate collecting headers in the request
      const headers: Record<string, string> = {};
      req.request.headers.forEach((value, key) => {
        headers[key] = value;
      });

      // Simulate a new context environment (for example, in a cross-service scenario) by
      // setting context to ROOT_CONTEXT, you don't need to take this step in real cross-service calls
      return await context.with(
        ROOT_CONTEXT,
        // Simulate service execution
        async () => await mockService({ headers }),
      );
    }),
  );

  return {
    start: () => mockServer.listen({ onUnhandledRequest: 'bypass' }),
    close: () => mockServer.close(),
  };
}

async function mockService(req: { headers: Record<string, string> }) {
  // Read the information of the current context from the headers

  const extractedContext = extractWithCozeLoopHeaders(
    context.active(),
    req.headers,
  );

  // Use the extracted context
  return await context.with(
    extractedContext,
    async () =>
      await cozeLoopTracer.traceable(
        async () => {
          const result = await doSomething();

          return HttpResponse.json({
            code: 0,
            msg: '',
            data: {
              result,
            },
          });
        },
        {
          name: 'ChildSpan',
          type: 'TransferBetweenServicesType',
        },
      ),
  );
}

export async function runTransferBetweenServices() {
  const mockServer = setupMockServer();
  mockServer.start();

  const result = await cozeLoopTracer.traceable(
    async span => {
      span.setAttribute('custom-tag', 'xxx');

      const headers: AxiosRequestConfig['headers'] = {};

      // Generate the information in the current context as headers
      injectWithCozeLoopHeaders(context.active(), headers);

      const resp = await axios.post(
        'http://mock/service/api/endpoint',
        {},
        {
          headers,
        },
      );
      return resp.data;
    },
    {
      name: 'ParentSpan',
      type: 'TransferBetweenServicesType',
      // The baggage fields will be automatically passed through
      baggages: {
        user_id: 'uid-123',
        message_id: 'msg-123',
        thread_id: 'thread-123',
      },
    },
  );

  mockServer.close();

  return result;
}
```

## Prompt 拉取与格式化 {#365da1db}
> 完整示例：https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/prompt/hub.ts

在使用 SDK 拉取 Prompt 前，需要在扣子罗盘的 Prompt 开发页面获取 Prompt Key 和版本号。
![Image=2826x669](https://p9-arcosite.byteimg.com/tos-cn-i-goo7wpa0wc/affe20efa44c471bb1d901e4e9bdb8af~tplv-goo7wpa0wc-image.image)
扣子罗盘 SDK 提供接口来拉取在扣子罗盘中创建并提交的 Prompt。通过 `hub.get()` 方法可以获取指定 Key 的 Prompt（支持指定版本或使用最新版本），然后使用 `hub.formatPrompt()` 方法完成变量替换。设置 `traceable` 为 `true` 后，SDK 会自动记录 Prompt 的获取和渲染过程，便于后续分析和优化（需完成 Tracer 初始化）。
以下是完整的示例代码。
```TypeScript
import { type Message, type PromptVariables, PromptHub } from '@cozeloop/ai';

// 1. 设置 ApiClient
const apiClient = new ApiClient({
  baseURL: 'https://api.coze.cn',
  token: 'your_access_token',
});

const hub = new PromptHub({
  workspaceId: 'your_workspace_id',
  apiClient,
  // 是否开启自动trace上报
  traceable: true,
});

// get prompt with version 0.0.1
const prompt1 = await hub.get('your_prompt_key', '0.0.1')
// get prompt with latest version
const prompt2 = await hub.get('your_prompt_key')

// format prompt to messages with variables
const messages = hub.formatPrompt(prompt1, { key: 'value' });
```

# 更多示例 {#8f7df8d6}
扣子罗盘 Node.js SDK 提供多种授权方式、常见使用方式的示例代码，便于开发者直接参考使用。 更多示例，访问 [Github](https://github.com/coze-dev/cozeloop-js/blob/main/examples)。
<!-- @cols-width: 121,279,462 -->
| | | | \
|**模块** |**示例文件** |**说明** |
|---|---|---|
| | | | \
|授权  |[oauth-jwt.ts](https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/auth/oauth-jwt.ts) |通过 OAuth JWT 方式实现授权。  |
| | | | \
|API Client |[api-client.ts](https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/api/api-client.ts) |基于 axios 的对基础 Loop 请求的轻量级封装，用于 PromptHub、Trace上报等。 |
| | | | \
|Prompt |[hub.ts](https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/prompt/hub.ts) |获取扣子罗盘空间内的 Prompt，或将 Prompt format。 |
|^^| | | \
| |[with-jinja.ts](https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/prompt/with-jinja.ts) |拉取扣子罗盘空间内的使用 Jinja2 模版的 Prompt，并进行变量替换。 |
|^^| | | \
| |[with-multi-part.ts](https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/prompt/with-multi-part.ts) |拉取扣子罗盘空间内的 Prompt，并注入多模态变量。 |
| | | | \
|Trace |\
| |[index.ts](https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/index.ts) |了解如何进行 Tracer 初始化。 |
|^^| | | \
| |[root.ts](https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/root.ts) |了解 root span 的概念以及如何进行 baggage 字段的上报，并自动串联到子 span 的字段。 |
|^^| | | \
| |[basic.ts](https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/basic.ts) |了解基础的自定义 span 上报与父子 span 串联。 |
|^^| | | \
| |[llm.ts](https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/llm.ts) |\
| | |了解 model span 的上报，包括上报模型相关的各种参数以及标准化的 Input / Output 上报。 |
|^^| | | \
| |[large_text.ts](https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/large-text.ts) |实现超大文本上报。 |
|^^| | | \
| |[multi_modality.ts](https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/multi-modality.ts) |实现多模态数据上报。 |
|^^| | | \
| |[transfer-between-services.ts](https://github.com/coze-dev/cozeloop-js/blob/main/examples/cozeloop-ai-node/src/tracer/transfer-between-services.ts) |Span 跨服务串联的示例。 |

# 异常处理 {#0cc60b23}

* 当调用SDK时缺少关键配置，SDK 会返回 [PropertyUnprovidedError](https://github.com/coze-dev/cozeloop-js/blob/main/packages/cozeloop-ai/src/error/common-error.ts)。当出现各类请求错误的时候，SDK 会抛出 [HttpError](https://github.com/coze-dev/cozeloop-js/blob/main/packages/cozeloop-ai/src/error/http-error.ts)，开发者可以根据自己的需求捕获对应的异常。
* 如果成功调用CozeLoop API，返回信息中 code 字段为 0。如果状态码为其他值，则表示接口调用失败，HttpError 会获取到异常接口的错误码，此时 msg 字段中包含详细错误信息。错误码详见：[错误码](https://www.coze.cn/open/docs/developer_guides/coze_error_codes)。
* span在自动或手动 end 后，默认异步批量定时上报到后端，为了避免影响业务主流程，在上报后端发生异常时将不会抛出错误，而是打印Error日志，这些日志一般带有`[cozeloop]`标识。开发者可以根据控制台Error日志，看到上报时发生异常记录的错误信息。
