一、操作场景
业务系统需要直接调用 AICC 机密推理服务,且具备代码改造条件时,可在业务代码中集成 AICC Node.js SDK。集成后由业务侧自行发起加密请求并解密响应,无需在业务侧额外部署安全代理。若业务系统不便修改代码,可改用安全代理方式接入,请参见 AICC-Proxy 集成快速入门。
二、前提条件
·运行环境为 Node.js 18 及以上版本。
·SDK 同时提供 ECMAScript 模块(ES Modules,ESM)与 CommonJS 两种入口,并带有 TypeScript 类型声明。
·已提交 AICC 试用申请并经审核通过,服务已开通。
·已获取 API 密钥(Application Programming Interface Key,API Key)、安全会话服务地址、网关地址与模型名称。
·运行环境可访问安全会话服务与网关地址。
注意:说明:
注意:请妥善保管 API 密钥,不要将 API 密钥写入代码仓库。
三、概念说明
·安全会话服务:负责握手并下发会话密钥的服务,地址对应代码中的 <AICC_BASE_URL>。
·网关:接收加密请求并返回加密响应的服务,地址对应代码中的 <AICC_GATEWAY_URL>。
·端点:服务端集群的路由标识。SDK 按模型名称自动发现端点,你无需处理。
·ModelPool:按模型名称发现端点,并管理各端点的会话池。
·Request:一轮 HTTP 请求的加解密上下文。每轮请求需新建一个。
四、步骤1:安装 SDK
注意:说明:
注意:SDK 暂不直接提供下载。AICC 试用申请通过后,请你提交工单联系我们获取。
执行以下命令,安装 SDK。
npm install @tencent/tcaicc
五、步骤2:发起非流式请求
以下示例使用 OpenAI Chat Completions 协议。请先将代码中尖括号包围的占位符替换为实际值。
import { Protocol, SessionFactory, SessionPool, defaultPolicy, newModelPool } from '@tencent/tcaicc'const BASE_URL = '<AICC_BASE_URL>' // 安全会话服务地址const GATEWAY_URL = '<AICC_GATEWAY_URL>' // 网关地址const API_KEY = '<API_KEY>'const MODEL = '<MODEL_NAME>' // 模型名称const TTL = 8 * 60 * 60 // 会话寿命与模型/端点绑定时长,单位为秒async function main(): Promise<void> { // 1. 创建模型池。回调必须为每个端点新建一个独立的工厂。 const models = newModelPool(BASE_URL, API_KEY, TTL, () => new SessionPool(new SessionFactory(BASE_URL, API_KEY, { ttl: TTL }), 4), // 4 为池中最多保留的会话数 ) try { // 2. 每轮 HTTP 请求取一个 Request。 const req = await models.newRequest(MODEL, []) const policy = defaultPolicy(Protocol.OPENAI) // 3. 加密请求体。 const body = { model: MODEL, messages: [{ role: 'user', content: '你好' }] } const encBody = req.encryptRequest(Buffer.from(JSON.stringify(body)), policy) // 4. 发送请求。 const r = await fetch(GATEWAY_URL, { method: 'POST', body: encBody.toString(), headers: { 'Content-Type': 'application/json', 'X-AICC-Mode': '1', // 标识这是密文请求 Authorization: `Bearer ${API_KEY}`, }, }) const raw = Buffer.from(await r.arrayBuffer()) if (!r.ok) throw new Error(`gateway ${r.status}: ${raw.toString()}`) // 5. 解密响应体。 const plain = req.decryptResponse(raw, policy) console.log(JSON.parse(plain.toString()).choices[0].message.content) req.close() } finally { models.close() }}main().catch((e) => { console.error(e) process.exit(1)})
上述代码用到的三个接口如下。
new SessionFactory(baseURL, apiKey, options)
| 参数 | 类型 | 必填 | 说明 |
| baseURL | string | 是 | 安全会话服务的完整地址。SDK 直接向该地址发送 POST 请求。 |
| apiKey | string | 是 | API 密钥。 |
| options.ttl | number | 是 | 会话寿命,单位为秒。取值为 0 表示使用默认值 86400。 |
new SessionPool(factory, num)
| 参数 | 类型 | 必填 | 说明 |
| factory | SessionFactory | 是 | 会话工厂。 |
| num | number | 是 | 池中最多保留的会话数,取值需大于等于 1。 |
newModelPool(baseURL, apiKey, ttl, build)
| 参数 | 类型 | 必填 | 说明 |
| baseURL | string | 是 | 安全会话服务的完整地址。 |
| apiKey | string | 是 | API 密钥。 |
| ttl | number | 是 | 模型与端点的绑定时长,单位为秒。取值为 -1 表示永不过期。 |
| build | function | 是 | 为指定端点创建会话池的回调。必须为每个端点新建独立的 SessionFactory。 |
六、步骤3:发起流式请求
流式请求的步骤与非流式请求相同,区别有以下三处。
1. 请求体设置 stream: true。
2. 请求头增加 Accept: text/event-stream。
3. 使用 StreamDecryptor 逐个解密服务器发送事件(Server-Sent Events,SSE)。SDK 不解析 SSE,你的程序需自行读取每一行。
import type { Policy, Request } from '@tencent/tcaicc'async function streamChat(req: Request, policy: Policy, encBody: Buffer): Promise<void> { const r = await fetch(GATEWAY_URL, { method: 'POST', body: encBody.toString(), headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream', 'X-AICC-Mode': '1', Authorization: `Bearer ${API_KEY}`, }, }) if (!r.ok || !r.body) throw new Error(`gateway ${r.status}: ${await r.text()}`) const sd = req.newStreamDecryptor() const decoder = new TextDecoder() let buf = '' let seenLast = false for await (const part of r.body) { buf += decoder.decode(part, { stream: true }) let nl: number while ((nl = buf.indexOf('\\n')) >= 0) { const line = buf.slice(0, nl).replace(/\\r$/, '') buf = buf.slice(nl + 1) if (!line.startsWith('data: ')) continue const data = line.slice('data: '.length) if (data === '[DONE]') continue const [out, isLast] = sd.decryptEvent(Buffer.from(data), policy) if (out !== null) { // out 为 null 表示合成尾片,请勿转发 const choices = JSON.parse(out.toString()).choices ?? [] if (choices.length > 0) process.stdout.write(choices[0].delta?.content ?? '') } if (isLast) seenLast = true } if (seenLast) break } if (!seenLast) throw new Error('stream truncated: final chunk missing') process.stdout.write('\\n')}
注意:说明:
七、选择协议
defaultPolicy 的参数决定加密哪些字段,取值需与你所调用的接口一致。
| 接口 | 协议常量 |
| /v1/chat/completions | Protocol.OPENAI |
| /v1/messages | Protocol.ANTHROPIC |
| /v1/responses | Protocol.RESPONSES |
使用 OpenAI Chat Completions 以外的协议时,还需在建池回调中为工厂设置密文绑定的协议。
import { SessionFactory, Protocol } from '@tencent/tcaicc'const factory = new SessionFactory(BASE_URL, API_KEY, { ttl: TTL })factory.setProtocol(Protocol.ANTHROPIC) // 或 Protocol.RESPONSES
八、使用须知
·每轮 HTTP 请求需新建一个 Request。同一轮请求的所有密文必须来自同一个 Request。
·加密后请尽快发送请求。加密时间与服务端当前时间的差值超过 1 小时,请求会被拒绝。
·程序退出前调用 models.close()。
九、网关返回 400 且错误码为 decrypt_failed,如何处理?
通常是因为同一轮请求中混用了不同 Request 加密的密文,或加密后等待时间过长才发送。请为每轮请求新建 Request,并在加密后立即发送。
十、抛出 SecuritySessionError 时如何处理?
表示握手失败,或服务端拒绝了请求。请读取 e.code 判断原因。如果 e.code 为 AttestationFailed 或 CertVerifyFailed,表示 SDK 判定服务端不可信,请停止使用该服务并联系技术支持,请勿绕过验证。
十一、抛出 TransportError 时如何处理?
表示网络失败,请稍后重试。
十二、抛出 DecryptError 时如何处理?
表示密钥不匹配或密文已损坏。请丢弃当前响应或流,重新发起请求。
十三、抛出 ClosedError 时如何处理?
表示对象已关闭。请重新调用 models.newRequest 取一个新的 Request。
十四、抛出 ErrPolicy、ErrBodyFormat 或 ErrUnsupported 时如何处理?
表示请求体或加密策略不符合要求。请确认请求体是合法的 JavaScript 对象表示法(JavaScript Object Notation,JSON),并且协议常量与所调用的接口一致。