Skip to main content
Glama

laya-mcp

把 Laya System-1 决策模型封装成一个工程化的 MCP Server(FastAPI 宿主)

一次前向传播回答一组「带类型的问题」,返回校准概率 + 可直接分支执行的决策契约, 让任意支持 MCP 的 Agent(Claude Code / Codex / Cursor / 自研 Agent)都能在毫秒级完成自动决策。

Python FastAPI MCP Laya


目录


Related MCP server: laya-mcp

这是什么

laya-mcp 把 Laya(一个 System-1 决策模型族) 包装成一个常驻服务,同时对外暴露两种接口:

接口

地址

面向谁

MCP Streamable HTTP

http://127.0.0.1:8077/mcp

任意 MCP 客户端 / Agent

REST API

http://127.0.0.1:8077/v1/...

脚本、看板、健康检查、非 MCP 调用方

OpenAPI 文档

http://127.0.0.1:8077/docs

人

它不是「又一个 LLM 接口」。Laya 是非生成式的:一次 forward pass 回答一组结构化问题, 输出的是校准过的概率分布,而不是一段文本。这使得它可以在 10~50ms 内做出「该走哪条路」的判断, 成本约为调用一次完整 LLM 的千分之一。

与传统做法对比

laya-test.py(见 examples/quickstart_router.py)演示了最小用法: 加载 Router → 构造问题 → router.predict() → 用阈值手写 if。

这个工程把它产品化了:

关注点

laya-test.py

laya-mcp

模型驻留

每次进程启动重新加载

常驻单例 + 启动预热(warmup)

阈值策略

每个调用方自己写 if conf >= 0.8

决策契约,由服务统一输出 grade

并发

单线程阻塞

线程池 + 事件循环不阻塞 + 结果缓存

接入方式

只能进程内 Python 调用

MCP / REST / CLI 三种入口共用同一套逻辑

可观测性

print

结构化日志 + 指标(计数 / 延迟分位) + /healthz

错误处理

直接抛异常

统一错误码 + HTTP 状态映射

配置

硬编码

LAYAMCP_* 环境变量 / .env

Agent 可发现性

无

工具自描述 + resources 知识库 + prompt 模板


核心概念:决策契约(Contract)

这是整个项目最重要的设计。 模型给出的是概率,但 Agent 需要的是策略: 「我现在可以直接执行吗?还是必须找人确认?」

原始概率无法直接回答这个问题——两个策略 0.79 / 0.78 时,最高分看起来很高,但它不是一个决策。 因此每次调用都会额外返回一个 contract 块,把概率翻译成 4 种 grade,Agent 直接分支即可:

grade

含义

Agent 应该做什么

✅ auto_execute

置信度越过阈值,且与次优有明显差距

直接执行,不要再花 token 重新推理

⚠️ escalate

高置信但存在近距平局,或问题本身语义上就该由人拍板

不要仅凭模型答案行动,走完整 LLM 分析或请人确认

🛑 ask_human

低置信 + 高风险场景

必须转人工

🔁 reconsider

低置信 + 低风险

可以重试:换更精确的 criteria、增加候选、或交给 LLM

grade 由三个信号共同决定(而不是单一置信度)

信号

作用

为什么单独看它不行

confidence

校准后的置信度

候选数量多时 softmax 会被抬高,必须用 Laya 的温度校准值

margin

top1 − top2

0.79 / 0.78 的两条相邻策略,不管最高分多高都不构成决策

needs_review

模型自带的「需人工复核」头

对语义上就要求人拍板的问题,高置信也依然是 escalate

契约结构示例

{
  "grade": "auto_execute",
  "autonomous": true,
  "requires_human": false,
  "threshold": 0.8,
  "margin_threshold": 0.05,
  "decisions": {
    "selected_strategy": {
      "value": "redis_cache",
      "confidence": 0.9231,
      "margin": 0.41,
      "grade": "auto_execute",
      "reason": "confidence 0.92 clears 0.80 and beats the runner-up by 0.41",
      "requires_human": false
    }
  },
  "grade_counts": { "auto_execute": 1, "escalate": 0, "ask_human": 0, "reconsider": 0 },
  "reasons": [],
  "summary": "all 1 answers clear the auto-execute bar; proceed",
  "routing": { "model": "english", "reason": "English Latin text" }
}

聚合规则:一个被卡住的问题会卡住整个请求。 整体 grade 取所有问题中最「保守」的那个 (优先级 ask_human > escalate > reconsider > auto_execute), 因为对 Agent 而言「部分可执行」比「整体不可执行」更危险。

Agent 典型分支伪代码

result = mcp.call("decide", state=diff, preset="code_change_risk")
grade = result["contract"]["grade"]

if grade == "auto_execute":
    merge_pull_request()                       # 直接干,零推理成本
elif grade == "escalate":
    run_full_llm_review(diff)                  # 花 token 深挖
elif grade == "ask_human":
    notify_reviewer(result["contract"]["reasons"])
else:  # reconsider
    result = mcp.call("decide", state=diff, preset="code_change_risk",
                      threshold="critical")    # 换阈值 / 换问题重试

为什么值得用

  • 大幅降低 Agent 决策成本:把「要不要做 / 走哪条路」交给 System-1 模型,避免为此唤起完整 LLM 的 CoT。

  • 不引入幻觉风险:非生成式,输出是封闭候选集上的概率,不会「编造」答案。

  • 策略集中在服务端:阈值、margin、风险等级都是配置,不用在每个 Agent 里重复实现。

  • 对 Agent 友好:工具自带详细 description 与 structuredContent,另有 laya://guide 等资源做知识注入。

  • 一套逻辑三种入口:MCP / REST / CLI 共用 laya_mcp.core,不会出现行为漂移。

  • 生产可用:健康检查、指标、结构化日志、超时与体积上限、线程安全、优雅降级(模型加载失败时 /healthz 返回 degraded 而非崩溃)。


架构

                        ┌─────────────────────────────┐
   MCP Agent  ────────► │  /mcp  StreamableHTTP       │
 (Claude Code /         │  (9 tools / 4 resources /   │
  Codex / Cursor)       │   2 prompts)                │
                        ├─────────────────────────────┤
   脚本 / 看板 ────────► │  /v1/...  REST (FastAPI)    │
                        └──────────────┬──────────────┘
                                       │  共用同一套实现
                        ┌──────────────▼──────────────┐
                        │        laya_mcp.core        │
                        │  settings / schema / preset │
                        │  engine(常驻+池+缓存)         │
                        │  contract(决策契约) / metrics │
                        └──────────────┬──────────────┘
                                       │
                        ┌──────────────▼──────────────┐
                        │  laya.Router (System-1)     │
                        │  english / multilingual /   │
                        │  typed-decisions            │
                        └─────────────────────────────┘

目录结构

laya-mcp/
├── src/laya_mcp/
│   ├── core/                 # 与传输方式无关的领域层
│   │   ├── settings.py       #   LAYAMCP_* 配置、阈值等级
│   │   ├── schema.py         #   公开问题 schema ↔ Laya 原生格式
│   │   ├── presets.py        #   5 个内置问题集
│   │   ├── contract.py       #   ★ 概率 → grade 的决策契约
│   │   ├── engine.py         #   单例 Router + 线程池 + LRU 缓存
│   │   ├── errors.py         #   统一错误分类与 HTTP 映射
│   │   └── metrics.py        #   计数 / 延迟分位
│   ├── api/                  # FastAPI 层
│   │   ├── app.py            #   create_app(),挂载 /mcp 与 /v1
│   │   ├── routes.py         #   REST 端点(不含决策逻辑)
│   │   ├── schemas.py        #   请求 / 响应模型
│   │   └── dependencies.py   #   DI:engine / settings / metrics
│   ├── mcp_server/           # MCP 层
│   │   ├── server.py         #   工具 / 资源 / Prompt 注册
│   │   └── questions.py      #   问题解析(inline / preset / 快捷参数)
│   ├── cli.py                # 命令行入口
│   └── logging_config.py     # 结构化日志
├── examples/quickstart_router.py   # 原始 laya-test.py 用法
├── tests/
└── pyproject.toml

关键实现说明

  • 模型只加载一次:DecisionEngine 进程级单例。启动时预热(LAYAMCP_WARMUP=true), 之后每次请求都是热态,毫秒级返回。

  • 不阻塞事件循环:推理跑在有界线程池里(LAYAMCP_WORKER_THREADS), torch 内部会释放 GIL,因此 CPU 上并发也有收益。

  • 结果缓存:对相同 (state, questions) 做进程内 LRU 缓存(默认 256 条), Agent 重试同样的问题几乎零成本;响应中 cached=true 可辨别。

  • MCP 挂载方式(容易踩坑):不能用 streamable_http_app() 再 mount 到 FastAPI—— Starlette 不会为子应用执行 lifespan,session manager 起不来,/mcp 会全部失败。 正确做法是在本应用的 lifespan 里进入 session manager:

    manager = StreamableHTTPSessionManager(app=server._lowlevel_server, ...)
    app.mount("/mcp", StreamableHTTPASGIApp(manager))
    # lifespan: async with manager.run(): yield

    参见 src/laya_mcp/api/app.py 的模块注释。

  • MCP SDK 版本:基于 mcp 2.x(from mcp.server.mcpserver import MCPServer)。 与 1.x 的 mcp.server.fastmcp.FastMCP 不兼容,升级时注意导入路径变化。


快速开始

环境要求

  • Python 3.13+

  • uv(推荐)

  • 磁盘约 3 GB(三个 checkpoint 合计约 2.2 GB)

  • CPU 即可运行;有 CUDA / Apple MPS 会自动加速

  • 首次运行需要联网从 HuggingFace 下载权重,之后可完全离线

安装

git clone <your-repo-url> laya-mcp
cd laya-mcp
uv sync                       # 创建 .venv 并安装依赖

自检

uv run laya-mcp doctor

输出示例:

laya-mcp 0.2.0
python: 3.13.x
settings: models=['english', 'multilingual'] device=auto port=8077
laya: 0.3.5 -> .../laya/__init__.py
torch: 2.x.x cuda=False
HF 缓存: 2.20 GB,包含 laya 检查点: True
fastapi: ok
uvicorn: ok
mcp: ok
pydantic_settings: ok
结论: 环境可用

启动服务

uv run laya-mcp serve
INFO  laya_mcp.engine  loading laya router  models=['english','multilingual'] device=cpu
INFO  laya_mcp.engine  laya router ready  loaded=['english','multilingual']
INFO  laya_mcp.api     engine ready in 67.8s
INFO  mcp.server...    StreamableHTTP session manager started
Uvicorn running on http://127.0.0.1:8077

⏳ 首次启动约 60~90 秒(在 CPU 上加载两个 checkpoint)。之后所有请求都是热态, 单次决策通常 10~50ms。

第一条请求

curl -s http://127.0.0.1:8077/v1/decide \
  -H 'content-type: application/json' \
  -d '{
    "state": "Fix a typo in README, one file, no interface change",
    "preset": "code_change_risk"
  }' | python -m json.tool

或者完全不启服务,直接一次性决策:

uv run laya-mcp ask \
  --state "Fix a typo in README, one file, no interface change" \
  --preset code_change_risk

接入 MCP Agent

服务启动后,任何支持 MCP 的客户端都可以直接接入。关键信息只有一条:

MCP Streamable HTTP 端点:http://127.0.0.1:8077/mcp

Claude Code

claude mcp add --transport http laya http://127.0.0.1:8077/mcp

Codex / Cursor / 通用 mcp.json

{
  "mcpServers": {
    "laya": {
      "type": "streamable-http",
      "url": "http://127.0.0.1:8077/mcp"
    }
  }
}

只给单个 Agent 用(stdio,无 HTTP)

不想起 HTTP 服务时,可以用 stdio 传输,由客户端自己拉起进程:

{
  "mcpServers": {
    "laya": {
      "command": "uv",
      "args": ["run", "--directory", "/abs/path/to/laya-mcp", "laya-mcp", "serve", "--transport", "stdio"]
    }
  }
}

stdio 模式下进程由客户端按需拉起,建议启动后先让 Agent 调一次 preload_warmup, 否则第一次决策会等待模型加载。HTTP 模式则无需关心——服务启动时已预热。

让 Agent「自动决策」的推荐接入方式

  1. 在系统提示里注入用法。服务已经在 MCP instructions 里写了使用指南, 另外可以让 Agent 读取 laya://guide 资源获取完整的问题编写规范。

  2. 强制 Agent 先查契约。约定:任何有副作用的动作前,先调用 decide / should_escalate, 并只在 contract.grade == "auto_execute" 时直接执行。

  3. 按风险等级传 threshold。删库、发钱、改线上配置这类动作,传 "high" 或 "critical"。

  4. 用 preset 起步,再按需内联问题。preset 与内联 questions 会合并到同一次前向传播, 边际成本几乎为零。


MCP 工具参考

共 9 个工具,全部返回结构化结果(文本 + structuredContent 字段同名)。

工具

用途

是否加载模型

典型耗时

decide

主力工具:对 state 回答一组带类型的问题,返回答案 + 契约

是

10~50ms

decide_yes_no

最省成本:单个是非问题 + 契约

是

~10ms

decide_shortlist

候选标签极多(几十~上百)时,先 embedding 粗筛 top-k 再决策

是

20~80ms

route_request

只做路由:判断语言 / 脚本 / 工作流,返回该用哪个 checkpoint 及原因

否

<1ms

list_presets

列出全部内置问题集及其 question id

否

<1ms

describe_questions

回显并校验一组问题(id、kind、候选、等级),不推理

否

<1ms

should_escalate

一站式:给定动作,直接回答「是否需要人工介入」

是

10~50ms

server_status

就绪状态、已加载 checkpoint、缓存条数、延迟分位

否

<1ms

preload_warmup

强制加载 checkpoint(仅 stdio 或关闭预热时需要)

是(慢)

30~90s

调用顺序建议(先便宜后昂贵)

route_request  <  list_presets / describe_questions  <  decide_yes_no  <  decide  <  decide_shortlist
   (<1ms)                  (<1ms)                      (~10ms)         (10~50ms)      (20~80ms)

decide 参数速查

参数

类型

说明

state

string | object | array

必填。模型真正读到的上下文:代码 / diff / 工单 / trace / 工具返回值。放原始材料,不要放摘要,摘要会掉准确率

questions

object | array

内联问题集,见 Question Schema

preset

string

内置问题集名;可与 questions 合并

threshold

number | string

auto_execute 所需置信度。数字 ∈ [0,1],或等级 trivial/low/normal/high/critical

model

string

强制 checkpoint:english / multilingual / typed-decisions

lang

string

提示 state 的语言(如 zh / en),跳过语言检测

task

string

强制使用 typed-decisions 的四个工作流之一

use_cache

bool

是否复用相同请求的缓存结果,默认 true

返回值结构

{
  "contract": { /* 见上文「决策契约」 */ },
  "grade": "auto_execute",          // 整体 grade,Agent 只需看这个
  "autonomous": true,               // 是否可完全自主执行
  "requires_human": false,
  "summary": "all 5 answers clear the auto-execute bar; proceed",
  "answers": {
    "blast_radius": {
      "kind": "choice",
      "value": "single_file",
      "confidence": 0.9642,
      "margin": 0.8713,
      "grade": "auto_execute",
      "reason": "confidence 0.96 clears 0.80 and beats the runner-up by 0.87",
      "requires_human": false,
      "probabilities": { "single_file": 0.9642, "module": 0.0929, "...": 0.0 },
      "raw": { /* Laya 原始返回,未加工 */ }
    }
  },
  "routing": { "model": "english", "reason": "English Latin text", "repo": "convaiinnovations/laya" },
  "latency_ms": 31.4,
  "cached": false,
  "usage": {}
}

Resources 与 Prompts

除了工具,服务还暴露 MCP Resources(可被 Agent 当知识库读取)与 Prompts(可复用模板)。

Resources

URI

内容

laya://guide

问题编写指南、kind 说明、grade 语义(强烈建议 Agent 读取)

laya://schema

decide / describe_questions 接受的问题 JSON Schema

laya://presets

全部内置问题集(JSON)

laya://status

实时就绪状态与延迟,等同 server_status

Prompts

名称

用途

decide_before_acting

把「打算执行的动作」转成一组问题集的填空模板

triage_message

对一条用户消息做分诊(意图 / 紧急度 / 情绪 / 流失风险)


内置问题集(Preset)

Preset 是问题的骨架,不是答案。它们覆盖了 Agent 最常遇到的五类场景:

名称

问题数

适用场景

包含的 question id

code_change_risk

5

代码改动 前置风险评审:爆炸半径、可回滚性、数据影响、测试覆盖、是否需人工

blast_radius, reversible, data_impact, test_coverage, needs_human_review

support_triage

4

客服工单分诊:意图、紧急度、情绪、流失风险

intent, is_urgent, frustration, churn_risk

input_guard

5

不可信输入的护栏:越狱、提示注入、敏感数据、危害程度、话题

jailbreak, prompt_injection, sensitive_data, harm_severity, topic

routing

4

模型/算力路由:难度、领域、是否需要工具、敏感性

difficulty, domain, needs_tools, is_sensitive

typed_decisions_agent_trace

5

Agent trace 可观测性(与 typed-decisions checkpoint 的工作流对齐)

action, needs_review, outcome, risk, urgency

# 查看全部 preset
uv run laya-mcp presets
# 查看单个 preset 的完整问题定义
curl -s http://127.0.0.1:8077/v1/presets/code_change_risk | python -m json.tool

preset + 自定义问题可以合并,一次前向传播全部回答:

{
  "state": "<diff 或工单>",
  "preset": "code_change_risk",
  "questions": {
    "target_release": {
      "kind": "choice",
      "instructions": "这个改动更适合进哪个版本?",
      "criteria": {
        "hotfix": "紧急修复,必须立刻发",
        "next_minor": "下个小版本即可",
        "backlog": "不急,排入待办"
      }
    }
  }
}

问题(Question)Schema

Laya 原生词汇是 choice / score / noul(且使用 t / ins / crit 短键)。 本工程定义了一套稳定、自描述的公开 schema,调用方不需要了解底层格式:

{
  "question_id": {
    "kind": "choice",                  // choice | score | yes_no
    "instructions": "选哪个方案?",       // 给模型的指令
    "criteria": {                      // 见下方「三种 kind」
      "redis_cache": "适合读多写少、容忍秒级延迟",
      "db_index": "适合查询条件固定、数据量中等"
    },
    "weight": 1.0,                     // 可选,建议性权重
    "description": "..."               // 可选,仅在罗列时展示
  }
}

三种 kind

kind

对应 Laya 类型

criteria 格式

说明

choice

choice

{标签: 何时选它}

N 选 1。每个候选的适用条件要写具体、可区分

score

score

["等级0", "等级1", ...]

有序等级列表,返回等级下标

yes_no

noul

{"true": "应该成立时的陈述"}

是非问题。陈述句要正向表述(true 表示「可以继续」)

编写要点(直接影响准确率)

  1. 一个问题只问一件事。不要问「这个改动安全吗」,而是拆成爆炸半径 / 可回滚 / 数据影响。

  2. 候选之间必须互斥且穷尽。留一个 other 兜底往往比硬选更好。

  3. criteria 写判定条件,不写形容词。✅「涉及共享 schema,会被其他团队消费」 ❌「影响很大」。

  4. state 放原始材料,代码、diff、完整工单原文,不要放你自己的总结。

  5. 问题数量 2~6 个最佳(上限由 LAYAMCP_MAX_QUESTIONS 控制,默认 32), 每个问题的边际成本极低——多问一个几乎不增加耗时。

  6. 不要用 Laya 做生成任务。它只做分类/打分,不是写作模型。

校验问题集

写好后先让服务回显校验,避免把无效问题发到模型:

curl -s http://127.0.0.1:8077/v1/questions/describe \
  -H 'content-type: application/json' \
  -d '{"preset": "input_guard"}' | python -m json.tool

REST API

Base path:/v1。完整交互式文档见 /docs。

决策

方法

路径

说明

POST

/v1/decide

回答一组带类型的问题(等同 MCP decide)

POST

/v1/decide/yes-no

单个是非问题

POST

/v1/decide/shortlist

大规模候选:embedding 粗筛 + 决策

POST

/v1/route

只做路由,不推理(微秒级)

目录 / 运维

方法

路径

说明

GET

/v1/presets

全部内置问题集(含问题定义)

GET

/v1/presets/{name}

单个问题集(规范化后的最终形态)

POST

/v1/questions/describe

校验并回显一组问题

GET

/v1/models

可用 checkpoint 及加载状态

GET

/v1/healthz

存活检查(模型加载失败返回 200 + degraded)

GET

/v1/status

引擎状态、阈值、缓存、延迟分位

GET

/v1/metrics

原始计数与延迟数据(供 Prometheus 等抓取)

GET

/v1/version

服务版本与 laya 版本

示例

# 1) 是非问题:这个动作能直接执行吗?
curl -s http://127.0.0.1:8077/v1/decide/yes-no \
  -H 'content-type: application/json' \
  -d '{
    "state": {"action": "DROP TABLE users", "env": "production"},
    "statement": "this action is safe to run without human review",
    "threshold": "critical"
  }' | python -m json.tool

# 2) 中文工单分诊(自动路由到 multilingual checkpoint)
curl -s http://127.0.0.1:8077/v1/decide \
  -H 'content-type: application/json' \
  -d '{"state": "客户反馈发票被重复扣款,语气很急", "preset": "support_triage"}' \
  | python -m json.tool

# 3) 只路由,不推理
curl -s http://127.0.0.1:8077/v1/route \
  -H 'content-type: application/json' \
  -d '{"state": "给我翻译一下这段中文"}' | python -m json.tool
# {"model":"multilingual","reason":"non-Latin script (han, 100% of letters); ..."}

统一错误格式

所有错误都是同一形状,并带 request_id 便于排查:

{
  "error": "invalid_question",
  "message": "at most 32 questions per call",
  "details": { "count": 40, "limit": 32 },
  "request_id": "0f3a9c1b2d4e5f60"
}

命令行 CLI

uv run laya-mcp --help

命令

说明

laya-mcp serve

启动服务(默认 FastAPI + MCP Streamable HTTP)

laya-mcp serve --transport stdio

仅提供 MCP,走标准输入输出,适合本地 Agent

laya-mcp ask --state ... --choices ...

一次性决策,不启服务(等价于 laya-test.py 的用法)

laya-mcp presets

列出内置问题集

laya-mcp tools

列出 MCP 工具(不加载模型)

laya-mcp doctor

检查环境、依赖与 checkpoint 缓存

serve 常用参数

uv run laya-mcp serve \
  --host 0.0.0.0 \
  --port 8077 \
  --models english,multilingual,typed-decisions \
  --device auto \
  --log-level INFO

参数

说明

--transport

streamable-http(默认,含 REST)/ stdio(仅 MCP)/ sse

--host / --port

监听地址与端口(默认 127.0.0.1:8077)

--models

常驻 checkpoint,逗号分隔,或 all

--device

auto / cpu / cuda / mps

--reload

开发热重载(仅 HTTP)

--no-warmup

启动时不加载模型(首次请求会变慢)

ask 示例

# N 选 1(--choices 是快捷写法)
uv run laya-mcp ask \
  --state "高并发下数据库查询变慢,Query 全表扫描,QPS 5000/s" \
  --instructions "根据性能瓶颈选择最合适的优化方案" \
  --choices "redis_cache,db_index,read_write_split,search_engine"

# 读取文件作为 state(@ 前缀)
uv run laya-mcp ask --state @./diff.patch --preset code_change_risk

# 输出完整 JSON(含契约)
uv run laya-mcp ask --state "..." --preset input_guard --json

# 是非问题
uv run laya-mcp ask --state "<发布计划>" \
  --statement "this can ship without human approval"

ask 的可读输出示例:

5/5 answers fail the auto-execute bar (...): ask a human before acting

blast_radius: single_file  (choice, confidence 0.64, margin 0.82, ask_human)
needs_human_review: True   (yes_no, confidence 0.60, margin 0.21, ask_human)

checkpoint: english - English Latin text

配置项(环境变量)

所有配置项前缀为 LAYAMCP_,也可写在项目根目录的 .env 里 (见 .env.example)。

服务

变量

默认值

说明

LAYAMCP_HOST

127.0.0.1

监听地址

LAYAMCP_PORT

8077

监听端口

LAYAMCP_TRANSPORT

streamable-http

streamable-http / stdio / sse

LAYAMCP_MCP_PATH

/mcp

MCP 挂载路径

LAYAMCP_API_PREFIX

/v1

REST 前缀

LAYAMCP_DOCS_ENABLED

true

是否开启 /docs

推理

变量

默认值

说明

LAYAMCP_MODELS

english,multilingual

常驻 checkpoint,逗号分隔或 all

LAYAMCP_DEVICE

auto

auto / cpu / cuda / mps(auto 优先 CUDA → MPS → CPU)

LAYAMCP_DEFAULT_MODEL

english

默认 checkpoint

LAYAMCP_AUTO_TASK_DETECTION

true

自动识别语言/脚本/工作流并路由

LAYAMCP_WARMUP

true

启动时预热加载模型

LAYAMCP_WORKER_THREADS

2

推理线程池大小(负载高时调大)

LAYAMCP_HF_TOKEN

无

私有镜像或受限下载时使用

策略(决策契约的阈值)

变量

默认值

说明

LAYAMCP_CONFIDENCE_THRESHOLD

0.80

达到此置信度才可能判为 auto_execute

LAYAMCP_MARGIN_THRESHOLD

0.05

top1−top2 低于此值视为「近距平局」→ escalate

LAYAMCP_MAX_STATE_CHARS

120000

state 最大字符数

LAYAMCP_MAX_QUESTIONS

32

单次调用最大问题数

LAYAMCP_MAX_CHOICES

60

单个 choice 问题最大候选数

日志与指标

变量

默认值

说明

LAYAMCP_LOG_LEVEL

INFO

DEBUG / INFO / WARNING / ERROR / CRITICAL

LAYAMCP_LOG_JSON

true

结构化 JSON 日志(接入 ELK / Loki 时保留)

LAYAMCP_METRICS_ENABLED

true

是否采集指标

LAYAMCP_REQUEST_ID_HEADER

x-request-id

请求 id 头名称

置信度等级

threshold 参数/配置支持用名字代替数字,便于按风险分级:

等级

数值

建议场景

trivial

0.60

无副作用的分类、打标签

low

0.70

内部草稿、低风险建议

normal

0.80(默认)

常规 Agent 决策

high

0.90

生产变更、影响用户的操作

critical

0.95

删数据、动钱、安全相关


Docker 部署

# 参考 Dockerfile
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim

WORKDIR /app
ENV UV_COMPILE_BYTECODE=1 \
    UV_LINK_MODE=copy \
    LAYAMCP_HOST=0.0.0.0 \
    LAYAMCP_PORT=8077 \
    HF_HOME=/models

COPY pyproject.toml uv.lock README.md ./
RUN uv sync --frozen --no-dev --no-install-project

COPY src ./src
RUN uv sync --frozen --no-dev

EXPOSE 8077
CMD ["uv", "run", "--no-dev", "laya-mcp", "serve"]
docker build -t laya-mcp .
docker run --rm -p 8077:8077 -v laya-models:/models \
  -e LAYAMCP_MODELS=english,multilingual \
  laya-mcp

建议把 /models 挂成 volume:首次下载约 2.2 GB,重建镜像不必重下。 生产环境请在服务前置反向代理并加鉴权——MCP 端点本身无内建认证。


开发与测试

uv sync
uv run laya-mcp doctor          # 环境自检
uv run laya-mcp tools           # 列出工具(不加载模型,适合快速验证)
uv run pytest -q                # 运行测试

不加载模型做冒烟测试

# 仅启动 app,不预热(秒级返回)
LAYAMCP_WARMUP=false uv run python -c "
from fastapi.testclient import TestClient
from laya_mcp.api.app import create_app
with TestClient(create_app()) as c:
    print(c.get('/').json())
    print(c.get('/v1/healthz').json())
"

扩展新工具

  1. 在 src/laya_mcp/core/ 里实现与传输无关的逻辑。

  2. 在 src/laya_mcp/mcp_server/server.py 里注册 @server.tool(...),返回 Pydantic 模型。

  3. 若要暴露 REST,在 src/laya_mcp/api/routes.py 加端点,复用同一函数,不要重写逻辑。

  4. 补充 laya://guide 里的说明,让 Agent 知道何时该用这个新工具。


常见问题 FAQ

正常。CPU 上加载 english + multilingual 两个 checkpoint 约需 6090 秒。 这是一次性成本——服务预热后单次决策仅 1050ms。

临时验证接口、不想等加载:--no-warmup(或 LAYAMCP_WARMUP=false), 此时首次决策会变慢。只验证工具列表可用 uv run laya-mcp tools(完全不加载模型)。

检查 routing.model 是否为 multilingual。Laya 的 english checkpoint 是 ModernBERT-large(512 token,只懂英文),中文会被自动路由到 multilingual(mmBERT-base,1024 token)。

若自动路由不符合预期,可显式指定:model="multilingual" 或 lang="zh"。 注意 multilingual 上下文更长(1024)但推理略慢,纯英文场景用 english 更快更准。

Laya 返回的是校准后的置信度,但候选数量多时原始 softmax 仍会被抬高, contract.py 里的 confidence_from_probs 已经做了校正。

另外注意启动时的告警:

laya: this checkpoint ships temperatures outside [0.5, 5] ... Treat confidence
from the affected buckets as uncalibrated.

命中这类 bucket 时,不要只看 confidence,务必参考 margin 与整体 grade—— 这正是契约存在的意义。

两个常见原因:

  1. 挂载方式错误。不能用 streamable_http_app() 再 mount 到 FastAPI(子应用 lifespan 不会执行, session manager 起不来)。请直接用 create_app()。

  2. 客户端连接方式不对。MCP 是有状态的 Streamable HTTP,客户端要用 streamable-http 传输类型 指向 http://<host>:<port>/mcp;用 SSE 或旧版 HTTP 传输连不上。

若前置了反向代理,请确保允许流式响应(关闭 buffering、开启 chunked、放行 mcp-session-id 头)。

可以。权重下载一次后会缓存在 HF_HOME(默认 ~/.cache/huggingface)。 用 uv run laya-mcp doctor 确认 包含 laya 检查点: True,之后设置 HF_HUB_OFFLINE=1 即可离线。

不会。模型是进程级单例,推理跑在有界线程池里,相同请求命中 LRU 缓存。 横向扩展只需多起几个进程/容器(每个进程会各自加载模型,注意 CPU/显存与内存占用)。

场景

用 Laya

用 LLM

「走哪条路 / 是不是 X / 严重程度几级」

✅

❌ 太贵太慢

需要生成文本、写代码、长链推理

❌

✅

高频、对延迟敏感(<100ms)

✅

❌

候选集封闭、要求可复现

✅

⚠️ 不稳定

实践上两者是互补关系:用 Laya 做前置判断与分流,只在 grade != auto_execute 时才唤起 LLM。

直接把 questions 内联传给 decide 即可,无需改代码:

{
  "state": "...",
  "questions": {
    "priority": {
      "kind": "choice",
      "instructions": "这条需求应该排什么优先级?",
      "criteria": {
        "p0": "线上故障,立刻处理",
        "p1": "本周内必须完成",
        "p2": "可以排入下个迭代"
      }
    }
  }
}

需要长期复用时,在 src/laya_mcp/core/presets.py 的 PRESETS 中注册一个新条目。

  • GET /v1/healthz — 存活(模型失败也返回 200,status=degraded)

  • GET /v1/status — 就绪、已加载 checkpoint、缓存、延迟分位

  • GET /v1/metrics — 原始计数与延迟,可直接被抓取

  • 所有响应带 x-request-id 与 x-response-time-ms 头


参考


许可证

见仓库根目录 LICENSE。使用 Laya 权重时请同时遵守其 HuggingFace 页面上的许可条款。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides MCP clients with typed, probabilistic decisions (choices, probabilities, scores) from TypeSafe's System One model, offering validated outputs and flexible provider support.
    2
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides an MCP interface to the Laya decision model, enabling typed queries (yes/no, multiple choice, score) with preflight token-budget reporting, honest confidence calibration, and structured error handling.
    Apache 2.0