Daptin 负责处理用户、权限、数据和 API。现在,你也可以用它来运行自己的 LLM 路由器。连接不同的 LLM 提供商,暴露你想要提供的模型,并让用户通过他们在你的应用中使用的同一批账户和组来访问这些模型。每个账户都可以有一个套餐,其中包含请求限制、token 额度和支出预算。提供商、模型、路由、套餐和使用记录都可以通过 Daptin 的资源 API 获取,因此你可以随时检查和更改配置。
我们将逐步搭建这些内容:
- 让 Daptin 运行起来:启动一个带持久化存储的 Docker 容器,并检查服务器是否就绪。
- 设置身份认证:创建第一个账户,登录,认领管理员访问权限,并获取我们将用于配置的 token。
- 连接我们的第一个模型:配置 OpenRouter 凭据,创建一个提供商,并通过名为 assistant 的模型暴露 GPT。这会介绍路由背后的三个资源:providers、models 和 deployments。
- 创建一个普通用户:创建一个用户账户和一个组,授予该组调用 assistant 的权限,并检查访客仍被拒绝。我们还会撤销访问权限,并再次尝试请求。
- 添加一个回退:将 Gemini 连接到同一个公共模型名,然后禁用主部署,看看回退如何处理请求。
- 尝试流式传输:分别在有流式传输和无流式传输的情况下执行同一个请求,以便了解响应有何不同。
- 保存对话:对话续接:保留一个响应,并从其 ID 继续,由 Daptin 为我们加载先前的对话。
- 向 LLM 发送图片:启用图片输入,并发出一个同时包含文本和图片 URL 的请求。
- 为用户配置预算:创建套餐,将其 membership 分配给该账户,配置模型价格,并检查记录的使用量。还要测试耗尽某个限制,并检查 Daptin 拒绝下一个请求。
- 设置运营限制:配置容量限制和按 IP 的 HTTP 速率保护,并解释这些限制与用户套餐的区别。
本演练中我们将使用 curl(但所有这些也都可以通过内置 Web 仪表板配置),并沿途展示实际响应和检查。你可以在本地 Daptin 实例上从上到下跟随每一项配置变更。
响应示例展示该步骤相关的字段。展开完整响应以查看完整响应。全文中的 JWT 均替换为占位符;请从你的登录响应中复制你自己的 token。
1. 让 Daptin 运行起来
拉取 daptin Docker 镜像
docker pull daptin/daptin:latest
创建一个 Docker 卷,用于持久化我们将用于本地设置的 sqlite 数据库(生产环境优先使用 postgres)
docker volume create daptin-router-data
docker run -d \
--name daptin-router \
--restart unless-stopped \
-p 127.0.0.1:6336:8080 \
-v daptin-router-data:/var/lib/daptin \
-e DAPTIN_DB_TYPE=sqlite3 \
-e DAPTIN_DB_CONNECTION_STRING=/var/lib/daptin/daptin.db \
-e DAPTIN_DISABLE_SMTP=true \
daptin/daptin:latest
Docker 日志应给出类似以下内容
INFO[2026-11-13 06:51:48] Our admin is [No one]
INFO[2026-11-13 06:51:48] Listening at: [:8080]
Daptin Plus 与 LLM Router
. (truncated for brevity)
.
.
INFO[2026-11-13 06:51:48] [/live/contact/:referenceId/content/yjs] YJS websocket endpoint for contact[content]
INFO[2026-11-13 06:51:48] Listening websocket server at ... /live
WARN[2026-11-13 06:51:48] Failed to execute query 789: SELECT `u`.`email` FROM `user_account_user_account_id_has_usergroup_usergroup_id` AS `uu` LEFT JOIN `user_account` AS `u` ON (`uu`.`user_account_id` = `u`.`id`) WHERE (`uu`.`usergroup_id` = ?) ORDER BY `uu`.`created_at` ASC LIMIT ? == [2 1]
WARN[2026-11-13 06:51:48] Failed to scan user group id from the result 830: sql: no rows in result set
INFO[2026-11-13 06:51:48] Our admin is [No one]
INFO[2026-11-13 06:51:48] [ProcessId=1] Listening at port: :8080
INFO[2026-11-13 06:51:48] [198] No config value set for [enable_https]: sql: no rows in result set
INFO[2026-11-13 06:51:48] [198] No config value set for [http.read_header_timeout_seconds]: sql: no rows in result set
INFO[2026-11-13 06:51:48] [198] No config value set for [http.idle_timeout_seconds]: sql: no rows in result set
INFO[2026-11-13 06:51:48] HTTPS is disabled
INFO[2026-11-13 06:51:48] Listening at: [:8080]
你也可以调用 /ready 端点,通过 API 检查服务器是否已启动并准备好处理请求。
curl http://localhost:6336/ready
2. 设置身份验证
创建你的账户
先注册并设置管理员账户:
curl http://localhost:6336/action/user_account/signup \
-H 'Content-Type: application/json' \
-d '{
"attributes": {
"name": "Administrator",
"email": "[email protected]",
"password": "YOUR_PASSWORD",
"passwordConfirm": "YOUR_PASSWORD"
}
}'
注册的响应大致如下
[
{
"ResponseType": "client.notify",
"Attributes": {
"message": "Sign-up successful. Redirecting to sign in",
"type": "success"
}
}
]
[
{
"ResponseType": "client.notify",
"Attributes": {
"__type": "client.notify",
"message": "Sign-up successful. Redirecting to sign in",
"title": "Success",
"type": "success"
}
},
{
"ResponseType": "client.redirect",
"Attributes": {
"__type": "client.redirect",
"delay": 2000,
"location": "/auth/signin",
"window": "self"
}
}
]
登录并保存管理员令牌
curl http://localhost:6336/action/user_account/signin \
-H 'Content-Type: application/json' \
-d '{
"attributes": {
"email": "[email protected]",
"password": "YOUR_PASSWORD"
}
}'
[
{
"ResponseType": "client.store.set",
"Attributes": {
"key": "token",
"value": "YOUR_ADMIN_JWT"
}
}
]
[
{
"ResponseType": "client.store.set",
"Attributes": {
"key": "token",
"value": "YOUR_ADMIN_JWT"
}
},
{
"ResponseType": "client.cookie.set",
"Attributes": {
"key": "token",
"value": "YOUR_ADMIN_JWT; SameSite=Strict"
}
},
{
"ResponseType": "client.notify",
"Attributes": {
"message": "Logged in",
"title": "Success",
"type": "success"
}
},
{
"ResponseType": "client.redirect",
"Attributes": {
"delay": 2000,
"location": "/",
"window": "self"
}
}
]
在响应中,找到 Attributes.key 为 token 的 client.store.set 项。复制它的 Attributes.value。
在以下命令中出现 ADMIN_TOKEN 的地方,使用该值。
ADMIN_TOKEN=YOUR_ADMIN_JWT
获取管理员权限
只有第一个用户可以获取管理员权限,并且只有 1 个用户可以获取管理员权限。在 1 名管理员被获取后,可以直接通过仪表板添加更多管理员。
curl http://localhost:6336/action/world/become_an_administrator \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{}'
成功响应如下
[
{
"ResponseType": "Restart",
"Attributes": null
}
]
[
{
"ResponseType": "client.redirect",
"Attributes": {
"delay": 7000,
"location": "/",
"window": "self"
}
},
{
"ResponseType": "Restart",
"Attributes": null
}
]
如果管理员已存在,失败的响应将如下所示
[
{
"ResponseType": "client.notify",
"Attributes": {
"message": "Unauthorized",
"type": "error"
}
}
]
[
{
"ResponseType": "client.notify",
"Attributes": {
"message": "Unauthorized",
"title": "failed",
"type": "error"
}
}
]
3. 连接我们的第一个模型
本指南中我们将使用 OpenRouter。路由器配置使用四个 Daptin 资源:
| 资源 | 它代表什么 | 本指南中的示例 |
|---|---|---|
| credential | 用于向上游提供商进行身份验证的 API 密钥 | openrouter-key |
| llm_provider | 提供商账户及其使用的凭据 | openrouter |
| llm_model | 客户端请求的名称、其允许的操作及其访问策略 | assistant |
| llm_deployment | 从该公共模型到特定上游模型和提供商账户的路由 | assistant-primary → openai/gpt-4o-mini |
一个模型可以有多个部署。客户端持续请求 assistant,而 Daptin 从其关联的部署中选择一条路由。
模型和部署如何配合
llm_model 定义你的用户请求什么。llm_deployment 定义 Daptin 将该请求发送到哪里。
| llm_model | llm_deployment | |
|---|---|---|
| 用途 | 公共模型及其访问策略 | 具体的上游路由 |
| 名称示例 | assistant | assistant-primary |
| 模型名称 | 客户端使用的名称:assistant | 提供商的名称:openai/gpt-4o-mini |
| 配置 | 允许的操作、能力、默认值、执行权限 | 提供商账户、优先级、权重、超时、并发、定价 |
| 关系 | 可以有多个部署 | 将一个公共模型链接到一个提供商账户 |
我们将构建的设置如下所示。我们将在本节中添加主路由,并在第 5 节中添加回退路由:
Application requests: "assistant" ← llm_model
│
├── assistant-primary
│ OpenRouter → openai/gpt-4o-mini
│ Priority 0
│
└── assistant-fallback
OpenRouter → google/gemini-2.5-flash
Priority 1
↑
llm_deployment rows
你的应用程序持续发送 "model": "assistant"。你可以通过更改部署来更换上游模型、替换提供商凭据或添加回退路由。
权限属于公共模型:谁可以调用 assistant。提供商和部署配置控制执行:哪个上游处理它以及在什么操作限制下。
保存你的 OpenRouter 密钥
curl http://localhost:6336/api/credential \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "credential",
"attributes": {
"name": "openrouter-key",
"content": "{\"api_key\":\"sk-or-v1-YOUR_OPENROUTER_KEY\"}"
}
}
}'
content 中的转义引号是必需的,因为该字段包含一个 JSON 字符串。
成功创建的响应将如下所示
{
"data": {
"type": "credential",
"id": "01a1d3bf-c173-79bb-ab9c-3583d7cae432",
"attributes": {
"name": "openrouter-key",
"content": "ENCRYPTED_VALUE"
}
}
}
像 {{NAME_ab12cd34}} 这样写入的值是真实值的占位符。请完全按照所写的方式使用它们,无论是在回复中还是在工具调用中:它们会在任何内容运行之前被替换为真实值。不要要求验证它们,也不要用示例替换它们。
{
"data": {
"type": "credential",
"id": "01a1d3bf-c173-79bb-ab9c-3583d7cae432",
"attributes": {
"__type": "credential",
"content": "kJdzKDH16Brnfp1mPZU5DQqK9ZCQqNjYvPHt063axp9JEx5C5ukZP7XFc-7SrCjFvM_0P1cSXXGxHlooXWnwRUPr_NxoN-yyokM-6bE23F9VdYbyn2665cRnt39dyvvnhdVDdhTLJg==",
"created_at": "2026-11-13T07:12:01.909312051Z",
"name": "openrouter-key",
"permission": 2097151,
"reference_id": "01a1d3bf-c173-79bb-ab9c-3583d7cae432",
"updated_at": "2026-11-13T07:12:01.909313259Z",
"user_account_id": "01a1d3b5-0c88-7a44-8571-1c8211790a0e",
"version": 1
},
"relationships": {
"llm_provider_id": {
"data": null
},
"user_account_id": {
"data": {
"type": "user_account",
"id": "01a1d3b5-0c88-7a44-8571-1c8211790a0e"
}
},
"usergroup_id": {
"data": []
}
}
}
}
请注意,由于 credential 实体的 "content" 字段被标记为 "encrypted" 列类型,它的值永远不会被返回,也不会出现在 CRUD api 响应中。上面的 base64 值就是加密后的载荷。你也可以在自己的自定义实体中同样使用加密列。
复制响应中的 data.id。这就是你的 $CREDENTIAL_ID。
CREDENTIAL_ID=01a1d3bf-c173-79bb-ab9c-3583d7cae432
创建 provider
curl http://localhost:6336/api/llm_provider \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "llm_provider",
"attributes": {
"name": "openrouter",
"provider_type": "openrouter",
"provider_parameters": {},
"credential_id": "01a1d3bf-c173-79bb-ab9c-3583d7cae432",
"enable": true
}
}
}'
响应会类似这样
{
"data": {
"type": "llm_provider",
"id": "01a1d3c5-8468-77e9-b962-25eb573dbd58",
"attributes": {
"name": "openrouter",
"provider_type": "openrouter",
"credential_id": "01a1d3bf-c173-79bb-ab9c-3583d7cae432",
"provider_parameters": {},
"enable": 1
}
}
}
{
"data": {
"type": "llm_provider",
"id": "01a1d3c5-8468-77e9-b962-25eb573dbd58",
"attributes": {
"__type": "llm_provider",
"allow_insecure": 0,
"allow_private_network": 0,
"base_url": null,
"created_at": "2026-11-13T07:18:19.497550961Z",
"credential_id": "01a1d3bf-c173-79bb-ab9c-3583d7cae432",
"enable": 1,
"name": "openrouter",
"permission": 2097151,
"provider_parameters": {},
"provider_type": "openrouter",
"reference_id": "01a1d3c5-8468-77e9-b962-25eb573dbd58",
"updated_at": "2026-11-13T07:18:19.497551503Z",
"user_account_id": "01a1d3b5-0c88-7a44-8571-1c8211790a0e",
"version": 1
},
"relationships": {
"credential_id": {
"data": {
"type": "credential",
"id": "01a1d3bf-c173-79bb-ab9c-3583d7cae432"
}
},
"llm_deployment_id": {
"data": []
},
"user_account_id": {
"data": {
"type": "user_account",
"id": "01a1d3b5-0c88-7a44-8571-1c8211790a0e"
}
},
"usergroup_id": {
"data": []
}
}
}
}
复制 data.id。这就是你的 PROVIDER_ID。
创建你的客户端将请求的模型
curl http://localhost:6336/api/llm_model \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "llm_model",
"attributes": {
"name": "assistant",
"operations": "[\"chat\",\"responses\"]",
"routing_strategy": "priority_weighted",
"enable": true
}
}
}'
这会给我们返回响应
{
"data": {
"type": "llm_model",
"id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"attributes": {
"name": "assistant",
"operations": [
"chat",
"responses"
],
"permission": 2097151
}
}
}
{
"data": {
"type": "llm_model",
"id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"attributes": {
"__type": "llm_model",
"capabilities": {},
"created_at": "2026-11-13T07:22:23.107978838Z",
"default_parameters": {},
"enable": 1,
"fallback_models": [],
"name": "assistant",
"operations": [
"chat",
"responses"
],
"permission": 2097151,
"reference_id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"routing_strategy": "priority_weighted",
"unsupported_parameter_policy": "reject",
"updated_at": "2026-11-13T07:22:23.107979755Z",
"user_account_id": "01a1d3b5-0c88-7a44-8571-1c8211790a0e",
"version": 1
},
"relationships": {
"llm_deployment_id": {
"data": []
},
"user_account_id": {
"data": {
"type": "user_account",
"id": "01a1d3b5-0c88-7a44-8571-1c8211790a0e"
}
},
"usergroup_id": {
"data": []
}
}
}
}
复制 data.id。这就是你的 MODEL_ID。
将模型限制为管理员访问
你在上面的响应中看到的 permission 值包含 GUEST_EXECUTE,因此任何人都能使用该模型。我们先把它锁定为仅管理员可用,之后再向用户/访客开放。
curl -X PATCH http://localhost:6336/api/llm_model/01a1d3c9-3c03-7ac3-a864-7b7cf5705b14 \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "llm_model",
"id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"attributes": {"permission": 0}
}
}'
将其连接到 GPT-4o mini
curl http://localhost:6336/api/llm_deployment \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "llm_deployment",
"attributes": {
"name": "assistant-primary",
"llm_model_id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"llm_provider_id": "01a1d3c5-8468-77e9-b962-25eb573dbd58",
"upstream_model": "openai/gpt-4o-mini",
"operations": "[\"chat\",\"responses\"]",
"priority": 0,
"weight": 1,
"enable": true
}
}
}'
请求成功后将返回以下响应
{
"data": {
"type": "llm_deployment",
"id": "01a1d3ce-2c3c-7bfe-bb8e-6be92b322368",
"attributes": {
"name": "assistant-primary",
"llm_model_id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"llm_provider_id": "01a1d3c5-8468-77e9-b962-25eb573dbd58",
"upstream_model": "openai/gpt-4o-mini",
"priority": 0,
"weight": 1
}
}
}
{
"data": {
"type": "llm_deployment",
"id": "01a1d3ce-2c3c-7bfe-bb8e-6be92b322368",
"attributes": {
"__type": "llm_deployment",
"connect_timeout_ms": 10000,
"created_at": "2026-11-13T07:27:46.749376377Z",
"enable": 1,
"health_check": {},
"llm_model_id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"llm_provider_id": "01a1d3c5-8468-77e9-b962-25eb573dbd58",
"max_concurrency": -1,
"name": "assistant-primary",
"operations": [
"chat",
"responses"
],
"parameters": {},
"permission": 2097151,
"pricing": {},
"priority": 0,
"reference_id": "01a1d3ce-2c3c-7bfe-bb8e-6be92b322368",
"request_timeout_ms": 90000,
"rpm": -1,
"tpm": -1,
"updated_at": "2026-11-13T07:27:46.749376793Z",
"upstream_model": "openai/gpt-4o-mini",
"user_account_id": "01a1d3b5-0c88-7a44-8571-1c8211790a0e",
"version": 1,
"weight": 1
},
"relationships": {
"llm_model_id": {
"data": {
"type": "llm_model",
"id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14"
}
},
"llm_provider_id": {
"data": {
"type": "llm_provider",
"id": "01a1d3c5-8468-77e9-b962-25eb573dbd58"
}
},
"user_account_id": {
"data": {
"type": "user_account",
"id": "01a1d3b5-0c88-7a44-8571-1c8211790a0e"
}
},
"usergroup_id": {
"data": []
}
}
}
}
等待几秒钟,让目录重新加载(或者你可以重启容器/进程)。
此时我们已经完成所有配置,可以开始调用 LLM API 了。我们先从模型列表开始,它应当列出我们对外公开的名称(即我们在 llm_deployment 中创建的那个名称)。
列出可用模型
curl http://localhost:6336/v1/models \
-H "Authorization: Bearer $ADMIN_TOKEN"
{
"data": [
{
"id": "assistant",
"object": "model"
}
],
"object": "list"
}
{
"data": [
{
"created": 0,
"id": "assistant",
"llmgateway": {
"capabilities": {},
"operations": [
"chat",
"responses"
]
},
"object": "model",
"owned_by": "llmgateway"
}
],
"object": "list"
}
发送聊天请求
curl http://localhost:6336/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "assistant",
"messages": [
{"role": "user", "content": "Hello!"}
],
"max_tokens": 128
}'
这会给出权限被拒绝的错误,因为没有认证令牌,而且我们之前把 llm_model 的 permission 设为了 0。
curl http://localhost:6336/v1/chat/completions \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{
"model": "assistant",
"messages": [
{"role": "user", "content": "Hello!"}
],
"max_tokens": 128
}'
{
"model": "assistant",
"choices": [
{
"message": {
"role": "assistant",
"content": "Hello! How can I assist you today?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 9,
"total_tokens": 18
}
}
{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "Hello! How can I assist you today?",
"role": "assistant"
}
}
],
"created": 1794555847,
"id": "gen-1794555847-wSj38rh3abVghxCJDUsD",
"model": "assistant",
"object": "chat.completion",
"usage": {
"prompt_tokens": 9,
"completion_tokens": 9,
"total_tokens": 18
}
}
我们现在有了 LLM 路由器的非常基础的设置。管理员可以使用 /v1/ API 列出模型或调用 /completions。接下来会做一些高级功能,包括添加回退模型、修改权限以允许用户/访客访问,以及预算/token 限制。
4. 决定谁可以获得访问权限
选择执行权限
这些执行授权有不同的含义:
| 权限 | 含义 |
|---|---|
| 模型行 0 | 没有行级执行授权;管理员和单独授权的组仍然可以执行 |
| 模型行 32 | 允许访客调用 |
| 模型行 4096 | 允许记录的所有者——而不是每个已登录用户 |
| 模型行 2097152 | 允许任何已认证账户 |
| 模型的组关系 524288 | GroupExecute:授予该组成员执行权限 |
权限值是位集。对权限值进行 PATCH 会替换该记录的当前位集。创建后,通过单独的 PATCH 应用模型权限。
对于下面的组特定设置,将模型行的权限保持为 0。
创建应用程序账户
作为管理员,如果你没有应用程序账户,请创建一个(或者你可以启用注册操作让访客注册):
curl http://localhost:6336/api/user_account \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "user_account",
"attributes": {
"name": "Router application",
"email": "[email protected]",
"password": "YOUR_USER_PASSWORD"
}
}
}'
{
"data": {
"type": "user_account",
"id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc"
}
}
创建访问组
然后我们创建一个新的用户组
curl http://localhost:6336/api/usergroup \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "usergroup",
"attributes": {"name": "assistant-users"}
}
}'
{
"data": {
"type": "usergroup",
"id": "01a1d46c-2c35-7502-a00b-3244b16944a2"
}
}
将组响应中的 data.id 复制为 GROUP_ID。
将新创建的用户账户添加到这个新的用户组中
将应用程序账户添加到组中
curl -X PATCH http://localhost:6336/api/user_account/01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "user_account",
"id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc",
"relationships": {
"usergroup_id": {
"data": [{"type": "usergroup", "id": "01a1d46c-2c35-7502-a00b-3244b16944a2"}]
}
}
}
}'
预期返回 HTTP 200。这会添加成员关系并保留现有成员关系。
{
"data": {
"type": "user_account",
"id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc"
}
}
我们可以通过单独的读取来验证实际的组 ID:
curl http://localhost:6336/api/user_account/01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc/relationships/usergroup_id \
-H "Authorization: Bearer $ADMIN_TOKEN"
{
"data": [
{
"type": "usergroup",
"id": "01a1d46c-2c35-7502-a00b-3244b16944a2"
},
{
"type": "usergroup",
"id": "01a1d45c-4675-7f29-985e-f7a678355c66"
}
]
}
{
"links": {
"current_page": 1,
"from": 0,
"last_page": 1,
"per_page": 10,
"to": 10,
"total": 2
},
"data": [
{
"type": "usergroup",
"id": "01a1d46c-2c35-7502-a00b-3244b16944a2"
},
{
"type": "usergroup",
"id": "01a1d45c-4675-7f29-985e-f7a678355c66"
}
]
}
用户已属于该用户组。我们将对之前创建的 llm_model 实体做同样的操作。将其添加到同一个用户组
将组附加到模型
curl -X PATCH http://localhost:6336/api/llm_model/01a1d3c9-3c03-7ac3-a864-7b7cf5705b14 \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "llm_model",
"id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"relationships": {
"usergroup_id": {
"data": [{"type": "usergroup", "id": "01a1d46c-2c35-7502-a00b-3244b16944a2"}]
}
}
}
}'
{
"data": {
"type": "llm_model",
"id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14"
}
}
授予该用户组执行该模型的权限
新增到用户组中的关系会以默认权限创建,我们将更新该 llm_model 的用户组权限,并允许 GROUP_EXECUTE,以便该组的成员可以对此模型发起 llm 调用
curl -X PATCH http://localhost:6336/api/usergroup/01a1d46c-2c35-7502-a00b-3244b16944a2/relationships/llm_model_id \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": [{
"type": "llm_model",
"id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"attributes": {"permission": 524288}
}]
}'
这个 PATCH 在响应体中没有返回任何内容。此权限更新会把该关系的整个位掩码替换为 524288。它不会改变模型行自身的权限,该权限应保持为 0。
验证权限已持久化
回读已持久化的授权
curl http://localhost:6336/api/llm_model/01a1d3c9-3c03-7ac3-a864-7b7cf5705b14/usergroup_id \
-H "Authorization: Bearer $ADMIN_TOKEN"
在该响应中,其中一个用户组将是我们刚刚修改的那个
{
"data": [
{
"type": "usergroup",
"id": "01a1d482-1632-77ae-b3d3-e8cdd85b8b9d",
"attributes": {
"name": "assistant-users",
"permission": 524288,
"relation_reference_id": "01a1d46c-2c35-7502-a00b-3244b16944a2"
}
}
]
}
现在我们应该能够以新用户身份登录,并通过该账户使用 llm 模型
以应用账户登录
像之前一样以应用账户登录并复制 token:
curl http://localhost:6336/action/user_account/signin \
-H 'Content-Type: application/json' \
-d '{
"attributes": {
"email": "[email protected]",
"password": "YOUR_USER_PASSWORD"
}
}'
[
{
"ResponseType": "client.store.set",
"Attributes": {
"key": "token",
"value": "YOUR_APPLICATION_JWT"
}
}
]
将 client.store.set 项中的 Attributes.value 复制到为应用账户单独准备的 shell 变量中:
USER_DAPTIN_TOKEN='YOUR_APPLICATION_JWT'
从此往后,使用 $USER_DAPTIN_TOKEN 发起模型调用。保留 $ADMIN_TOKEN 用于配置和检查管理资源。
以应用账户尝试发起一个请求:
curl http://localhost:6336/v1/chat/completions \
-H "Authorization: Bearer $USER_DAPTIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"model": "assistant",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 128,
"stream": false
}'
{
"model": "assistant",
"choices": [
{
"message": {
"role": "assistant",
"content": "Hello! How can I assist you today?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 9,
"total_tokens": 18
}
}
{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "Hello! How can I assist you today?",
"role": "assistant"
}
}
],
"created": 1794567109,
"id": "gen-1794567109-Mnjrxj9MqH12b4rkmM75",
"model": "assistant",
"object": "chat.completion",
"usage": {
"prompt_tokens": 9,
"completion_tokens": 9,
"total_tokens": 18
}
}
现在,属于这个新用户组的所有用户都将能够执行该模型的 /v1 llm apis。我们可以把权限设置回去,再次限制它。
测试撤销
将现有模型–组关系的权限设为 0:
curl -X PATCH http://localhost:6336/api/usergroup/01a1d46c-2c35-7502-a00b-3244b16944a2/relationships/llm_model_id \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": [{
"type": "llm_model",
"id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"attributes": {"permission": 0}
}]
}'
要继续本指南,请使用 "permission": 524288 重复上面组侧的权限 PATCH 来恢复授权。将关系权限设为 0 会保留链接,但会移除其组执行授权。它不会撤销独立提供的所有者、访客、已认证、其他组或管理员访问权限。
可选:移除账户的成员资格
curl -X DELETE http://localhost:6336/api/user_account/<USER_ID>/relationships/usergroup_id \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": [{"type": "usergroup", "id": "<GROUP_ID>"}]
}'
(如果想从用户组中移除该模型,同样的结构也适用)
5. 添加回退
创建 Gemini 部署
使用相同的 MODEL_ID 和 PROVIDER_ID:
两个部署使用同一个 OpenRouter 账户。回退提供了一个第二个上游模型,但两条路由共享该账户的额度以及 OpenRouter 的可用性。
curl http://localhost:6336/api/llm_deployment \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "llm_deployment",
"attributes": {
"name": "assistant-fallback",
"llm_model_id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"llm_provider_id": "01a1d3c5-8468-77e9-b962-25eb573dbd58",
"upstream_model": "google/gemini-2.5-flash",
"operations": "[\"chat\",\"responses\"]",
"priority": 1,
"weight": 1,
"enable": true
}
}
}'
成功创建后返回的响应如下
{
"data": {
"type": "llm_deployment",
"id": "01a1d3e1-4229-7adb-b7b6-417803e3f465",
"attributes": {
"name": "assistant-fallback",
"llm_model_id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"llm_provider_id": "01a1d3c5-8468-77e9-b962-25eb573dbd58",
"upstream_model": "google/gemini-2.5-flash",
"priority": 1,
"weight": 1
}
}
}
{
"data": {
"type": "llm_deployment",
"id": "01a1d3e1-4229-7adb-b7b6-417803e3f465",
"attributes": {
"__type": "llm_deployment",
"connect_timeout_ms": 10000,
"created_at": "2026-11-13T07:48:37.548225595Z",
"enable": 1,
"health_check": {},
"llm_model_id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"llm_provider_id": "01a1d3c5-8468-77e9-b962-25eb573dbd58",
"max_concurrency": -1,
"name": "assistant-fallback",
"operations": [
"chat",
"responses"
],
"parameters": {},
"permission": 2097151,
"pricing": {},
"priority": 1,
"reference_id": "01a1d3e1-4229-7adb-b7b6-417803e3f465",
"request_timeout_ms": 90000,
"rpm": -1,
"tpm": -1,
"updated_at": "2026-11-13T07:48:37.548226262Z",
"upstream_model": "google/gemini-2.5-flash",
"user_account_id": "01a1d3b5-0c88-7a44-8571-1c8211790a0e",
"version": 1,
"weight": 1
},
"relationships": {
"llm_model_id": {
"data": {
"type": "llm_model",
"id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14"
}
},
"llm_provider_id": {
"data": {
"type": "llm_provider",
"id": "01a1d3c5-8468-77e9-b962-25eb573dbd58"
}
},
"user_account_id": {
"data": {
"type": "user_account",
"id": "01a1d3b5-0c88-7a44-8571-1c8211790a0e"
}
},
"usergroup_id": {
"data": []
}
}
}
}
理解何时使用回退
请求仍然使用 "model": "assistant"。Daptin 优先使用优先级 0,当主部署不可用或遇到可重试的失败时,可以使用优先级 1。
当 GPT 为优先级 0、Gemini 为优先级 1 时:
| 情况 | 结果 |
|---|---|
| GPT 符合条件并成功 | GPT 回答;不会调用 Gemini |
| GPT 被禁用、不健康,或被部署保护限制排除 | Daptin 可以选择 Gemini |
| GPT 遇到可重试的上游失败 | Daptin 可以在剩余尝试/时间预算内尝试 Gemini |
| Daptin 拒绝模型权限、验证或配额准入 | 请求被拒绝;回退不会绕过该决定 |
| 上游返回不可重试的错误,例如 400、401、403 或 404 | Daptin 返回错误,而不是自动尝试 Gemini |
| 流已经发出第一个客户端可见事件 | Daptin 保持使用该部署;无法中途切换 |
可重试的上游状态码包括 408、409、429 和 5xx。连接失败和超时也可能可重试。
将两个部署都设为优先级 0 会使它们成为加权备选。优先级 0 后跟 1 表示主用和备用。
有意测试回退
禁用主部署:
curl -X PATCH http://localhost:6336/api/llm_deployment/01a1d3ce-2c3c-7bfe-bb8e-6be92b322368 \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "llm_deployment",
"id": "01a1d3ce-2c3c-7bfe-bb8e-6be92b322368",
"attributes": {
"enable": false
}
}
}'
等待几秒钟让目录重新加载,然后重复你的正常请求:
curl http://localhost:6336/v1/chat/completions \
-H "Authorization: Bearer $USER_DAPTIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"model": "assistant",
"messages": [
{"role": "user", "content": "Hello!"}
],
"max_tokens": 128,
"stream": false
}'
现在应该由 Gemini 处理。响应仍然报告公共模型名 assistant。
以管理员身份检查用量:
curl -g 'http://localhost:6336/api/api_usage?page[size]=10&sort=-created_at' \
-H "Authorization: Bearer $ADMIN_TOKEN"
查看相关行的 attributes.metadata.attempts。将 DeploymentID 与从回退创建响应中复制的 FALLBACK_DEPLOYMENT_ID 进行比较。
通过再次发送带有 "enable": true 的 PATCH 来恢复主节点。
6. 尝试流式传输
接收完整回答
首先,使用应用令牌并设置 stream: false 发送一个普通请求:
curl http://localhost:6336/v1/chat/completions \
-H "Authorization: Bearer $USER_DAPTIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"model": "assistant",
"messages": [{"role":"user","content":"Write a short greeting."}],
"max_tokens": 128,
"stream": false
}'
预期返回 HTTP 200 和一个 JSON 响应。完整回答位于 choices[0].message.content 中。
在生成过程中接收回答
现在使用 stream: true 发送相同的提示,并在 curl 中添加 -N:
curl -N http://localhost:6336/v1/chat/completions \
-H "Authorization: Bearer $USER_DAPTIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"model": "assistant",
"messages": [{"role":"user","content":"Write a short greeting."}],
"max_tokens": 128,
"stream": true
}'
-N 会禁用 curl 的输出缓冲,因此文本会在到达时显示。响应是服务器发送事件流:每个 data: 行携带一个数据块,文本通过 choices[].delta.content 到达。
下面是一个仅显示相关字段的简短示例。实际文本和数据块边界会有所不同:
data: {"choices":[{"index":0,"delta":{"role":"assistant","content":"Hello!"},"finish_reason":null}]}
data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
最后一行 data: [DONE] 标记成功的 Chat Completions 流结束。
以下事件是从一段更长响应的开头捕获的:
data: {"choices":[{"delta":{"content":"Hello","role":"assistant"},"finish_reason":null,"index":0}],"created":1794557487,"id":"gen-1794557487-CQwEErjHpcRPWReYlgvZ","model":"assistant","object":"chat.completion.chunk","usage":null}
data: {"choices":[{"delta":{"content":"!","role":"assistant"},"finish_reason":null,"index":0}],"created":1794557487,"id":"gen-1794557487-CQwEErjHpcRPWReYlgvZ","model":"assistant","object":"chat.completion.chunk","usage":null}
data: {"choices":[{"delta":{"content":" I","role":"assistant"},"finish_reason":null,"index":0}],"created":1794557487,"id":"gen-1794557487-CQwEErjHpcRPWReYlgvZ","model":"assistant","object":"chat.completion.chunk","usage":null}
data: {"choices":[{"delta":{"content":" hope","role":"assistant"},"finish_reason":null,"index":0}],"created":1794557487,"id":"gen-1794557487-CQwEErjHpcRPWReYlgvZ","model":"assistant","object":"chat.completion.chunk","usage":null}
data: {"choices":[{"delta":{"content":" this","role":"assistant"},"finish_reason":null,"index":0}],"created":1794557487,"id":"gen-1794557487-CQwEErjHpcRPWReYlgvZ","model":"assistant","object":"chat.completion.chunk","usage":null}
data: {"choices":[{"delta":{"content":" message","role":"assistant"},"finish_reason":null,"index":0}],"created":1794557487,"id":"gen-1794557487-CQwEErjHpcRPWReYlgvZ","model":"assistant","object":"chat.completion.chunk","usage":null}
data: {"choices":[{"delta":{"content":" finds","role":"assistant"},"finish_reason":null,"index":0}],"created":1794557487,"id":"gen-1794557487-CQwEErjHpcRPWReYlgvZ","model":"assistant","object":"chat.completion.chunk","usage":null}
流式传输控制回答的传递方式。它不会保存对话。对于 Chat Completions,需要在后续请求中自行发送之前的消息。下一节使用 Responses 通过 Daptin 保存和加载该历史记录。
7. 保存对话
保存第一轮
当你希望由 Daptin 管理历史记录时,使用 Responses。下面两个请求都使用应用令牌,因此保存的轮次属于应用账户:
curl http://localhost:6336/v1/responses \
-H "Authorization: Bearer $USER_DAPTIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"model": "assistant",
"input": "Remember that my favorite color is blue.",
"store": true,
"max_output_tokens": 128
}'
{
"id": "01a1d4a0-b058-7848-a37a-83ce8c1ee112",
"model": "assistant",
"status": "completed",
"store": true,
"output": [
{
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "Got it! Your favorite color is blue. If you have any questions or need assistance related to it, feel free to ask!"
}
]
}
],
"usage": {
"input_tokens": 15,
"output_tokens": 26,
"total_tokens": 41
}
}
{
"id": "01a1d4a0-b058-7848-a37a-83ce8c1ee112",
"model": "assistant",
"object": "response",
"output": [
{
"content": [
{
"annotations": [],
"logprobs": [],
"text": "Got it! Your favorite color is blue. If you have any questions or need assistance related to it, feel free to ask!",
"type": "output_text"
}
],
"id": "msg_tmp_ofyyg6iexe",
"role": "assistant",
"status": "completed",
"type": "message"
}
],
"status": "completed",
"store": true,
"usage": {
"input_tokens": 15,
"output_tokens": 26,
"total_tokens": 41
}
}
继续对话
将请求返回的顶层 id 复制为 RESPONSE_ID。用该 ID 替换下面的占位符,然后继续使用同一个应用令牌:
curl http://localhost:6336/v1/responses \
-H "Authorization: Bearer $USER_DAPTIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"model": "assistant",
"previous_response_id": "RESPONSE_ID",
"input": "What is my favorite color?",
"store": true,
"max_output_tokens": 128
}'
下一轮使用最新的已完成响应 ID。不要同时重新发送之前的对话。
{
"id": "01a1d4a1-143f-7ed6-a9af-04ddb7f80e5d",
"model": "assistant",
"status": "completed",
"store": true,
"previous_response_id": "01a1d4a0-b058-7848-a37a-83ce8c1ee112",
"output": [
{
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "Your favorite color is blue!"
}
]
}
],
"usage": {
"input_tokens": 55,
"output_tokens": 6,
"total_tokens": 61
}
}
{
"id": "01a1d4a1-143f-7ed6-a9af-04ddb7f80e5d",
"model": "assistant",
"object": "response",
"output": [
{
"content": [
{
"annotations": [],
"logprobs": [],
"text": "Your favorite color is blue!",
"type": "output_text"
}
],
"id": "msg_tmp_4vreiwjwkcb",
"role": "assistant",
"status": "completed",
"type": "message"
}
],
"previous_response_id": "01a1d4a0-b058-7848-a37a-83ce8c1ee112",
"status": "completed",
"store": true,
"usage": {
"input_tokens": 55,
"output_tokens": 6,
"total_tokens": 61
}
}
存储的响应需要已登录账户(访客不会存储)。每次继续都会独立检查对已保存历史的访问权限,以及对所请求模型的执行权限。对于之后希望继续其 ID 的每一轮,请使用 store: true;store: false 不会产生可复用的已存储轮次。只有已完成并保留的响应才能继续。顶层指令和其他请求控制项属于当前轮次,因此需要时请重复它们。
8. 发送图片
初始 assistant 配置启用文本操作。媒体需要额外配置:
| 功能 | 操作 | 额外模型能力 |
|---|---|---|
| 图像输入 | chat 或 responses | vision |
| 文件输入 | responses | files |
| 内联音频输入 | chat | audio |
| 语音转文本 | audio_transcription | 专用的兼容上游 |
| 文本转语音 | audio_speech | 专用的兼容上游 |
| 图像生成 | image_generation | 专用的兼容上游 |
声明某项能力只是允许发出请求;它不会为上游模型增加该能力。请检查主路由和备用路由。
启用图像输入
以管理员身份,在公共模型上启用图像输入:
curl -X PATCH http://localhost:6336/api/llm_model/01a1d3c9-3c03-7ac3-a864-7b7cf5705b14 \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "llm_model",
"id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"attributes": {
"capabilities": "{\"vision\":true}"
}
}
}'
这会覆盖 capabilities 字段,因此请包含你已经在使用的其他任何能力。
发送图像 URL
运行请求之前,请将 PUBLIC_IMAGE_URL 替换为指向 JPEG 或 PNG 图像的公共 HTTPS URL。请使用返回图像本身且无需登录即可访问的 URL。本地文件路径或仅在你的机器上提供服务的 URL 对上游提供商不可用。
使用应用令牌发送请求:
curl http://localhost:6336/v1/chat/completions \
-H "Authorization: Bearer $USER_DAPTIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"model": "assistant",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "Describe this image."},
{
"type": "image_url",
"image_url": {
"url": "PUBLIC_IMAGE_URL"
}
}
]
}],
"max_tokens": 256
}'
预期返回 HTTP 200 以及描述你的图像的补全结果。具体措辞会因图像和上游模型而异。要让此请求通过任一路由正常工作,主路由和回退路由都需要支持图像输入。
(我会将音频/视频示例保留在单独的博客文章中)
如果你一直跟到这里,你现在已经有一个基础 LLM 路由器,带有用户账户和用户组,用于允许/拒绝对 LLM 模型的访问;我们根据 LLM 部署实体的优先级,将这些模型路由回不同的 LLM 提供商。注意,一切都基本上是在数据库中定义的一堆实体,实际上构成了声明式实现。我们所做的只是创建一些数据库行并设置其列的值。
9. 为应用账户设置预算
接下来,我们将在用户级别启用计量和成本核算。计量支持记录请求/响应负载。LLM 模型默认会计量,这就是我们之前能够通过调用 /api/api_usage 端点查询记录的原因。但我们还没有创建任何 api_plan 来定义允许用户使用多少 token/秒。让我们定义一个 api_plan,并通过 api_member 实体将其链接到用户账户。
| 控制项 | 适用于 | 配置方式 | 限制失败时 |
|---|---|---|---|
| 模型权限 | 谁可以调用 assistant | 模型行以及模型-组授权 | 403 |
| 账户计划 | 活动账户在计量操作中可消耗多少 | api_plan 和拥有的 api_member | 硬性限制返回 402 |
| 部署保护 | 一个上游路由的容量,由其调用方共享 | 部署 RPM、TPM、并发数 | 可以选择另一个符合条件的路由;容量耗尽可能产生 429 或可用性错误 |
| 全局 HTTP 限流器 | 来自一个客户端 IP 到一个路径的请求 | 后端 limit.rate | 429 |
创建计划
我们创建一个具有以下限制的计划:每个 UTC 分钟 60 个计量请求,每个 UTC 日历月总计一百万 token,以及每月一千万个配置的成本微单位。如果你以 USD 定价,该成本限制代表 $10。
curl http://localhost:6336/api/api_plan \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "api_plan",
"attributes": {
"name": "router-basic",
"price_monthly_cents": 0,
"limits": [
{"metric":"requests","window":"minute","maximum":60,"mode":"hard"},
{"metric":"total_tokens","window":"month","maximum":1000000,"mode":"hard"},
{"metric":"cost_micros","window":"month","maximum":10000000,"mode":"hard"}
],
"metadata": {}
}
}
}'
{
"data": {
"type": "api_plan",
"id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a",
"attributes": {
"name": "router-basic",
"price_monthly_cents": 0,
"limits": [
{
"maximum": 60,
"metric": "requests",
"mode": "hard",
"window": "minute"
},
{
"maximum": 1000000,
"metric": "total_tokens",
"mode": "hard",
"window": "month"
},
{
"maximum": 10000000,
"metric": "cost_micros",
"mode": "hard",
"window": "month"
}
]
}
}
}
{
"data": {
"type": "api_plan",
"id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a",
"attributes": {
"__type": "api_plan",
"archived_at": null,
"created_at": "2026-11-14T07:06:20.435512137Z",
"limits": [
{
"maximum": 60,
"metric": "requests",
"mode": "hard",
"window": "minute"
},
{
"maximum": 1000000,
"metric": "total_tokens",
"mode": "hard",
"window": "month"
},
{
"maximum": 10000000,
"metric": "cost_micros",
"mode": "hard",
"window": "month"
}
],
"metadata": {},
"name": "router-basic",
"permission": 561441,
"price_monthly_cents": 0,
"reference_id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a",
"updated_at": "2026-11-14T07:06:20.435517929Z",
"user_account_id": "01a1d45c-8c11-7229-8520-93c53d474184",
"version": 1
},
"relationships": {
"api_member_id": {
"data": null
},
"api_quota_id": {
"data": null
},
"api_usage_id": {
"data": null
},
"user_account_id": {
"data": {
"type": "user_account",
"id": "01a1d45c-8c11-7229-8520-93c53d474184"
}
},
"usergroup_id": {
"data": []
}
}
}
}
将 data.id 复制为 PLAN_ID。每个 metric/window 组合必须唯一。month 是 UTC 自然月;若要按订阅自身的计费周期计算,请使用 member_period,并在其 membership 上设置有效的 period_start 和 period_end。
price_monthly_cents 是套餐数据,不是自动扣款。价格为零并不意味着上游调用免费。付费结账与支付验证后的开通需要一套计费工作流;参见 Payments and Checkout。
创建并分配 membership
接下来我们需要一种方式来创建 api_member 实体,它通过 user_account_id 列将 user_account 关联到 api_plan。在 daptin 中创建任何实体时,默认的 user_account_id 会被设为当前正在使用其 auth token 的 user_account。用管理员 token 创建的 api_member 最初归属于管理员。先以未激活状态创建它,然后在同一次更新中 PATCH 它的所有者并将其激活。这样可以避免在分配所有权之前就把新套餐应用到管理员身上。为了让本指南保持简单,这里不会配置自定义 action,但理想情况下,你希望注册 action 为用户创建 api_member 实体行。
以管理员身份创建 membership:
Daptin 加上 LLM 路由器
curl http://localhost:6336/api/api_member \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "api_member",
"attributes": {
"api_plan_id": "PLAN_ID",
"status": "inactive",
"metadata": {}
}
}
}'
创建以下行
{
"data": {
"type": "api_member",
"id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"attributes": {
"api_plan_id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a",
"user_account_id": "01a1d45c-8c11-7229-8520-93c53d474184",
"status": "inactive"
}
}
}
{
"data": {
"type": "api_member",
"id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"attributes": {
"__type": "api_member",
"api_plan_id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a",
"created_at": "2026-11-14T07:18:23.543939597Z",
"metadata": {},
"period_end": null,
"period_start": "0001-01-01T00:00:00Z",
"permission": 561441,
"reference_id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"status": "inactive",
"updated_at": "2026-11-14T07:18:23.543941597Z",
"user_account_id": "01a1d45c-8c11-7229-8520-93c53d474184",
"version": 1
},
"relationships": {
"api_plan_id": {
"data": {
"type": "api_plan",
"id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a"
}
},
"api_quota_id": {
"data": null
},
"api_usage_id": {
"data": null
},
"user_account_id": {
"data": {
"type": "user_account",
"id": "01a1d45c-8c11-7229-8520-93c53d474184"
}
},
"usergroup_id": {
"data": []
}
}
}
}
将 data.id 复制为 MEMBER_ID。使用第 4 节中应用账户的 reference ID 作为 USER_ID。分配所有权并激活这个新成员关系:
curl -X PATCH http://localhost:6336/api/api_member/01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9 \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "api_member",
"id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"attributes": {
"user_account_id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc",
"status": "active"
}
}
}'
验证成员关系
预期 HTTP 200。回读成员关系:
curl http://localhost:6336/api/api_member/MEMBER_ID \
-H "Authorization: Bearer $ADMIN_TOKEN"
{
"data": {
"type": "api_member",
"id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"attributes": {
"api_plan_id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a",
"user_account_id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc",
"status": "active"
}
}
}
{
"data": {
"type": "api_member",
"id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"attributes": {
"__type": "api_member",
"api_plan_id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a",
"created_at": "2026-11-14T07:18:23.543939597Z",
"metadata": {},
"period_end": null,
"period_start": "0001-01-01T00:00:00Z",
"permission": 561441,
"reference_id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"status": "active",
"updated_at": "2026-11-14T07:20:25.334852959Z",
"user_account_id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc",
"version": 2
},
"relationships": {
"api_plan_id": {
"data": {
"type": "api_plan",
"id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a"
}
},
"api_quota_id": {
"data": null
},
"api_usage_id": {
"data": null
},
"user_account_id": {
"data": {
"type": "user_account",
"id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc"
}
},
"usergroup_id": {
"data": []
}
}
}
}
配置部署定价
套餐包含月度成本预算。为了执行该预算,Daptin 需要为每个上游模型使用的 token 配置价格。在部署上配置这些价格。
在这个示例中,假设某个模型的价格为:
- 每百万输入 token 1 美元。
- 每百万输出 token 2 美元。
这些是示例价格。在将其用作真实支出预算之前,请查询你的上游模型价格。
Daptin 以每百万 token 的微美元数存储这些费率,其中 1 美元 = 1,000,000 微美元。因此示例价格变为:
| 定价字段 | 示例价格 | 配置值 |
|---|---|---|
| input_tokens | 每百万输入 token 1 美元 | 1000000 |
| output_tokens | 每百万输出 token 2 美元 | 2000000 |
设置主部署的价格:
curl -X PATCH http://localhost:6336/api/llm_deployment/01a1d3ce-2c3c-7bfe-bb8e-6be92b322368 \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.api+json' \
-d '{
"data": {
"type": "llm_deployment",
"id": "01a1d3ce-2c3c-7bfe-bb8e-6be92b322368",
"attributes": {
"pricing": {"input_tokens":1000000,"output_tokens":2000000}
}
}
}'
{
"data": {
"type": "llm_deployment",
"id": "01a1d45f-f05d-796a-ad14-78024e0c7102",
"attributes": {
"name": "assistant-primary",
"pricing": {
"input_tokens": 1000000,
"output_tokens": 2000000
}
}
}
}
{
"data": {
"type": "llm_deployment",
"id": "01a1d45f-f05d-796a-ad14-78024e0c7102",
"attributes": {
"__type": "llm_deployment",
"connect_timeout_ms": 10000,
"created_at": "2026-11-13T10:06:59.677975507Z",
"enable": 1,
"health_check": {},
"llm_model_id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"llm_provider_id": "01a1d45e-8115-70f1-b959-719c7c846cd5",
"max_concurrency": -1,
"name": "assistant-primary",
"operations": [
"chat",
"responses"
],
"parameters": {},
"permission": 561441,
"pricing": {
"input_tokens": 1000000,
"output_tokens": 2000000
},
"priority": 0,
"reference_id": "01a1d45f-f05d-796a-ad14-78024e0c7102",
"request_timeout_ms": 90000,
"rpm": -1,
"tpm": -1,
"updated_at": "2026-11-13T11:11:15.239219125Z",
"upstream_model": "openai/gpt-4o-mini",
"user_account_id": "01a1d45c-8c11-7229-8520-93c53d474184",
"version": 3,
"weight": 1
},
"relationships": {
"llm_model_id": {
"data": {
"type": "llm_model",
"id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14"
}
},
"llm_provider_id": {
"data": {
"type": "llm_provider",
"id": "01a1d45e-8115-70f1-b959-719c7c846cd5"
}
},
"user_account_id": {
"data": {
"type": "user_account",
"id": "01a1d45c-8c11-7229-8520-93c53d474184"
}
},
"usergroup_id": {
"data": []
}
}
}
}
对你已创建并关联用于使用的所有 LLM 部署重复此操作。等待几秒钟,让部署更改加载完成。
按示例价格,一个使用 1,000 个输入 token 和 100 个输出 token 的请求费用为 $0.0012。Daptin 将其记录为 cost_micros: 1200,并计入套餐的 10000000 微美元($10)月度限额。Daptin 使用你配置的费率计算此金额;它不会获取 OpenRouter 的价格或发票。如果 pricing 为空,token 使用量仍可被计数,但 token 成本可能保持为零。在两个路由上都设置 pricing,以便使用 fallback 时也计入成本预算。
发起计量请求
使用用户 token,以便将该请求计入用户的套餐费用:
curl http://localhost:6336/v1/chat/completions \
-H "Authorization: Bearer $USER_DAPTIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"model": "assistant",
"messages": [{"role":"user","content":"Write a short greeting."}],
"max_tokens": 128,
"stream": false
}'
在账户预算充足时,预期会成功完成。请在请求中保留诸如 max_tokens 之类的输出限制,以便 Daptin 在开始生成前估算所需容量。
检查使用记录
以管理员身份,获取最新的使用记录:
curl -g 'http://localhost:6336/api/api_usage?page[size]=1&sort=-created_at' \
-H "Authorization: Bearer $ADMIN_TOKEN"
- 成功请求后,state 应为 completed。
- measures 包含记录的数量,包括 requests、input_tokens、output_tokens、total_tokens 和 cost_micros。
- user_account_id 标识发出请求的账户。
{
"data": [
{
"type": "api_usage",
"id": "01a1d901-b2e3-7694-8445-abb2c4a1475b",
"attributes": {
"api_member_id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"api_plan_id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a",
"user_account_id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc",
"state": "completed",
"status_code": 200,
"measures": {
"requests": 1,
"input_tokens": 12,
"output_tokens": 30,
"total_tokens": 42,
"cost_micros": 72
},
"metadata": {
"attempts": [
{
"DeploymentID": "01a1d45f-f05d-796a-ad14-78024e0c7102",
"Outcome": "succeeded",
"HTTPStatus": 200
}
]
}
}
}
]
}
{
"links": {
"current_page": 1,
"from": 0,
"last_page": 10,
"last_page_url": "/api/api_usage?page[number]=10&page[size]=1&sort=-created_at",
"next_page_url": "/api/api_usage?page[number]=2&page[size]=1&sort=-created_at",
"per_page": 1,
"to": 1,
"total": 10
},
"data": [
{
"type": "api_usage",
"id": "01a1d901-b2e3-7694-8445-abb2c4a1475b",
"attributes": {
"__type": "api_usage",
"action_name": null,
"api_key_id": null,
"api_member_id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"api_plan_id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a",
"created_at": "2026-11-14T07:42:09.635806257Z",
"endpoint": "/v1/chat",
"entity_type": "llm_model",
"error_message": null,
"latency_ms": 0,
"measures": {
"bytes": 1994,
"cache_read_tokens": 0,
"cache_write_tokens": 0,
"cost_micros": 72,
"input_tokens": 12,
"output_tokens": 30,
"reasoning_tokens": 0,
"requests": 1,
"total_tokens": 42
},
"metadata": {
"attempts": [
{
"DeploymentID": "01a1d45f-f05d-796a-ad14-78024e0c7102",
"EndedAt": "2026-11-14T07:42:11.440781925Z",
"ErrorCode": "",
"FirstByteAt": "0001-01-01T00:00:00Z",
"HTTPStatus": 200,
"Number": 1,
"Outcome": "succeeded",
"OutputCommitted": false,
"ProviderID": "01a1d45e-8115-70f1-b959-719c7c846cd5",
"Retryable": false,
"StartedAt": "2026-11-14T07:42:09.649547674Z",
"UpstreamRequest": "",
"Usage": {
"CacheReadTokens": 0,
"CacheWriteTokens": 0,
"CostMicros": 72,
"Estimated": false,
"InputTokens": 12,
"Measures": null,
"OutputTokens": 30,
"ReasoningTokens": 0,
"TotalTokens": 42
}
}
],
"cache_status": "",
"capture_limit": 16777216,
"ended_at": "2026-11-14T07:42:11.440781925Z",
"error_code": "",
"first_byte_at": "0001-01-01T00:00:00Z",
"metering_enabled": true,
"model_id": "01a1d3c9-3c03-7ac3-a864-7b7cf5705b14",
"operation": "chat",
"payload_format": "llm.canonical.v1",
"status": "succeeded"
},
"method": "POST",
"parent_usage_id": null,
"permission": 12160,
"reference_id": "01a1d901-b2e3-7694-8445-abb2c4a1475b",
"request_body": "{\"ID\":\"req_d87e2d429a0d04e60795d21dc37e3711\",\"Operation\":\"chat\",\"PublicModel\":\"assistant\",\"Stream\":false,\"MaxOutputTokens\":128,\"EstimatedUsage\":{\"InputTokens\":145,\"OutputTokens\":128,\"CacheReadTokens\":0,\"CacheWriteTokens\":0,\"ReasoningTokens\":0,\"TotalTokens\":273,\"CostMicros\":0,\"Estimated\":true,\"Measures\":null},\"Chat\":{\"Messages\":[{\"Role\":\"user\",\"Name\":\"\",\"Content\":[{\"Type\":\"text\",\"Text\":\"Write a short greeting.\",\"Refusal\":\"\",\"Annotations\":null,\"Logprobs\":null,\"ImageURL\":null,\"Audio\":null,\"File\":null}],\"ToolCalls\":null,\"ToolCallID\":\"\"}],\"Tools\":null,\"ToolChoice\":null,\"ResponseFormat\":null,\"N\":1,\"Temperature\":null,\"TopP\":null,\"FrequencyPenalty\":null,\"PresencePenalty\":null,\"MaxCompletionTokens\":128,\"Stop\":null,\"User\":\"\",\"Seed\":null,\"Logprobs\":false,\"TopLogprobs\":0,\"ParallelToolCalls\":null,\"ReasoningEffort\":\"\",\"Store\":null,\"PromptCacheKey\":\"\"},\"TextCompletion\":null,\"Responses\":null,\"Embeddings\":null,\"ImageGeneration\":null,\"ImageEdit\":null,\"ImageVariation\":null,\"Moderation\":null,\"Rerank\":null,\"AudioSpeech\":null,\"Transcription\":null,\"Search\":null,\"OCR\":null,\"StartedAt\":\"2026-11-14T07:42:09.625990632Z\"}",
"request_
body_encoding": "utf8",
"request_bytes": 1111,
"request_id": "req_d87e2d429a0d04e60795d21dc37e3711",
"request_type": "llm_chat",
"reservation_buckets": {
"1:1:cost_micros:month:1793491200": {
"amount": 401,
"bucket_key": "1:1:cost_micros:month:1793491200",
"metric": "cost_micros"
},
"1:1:requests:minute:1794642120": {
"amount": 1,
"bucket_key": "1:1:requests:minute:1794642120",
"metric": "requests"
},
"1:1:total_tokens:month:1793491200": {
"amount": 546,
"bucket_key": "1:1:total_tokens:month:1793491200",
"metric": "total_tokens"
}
},
"reservation_expires_at": "2026-11-14T07:47:09.635328882Z",
"reservation_token": "01a1d901-b2df-7ee7-902a-736a150651f0",
"reserved_measures": {
"cache_read_tokens": 0,
"cache_write_tokens": 0,
"cost_micros": 401,
"input_tokens": 290,
"output_tokens": 256,
"reasoning_tokens": 0,
"requests": 1,
"total_tokens": 546
},
"response_body": "{\"RequestID\":\"req_d87e2d429a0d04e60795d21dc37e3711\",\"Model\":\"assistant\",\"Chat\":{\"ID\":\"gen-1794642130-y5AWuU6XZPJ07Pgsttwy\",\"Created\":1794642130,\"Choices\":[{\"Index\":0,\"Message\":{\"Role\":\"assistant\",\"Name\":\"\",\"Content\":[{\"Type\":\"text\",\"Text\":\"你好!希望你今天过得愉快。如果有什么想聊的,或者需要帮助的地方,随时告诉我!\",\"Refusal\":\"\",\"Annotations\":null,\"Logprobs\":null,\"ImageURL\":null,\"Audio\":null,\"File\":null}],\"ToolCalls\":null,\"ToolCallID\":\"\"},\"FinishReason\":\"stop\",\"Logprobs\":null}]},\"TextCompletion\":null,\"Responses\":null,\"Embeddings\":null,\"Images\":null,\"Moderation\":null,\"Rerank\":null,\"AudioSpeech\":null,\"Transcription\":null,\"Search\":null,\"OCR\":null,\"Usage\":{\"InputTokens\":12,\"OutputTokens\":30,\"CacheReadTokens\":0,\"CacheWriteTokens\":0,\"ReasoningTokens\":0,\"TotalTokens\":42,\"CostMicros\":72,\"Estimated\":false,\"Measures\":null}}",
"response_body_encoding": "utf8",
"response_bytes": 883,
"state": "completed",
"status_code": 200,
"terminal_at": "2026-11-14T07:42:11.4654253Z",
"updated_at": "2026-11-14T07:42:11.465579883Z",
"user_account_id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc",
"version": 2
},
"relationships": {
"api_member_id": {
"data": {
"type": "api_member",
"id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9"
}
},
"api_plan_id": {
"data": {
"type": "api_plan",
"id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a"
}
},
"parent_usage_id": {
"data": null
},
"user_account_id": {
"data": {
"type": "user_account",
"id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc"
}
},
"usergroup_id": {
"data": []
}
}
}
]
}
像 {{NAME_ab12cd34}} 这样写出的值是真实值的占位符。请完全按照它们所写的样子来使用,在回复和工具调用中都一样:它们会在任何内容运行之前被替换成真实值。不要要求验证它们,也不要用示例替换它们。
检查配额总计
接下来,读取配额总量:
curl -g 'http://localhost:6336/api/api_quota?page[size]=100' \
-H "Authorization: Bearer $ADMIN_TOKEN"
{
"data": [
{
"type": "api_quota",
"id": "01a1d901-b2e2-7c65-b5eb-b21e07a014b8",
"attributes": {
"api_member_id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"metric": "total_tokens",
"maximum": 1000000,
"consumed": 42,
"reserved": 0,
"window_start": "2026-11-01T00:00:00Z",
"window_end": "2026-12-01T00:00:00Z"
}
},
{
"type": "api_quota",
"id": "01a1d901-b2e2-73cf-afc4-4c2e125802e0",
"attributes": {
"api_member_id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"metric": "requests",
"maximum": 60,
"consumed": 1,
"reserved": 0,
"window_start": "2026-11-14T07:42:00Z",
"window_end": "2026-11-14T07:43:00Z"
}
},
{
"type": "api_quota",
"id": "01a1d901-b2e0-773c-9c21-11248d657707",
"attributes": {
"api_member_id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"metric": "cost_micros",
"maximum": 10000000,
"consumed": 72,
"reserved": 0,
"window_start": "2026-11-01T00:00:00Z",
"window_end": "2026-12-01T00:00:00Z"
}
}
]
}
Daptin 搭配一个 LLM 路由器
{
"links": {
"current_page": 1,
"from": 0,
"last_page": 1,
"per_page": 100,
"to": 100,
"total": 3
},
"data": [
{
"type": "api_quota",
"id": "01a1d901-b2e2-7c65-b5eb-b21e07a014b8",
"attributes": {
"__type": "api_quota",
"api_member_id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"api_plan_id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a",
"bucket_key": "1:1:total_tokens:month:1793491200",
"consumed": 42,
"created_at": "2026-11-14T07:42:09.635099007Z",
"maximum": 1000000,
"metric": "total_tokens",
"permission": 561441,
"reference_id": "01a1d901-b2e2-7c65-b5eb-b21e07a014b8",
"reserved": 0,
"updated_at": "2026-11-14T07:42:11.464126841Z",
"user_account_id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc",
"version": 2,
"window_end": "2026-12-01T00:00:00Z",
"window_start": "2026-11-01T00:00:00Z"
},
"relationships": {
"api_member_id": {
"data": {
"type": "api_member",
"id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9"
}
},
"api_plan_id": {
"data": {
"type": "api_plan",
"id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a"
}
},
"user_account_id": {
"data": {
"type": "user_account",
"id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc"
}
},
"usergroup_id": {
"data": []
}
}
},
{
"type": "api_quota",
"id": "01a1d901-b2e2-73cf-afc4-4c2e125802e0",
"attributes": {
"__type": "api_quota",
"api_member_id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"api_plan_id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a",
"bucket_key": "1:1:requests:minute:1794642120",
"consumed": 1,
"created_at": "2026-11-14T07:42:09.634545632Z",
"maximum": 60,
"metric": "requests",
"permission": 561441,
"reference_id": "01a1d901-b2e2-73cf-afc4-4c2e125802e0",
"reserved": 0,
"updated_at": "2026-11-14T07:42:11.44913905Z",
"user_account_id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc",
"version": 2,
"window_end": "2026-11-14T07:43:00Z",
"window_start": "2026-11-14T07:42:00Z"
},
"relationships": {
"api_member_id": {
"data": {
"type": "api_member",
"id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9"
}
},
"api_plan_id": {
"data": {
"type": "api_plan",
"id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a"
}
},
"user_account_id": {
"data": {
"type": "user_account",
"id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc"
}
},
"usergroup_id": {
"data": []
}
}
},
{
"type": "api_quota",
"id": "01a1d901-b2e0-773c-9c21-11248d657707",
"attributes": {
"__type": "api_quota",
"api_member_id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9",
"api_plan_id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a",
"bucket_key": "1:1:cost_micros:month:1793491200",
"consumed": 72,
"created_at": "2026-11-14T07:42:09.63303309Z",
"maximum": 10000000,
"metric": "cost_micros",
"permission": 561441,
"reference_id": "01a1d901-b2e0-773c-9c21-11248d657707",
"reserved": 0,
"updated_at": "2026-11-14T07:42:11.443380383Z",
"user_account_id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc",
"version": 2,
"window_end": "2026-12-01T00:00:00Z",
"window_start": "2026-11-01T00:00:00Z"
},
"relationships": {
"api_member_id": {
"data": {
"type": "api_member",
"id": "01a1d8eb-f034-7cd4-97fa-f8e6bfbe5cd9"
}
},
"api_plan_id": {
"data": { "type": "api_plan", "id": "01a1d8e0-e793-75f0-ba75-1d103fa8994a" } }, "user_account_id": { "data": { "type": "user_account", "id": "01a1d46b-46f6-7b6c-a6ea-3f9fe8cc26fc" } }, "usergroup_id": { "data": [] } } } ] }
理解配额总计与预留
每一行跟踪一个时间窗口内的一项指标。使用此计划时,请关注当前分钟的 requests、当前月的 tokens,以及当前月的 cost。
| 字段 | 含义 |
|---|---|
| metric | 此行计数的内容,例如 requests 或 total_tokens |
| maximum | 该计划和窗口对此指标的限额 |
| consumed | 已计入限额的用量 |
| reserved | 为仍在运行的请求保留的容量 |
| window_start , window_end | 此行覆盖的 UTC 时间范围 |
另一个请求的可用容量为:
maximum - consumed - reserved
例如,一个最大值为 1,000,000、已消耗用量为 200,000、预留为 10,000 的 token 配额,有 790,000 个 token 可用于新工作。在调用提供商之前,Daptin 会估算一个请求可能使用多少,并预留该数量。当请求完成时,它会记录用量并释放预留中未使用的部分。这可以防止同时进行的请求全都花掉同一份剩余预算。
新请求必须适合可用容量。如果它需要预留 20,000 个 token,但只有 15,000 个可用,Daptin 会返回 HTTP 402 而不调用提供商——即使该账户尚未消耗其全部限额。回退尝试可能会增加所需的预留。对话延续还会包含重放给模型的较早消息,因此其输入用量会随着对话增长。
## 测试硬性限制
临时将此计划改为零请求硬性限制:
curl -X PATCH http://localhost:6336/api/api_plan/PLAN_ID \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H 'Content-Type: application/vnd.api+json' \ -d '{ "data": { "type": "api_plan", "id": "PLAN_ID", "attributes": { "limits": [ {"metric":"requests","window":"minute","maximum":0,"mode":"hard"} ] } } }'
然后使用用户令牌重复 /completions 调用,预期会得到“insufficient quota”响应
curl http://localhost:6336/v1/chat/completions \ -H "Authorization: Bearer $USER_DAPTIN_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "model": "assistant", "messages": [{"role":"user","content":"Write a short greeting."}], "max_tokens": 128, "stream": false }'
预期响应(HTTP 402):
{"error":{"code":"insufficient_quota","message":"insufficient quota","request_id":"req_1a0a162ad2e6393984bb78afe1db5368","type":"insufficient_quota"}}
未激活或缺失的成员资格意味着不适用计划定义的上限,因此所有请求都会被放行。
## 10. 设置运行限制
## 保护上游部署免受过载
你还可以在 LLM Deployment 上应用上游限制,以保护上游免受过载/速率限制。例如:
curl -X PATCH http://localhost:6336/api/llm_deployment/PRIMARY_DEPLOYMENT_ID \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H 'Content-Type: application/vnd.api+json' \ -d '{ "data": { "type": "llm_deployment", "id": "PRIMARY_DEPLOYMENT_ID", "attributes": {"rpm":60,"tpm":100000,"max_concurrency":4} } }'
## 添加按 IP 的 HTTP 速率保护
全局限制器在认证之前运行,并且与账户配额分开。其分桶是客户端 IP 加请求路径,使用一秒的 UTC 窗口。默认值是每个 IP/路径每秒 500 个请求。此示例为每个推理路径设置每秒五个:
curl http://localhost:6336/_config/backend/limit.rate \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "version": "1", "limits": { "/v1/chat/completions": 5, "/v1/responses": 5 } }'
速率限制的更改需要重启
有关更多详细信息,请参阅 API 计量与速率限制。