HTTP API
HTTP API
laya-serve 通过 TypeSafe Jev 的 /v1/systemone 线上协议暴露 Laya。一个针对 Jev 写的客户端
—— hs-jev、typesafe-sdk,或你自己的 —— 把 base URL 指向这台服务器就能继续用:Laya 的
predict() 输出本就 schema 兼容,服务器只加了 HTTP 这一层:一条决策路由、一个健康探测、一个
可选的 bearer 校验和请求限额。
pip install "laya[serve]"
laya-serve # http://0.0.0.0:8000
同一个入口也能嵌进任何 ASGI 服务器:laya.serve.create_app() 构建这个 FastAPI 应用,可选地
注入一个 Router(create_app(router)),而不是从环境里构建一个。
配置
全部是环境变量,所以一个镜像既能服务笔记本上的开发运行,也能服务一个 systemd 单元。
| 环境变量 | 含义 | 默认值 |
|---|---|---|
LAYA_HOST |
绑定地址 | 0.0.0.0 |
LAYA_PORT |
绑定端口 | 8000 |
LAYA_ROOT_PATH |
在反向代理之后服务时的公开 URL 前缀 | 空 |
LAYA_DEVICE |
每个 checkpoint 用的 torch 设备 | auto |
LAYA_PRELOAD |
启动时就构建 checkpoint,而不是懒加载 | 1 |
LAYA_MODELS |
要预加载的逗号列表(english,multilingual,typed-decisions);留空 = 全部 |
全部 |
LAYA_THREADS |
限制 CPU 上 torch 的进程内线程数;保持在物理核心数以内 —— 超额订阅逻辑核心会带来很大的性能回退 | torch 默认 |
LAYA_AUTO_TASK |
自动路由到 typed-decisions checkpoint | 0 |
LAYA_API_KEY |
设置后要求 Authorization: Bearer <key> |
无 |
LAYA_LOG_LEVEL |
uvicorn 日志级别 | info |
LAYA_MAX_CONCURRENT |
通过鉴权后一次接纳的请求数;超出的拿到 503 |
16 |
对于一个发布在 /laya 这类前缀下的部署,设置 LAYA_ROOT_PATH=/laya。FastAPI 在生成 OpenAPI
和 Swagger UI URL 时会用它。把反向代理配置成在把请求转发给 Laya 之前剥掉 /laya;应用内部的
路由仍然是 /health 和 /v1/systemone。
容器(包括 CUDA 和 ARM64 镜像)见 Docker 快速开始。
端点
GET /health
始终开放(无需鉴权),并且在推理期间保持响应,因为 CPU 密集的前向传播跑在它自己的 worker 上, 而不是事件循环上。
{"status": "ok", "loaded": ["english", "multilingual"], "revisions": {"english": "..."}, "device": "auto"}
loaded 列出常驻内存的 checkpoint,revisions 列出每一个加载时所来自的 artifact 版本,于是
一个部署能确认它实际在服务什么。
POST /v1/systemone
一次请求带一个 state 和它上面任意数量的问题:
curl -s localhost:8000/v1/systemone -H 'content-type: application/json' -d '{
"state": "I was charged twice this month, I want my money back",
"questions": {
"queue": {"type": "choice", "instructions": "Which team?",
"criteria": {"billing": "billing and refunds", "tech": "login and app issues",
"other": "everything else"}},
"urgency": {"type": "score", "instructions": "How urgent?",
"criteria": ["calm", "firm", "angry", "furious"]}
}
}'
| 字段 | 必填 | 含义 |
|---|---|---|
state |
是 | 要据以决策的文本、邮件、工单或 JSON 文档;缺失或为 null 的 state 会得到 400 |
questions |
是 | 按问题 id 索引的对象;每个问题是 choice / score / noul,带 instructions 和 criteria |
model |
否 | 指名一个 checkpoint;其他任何值都被忽略(见下) |
task |
否 | 用工作流名强制指定一个 checkpoint,而不是让路由决定;未知的名字会得到一个点名它的 422 |
lang |
否 | 一个语言代码(de、en-US),当它指名一种语言时跳过检测;空白或无法识别的代码会落到检测这一步 |
lang_guess |
否 | 来自客户端自带识别器的语言代码,在 lang 之后、检测之前参考;任何非英文代码都路由到 multilingual checkpoint |
max_len |
否 | 这次请求的总 token 窗口,由 LAYA_MAX_TOKEN_BUDGET 设上限 |
head_max_len |
否 | 选项提示词共享的 token 窗口,同样受上限约束;什么时候一个问题需要它,见放宽 token 预算 |
min_confidence |
否 | [0.0, 1.0] 范围内的弃答阈值;answer_confidence 低于它的答案会带 low_confidence 标记返回,而答案本身仍然保留 |
model、task、lang、lang_guess、max_len、head_max_len 和 min_confidence 是
Router.predict 接受、而 JSON body 可以写明的参数;每一个只在请求发了它时才转发,所以缺了某个
时,由部署自己的 Router(...) 设置说了算。predict 还接受的五个钩子参数 —— hooks、
on_predict_start、on_predict_end、hooks_raise、hooks_timeout —— 会被以 422 拒绝,
而不是被丢弃:钩子是一个在服务器进程内部运行的可调用对象,而最后两个说明部署安装的钩子如何
执行,所以调用方发来的任何值在这里都没有意义。同样这五个会被一个带 base_url 的 LangChain
节点(laya.integrations.langchain)在客户端拒绝,所以一条链和一个裸 HTTP 客户端现在得到同样
的答复。
接受 model 是为了让 Jev 客户端能继续发它。公开的 Hugging Face id
(convaiinnovations/laya-multilingual、convaiinnovations/laya-typed-decisions)、checkpoint
名(english、multilingual、typed-decisions)及其别名会选定一个 checkpoint;其他任何值
—— 包括像 jev-1 这样的 Jev id —— 都表示「让路由器来选」,响应里的 routing 块记录选了什么
以及为什么。
响应
{
"model": "laya-rl-agent",
"answers": {
"queue": {"type": "choice", "choice": "billing",
"probabilities": {"billing": 0.9281, "tech": 0.0412, "other": 0.0307},
"confidence": 0.4534, "answer_confidence": 0.9281,
"action": {"act_probability": 1.0}},
"urgency": {"type": "score", "score": 2.6389,
"legend": {"0": "calm", "1": "firm", "2": "angry", "3": "furious"},
"probabilities": {"0": 0.0099, "1": 0.0713, "2": 0.536, "3": 0.3828},
"confidence": 0.3542, "answer_confidence": 0.536,
"action": {"act_probability": 1.0}}
},
"usage": {"input_tokens": 74, "output_tokens": 0},
"routing": {"model": "english", "repo": "convaiinnovations/laya", "reason": "English Latin text",
"detection": {"script": "latin", "language": "en", "is_english": true, "non_latin_fraction": 0.0}}
}
answers 和 usage 是 Jev 客户端要解码的键;model 是决策头那个固定的名字,而实际作答的
checkpoint 在 routing 里(model、repo、reason,以及它背后的 detection 或 lang_guess
证据)。
| 答案类型 | 键 |
|---|---|
choice |
choice(argmax 选项),每个选项的 probabilities |
score |
score(期望档位索引,可能落在档位之间),以 "0".. "k-1" 为键的 probabilities,把索引映射到档位文本的 legend |
noul |
noul,yes 选项的概率 |
| 全部 | confidence、answer_confidence 和 action.act_probability |
置信度:两个数字,不可互换
answer_confidence是落在所报告答案上的概率质量(max(p))。它正是温度缩放所拟合的量,也是 本仓库 ECE 数字所计算的量,所以它承载着基准与已知限制页所依赖的门控性质 —— 但只对你的流量上验证过温度拟合的 checkpoint 成立。confidence每种类型含义不同:在choice和score上是归一化熵1 - H(p)/log(k),在noul上是max(p_yes, p_no)(此时它等于answer_confidence)。
绝不要把这两个数字拿来和同一个阈值比较。从 Jev 迁移时还要注意这个差别:TypeSafe 把置信度定义为
(n*p_max - 1)/(n - 1),所以从 Jev 部署带过来的阈值在 Laya 的熵值上门的开关方式不一样。
成功的响应还会带上 Server-Timing: inference;dur=<ms> 和 X-Inference-Time-Ms。
限额
请求防护栏在 tokenization 之前检查,所以一次超大的请求除了它读过的字节外不花服务器任何成本。它们
每一个都是 413;detail 说明撞上了哪条限额。
| 限额 | 值 |
|---|---|
| 请求体 | 2 MiB,在流式传输时强制 —— 分块或低报的 Content-Length 绕不过去 |
state |
模型所看到的文本 50,000 个字符 —— 字符串 state 就是字符串本身,对象或数组则是 json.dumps(state, ensure_ascii=False) |
| 每请求问题数 | 64 |
每个 choice 问题的选项数 |
100 |
每个 score 问题的档位数 |
32 |
| 所有问题的选项总数 | 512 |
| 同时接纳的请求数 | LAYA_MAX_CONCURRENT(16) |
选项上限只是 HTTP 层的放大防护;模型本身要把选项 token 放进一个 head_max_len=192 的窗口,
所以一个在 HTTP 上限内的问题,当选项文本合起来超过那个预算时,仍然可能被拒绝为一个 422。
评估 harness 在没有 HTTP 层的情况下进程内跑同样的请求。
错误
| 状态码 | 何时 | body 的 detail |
|---|---|---|
400 |
body 不是合法 JSON、不是对象、没有 questions、state 缺失或为 null,或 questions 不是对象 |
哪里不对 |
401 |
设置了 LAYA_API_KEY 而 bearer token 缺失或错误 |
invalid or missing bearer token |
413 |
上面任何一条限额 | 哪条限额、超了多少 |
422 |
问题的 JSON 合法但对 Laya 无效(未知类型、选项超过 head 预算),或者某个请求控制项(lang、min_confidence、某个钩子参数)不是这个端点接受的形式 |
点名那个问题或字段,以及要修什么 |
500 |
推理因其他任何原因失败 | inference failed —— 永远是这串字符,所以路径、权重和内存状态永远不会泄漏;原因在服务器日志里 |
503 |
LAYA_MAX_CONCURRENT 个请求已经在途 |
server busy, try again later |
超载的负载是被拒绝,不是排队:客户端在流式传输慢 body 时占着接纳槽位,也饿不死 /health,而
一次重试可以拿走被拒客户端留下的槽位。
并发模型
推理是一次同步的 torch 调用,在 CPU 上要花几百毫秒到几秒,所以它绝不跑在事件循环上:请求交给一个
单 worker 的执行器,也就是一次一个前向传播 —— 这正是单设备上单个 checkpoint 想要的形状。接纳
(LAYA_MAX_CONCURRENT 信号量)在读任何 body 字节之前检查,并一直持有到推理结束;推理闸门只在
body 完整之后才加入,所以一个慢客户端占着接纳槽位,却从不占推理槽位。
(还)不在这里的东西
这台服务器有意只讲一种协议。没有 OpenAI 兼容端点,也没有批处理端点;改成在一次请求里跑多个问题,
因为每个问题集共享一次前向传播。laya CLI 和 MCP 服务器覆盖本地使用 —— 见
README。