手册

接入 AI 服务

你已经在运营 AI 接口:用 new-api 搭的中转站、网关、聚合商,或者自己部署的开源模型。TapeAPI 在它前面加一个签名旁路。你的接口、密钥和计费完全不变;你的用户照旧用官方 SDK,只改 base URL。

本指南面向在上游服务商条款范围内合规经营的服务方(见合规)。

你得到什么

  • 链上身份。 服务就是一枚 TapeOut 电路的容器。谁在回答,查链就知道;换域名、换服务器,用户按链上记录自动跟随。
  • 钉在链上的价目表。 清单的 ai 字段(TAP-20 §3.9)列出每种 API 格式一个端点,以及你的价目表:每个模型、每个币种一个价格,按每百万 token 计,需要时再加缓存价与推理价。任何人都能重算一次调用应当多少钱。
  • 每次调用一份签名的用量回执(TAP-21 §3.5):模型、token 数、各币种金额、回答是否完整,以及确切的请求字节与回应字节的哈希,由电路持有人委托的密钥签名。回执随回答一起送达(一个响应头,或官方 SDK 会忽略的 SSE 注释),不读回执的客户端什么都不受影响。
  • 不托管任何东西。 TapeAPI 不持有你的密钥、资金或流量。旁路跑在你自己的机器上;身份和价目表在链上,不经过我们的任何服务器就能读取。
  • 用户不用改代码。 OpenAI Chat Completions、OpenAI Responses、Anthropic Messages 与 OpenAI Embeddings 逐字节透传,流式也一样。 Claude Code、Codex 和官方 SDK 照常使用。

回执证明什么,不证明什么

回执证明的是:谁回答的(链上 signer)、针对哪些确切的请求字节、给出了哪些确切的回应字节、声称了多少用量与价格。它不证明实际运行的是哪个模型:服务方可以把便宜模型的回答标成贵模型。签名带来的是这种替换可追责:回执不可抵赖,抽检(任何人发测试题、公开结果)会留下证据。请对你的用户如实说明这一点。

选择旁路的运行方式

同一个旁路(@tapeapi/server/ai-proxy 的 createAIProxy)有几种包装。无论选哪种,都由你自己运行:它经手用户的 API 密钥,所以 TapeAPI 永远不替你托管。

你在用选这个位置
new-apidocker-compose 一键包:new-api 加上它前面的旁路,两者都只在回环地址上,由你的 HTTPS 反向代理对外examples/new-api-sidecar/
任何 OpenAI 或 Anthropic 兼容接口,有服务器Node:示例入口,或把 createAIProxy 嵌进你自己的服务(它是 fetch 风格的处理函数)examples/ai-proxy/index.mjs
这类接口,没有自己的服务器Cloudflare Worker,放在你自己的主机名上、你的接口前面examples/ai-proxy/worker.js
LiteLLM Proxy计划做成 LiteLLM 回调插件;在那之前,把 Node 或 Worker 版旁路放在 LiteLLM 前面暂无

旁路放在网关前面,不嵌进网关里面:new-api 的文本转发没有钩子;而且放在前面,回执覆盖的才是你向自己用户收取的价格与用量。

new-api 一键包速览

git clone https://github.com/BruceLanLan/tapeapi.git && cd tapeapi/examples/new-api-sidecar
cp env.example .env && cp models.example.json models.json     # 两个都要填
docker compose up -d

你的反向代理把 https://api.example.com 转给旁路(127.0.0.1:8080),把 new-api 的网页控制台用单独的主机名转给 127.0.0.1:3000。身份变量设齐之前,旁路处于设置模式,并说明缺了什么。完整步骤、反向代理设置、如何把旁路加进已有的 new-api 部署、以及如何不用 Docker 在本地试一遍,见一键包的 README。

价目表

价目表是一个 JSON 数组;旁路把它发布在清单里,并按它为每份回执计价:

[
  { "id": "claude-sonnet-4-5", "aliases": ["claude-sonnet-4-5-20250929"], "prices": [
    { "currency": "USDT", "unit": "1M tokens", "input": "3", "output": "15", "cacheRead": "0.3", "cacheWrite": "3.75", "cacheWrite1h": "6" }
  ] },
  { "id": "text-embedding-3-small", "formats": ["openai-embeddings"], "prices": [
    { "currency": "USDT", "unit": "1M tokens", "input": "0.02", "output": "0" }
  ] }
]
  • 币种:BEM、BNB、USDT、USDC、ETH、USD1,或仅作展示的 USD;每个币种一项。
  • 价格是按每百万 token 计的十进制字符串:input 与 output,可选 cacheRead、cacheWrite、cacheWrite1h 与 reasoning。
  • 回执按上游报告的模型名,精确匹配每个 id 与别名。如果你的网关会改写模型名,把上游报告的名字写进 aliases,否则这些回执没有价格。
  • 价格只是公示,现在不结算。 用户照旧按现在的方式付费给你;回执里的金额是一个可以核对的声明,不是一笔付款。

身份与发布清单

身份的步骤与任何 TapeAPI 服务相同(见运行服务),在持有人操作台 tapeapi.fun/console 完成:连接持有电路的钱包,生成服务密钥(它就是旁路的 SIGNER_KEY),签委托(操作台从旁路设置模式的健康检查里读出签名地址;委托有效 90 天),然后把清单发布上链。

现状: 持有人操作台目前还不能发布带 ai 字段的清单。它的发布步骤只接受自己构造的字段(外加形状完全符合的 mcp 字段),所以会拒绝旁路的清单,不发交易。生成密钥、签委托可以照常进行,旁路也会照常签回执;但清单上链之前,按 TapeOut 名字解析服务的客户端找不到它。情况变化时本指南会更新。

续期。 在委托的最后 30 天内续期:操作台第 4 步选“续期”(服务密钥不变),设置新的 DELEGATION_EXPIRES 与 DELEGATION_SIG,重启旁路,再发布一次清单。委托过期后服务会停止,直到续期。

你的用户要做什么

密钥和 SDK 都不变,只把 base URL 换成你清单里对应格式的端点:

客户端base URL
OpenAI SDK 与各类 OpenAI 兼容工具https://api.example.com/v1
Anthropic SDK、Claude Code(ANTHROPIC_BASE_URL)https://api.example.com(SDK 自己加 /v1)
Codex(config.toml 里的 base_url,wire_api = "responses")https://api.example.com/v1

核验回执。 开发者包裹官方 SDK 的 fetch;之后每个回答都会对照链上清单核验(signer、价目表、确切字节),核验失败就报错,从不静默吞掉:

import OpenAI from 'openai'
import { createTapeAPI, rpcUrlsFor, ai } from '@tapeapi/sdk'

const api = createTapeAPI({ rpcUrls: rpcUrlsFor(56) })            // 不同运营方的 BNB Chain 节点,两家一致才算
const svc = await api.resolve('42.1013.tape')                    // 你的服务的 TapeOut 名字
const fetch = ai.createVerifyingFetch({ api, service: svc })
const baseURL = svc.manifest.ai.endpoints.find((e) => e.format === 'openai-chat').baseUrl
const client = new OpenAI({ baseURL, apiKey: process.env.RELAY_KEY, fetch })

Claude Code 与 Codex 用户自己读不到回执。他们在本机运行核验代理 tapeapi-verify,把客户端指向它:

npx -y --package=https://github.com/BruceLanLan/tapeapi/releases/download/v0.7.0/tapeapi-sdk-0.7.0.tgz tapeapi-verify 42.1013.tape
ANTHROPIC_BASE_URL=http://127.0.0.1:8790 claude          # Codex:OPENAI_BASE_URL=http://127.0.0.1:8790/v1 codex

它在链上解析你的服务,回答原样透传,每次调用打印一行结论;加 --strict 时,核验失败会变成客户端看得到的错误。单份回执也可以贴到 核验页。这些都需要你的清单已经上链(见上面的现状)。

请求加盐(两者默认开启)。 回执带 requestSha256,即确切请求字节的 SHA-256,而官方 SDK 每次都用同样的方式序列化请求。于是短提示词("是"、一个要做嵌入的词、已知问题清单里的一问)可以通过对猜测取哈希,从别人分享的回执上确认出来。所以 createVerifyingFetch 与 tapeapi-verify 会在回执路径上每个请求正文的 JSON 文本之后追加 64 个随机空白字符(空格、制表、换行、回车,共 128 个随机比特),回执按实际发出的字节核验。

  • 为什么不影响提示词缓存: JSON 允许值之后出现空白(RFC 8259 §2),上游解析出的请求完全相同。这些空白在所有字符串之外,不属于任何消息,也不会变成 token;缓存(OpenAI 的自动前缀缓存、Anthropic 的 cache_control 断点)按解析后提示词的 token 取键,而 token 完全一样。
  • 不增加、也不修改任何字段。 尤其是 user(OpenAI)和 metadata.user_id(Anthropic,Claude Code 本来就会设置)保持客户端写的样子:网关靠它们把同一段对话路由到同一个账号,缓存才能命中。
  • 不处理的: 压缩过的正文(Content-Encoding 不是 identity,追加会破坏它)、非 JSON 正文,以及所有不出回执的路径。 salt: false / --no-salt 完全按客户端写的字节发送。
  • 如果客户端与旁路之间有代理重新序列化请求,字节本来就会变;这时回执核验会在 requestSha256 上失败,这样的代理因此会暴露出来。
  • 尚未实测: 按 JSON 语法,所有上游都必须接受尾随空白,测试也对参考旁路验证过;对 OpenAI Chat、OpenAI Responses、 Anthropic Messages 线上接口的实测还没有做。

费用

没有强制协议费,今天协议也不收任何费用:ai 字段里的价格只是公示、不结算,付费调用用的托管合约还没有部署(要先通过独立审计)。以后付费调用经由 TapeAPI 托管合约结算时,默认从服务方所得中扣 1% 作为维护贡献(用户的价格不变);任何服务方都可以为自己的服务把它设为 0,运营方没有费率开关。见 docs/FEES.md。

局限

  • 回执证明谁回答的、声称了什么,不证明运行的是哪个模型(见上)。
  • 带回执的格式:OpenAI Chat Completions、OpenAI Responses、Anthropic Messages、OpenAI Embeddings。/v1 下的其它路径原样透传、不带回执。暂不支持:Gemini 原生接口、WebSocket 模式(Realtime、Responses WebSocket)、Batch。
  • 回执在旁路内存里保留一小时(可配置);随回答送达的那一份才是主要的。免费的 receipt 方法按 IP 单独限流。如果你的上游回答 id 可以猜(Ollama 的是 chatcmpl- 加一个小于 999 的数,旁路日志会提示),请打开 requireRequestHash(RECEIPT_REQUIRE_HASH=1),让取回执必须同时给出请求哈希。
  • 旁路自己不做鉴权:用户的密钥原样交给你的网关,由网关决定。
  • 旁路会把客户端的会话头(x-claude-code-session-id、session-id、thread-id)转发给你的网关(客户端期望如此);拿到它们的一方能把同一用户的请求串成一段会话。设 FORWARD_SESSION_HEADERS=0(forwardSessionHeaders: false)即不转发;用户也可以在自己这一侧用 tapeapi-verify --strip-session-headers 做到同样的事。

合规

TapeAPI 面向在上游服务商条款范围内合规经营的服务方。身份、价目与信誉在链上,不被任何单一平台绑架;但本协议不提供、也不帮助规避上游服务商的封禁或地区限制。

安全

  • 旁路经手用户的 API 密钥。请自己运行,放在你自己的机器或账户上;不要交给任何第三方托管。
  • 服务密钥(SIGNER_KEY)为每份回执签名。不要让它进 git、聊天或截图;一旦泄露,在操作台生成新密钥并重签委托,而不是续期。
  • 旁路不保存提示词或回答:它只把签过名的回执(哈希、模型、token 数、金额)保留一小时,别无其它。

为 TapeOut 做一个服务层的想法来自 @Theairresearch。

在 GitHub 上编辑此页