1. ChatGPT Work Data agent 的模型调用侧:多租户问数为什么先划隔离键
OpenAI 近期把 Data agent 放进 ChatGPT Work,让用户通过自然语言连接企业数据、分析变化并生成可分享的交互式仪表盘。对多租户 SaaS 开发者来说,交互层再顺滑,底层也会落到一次次模型请求:A 租户问“本月退款率为什么上升”,B 租户问“库存周转是否异常”,每一次问数、解释、图表摘要、多轮追问都会消耗 Token。本文不讨论 Data agent 的前端体验,只聚焦模型调用侧:当这些请求从你的 SaaS 后端发出时,TaoToken 的隔离键怎么划,才能既能计量,又能限流,还能对账。准备凭据时,先到 TaoToken 官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=tenant_isolation_intro 获取 Key,并把模型请求的 Base URL 设为 https://taotoken.net/api。
这里“隔离键”不是单指一个字段,而是一组能贯穿请求、Key、日志、账单的标识。最小集合至少包括 tenant_id、workspace_id、scene、env。tenant_id 回答“是谁在消耗 Token”;workspace_id 回答“租户内哪个项目或工作区在消耗”;scene 回答“是问数对话、仪表盘刷新、字段解释还是报告摘要”;env 回答“是否生产流量”。如果只用一个全局 Key 接所有租户,短期省事,长期会出现三个后果:第一,账单无法拆分,财务只能按总用量摊派;第二,限流互相影响,一个租户的批量问数把其他租户拖慢;第三,排障困难,出现异常模型调用时无法快速定位租户与场景。
多租户问数的 Token 消耗和普通聊天不同。普通聊天通常是一问一答,而问数场景经常包含 schema 摘要、指标解释、SQL 生成辅助、结果解释、图表标题、异常归因、多轮追问。一次用户点击可能触发多次模型调用。如果没有隔离键,你只知道“今天用了多少 Token”,不知道“谁用的、为什么用、是否该由该租户承担”。所以隔离键的第一目标不是安全口号,而是成本归因与限流边界。
本文给出可复现产出:一套隔离键规则,一张租户账单表,以及 TaoToken 在模型调用侧的 Key 与 Base URL 配置方式。文中不会让 MCP、Agent 或代理直接连接 Oracle、MySQL 生产库;所有 SQL 和命令都由你在本地或测试环境执行,问数 SQL 应由你的数据服务在受控只读副本上完成。
2. 隔离键四段式规则:tenant_id + workspace + scene + env
先确定规则,再写代码。很多团队反过来:先在代码里到处传 tenantId,最后发现日志字段不统一、Key 命名混乱、账单无法聚合。建议把隔离键固定为四段式:
| 段位 | 字段 | 示例 | 来源 | 是否进入 Key 别名 | 是否进入请求头 | 用途 |
|---|---|---|---|---|---|---|
| 1 | tenant_id | acme | 服务端会话/JWT | 是 | 是 | 租户账单、限流、审计 |
| 2 | workspace_id | analytics | 服务端上下文 | 建议 | 是 | 租户内项目分摊 |
| 3 | scene | data_qa_chat | 路由/任务类型 | 是 | 是 | 区分问数、刷新、解释 |
| 4 | env | prod | 部署环境 | 是 | 是 | 隔离测试与生产消耗 |
Key 别名建议采用:
tt_{tenant_id}_{env}_{scene}_{purpose}例如:
tt_acme_prod_data_qa_chat tt_acme_prod_dashboard_refresh tt_globex_staging_schema_explain这样在控制台看到 Key 别名时,就能知道它属于哪个租户、哪个环境、哪个场景。请求头建议至少带:
X-Tenant-Id: acme X-Workspace-Id: analytics X-Scene: data_qa_chat X-Env: prod X-Request-Id: 01J...注意:tenant_id 必须由服务端鉴权后注入,不能信任前端 body 里的 tenantId。否则租户 A 可以伪造租户 B 的 ID,导致账单和限流全部错位。更稳的做法是:网关从 JWT、Session 或 API Gateway 上下文里取 tenant_id,然后覆盖请求体中的同名字段。
隔离键规则可以直接落成配置:
{ "isolationKey": { "tenantId": { "source": "server_session", "required": true, "trustClient": false }, "workspaceId": { "source": "server_context", "required": false, "default": "default" }, "scene": { "source": "route_or_task", "required": true, "allowed": [ "data_qa_chat", "dashboard_refresh", "schema_explain", "report_summary" ] }, "env": { "source": "deployment_env", "required": true, "allowed": ["dev", "staging", "prod"] } } }这份配置本身不调用模型,但它是后续 Key 创建、请求头注入、日志落库的依据。你可以把它放在配置中心,也可以放在环境变量里。关键是所有问数请求都必须经过同一套提取逻辑,而不是每个服务各自拼字段。
3. 在 TaoToken 创建租户级 Key:控制台路径与 Base URL 配置
隔离键确定后,下一步是准备凭据。TaoToken 的官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=create_tenant_key ,你可以从这里进入控制台。创建 Key 时建议按“租户 + 环境 + 场景”粒度来建,不要把生产、测试、问数、摘要全部塞进一个 Key。
推荐步骤:
- 打开 TaoToken 官网,登录控制台。
- 进入 API Keys 页面,为每个租户创建独立 Key。
- Key 别名按第 2 节的规则命名,例如
tt_acme_prod_data_qa_chat。 - 复制 Key 后只放在服务端环境变量或密钥管理服务中,不要写进前端、移动端或公开仓库。
- 把模型请求的 Base URL 统一设为
https://taotoken.net/api。 - 在测试环境先用一个租户 Key 发一条最小请求,确认鉴权、模型、用量字段都正常。
环境变量可以这样放:
export TAOTOKEN_API_KEY="YOUR_API_KEY" export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_MODEL="YOUR_MODEL_ID"如果你使用 OpenAI 兼容 SDK,通常只需要设置 baseURL 和 apiKey。注意 Base URL 是https://taotoken.net/api,不要在末尾多写/v1,也不要写成带 UTM 参数的地址。UTM 只用于官网和文档入口,模型请求地址保持干净。
最小连通性测试可以在本地执行:
curl -sS "https://taotoken.net/api/v1/chat/completions" \ -H "Authorization: Bearer ${TAOTOKEN_API_KEY}" \ -H "Content-Type: application/json" \ -H "X-Tenant-Id: acme" \ -H "X-Workspace-Id: analytics" \ -H "X-Scene: data_qa_chat" \ -H "X-Env: staging" \ -d '{ "model": "'"${TAOTOKEN_MODEL}"'", "messages": [ { "role": "user", "content": "用一句话说明退款率环比变化可能受哪些维度影响。" } ], "stream": false }'如果返回 401,先检查Authorization头和 Key 是否有多余空格;如果返回 404,检查 Base URL 是否误写成https://taotoken.net/api/v1后又在 SDK 里重复拼接;如果返回 429,先看是不是多个租户共用了同一个 Key。多租户场景下,Key 本身就是限流和账单的第一道边界。
创建更多租户 Key 时,仍然从 API Keys 页面进入:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=tenant_isolation_apikey 。建议把 Key 创建动作纳入租户开通流程:新租户开通时自动生成tt_{tenant}_{env}_data_qa_chat,租户停用时禁用 Key,而不是删除日志。
4. 网关代理写法:把隔离键注入请求头并保留 request_id
多租户 SaaS 不应让前端直接调用模型接口。更合理的结构是:前端调用你的业务后端,业务后端完成鉴权、租户识别、权限校验、问数 SQL 执行,再把需要模型解释的内容发往 TaoToken。下面是一个简化的 Node.js/TypeScript 代理示例,重点不是业务逻辑,而是隔离键如何从服务端上下文进入请求头。
// server/tenantModelProxy.ts import express from "express"; import fetch from "node-fetch"; const app = express(); app.use(express.json()); // 生产环境建议从密钥管理服务按租户加载,不要硬编码 const TENANT_KEY_MAP: Record<string, string> = { acme: process.env.TAOTOKEN_KEY_ACME!, globex: process.env.TAOTOKEN_KEY_GLOBEX! }; const TAOTOKEN_BASE_URL = process.env.TAOTOKEN_BASE_URL!; const DEFAULT_MODEL = process.env.TAOTOKEN_MODEL!; app.post("/api/data-qa", async (req, res) => { // 真实项目中由鉴权中间件写入,不要从 req.body.tenantId 取 const auth = (req as any).auth; const tenantId = auth?.tenantId as string; const workspaceId = (req.body.workspaceId as string) || "default"; const scene = "data_qa_chat"; const env = process.env.NODE_ENV === "production" ? "prod" : "staging"; const apiKey = TENANT_KEY_MAP[tenantId]; if (!apiKey) { return res.status(403).json({ error: "tenant_not_configured" }); } const requestId = req.headers["x-request-id"]?.toString() || crypto.randomUUID(); const startedAt = Date.now(); const upstream = await fetch(`${TAOTOKEN_BASE_URL}/v1/chat/completions`, { method: "POST", headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json", "X-Tenant-Id": tenantId, "X-Workspace-Id": workspaceId, "X-Scene": scene, "X-Env": env, "X-Request-Id": requestId }, body: JSON.stringify({ model: req.body.model || DEFAULT_MODEL, messages: req.body.messages, stream: false }) }); const data = await upstream.json(); const latencyMs = Date.now() - startedAt; // usage 字段由上游返回,写入你的租户账单表 console.log(JSON.stringify({ requestId, tenantId, workspaceId, scene, env, model: req.body.model || DEFAULT_MODEL, usage: data.usage, status: upstream.status, latencyMs })); return res.status(upstream.status).json(data); }); app.listen(3000, () => { console.log("tenant model proxy listening on :3000"); });这段代码的关键点有三个:
第一,tenant_id 来自服务端鉴权上下文,不来自前端 JSON。否则租户可以伪造身份。 第二,每个租户映射到独立 Key。这样 TaoToken 侧看到的是不同 Key 的用量,限流和账单天然分层。 第三,请求头里保留 workspace、scene、env、request_id。日志系统、账单系统、排障系统都靠这些字段串联。
不要把这段代理直接连到生产库。问数场景里的 SQL 应该由你的数据服务在只读副本或数仓上执行,模型只负责解释和总结。MCP、Agent 或类似自动化方案也不应绕过权限直接执行生产库 DDL/DML。本文所有 SQL 只建议在本地或测试库执行。
5. 租户账单表设计:从 Token 日志到可对账 SQL
有了隔离键,下一步是让账单可复现。建议在业务库或日志库中建一张模型用量表,字段至少覆盖请求标识、租户、工作区、场景、Key 别名、模型、Token 用量、延迟和时间。
CREATE TABLE tenant_model_usage ( id BIGSERIAL PRIMARY KEY, request_id TEXT NOT NULL, tenant_id TEXT NOT NULL, workspace_id TEXT, scene TEXT NOT NULL, env TEXT NOT NULL, key_alias TEXT NOT NULL, model TEXT NOT NULL, prompt_tokens INT NOT NULL DEFAULT 0, completion_tokens INT NOT NULL DEFAULT 0, total_tokens INT GENERATED ALWAYS AS (prompt_tokens + completion_tokens) STORED, latency_ms INT, status_code INT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_tenant_model_usage_tenant_day ON tenant_model_usage (tenant_id, created_at); CREATE INDEX idx_tenant_model_usage_scene ON tenant_model_usage (scene, created_at);插入数据时,不要把前端上报的用量直接当作财务依据。更稳的是以服务端代理收到的上游响应为准,或者以 TaoToken 控制台导出的账单为对账依据。业务表用于实时看板和限流,控制台账单用于最终结算,两者通过request_id、tenant_id、key_alias、时间窗口对齐。
按租户和日期聚合:
SELECT tenant_id, date_trunc('day', created_at) AS day, scene, sum(prompt_tokens) AS prompt_tokens, sum(completion_tokens) AS completion_tokens, sum(total_tokens) AS total_tokens, count(*) AS request_count, round(avg(latency_ms)) AS avg_latency_ms FROM tenant_model_usage WHERE created_at >= now() - interval '7 days' GROUP BY tenant_id, day, scene ORDER BY tenant_id, day, scene;按租户做成本估算时,可以把单价替换成你与 TaoToken 的实际结算单价。下面 SQL 只是结构示例,不要直接使用未核实的数字:
SELECT tenant_id, date_trunc('month', created_at) AS month, sum(total_tokens) AS total_tokens, sum(total_tokens) * :unit_price AS estimated_cost FROM tenant_model_usage WHERE env = 'prod' GROUP BY tenant_id, month ORDER BY tenant_id, month;建议再加两个视图:
CREATE VIEW tenant_daily_usage AS SELECT tenant_id, date_trunc('day', created_at) AS day, sum(total_tokens) AS total_tokens FROM tenant_model_usage WHERE env = 'prod' GROUP BY tenant_id, day; CREATE VIEW tenant_scene_usage AS SELECT tenant_id, scene, sum(total_tokens) AS total_tokens FROM tenant_model_usage WHERE env = 'prod' GROUP BY tenant_id, scene;这样你就能回答三个问题:哪个租户消耗最多?哪个场景消耗最多?某天异常升高是问数对话还是仪表盘刷新导致的?如果发现某个租户的dashboard_refresh用量突然上升,可以先在网关层对该租户该场景做限流,而不是直接停掉整个租户。
6. 排障清单:401、429、串租与 Codex/Claude Code 配置错位
多租户问数接入模型网关时,常见问题集中在鉴权、限流、租户识别和工具配置错位。下面按现象给出排查顺序。
现象一:401 Unauthorized 或 invalid api key。
先检查请求头:
curl -i "https://taotoken.net/api/v1/chat/completions" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"YOUR_MODEL_ID","messages":[{"role":"user","content":"ping"}]}'常见原因包括:Key 复制时带了空格;环境变量未加载;Base URL 写成了官网地址而不是https://taotoken.net/api;把 UTM 参数错误地拼到了 API 地址上。记住,UTM 只用于官网、文档、控制台入口,模型请求 Base URL 不加 UTM。
现象二:404 Not Found。
多数是 Base URL 末尾路径重复。例如 SDK 内部拼接/v1/chat/completions,而你把 baseURL 写成了https://taotoken.net/api/v1,最终变成/api/v1/v1/chat/completions。统一使用:
https://taotoken.net/api现象三:429 Too Many Requests。
多租户场景优先检查是否多个租户共用了同一个 Key。如果 A 租户做了批量仪表盘刷新,B 租户的正常问数就可能被一起限流。解决方式是按租户拆分 Key,并在你的网关层做租户级令牌桶。网关限流键建议使用:
tenant_id + scene + env而不是只按 IP 或全局 QPS。
现象四:账单里出现串租。
检查 tenant_id 是否从req.body.tenantId读取。必须改为从服务端 Session、JWT 或 API Gateway 上下文读取,并在日志中记录X-Request-Id。如果已经出现串租,先停止使用前端传入的 tenant_id,再按请求日志回溯。
现象五:Claude Code 配置不生效。
Claude Code 使用settings.json或ANTHROPIC_*环境变量。示例:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY", "ANTHROPIC_MODEL": "YOUR_MODEL_ID" } }或在 shell 中:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="YOUR_API_KEY" export ANTHROPIC_MODEL="YOUR_MODEL_ID"现象六:Codex 配置不生效。
Codex 使用config.toml,不要把ANTHROPIC_*套到 Codex 上。示例:
model = "YOUR_MODEL_ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"然后设置:
export TAOTOKEN_API_KEY="YOUR_API_KEY"注意env_key指向的是环境变量名,不是 Key 本身。不要把 Key 明文写进config.toml后提交到仓库。
7. 开发工具侧验证:Claude Code、Codex、CC Switch 三件套
多租户问数的主链路在服务端,但开发和运维侧也会用 Claude Code、Codex、CC Switch 等工具做脚本、排障和配置验证。这里的原则是:业务租户 Key 与开发者个人 Key 分开,生产 Key 与测试 Key 分开。开发工具只用于验证配置是否正确,不要拿生产问数 Key 做个人实验。
Claude Code 侧已经给出settings.json和ANTHROPIC_*示例。再补充一个可复制的settings.json:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY", "ANTHROPIC_MODEL": "YOUR_MODEL_ID", "ANTHROPIC_SMALL_FAST_MODEL": "YOUR_FAST_MODEL_ID" } }Codex 侧使用config.toml:
model = "YOUR_MODEL_ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"CC Switch 类工具通常关注三件套:供应商名称、Base URL、API Key。配置时不要只填 Key,还要把供应商的 Base URL 指向https://taotoken.net/api。一个 JSON 结构示例:
{ "providers": [ { "name": "taotoken-tenant-acme-staging", "baseUrl": "https://taotoken.net/api", "apiKey": "YOUR_API_KEY", "models": ["YOUR_MODEL_ID"] } ] }如果你在团队内共享配置模板,建议把YOUR_API_KEY留空,并写明从 TaoToken 官网创建:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=dev_tool_config 。团队成员的 Key 应各自创建,不要共用一个生产租户 Key。这样才能在控制台里区分“某个人调试用超了”和“某个租户生产问数超了”。
8. 多租户问数上线检查表与 CTA
上线前可以用下面这份检查表快速过一遍:
- tenant_id 是否只从服务端鉴权上下文读取?
- 是否每个租户、每个环境、每个关键场景拥有独立 Key 别名?
- 模型请求 Base URL 是否统一为
https://taotoken.net/api? - 请求头是否包含
X-Tenant-Id、X-Workspace-Id、X-Scene、X-Env、X-Request-Id? - 模型用量是否写入
tenant_model_usage表? - 是否能按租户、日期、场景聚合出 Token 账单?
- 是否在网关层按
tenant_id + scene + env做限流? - 是否禁止前端、移动端、公开仓库出现真实 Key?
- 是否禁止 MCP、Agent、代理直连 Oracle/生产库?
- 是否把问数 SQL 放在本地或受控只读副本执行,模型只做解释与总结?
如果这 10 项都通过,你就能把 ChatGPT Work Data agent 带来的“自然语言问数”体验,落到可计量、可限流、可对账的多租户模型调用链路上。隔离键不是额外负担,而是租户账单和稳定性的地基。
需要快速验证模型对话能力,可以从模型对话入口开始:https://taotoken.net/models/detail/chat?utm_source=taotoken_aicg_blog_end&utm_content=tenant_isolation_chat 。如果你的团队要长期做多租户问数、代码辅助和自动化排障,可以再看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=tenant_isolation_coding 。准备好租户级 Key 后,进入 API Keys 页面创建:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=tenant_isolation_apikey 。Claude Code 的详细配置可参考文档:https://taotoken.net/doc/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=tenant_isolation_claudecode 。把 Base URL 设为https://taotoken.net/api,用YOUR_API_KEY先跑通本地测试,再按租户拆分 Key,最后把用量日志接入你的账单表。