文档导航

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。