# Local Model Service Protocol (LMSP) 1.0, 草案 > Made for Agent 发布开放、可被机器读取的协议,让 AI Agent、模型和工具能够互相发现、互相通信。第一个协议是 LMSP(本地模型服务协议)。 Source: https://madeforagent.com/zh-cn/llms-full.txt Updated: 2026-11-08 --- # 本地模型服务协议(LMSP) > LMSP 是一套开放协议,用于发现和调用运行在用户自己设备上的语言模型,涵盖服务发现、模型目录、流式响应、持久化的异步任务,以及 OpenAI 兼容接口。 Source: https://madeforagent.com/zh-cn/lmsp/ Language: zh-CN Updated: 2026-11-08 ## LMSP 解决什么问题 {#what} LMSP(Local Model Service Protocol,本地模型服务协议)是一套开放协议,面向在用户自己设备或局域网中运行语言模型的软件,以及使用这些软件的客户端。 常驻的本地服务会在约定地址上描述自己并列出模型,用 JSON 或服务器发送事件(SSE)回应生成请求,并把长时间的生成作为持久化任务来运行:任务可以取消,客户端断线后也可以重新接上。同一个服务还兼容 OpenAI API,因此今天已有的客户端可以继续使用。 LMSP 并不取代 OpenAI API、Ollama 或 MCP,而是把它们的优点组合成一个适合本地常驻服务的协议层。 ## 概览 {#glance} | 主题 | 概要 | | --- | --- | | 默认地址 | `http://127.0.0.1:17832` | | 服务描述 | `GET /.well-known/lmsp.json` | | 核心接口 | `/lmsp/v1`,基于 HTTP 的 JSON,流式传输使用服务器发送事件(SSE) | | 兼容接口 | 面向 OpenAI 客户端的 `/v1`,面向 Ollama 客户端的可选 `/api` | | 服务发现 | 手动配置、探测本机约定地址、可选的 mDNS 与 DNS-SD(`_lmsp._tcp.local.`) | | 认证 | 通过本机配对获取的 Bearer Token | | 持久化 | 嵌入式 SQLite,任务在服务重启后仍然保留 | | 契约 | OpenAPI 3.2 | ## 快速试用 {#try} 如果你的机器上正运行着 LMSP 服务,完整流程如下。请先按[第 10.2 节](https://madeforagent.com/zh-cn/lmsp/1.0/security.md#pairing)完成配对以获得令牌,并把它保存在 `LMSP_TOKEN` 中。 ```sh # 1. 发现服务 curl http://127.0.0.1:17832/.well-known/lmsp.json # 2. 列出本地模型 curl http://127.0.0.1:17832/lmsp/v1/models \ -H "Authorization: Bearer $LMSP_TOKEN" # 3. 生成响应 curl http://127.0.0.1:17832/lmsp/v1/responses \ -H "Authorization: Bearer $LMSP_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"qwen3:14b-q4_k_m","input":[{"type":"message","role":"user","content":[{"type":"input_text","text":"Hello"}]}]}' ``` ## 与其他协议的关系 {#related} | 协议 | 覆盖范围 | 与 LMSP 的关系 | | --- | --- | --- | | OpenAI API | 云端托管的模型,Responses API 是其最新接口。 | LMSP 的核心接口沿用 Responses 的结构,服务同时提供兼容 OpenAI 的接口。 | | Ollama | 本地运行时,已提供兼容 OpenAI 的接口。 | 它可以作为适配器放在 LMSP 服务之后,LMSP 也定义了可选的兼容 Ollama 的接口。 | | MCP | 把智能体连接到工具和上下文。 | LMSP 把智能体连接到模型。两者都倾向于无状态发现,而不是强制会话。 | | A2A | 智能体之间的协作。 | LMSP 的服务描述和任务分别借鉴了其服务卡片和显式任务状态。 | ## 资源 {#resources} - [规范 1.0 版(草案)](https://madeforagent.com/zh-cn/lmsp/1.0.md)。 - OpenAPI 契约,提供 [JSON](https://madeforagent.com/lmsp/1.0/openapi.json) 和 [YAML](https://madeforagent.com/lmsp/1.0/openapi.yaml) 两种格式。 - 面向语言模型的 [llms.txt](https://madeforagent.com/zh-cn/llms.txt) 和 [llms-full.txt](https://madeforagent.com/zh-cn/llms-full.txt)。 - 每个页面都提供 Markdown 版本:把页面地址末尾的斜杠换成 `.md` 即可,例如 [`/zh-cn/lmsp/1.0/tasks.md`](https://madeforagent.com/zh-cn/lmsp/1.0/tasks.md)。 ## 常见问题 ### LMSP 是什么? LMSP(Local Model Service Protocol,本地模型服务协议)是一套开放协议,面向运行在用户自己设备或局域网中的语言模型服务,规定客户端如何发现服务、列出模型、请求响应、接收流式输出以及运行长任务。 ### LMSP 会取代 OpenAI API、Ollama 或 MCP 吗? 不会。LMSP 建立在它们之上:核心接口沿用 OpenAI Responses 的结构,服务可以同时提供兼容 OpenAI 和兼容 Ollama 的接口,协议还借鉴了 MCP 的无状态发现和 A2A 的任务状态。 ### 现有的 OpenAI 客户端可以使用 LMSP 服务吗? 通常可以。只需把 base URL 改为 LMSP 服务的地址,并把 LMSP 令牌作为 API Key。但它们无法自动发现服务,因为服务发现需要支持 LMSP 的客户端。 ### 客户端如何发现本地的 LMSP 服务? 按顺序尝试三种方式:先使用用户手动输入的地址,再访问本机约定地址的 /.well-known/lmsp.json,最后在局域网中通过可选的 mDNS 与 DNS-SD 浏览 _lmsp._tcp.local.。 ### 只监听 localhost 的模型服务安全吗? 不一定,因为网页可能尝试访问本机端口。LMSP 服务默认绑定回环地址,要求客户端通过本机配对获取 Bearer Token,校验 Origin 与 Host 请求头,局域网模式下必须启用认证。 ### 这份规范目前处于什么状态? 1.0 版是首个公开草案,仍可能发生不兼容的修改。英文文本是规范性文本,译文仅为方便阅读而提供。 --- # 概述 > LMSP 1.0 草案概述:本地模型服务协议是什么、它所借鉴的设计、分层架构、阅读规范的方法,以及全文使用的术语。 Source: https://madeforagent.com/zh-cn/lmsp/1.0/ Language: zh-CN Specification: LMSP 1.0 (draft) Updated: 2026-11-08 ## 1.1 摘要 {#abstract} 本地模型服务协议(LMSP)规定了客户端如何发现并使用运行在用户自己设备或局域网中的语言模型服务。它定义了服务发现、模型目录、同步与流式生成,以及在服务重启后仍然保留的异步任务,还规定了同一个服务如何在 LMSP 接口之外提供兼容 OpenAI 的接口,使现有客户端可以继续使用。 LMSP 并不取代 OpenAI API、Ollama 或 MCP,而是把它们的优点组合成一个适合本地常驻服务的协议层。 ## 1.2 本文档的状态 {#status} 这是 LMSP 1.0 的首个公开草案,发布于 2026-11-08。“LMSP”是暂定名称。本文欢迎反馈,在 1.0 定稿之前可能发生不兼容的修改。 英文文本是规范性文本。译文仅为方便阅读而提供;两者如有差异,以英文为准。 ## 1.3 设计依据 {#design-basis} LMSP 复用已被验证的设计,而不是另起炉灶: - **OpenAI Responses API。** 结构化的输出项、语义化的服务器发送事件,以及支持查询和取消的长任务后台执行。它是 LMSP 核心对话接口的参考。参见[流式响应](https://developers.openai.com/api/docs/guides/streaming-responses)和[后台模式](https://developers.openai.com/api/docs/guides/background)。 - **Ollama。** 它提供兼容 OpenAI 的 `/v1/models`、`/v1/chat/completions` 和 `/v1/responses` 接口,说明“统一服务+OpenAI 适配层”能显著降低现有客户端的接入成本。参见 [Ollama 的 OpenAI 兼容性](https://docs.ollama.com/api/openai-compatibility)。 - **模型上下文协议(MCP)。** 其最新设计已从强制初始化会话转向无状态发现:每个请求独立携带版本信息,客户端通过发现接口获取服务端能力。这非常适合本地服务,因为服务重启、客户端崩溃和连接断开都不应破坏协议状态。参见 [MCP](https://modelcontextprotocol.io/)。 - **Agent2Agent(A2A)。** 服务卡片、显式任务状态、任务订阅和取消机制,为 LMSP 的服务描述和异步任务模型提供了借鉴。参见 [A2A](https://a2a-protocol.org/)。 ## 1.4 架构 {#architecture} LMSP 分为三层,其下是对接各模型运行时的适配器: ```text +---------------------------------------------------------------+ | Discovery mDNS / DNS-SD + well-known service document | +---------------------------------------------------------------+ | Core model catalog, responses, SSE, async tasks | +---------------------------------------------------------------+ | Compatibility OpenAI API, optional Ollama API | +---------------------------------------------------------------+ | Adapters llama.cpp, Ollama, vLLM, custom runtimes | +---------------------------------------------------------------+ ``` - **发现层。** mDNS/DNS-SD 与 Well-Known 服务描述文档([第 3 章](https://madeforagent.com/zh-cn/lmsp/1.0/discovery.md))。 - **核心层。** 模型目录、同步响应、服务器发送事件和异步任务([第 5 至 7 章](https://madeforagent.com/zh-cn/lmsp/1.0/models.md))。 - **兼容层。** 兼容 OpenAI 的 API,以及可选的兼容 Ollama 的 API([第 8 章](https://madeforagent.com/zh-cn/lmsp/1.0/compatibility.md))。 - **适配器。** 对接 llama.cpp、Ollama、vLLM 或自研运行时的桥接层。 核心原则是:**一个模型执行内核,对外同时提供 LMSP 和 OpenAI 两种接口**。服务在同一个内核之上提供这两类接口,因此模型加载、请求执行和任务跟踪都只有一处实现。 ## 1.5 规范性用语 {#conformance} 本文中的关键词 **必须**(MUST)、**不得**(MUST NOT)、**必需**(REQUIRED)、**应**(SHOULD)、**不应**(SHOULD NOT)、**建议**(RECOMMENDED)、**可以**(MAY)和**可选**(OPTIONAL),仅当以粗体出现时,才按 [BCP 14](https://www.rfc-editor.org/info/bcp14)([RFC 2119](https://www.rfc-editor.org/rfc/rfc2119)、[RFC 8174](https://www.rfc-editor.org/rfc/rfc8174))中的含义解释。 每一章都标明是规范性的还是资料性的。规范性章节定义要求,资料性章节提供指引,示例均为资料性内容。JSON 使用 UTF-8 编码,字段名使用 `snake_case`。 ## 1.6 术语 {#terminology} | 术语 | 含义 | | --- | --- | | 服务 | 实现 LMSP 并通过适配器运行模型的常驻进程。 | | 客户端 | 发现并调用服务的软件:智能体、应用或工具。 | | 适配器 | 把服务连接到某个模型运行时(如 llama.cpp、Ollama 或 vLLM)的组件。 | | 模型 | 本地可用的模型,由稳定的 `id` 标识。 | | 响应 | 一次生成请求的结果:输出项、用量和状态。 | | 任务 | 对响应请求的持久化异步执行,具有明确的状态。 | | 事件 | 生成响应期间发出的一条带序号的服务器发送事件。 | --- # 传输与部署 > LMSP 服务的默认监听地址、传输方式、流式格式和 URL 布局,以及它如何作为独立于模型进程的操作系统服务常驻运行。 Source: https://madeforagent.com/zh-cn/lmsp/1.0/transport/ Language: zh-CN Specification: LMSP 1.0 (draft) Updated: 2026-11-08 ## 2.1 默认部署 {#deployment} 服务默认**必须**监听回环地址 `127.0.0.1` 和 `::1`。约定端口为 `17832`,因此默认基础地址是 `http://127.0.0.1:17832`。 | 项目 | 取值 | | --- | --- | | 监听地址 | `127.0.0.1` 和 `::1` | | 传输 | HTTP/1.1 或 HTTP/2,承载 JSON | | 流式传输 | 服务器发送事件,`Content-Type: text/event-stream` | | 核心接口前缀 | `/lmsp/v1` | | OpenAI 兼容接口前缀 | `/v1` | | 服务描述 | `GET /.well-known/lmsp.json` | | 机器可读契约 | `GET /lmsp/v1/openapi.json` | | 可选的本机通道 | Unix Domain Socket 或 Windows Named Pipe | 请求和响应使用 UTF-8 编码的 JSON(`Content-Type: application/json`),流(`text/event-stream`)和错误(`application/problem+json`)除外。时间戳为整数:自 Unix 纪元起的秒数。标识符是不透明字符串;**建议**带上 `resp_`、`task_` 之类的类型前缀。 接口契约是一份 OpenAPI 3.2 文档。OpenAPI 3.2 可以描述服务器发送事件、JSON Lines 和 JSON Sequence,因此流式接口与其余接口在同一份文档中定义。本版本的契约发布在 [`/lmsp/1.0/openapi.json`](https://madeforagent.com/lmsp/1.0/openapi.json)。 服务**可以**另外在 Unix Domain Socket 或 Windows Named Pipe 上接受连接,HTTP 语义相同。在 1.0 中,这类端点如何对外声明交由扩展决定。 ## 2.2 常驻运行 {#operation} 服务**应**由操作系统托管:macOS 使用 launchd,Linux 使用 systemd,Windows 使用 Windows Service。 服务进程与模型进程**应**分离。模型崩溃**应**只影响使用该模型的任务,**不应**导致服务发现、模型目录和历史任务查询同时失效。 --- # 服务发现 > LMSP 客户端如何通过手动配置、探测本机地址和可选的 mDNS/DNS-SD 找到本地服务,以及如何读取 Well-Known 服务描述文档。 Source: https://madeforagent.com/zh-cn/lmsp/1.0/discovery/ Language: zh-CN Specification: LMSP 1.0 (draft) Updated: 2026-11-08 ## 3.1 发现方式 {#methods} 客户端按以下顺序尝试各种方式来发现服务: 1. **手动配置。** 用户直接输入服务地址。 2. **探测本机约定地址。** 客户端向候选地址请求 `/.well-known/lmsp.json`。约定的候选地址是 `http://127.0.0.1:17832` 和 `http://[::1]:17832`。 3. **局域网发现。** 客户端通过 mDNS 与 DNS-SD 浏览 `_lmsp._tcp.local.`。 某种方式只要得到一份有效的服务描述([第 3.3 节](https://madeforagent.com/zh-cn/lmsp/1.0/discovery.md#service-description))即告成功。用户手动输入的地址始终优先于自动发现的地址。 ## 3.2 局域网广播 {#mdns} DNS-SD 使用 PTR 记录查找服务实例,通过 SRV 记录获取主机与端口,通过 TXT 记录携带少量附加信息;与 mDNS 结合可实现零配置的局域网发现([RFC 6763](https://www.rfc-editor.org/rfc/rfc6763)、[RFC 6762](https://www.rfc-editor.org/rfc/rfc6762))。 示例广播: ```text Service type: _lmsp._tcp.local. Instance name: Workstation LMSP._lmsp._tcp.local. TXT record: v=1 id=svc_01J8M4Y8K6M5E6K6Z7Z0Y8W3M2 path=/ auth=pairing ``` | TXT 键 | 含义 | | --- | --- | | `v` | 广播格式的版本。LMSP 1.0 为 `1`。 | | `id` | 服务 id,与服务描述中的 `service.id` 相同。 | | `path` | 服务根路径的 URL 路径。客户端请求 `{path}.well-known/lmsp.json`。 | | `auth` | 认证提示。`pairing` 表示客户端必须先配对才能使用服务。 | 规则: - 服务**不得**在 TXT 记录中放置访问令牌、模型清单、文件路径或其他敏感信息。该记录只负责找到服务,真实能力**必须**从服务描述中读取。 - 服务**必须**默认关闭 mDNS 广播,仅在用户明确开启“局域网发现”时才启用。仅在本机使用的服务无需广播。 - 在局域网中广播的服务需要监听回环地址之外的地址,而这只有在局域网模式下才被允许([第 10.5 节](https://madeforagent.com/zh-cn/lmsp/1.0/security.md#lan))。 - 客户端**必须**把 TXT 记录视为未经验证的提示,并通过读取服务描述来确认服务。 ## 3.3 服务描述文档 {#service-description} ```http GET /.well-known/lmsp.json ``` 服务**必须**以 `application/json` 提供该文档,且不要求认证。 ```json { "protocol": "lmsp", "versions": ["1.0"], "service": { "id": "svc_01J8M4Y8K6M5E6K6Z7Z0Y8W3M2", "name": "Workstation Local Models", "version": "1.0.0" }, "interfaces": [ { "kind": "lmsp", "version": "1.0", "url": "http://127.0.0.1:17832/lmsp/v1", "transport": "http+json" }, { "kind": "openai", "version": "responses+chat", "url": "http://127.0.0.1:17832/v1", "transport": "http+json" } ], "capabilities": { "streaming": true, "tasks": true, "event_resume": true, "conversations": true, "model_management": true }, "authentication": { "type": "bearer", "pairing_url": "http://127.0.0.1:17832/lmsp/v1/pair" }, "openapi": "http://127.0.0.1:17832/lmsp/v1/openapi.json" } ``` | 字段 | 含义 | | --- | --- | | `protocol` | 始终为 `"lmsp"`。 | | `versions` | 服务支持的 LMSP 版本,格式为 `major.minor` 字符串。 | | `service` | `id` 是稳定的服务标识符,`name` 是显示名称,`version` 是服务软件的版本。 | | `interfaces` | 所提供的接口。每项包含 `kind`(`lmsp`、`openai` 或 `ollama`)、`version`、基础 `url` 和 `transport`。对 `openai` 而言,`version` 表示所提供的 API 系列,例如 `responses+chat`。 | | `capabilities` | 布尔型功能标志,见下表。 | | `authentication` | `type` 为 `bearer`。`pairing_url` 是客户端用配对码换取令牌的地址([第 10.2 节](https://madeforagent.com/zh-cn/lmsp/1.0/security.md#pairing))。 | | `openapi` | 描述 LMSP 接口的 OpenAPI 文档的 URL。 | 能力标志: | 标志 | 含义 | | --- | --- | | `streaming` | 响应可以通过服务器发送事件流式返回。在 1.0 中**必须**为 `true`。 | | `tasks` | 提供异步任务。在 1.0 中**必须**为 `true`。 | | `event_resume` | 任务事件流可以订阅和恢复(`GET /lmsp/v1/tasks/{task_id}/events`)。 | | `conversations` | 服务保存响应,因此 `store` 和 `previous_response_id` 可用,`GET /lmsp/v1/responses/{response_id}` 也可用。 | | `model_management` | 可以显式加载和卸载模型。 | ## 3.4 客户端行为 {#client-behavior} 客户端: - **应**从 `versions` 中选择自己也支持的最高版本。 - **应**根据自己所能使用的协议,从 `interfaces` 中选择 LMSP 接口或 OpenAI 接口。 - **必须**只使用服务描述中声明的能力,缺失的标志视为 `false`。 - **必须**忽略不认识的扩展字段。 - **应**定期或在请求失败时重新读取服务描述,而不是永久缓存。 - **应**用 `service.id` 而不是地址来记住所选的服务,因为地址可能变化。 --- # 接口总表 > LMSP 1.0 全部接口的总表,包含方法、路径、必需性和用途,以及关于路径、认证和可选接口的约定。 Source: https://madeforagent.com/zh-cn/lmsp/1.0/endpoints/ Language: zh-CN Specification: LMSP 1.0 (draft) Updated: 2026-11-08 ## 4.1 接口总表 {#table} 合规的服务**必须**实现标记为“必需”的每个接口,**可以**实现可选接口,并通过其[服务描述](https://madeforagent.com/zh-cn/lmsp/1.0/discovery.md#service-description)中的能力标志声明。 | 方法 | 路径 | 必需性 | 用途 | | --- | --- | --- | --- | | GET | `/.well-known/lmsp.json` | 必需 | [服务与能力发现](https://madeforagent.com/zh-cn/lmsp/1.0/discovery.md#service-description) | | GET | `/lmsp/v1/openapi.json` | 必需 | 机器可读的接口规范 | | GET | `/lmsp/v1/health` | 必需 | [存活与就绪检查](https://madeforagent.com/zh-cn/lmsp/1.0/common-rules.md#health) | | POST | `/lmsp/v1/pair` | 必需 | [用配对码换取 Bearer Token](https://madeforagent.com/zh-cn/lmsp/1.0/security.md#pairing) | | GET | `/lmsp/v1/models` | 必需 | [获取本地模型列表](https://madeforagent.com/zh-cn/lmsp/1.0/models.md#list) | | GET | `/lmsp/v1/models/{model_id}` | 必需 | [获取单个模型详情](https://madeforagent.com/zh-cn/lmsp/1.0/models.md#operations) | | POST | `/lmsp/v1/responses` | 必需 | [同步或流式生成](https://madeforagent.com/zh-cn/lmsp/1.0/responses.md#request) | | GET | `/lmsp/v1/responses/{response_id}` | 可选 | [查询已保存的响应](https://madeforagent.com/zh-cn/lmsp/1.0/responses.md#sync) | | POST | `/lmsp/v1/tasks` | 必需 | [创建异步生成任务](https://madeforagent.com/zh-cn/lmsp/1.0/tasks.md#create) | | GET | `/lmsp/v1/tasks/{task_id}` | 必需 | [查询异步任务](https://madeforagent.com/zh-cn/lmsp/1.0/tasks.md#create) | | POST | `/lmsp/v1/tasks/{task_id}/cancel` | 必需 | [取消异步任务](https://madeforagent.com/zh-cn/lmsp/1.0/tasks.md#cancel) | | GET | `/lmsp/v1/tasks/{task_id}/events` | 可选 | [订阅或恢复事件流](https://madeforagent.com/zh-cn/lmsp/1.0/tasks.md#events) | | POST | `/lmsp/v1/models/{model_id}/load` | 可选 | [预加载模型](https://madeforagent.com/zh-cn/lmsp/1.0/models.md#operations) | | POST | `/lmsp/v1/models/{model_id}/unload` | 可选 | [卸载模型](https://madeforagent.com/zh-cn/lmsp/1.0/models.md#operations) | `/v1` 下兼容 OpenAI 的接口,以及可选的兼容 Ollama 的接口,列在[第 8 章](https://madeforagent.com/zh-cn/lmsp/1.0/compatibility.md)。 ## 4.2 约定 {#conventions} - **请求体。** 请求体和响应体均为 JSON(`application/json`),事件流(`text/event-stream`)和错误(`application/problem+json`)除外。 - **路径参数。** 花括号中的名称(如 `{model_id}`)是占位符。取值**必须**经过百分号编码。形如 `qwen3:14b-q4_k_m` 的模型 id 要写成 `qwen3%3A14b-q4_k_m`。 - **认证。** 除 `/.well-known/lmsp.json`、`/lmsp/v1/openapi.json`、`/lmsp/v1/health` 和 `/lmsp/v1/pair` 之外,每个接口都要求 `Authorization: Bearer `。 - **可选接口。** 服务未实现的可选接口**必须**返回 `501 Not Implemented`,错误码为 `unsupported_capability`。 - **版本与错误。** 请求可以携带版本请求头,所有错误使用统一的格式,两者均在[第 9 章](https://madeforagent.com/zh-cn/lmsp/1.0/common-rules.md)中定义。 - **契约。** 发布在 [`/lmsp/1.0/openapi.json`](https://madeforagent.com/lmsp/1.0/openapi.json) 的 OpenAPI 文档是本章的机器可读形式。两者如有差异,以文本为准。 --- # 模型目录 > LMSP 服务如何列出本地模型,包括模型对象及其能力标志、模型生命周期状态、稳定的模型 id 和目录修订号。 Source: https://madeforagent.com/zh-cn/lmsp/1.0/models/ Language: zh-CN Specification: LMSP 1.0 (draft) Updated: 2026-11-08 ## 5.1 获取模型列表 {#list} ```http GET /lmsp/v1/models?capability=chat&ready_only=false Authorization: Bearer ``` | 查询参数 | 含义 | | --- | --- | | `capability` | 只返回具备指定能力的模型,例如 `chat`、`embeddings` 或 `tools`。 | | `ready_only` | 为 `true` 时只返回 `status` 为 `ready` 的模型,默认为 `false`。 | ```json { "object": "list", "catalog_revision": "42", "data": [ { "id": "qwen3:14b-q4_k_m", "object": "model", "display_name": "Qwen3 14B Q4_K_M", "owned_by": "local", "family": "qwen3", "revision": "sha256:8f27c1a4e9b05d36f2a7c8e1b4d90a63c5e2f7b8a1d4c9e60b3f5a7d2c8e1b04", "status": "ready", "capabilities": { "chat": true, "completion": true, "embeddings": false, "tools": true, "vision": false, "structured_output": true, "streaming": true }, "input_modalities": ["text"], "output_modalities": ["text"], "context_window": 32768, "max_output_tokens": 8192, "runtime": { "backend": "llama.cpp", "device": "metal", "load_on_demand": true } } ] } ``` 为避免客户端反复拉取完整目录,服务**应**返回 `ETag` 响应头、`catalog_revision` 字段,或两者都返回: - `catalog_revision` 是不透明字符串,目录每次变化时都会改变。 - 发送 `ETag` 的服务**应**对匹配的 `If-None-Match` 返回 `304 Not Modified`。 ## 5.2 模型对象 {#object} | 字段 | 含义 | | --- | --- | | `id` | 稳定的模型标识符,所有指明模型的请求都使用它。 | | `object` | 始终为 `"model"`。 | | `display_name` | 便于阅读的名称,用户可以修改。 | | `aliases` | 可选的别名列表,仅供参考。 | | `owned_by` | 模型的来源。安装在本设备上的模型为 `local`。 | | `family` | 模型家族,例如 `qwen3`。 | | `revision` | 模型内容的摘要,例如 `sha256:8f27...`。 | | `status` | 生命周期状态([第 5.3 节](https://madeforagent.com/zh-cn/lmsp/1.0/models.md#lifecycle))。 | | `capabilities` | 布尔标志:`chat`、`completion`、`embeddings`、`tools`、`vision`、`structured_output` 和 `streaming`。 | | `input_modalities`、`output_modalities` | 模型读取和写入的内容类型。1.0 只定义了 `text`。 | | `context_window` | 上下文的最大 token 数。 | | `max_output_tokens` | 单次响应的最大 token 数。 | | `runtime` | `backend`(例如 `llama.cpp`)、`device`(例如 `metal`)和 `load_on_demand`。 | | `error` | `status` 为 `error` 时存在,是一个 problem 对象([第 9.3 节](https://madeforagent.com/zh-cn/lmsp/1.0/common-rules.md#errors))。 | 模型 `id` **必须**稳定。显示名和别名可由用户修改,因此**不得**用作主键。 ## 5.3 模型生命周期 {#lifecycle} ```text installed --> loading --> ready --> unloading --> installed | +-----> error ``` | 状态 | 含义 | | --- | --- | | `installed` | 模型已存在,但未加载。 | | `loading` | 正在分配内存或初始化运行时。 | | `ready` | 可立即处理请求。 | | `unloading` | 正在释放资源。 | | `error` | 加载失败。模型对象**应**附带结构化错误。 | 对处于 `error` 状态的模型再次发起加载请求,会使其回到 `loading`。 当请求指定的模型处于 `installed` 状态且其 `runtime.load_on_demand` 为 `true` 时,服务会在处理请求的过程中加载它。如果模型无法立即使用且不会按需加载,服务返回 `409` 和 `model_not_ready`。加载失败时,错误码为 `model_load_failed`。 ## 5.4 模型操作 {#operations} `GET /lmsp/v1/models/{model_id}` 返回单个模型对象。未知的 id 返回 `404` 和 `model_not_found`。 两个可选接口用于显式控制加载。它们在 `model_management` 能力为 `true` 时可用,并要求 `models:manage` 权限范围([第 10.3 节](https://madeforagent.com/zh-cn/lmsp/1.0/security.md#scopes)): | 接口 | 行为 | | --- | --- | | `POST /lmsp/v1/models/{model_id}/load` | 预加载模型。加载已开始时返回模型对象和 `202 Accepted`,模型已经是 `ready` 时返回 `200`。 | | `POST /lmsp/v1/models/{model_id}/unload` | 卸载模型,返回模型对象。重复请求无害。 | 两者都接受 `Idempotency-Key`([第 9.2 节](https://madeforagent.com/zh-cn/lmsp/1.0/common-rules.md#idempotency))。 --- # 响应与流式传输 > LMSP 的核心对话接口:Responses 风格的请求与输出项、同步与流式生成、服务器发送事件的类型、事件 id、断线重连以及工具调用。 Source: https://madeforagent.com/zh-cn/lmsp/1.0/responses/ Language: zh-CN Specification: LMSP 1.0 (draft) Updated: 2026-11-08 ## 6.1 请求 {#request} ```http POST /lmsp/v1/responses Authorization: Bearer Content-Type: application/json Idempotency-Key: 01J8M5T7Z2K4N6Q8S0V3X5B9DG LMSP-Version: 1.0 ``` ```json { "model": "qwen3:14b-q4_k_m", "input": [ { "type": "message", "role": "user", "content": [ { "type": "input_text", "text": "Describe a local model service in one sentence." } ] } ], "stream": false, "max_output_tokens": 512, "temperature": 0.7, "response_format": { "type": "text" }, "store": true } ``` 核心协议采用 Responses 风格的 `input`/`output` 结构,而不是只支持简单的 messages 字符串,以便用同一种请求格式统一承载: - 多轮消息; - 文本、图片、音频和文件; - 工具调用及工具结果; - 结构化输出; - 推理摘要; - 多模态输出。 LMSP 1.0 只定义文本内容。图片、音频和文件的内容类型留待后续版本。 | 字段 | 必需 | 含义 | | --- | --- | --- | | `model` | 是 | 要使用的模型 id。 | | `input` | 是 | 输入项数组。`message` 项包含 `role`(`system`、`user` 或 `assistant`)和由多个部分组成的 `content` 数组,例如 `input_text`。使用工具时会增加 `function_call` 和 `function_call_output` 项([第 6.5 节](https://madeforagent.com/zh-cn/lmsp/1.0/responses.md#tools))。 | | `stream` | 否 | 为 `true` 时接收服务器发送事件,默认为 `false`。 | | `max_output_tokens` | 否 | 生成 token 数的上限。 | | `temperature` | 否 | 采样温度。 | | `response_format` | 否 | `{"type": "text"}`(默认),或用于结构化输出的 `{"type": "json_schema", "json_schema": {"name": "...", "schema": {...}}}`。需要 `structured_output` 能力。 | | `tools`、`tool_choice` | 否 | 模型可以调用的函数工具([第 6.5 节](https://madeforagent.com/zh-cn/lmsp/1.0/responses.md#tools))。需要 `tools` 能力。 | | `store` | 否 | 请求服务保存该响应,以便之后查询,并可用作 `previous_response_id`。默认为 `false`。没有 `conversations` 能力的服务会忽略它。 | | `previous_response_id` | 否 | 接着已保存的某个响应继续对话。需要 `conversations` 能力。 | | `extensions` | 否 | 私有扩展([第 9.1 节](https://madeforagent.com/zh-cn/lmsp/1.0/common-rules.md#versioning))。 | 请求需要模型不具备的能力时,返回 `400` 和 `unsupported_capability`。请求放不进上下文窗口时,返回 `400` 和 `context_length_exceeded`。 能够保存会话的服务**可以**额外支持 `previous_response_id`;否则每次请求都**必须**携带完整上下文。服务**不得**依赖 HTTP 连接保存对话状态。 ## 6.2 同步响应 {#sync} 当 `stream` 为 `false` 时,服务在生成结束后返回 `200 OK` 和一个响应对象。 ```json { "id": "resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF", "object": "response", "created_at": 1794200000, "status": "completed", "model": "qwen3:14b-q4_k_m", "output": [ { "id": "msg_01J8M6Q4W5C8H2K9P3S7Y1A6ZE", "type": "message", "role": "assistant", "content": [ { "type": "output_text", "text": "A local model service is a resident interface layer that discovers, manages and calls models on the user's own device." } ] } ], "usage": { "input_tokens": 28, "output_tokens": 31, "total_tokens": 59 }, "error": null, "incomplete_details": null } ``` | 字段 | 含义 | | --- | --- | | `id` | 响应标识符,例如 `resp_...`。 | | `object` | 始终为 `"response"`。 | | `created_at` | 创建时间,自 Unix 纪元起的秒数。 | | `status` | `in_progress`、`completed`、`incomplete`、`failed` 或 `cancelled`。同步响应返回时已处于最终状态。 | | `model` | 生成该响应的模型 id。 | | `output` | 输出项数组。 | | `usage` | token 用量:`input_tokens`、`output_tokens` 和 `total_tokens`。 | | `error` | `null`;当 `status` 为 `failed` 时是一个 problem 对象([第 9.3 节](https://madeforagent.com/zh-cn/lmsp/1.0/common-rules.md#errors))。 | | `incomplete_details` | `null`;当 `status` 为 `incomplete` 时是带有 `reason`(例如 `max_output_tokens`)的对象。 | 输出项类型: | 项类型 | 结构 | | --- | --- | | `message` | `id`、`role`(`assistant`)和 `content`,后者是 `output_text` 部分的数组,每个部分带有 `text`。 | | `function_call` | `id`、`call_id`、`name` 和 `arguments`,后者是 JSON 编码的字符串。 | | `reasoning` | `id` 和 `summary`,后者是 `summary_text` 部分的数组,每个部分带有 `text`。 | 如果生成失败,同步请求返回错误状态码和 problem 文档([第 9.3 节](https://madeforagent.com/zh-cn/lmsp/1.0/common-rules.md#errors)),而不是响应对象。`failed` 状态只出现在流、任务和已保存的响应中。 `GET /lmsp/v1/responses/{response_id}` 返回以 `store: true` 保存的响应。它是可选接口,需要 `conversations` 能力。未知的 id 返回 `404` 和 `response_not_found`。 ## 6.3 流式响应 {#streaming} 要使用流式传输,在请求中设置 `stream`: ```json { "stream": true } ``` 服务返回 `200 OK` 和 `Content-Type: text/event-stream`,并发送语义化事件。每个事件包含 `id`、`event` 名称和一个 JSON `data` 对象: ```text id: 1 event: response.created data: {"response_id":"resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF","status":"in_progress"} id: 2 event: response.output_item.added data: {"response_id":"resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF","output_index":0,"item":{"id":"msg_01J8M6Q4W5C8H2K9P3S7Y1A6ZE","type":"message","role":"assistant","content":[]}} id: 3 event: response.output_text.delta data: {"response_id":"resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF","item_id":"msg_01J8M6Q4W5C8H2K9P3S7Y1A6ZE","delta":"A local model "} id: 4 event: response.output_text.delta data: {"response_id":"resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF","item_id":"msg_01J8M6Q4W5C8H2K9P3S7Y1A6ZE","delta":"service is a resident interface layer."} id: 5 event: response.output_item.done data: {"response_id":"resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF","output_index":0,"item":{"id":"msg_01J8M6Q4W5C8H2K9P3S7Y1A6ZE","type":"message","role":"assistant","content":[{"type":"output_text","text":"A local model service is a resident interface layer."}]}} id: 6 event: response.completed data: {"response_id":"resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF","status":"completed","usage":{"input_tokens":28,"output_tokens":12,"total_tokens":40}} ``` 最少定义以下事件: | 事件 | 含义 | 数据字段 | | --- | --- | --- | | `response.created` | 响应已创建。 | `response_id`、`status`(`in_progress`) | | `response.output_item.added` | 开始了一个新的输出项。 | `response_id`、`output_index`、`item` | | `response.output_text.delta` | 文本被追加到某个 message 项。 | `response_id`、`item_id`、`delta` | | `response.output_item.done` | 一个输出项已完成。 | `response_id`、`output_index`、`item` | | `response.completed` | 响应已完成。最终事件。 | `response_id`、`status`、`usage` | | `response.incomplete` | 生成提前停止,例如达到 token 上限。最终事件。 | `response_id`、`status`、`incomplete_details`、`usage` | | `response.failed` | 生成失败。最终事件。 | `response_id`、`status`、`error` | | `response.cancelled` | 响应已取消。最终事件。 | `response_id`、`status` | | `error` | 与某个具体响应无关的问题,出现后流无法继续。 | `error`,一个 problem 对象 | 服务在最终事件或 `error` 事件之后关闭流。 服务**可以**发送额外的事件,例如工具调用参数、图片、音频和结构化 JSON 增量。客户端**必须**忽略不认识的事件类型。 如果客户端关闭了流式请求的连接,服务**应**停止生成。 ## 6.4 事件 id 与重连 {#reconnection} 事件的 `id` **必须**是整数序号,在该响应内严格递增,从 1 开始。 连接断开后,客户端通过发送自己收到的最后一个 id 来恢复。可以使用请求头: ```http Last-Event-ID: 2 ``` 或使用查询参数: ```http GET /lmsp/v1/tasks/{task_id}/events?after=2 ``` 两者都发往任务的事件接口([第 7.4 节](https://madeforagent.com/zh-cn/lmsp/1.0/tasks.md#events))。服务只补发序号大于 2 的事件。两者同时出现时,以 `after` 为准。 如果请求的事件已超出保留期,服务返回 `410 Gone` 和错误码 `event_history_expired`,客户端改为通过 `GET /lmsp/v1/tasks/{task_id}` 获取最终结果。 直接从 `POST /lmsp/v1/responses` 流式返回的响应属于该 HTTP 连接,无法恢复。需要可恢复输出的客户端**应**创建[任务](https://madeforagent.com/zh-cn/lmsp/1.0/tasks.md)并跟随它的事件。 ## 6.5 工具调用 {#tools} LMSP 1.0 定义由客户端执行的函数工具,服务本身不运行工具。`tools:execute` 权限范围预留给会执行工具的扩展([第 10.3 节](https://madeforagent.com/zh-cn/lmsp/1.0/security.md#scopes))。 首先,客户端列出工具。每个条目的 `type` 为 `function`,并带有 `name`、`description` 和 `parameters`(一个 JSON Schema 对象)。`tool_choice` 可以是 `auto`(默认)、`none`、`required`,或 `{"type": "function", "name": "get_weather"}`。 ```json { "model": "qwen3:14b-q4_k_m", "input": [ { "type": "message", "role": "user", "content": [ { "type": "input_text", "text": "What is the weather in Hangzhou?" } ] } ], "tools": [ { "type": "function", "name": "get_weather", "description": "Get the current weather for a city.", "parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] } } ], "tool_choice": "auto" } ``` 当模型决定调用工具时,响应中会包含一个 `function_call` 项,其 `arguments` 是 JSON 编码的字符串。在流中,该项会以完整形式出现在 `response.output_item.done` 中。 ```json { "id": "resp_01J8M7B3N6Q9T2V5X8Z1C4D7FH", "object": "response", "created_at": 1794200050, "status": "completed", "model": "qwen3:14b-q4_k_m", "output": [ { "id": "fc_01J8M7B3N8R1S4W7Y0A3E6G9JK", "type": "function_call", "call_id": "call_01J8M7B3N9T2V5X8Z1C4D7F0HM", "name": "get_weather", "arguments": "{\"city\":\"Hangzhou\"}" } ], "usage": { "input_tokens": 74, "output_tokens": 18, "total_tokens": 92 }, "error": null, "incomplete_details": null } ``` 客户端运行工具,然后发送一个携带完整上下文的新请求:之前的输入项、收到的 `function_call` 项,以及一个 `call_id` 相同的 `function_call_output` 项。其 `output` 是字符串,通常为 JSON 编码。 ```json { "type": "function_call_output", "call_id": "call_01J8M7B3N9T2V5X8Z1C4D7F0HM", "output": "{\"temperature_c\":17,\"condition\":\"light rain\"}" } ``` --- # 异步任务 > LMSP 如何把长时间的生成作为持久化任务来运行,包括创建任务、任务状态机、幂等取消、事件流,以及未完成任务的重启策略。 Source: https://madeforagent.com/zh-cn/lmsp/1.0/tasks/ Language: zh-CN Specification: LMSP 1.0 (draft) Updated: 2026-11-08 ## 7.1 创建任务 {#create} 客户端通过发送它想要运行的响应请求来创建任务,请求要包在任务信封中。建议携带 `Idempotency-Key` 请求头([第 9.2 节](https://madeforagent.com/zh-cn/lmsp/1.0/common-rules.md#idempotency))。 ```http POST /lmsp/v1/tasks Authorization: Bearer Idempotency-Key: 01J8M7C4Y3N5P8R2T6V9X1Z4BH ``` ```json { "type": "response", "input": { "model": "qwen3:14b-q4_k_m", "input": [ { "type": "message", "role": "user", "content": [ { "type": "input_text", "text": "Write a long report." } ] } ] }, "on_restart": "fail" } ``` | 字段 | 必需 | 含义 | | --- | --- | --- | | `type` | 是 | `response`,1.0 中唯一的任务类型。 | | `input` | 是 | 一个响应请求,与 `POST /lmsp/v1/responses` 的请求相同。其中的 `stream` 字段会被忽略。 | | `on_restart` | 否 | `fail`(默认)、`retry` 或 `resume`。参见[第 7.5 节](https://madeforagent.com/zh-cn/lmsp/1.0/tasks.md#restart)。 | 服务**必须**先持久化任务,再作出应答。随后返回: ```http HTTP/1.1 202 Accepted ``` ```json { "id": "task_01J8M8R5X3V7B2N4Q9T1W6Y0ZC", "object": "task", "type": "response", "status": "queued", "created_at": 1794200100, "updated_at": 1794200100, "response_id": "resp_01J8M8R5X4B9D2F6H1K3N7Q0ZW", "events_url": "/lmsp/v1/tasks/task_01J8M8R5X3V7B2N4Q9T1W6Y0ZC/events", "on_restart": "fail", "response": null, "error": null } ``` | 任务字段 | 含义 | | --- | --- | | `id` | 任务标识符,例如 `task_...`。 | | `object` | 始终为 `"task"`。 | | `type` | `response`。 | | `status` | 当前状态([第 7.2 节](https://madeforagent.com/zh-cn/lmsp/1.0/tasks.md#states))。 | | `created_at`、`updated_at` | 时间,自 Unix 纪元起的秒数。 | | `response_id` | 该任务所产生的响应的 id,在创建任务时分配。 | | `events_url` | 事件流的路径。`event_resume` 能力为 `true` 时存在。 | | `on_restart` | 该任务的重启策略。 | | `response` | `status` 为 `succeeded` 后是响应对象,否则为 `null`。 | | `error` | `status` 为 `failed` 时是一个 problem 对象([第 9.3 节](https://madeforagent.com/zh-cn/lmsp/1.0/common-rules.md#errors)),否则为 `null`。 | `GET /lmsp/v1/tasks/{task_id}` 返回任务对象。不使用事件流的客户端轮询该接口。未知的 id 返回 `404` 和 `task_not_found`。已结束的任务如下所示: ```json { "id": "task_01J8M8R5X3V7B2N4Q9T1W6Y0ZC", "object": "task", "type": "response", "status": "succeeded", "created_at": 1794200100, "updated_at": 1794200162, "response_id": "resp_01J8M8R5X4B9D2F6H1K3N7Q0ZW", "events_url": "/lmsp/v1/tasks/task_01J8M8R5X3V7B2N4Q9T1W6Y0ZC/events", "on_restart": "fail", "response": { "id": "resp_01J8M8R5X4B9D2F6H1K3N7Q0ZW", "object": "response", "created_at": 1794200100, "status": "completed", "model": "qwen3:14b-q4_k_m", "output": [ { "id": "msg_01J8M8R6A2C5E8G1J4M7P0S3VY", "type": "message", "role": "assistant", "content": [ { "type": "output_text", "text": "Local model services make on-device inference a shared, discoverable utility..." } ] } ], "usage": { "input_tokens": 12, "output_tokens": 1480, "total_tokens": 1492 }, "error": null, "incomplete_details": null }, "error": null } ``` ## 7.2 状态机 {#states} ```text queued --> loading --> running --> succeeded | | | | | +--> failed | | +--> cancelling --> cancelled | +--------------> failed +--------------------------> cancelled ``` | 状态 | 含义 | 后续状态 | | --- | --- | --- | | `queued` | 已接受并持久化,等待开始。 | `loading`、`cancelled`、`failed` | | `loading` | 正在加载模型。 | `running`、`cancelling`、`failed` | | `running` | 模型正在生成。 | `succeeded`、`failed`、`cancelling` | | `cancelling` | 已请求取消,后端尚未停止。 | `cancelled` | | `succeeded` | 终态。响应已完成。 | 无 | | `failed` | 终态。 | 无 | | `cancelled` | 终态。 | 无 | 终态只有 `succeeded`、`failed` 和 `cancelled`。处于终态的任务**不得**再发生变化。响应以 `incomplete` 结束的任务(例如达到 token 上限)是 `succeeded`。 每次状态转换都是原子的,因此任务不会既被取消又已完成,先提交的转换生效。 ## 7.3 取消 {#cancel} ```http POST /lmsp/v1/tasks/{task_id}/cancel ``` 服务返回 `200 OK` 和当前任务对象。`queued` 任务立即变为 `cancelled`;`loading` 或 `running` 任务先变为 `cancelling`,直到后端停止后再变为 `cancelled`。 取消操作**必须**幂等:重复取消已处于 `cancelling` 或 `cancelled` 的任务,仍返回当前任务,不产生新错误。 取消已经 `succeeded` 或 `failed` 的任务,返回 `409` 和 `task_not_cancelable`。未知的 id 返回 `404` 和 `task_not_found`。 ## 7.4 事件流 {#events} ```http GET /lmsp/v1/tasks/{task_id}/events Accept: text/event-stream ``` 该接口是可选的,在 `event_resume` 能力为 `true` 时可用。 - 事件是[第 6.3 节](https://madeforagent.com/zh-cn/lmsp/1.0/responses.md#streaming)中的响应事件,对应该任务所产生的响应。序号与恢复规则遵循[第 6.4 节](https://madeforagent.com/zh-cn/lmsp/1.0/responses.md#reconnection)。 - 事件流以 `response.created` 开始,它在任务被接受时记录。流以与结果相符的一个最终事件结束:`response.completed`、`response.incomplete`、`response.failed` 或 `response.cancelled`。 - 较晚连接的客户端先收到自己错过的事件,再收到实时事件。服务在最终事件之后关闭流。 - 事件会保留一段保留期([第 11.2 节](https://madeforagent.com/zh-cn/lmsp/1.0/persistence.md#rules))。更早的事件返回 `410 Gone` 和 `event_history_expired`。 ## 7.5 重启语义 {#restart} `on_restart` 规定服务重启时对未完成任务如何处理: | 取值 | 行为 | | --- | --- | | `fail` | 默认值。服务把未完成的任务标记为 `failed`,错误码为 `server_restarted`。 | | `retry` | 服务从头重新执行该任务。 | | `resume` | 服务从快照继续执行。仅当后端明确支持可恢复快照时才允许,否则创建请求会被拒绝,返回 `400` 和 `unsupported_capability`。 | 服务重启时处于 `cancelling` 的任务,无论其策略如何,都会完成取消并变为 `cancelled`。 不建议把 `retry` 作为默认值,因为任务可能已经执行了工具或产生了外部副作用。选择 `retry` 的客户端**必须**同时使用 `Idempotency-Key`,并使其工具调用幂等。 --- # OpenAI 与 Ollama 兼容层 > LMSP 服务如何通过叠加在同一个执行内核之上的轻量数据转换层,兼容现有的 OpenAI 和 Ollama 客户端,以及这种兼容性的边界。 Source: https://madeforagent.com/zh-cn/lmsp/1.0/compatibility/ Language: zh-CN Specification: LMSP 1.0 (draft) Updated: 2026-11-08 ## 8.1 兼容 OpenAI 的接口 {#openai} 服务**应**在 LMSP 接口之外提供兼容 OpenAI 的接口,并在 `interfaces` 中以 `kind` 为 `openai` 声明。为覆盖现有客户端,提供该接口的服务**必须**在 `/v1` 前缀下至少实现以下接口: | 接口 | 说明 | | --- | --- | | `GET /v1/models` | 列出 LMSP 目录中的模型。每个 `id` 都是 LMSP 模型 id。 | | `POST /v1/chat/completions` | Chat Completions,支持流式和非流式。 | | `POST /v1/responses` | OpenAI Responses 格式。 | | `POST /v1/embeddings` | LMSP 1.0 核心接口没有 embeddings 接口。适配器直接提供此接口,供 `capabilities.embeddings` 为 `true` 的模型使用。 | 这些接口接受与 LMSP 接口相同的 Bearer Token。使用 OpenAI 协议的客户端把 LMSP 令牌设置为 API Key 即可。 首个实现可以先从 `/v1/models` 和 `/v1/chat/completions` 开始([第 12 章](https://madeforagent.com/zh-cn/lmsp/1.0/mvp.md))。 ## 8.2 兼容 Ollama 的接口 {#ollama} 服务**应**同时提供以下接口,让为 Ollama 编写的客户端也能使用: | 接口 | 说明 | | --- | --- | | `GET /api/tags` | 列出 LMSP 目录中的模型。 | | `POST /api/chat` | 对话。 | | `POST /api/generate` | 纯文本生成。 | 提供这些接口的服务以 `kind` 为 `ollama` 声明一个接口。 ## 8.3 同一个执行内核 {#core} 兼容层**必须**只做数据转换,**不得**另建第二套模型管理逻辑。兼容接口上的请求会被映射为 LMSP 响应,与原生请求在同一个内核和适配器上运行,使用同一个模型目录和同一套认证。 ```text OpenAI Chat Completions | 请求映射 v LMSP Response | 后端适配 v llama.cpp / Ollama / vLLM / 其他运行时 ``` 在 `/v1` 和 `/api` 接口上,服务**应**按对应 API 所定义的格式报告错误,让未修改的客户端能够解析。LMSP 接口始终使用 `application/problem+json`。 ## 8.4 边界 {#boundaries} 兼容性有其限度,值得直说: - 现有的 OpenAI 兼容客户端通常只需修改 `base_url`,但不能自动获得 LMSP 的发现能力。 - 只有支持 LMSP 的客户端,才能自动发现服务、读取详细能力、恢复事件流和管理异步任务。 - 没有任何服务端协议能让完全不可配置、且只接受官方云地址的客户端接入本地服务。 --- # 通用协议规则 > 适用于所有 LMSP 接口的通用规则,包括版本请求头与扩展、幂等键、带错误码的 problem+json 错误格式,以及健康检查。 Source: https://madeforagent.com/zh-cn/lmsp/1.0/common-rules/ Language: zh-CN Specification: LMSP 1.0 (draft) Updated: 2026-11-08 ## 9.1 版本与扩展 {#versioning} 每个 LMSP 请求**可以**携带以下请求头: ```http LMSP-Version: 1.0 LMSP-Client: example-client/1.2.0 ``` `LMSP-Version` 是客户端所面向的协议版本。`LMSP-Client` 以 `name/version` 的形式标识客户端软件,用于诊断。服务**不得**据此改变协议行为。 规则: - 路径中的 `v1` 表示主版本。 - 指明未知主版本的请求返回 `426 Upgrade Required` 和错误码 `unsupported_protocol_version`。 - 新增可选字段**不得**破坏 1.x 兼容性。 - 服务和客户端**必须**忽略不认识的字段。 - 私有扩展放入 `extensions` 对象,使用反向域名命名: ```json { "extensions": { "com.example.cache": { "enabled": true } } } ``` ## 9.2 幂等 {#idempotency} 创建类请求,例如创建响应、创建任务或加载模型,支持幂等键: ```http Idempotency-Key: ``` 服务保存“幂等键+请求体摘要+结果”: - 相同幂等键和相同请求体,返回原结果。 - 相同幂等键但请求体不同,返回 `409 Conflict` 和 `idempotency_conflict`。 幂等键的作用域是客户端和接口。它**应**是不超过 255 个字符的唯一值,例如 ULID 或 UUID。服务**应**至少保存幂等键 24 小时。 服务**可以**在流式请求上忽略该幂等键。需要安全重试的客户端应使用[任务](https://madeforagent.com/zh-cn/lmsp/1.0/tasks.md)。 ## 9.3 错误格式 {#errors} LMSP 接口上的所有错误都使用 `application/problem+json`([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)): ```json { "type": "urn:lmsp:error:model-not-found", "title": "Model not found", "status": 404, "code": "model_not_found", "detail": "No local model with id 'qwen3:14b-q4_k_m'", "request_id": "req_01J8M9S6Y4W8C3P5R1V7Z2A0DH", "retryable": false } ``` | 字段 | 必需 | 含义 | | --- | --- | --- | | `type` | 是 | 标识错误种类的 URI。LMSP 使用 `urn:lmsp:error:`,后接把下划线换成连字符的错误码。 | | `title` | 是 | 简短的、便于阅读的摘要。 | | `status` | 是 | HTTP 状态码。 | | `code` | 是 | 机器可读的错误码,取自下表。 | | `detail` | 否 | 对本次错误的说明,便于阅读。**不得**包含密钥、提示词或生成内容。 | | `request_id` | 否 | 请求的标识符,用于与服务日志关联。 | | `retryable` | 是 | 稍后重复请求是否可能成功。 | 常用错误码: | 错误码 | HTTP 状态码 | 可重试 | 含义 | | --- | --- | --- | --- | | `invalid_request` | 400 | 否 | 请求格式错误或参数无效。 | | `unauthorized` | 401 | 否 | 令牌缺失、无效、过期或已被撤销,或配对码错误。 | | `forbidden` | 403 | 否 | 令牌缺少所需的权限范围,或 `Origin`、`Host` 校验未通过。 | | `model_not_found` | 404 | 否 | 没有该 id 的本地模型。 | | `model_not_ready` | 409 | 是 | 模型未加载,且不会按需加载。 | | `model_load_failed` | 500 | 是 | 加载模型失败。 | | `context_length_exceeded` | 400 | 否 | 输入放不进模型的上下文窗口。 | | `unsupported_capability` | 400 或 501 | 否 | 模型或服务不支持请求所需的能力。未实现的可选接口返回 501。 | | `unsupported_protocol_version` | 426 | 否 | 服务不支持所请求的主版本。 | | `response_not_found` | 404 | 否 | 没有该 id 的已保存响应。 | | `task_not_found` | 404 | 否 | 没有该 id 的任务。 | | `task_not_cancelable` | 409 | 否 | 任务已经 succeeded 或 failed。 | | `idempotency_conflict` | 409 | 否 | 该幂等键已用于另一个不同的请求体。 | | `event_history_expired` | 410 | 否 | 请求的事件已超出保留期。 | | `rate_limited` | 429 | 是 | 请求过多。 | | `resource_exhausted` | 503 | 是 | 设备内存或其他资源耗尽。 | | `backend_unavailable` | 503 | 是 | 无法连接模型运行时。 | | `server_restarted` | 503 | 是 | 任务未完成时服务重启了。 | | `internal_error` | 500 | 视情况 | 服务内部发生了意外故障。 | `retryable` 字段具有权威性,上表给出的是常见取值。服务**可以**定义更多错误码。客户端遇到不认识的错误码时,**必须**根据 HTTP 状态码和 `retryable` 字段处理。 ## 9.4 健康检查 {#health} ```http GET /lmsp/v1/health ``` ```json { "status": "ready", "service_id": "svc_01J8M4Y8K6M5E6K6Z7Z0Y8W3M2", "uptime_seconds": 86420, "loaded_models": 1, "active_tasks": 2, "storage": "ok" } ``` | 字段 | 含义 | | --- | --- | | `status` | `starting`、`ready`、`degraded` 或 `stopping`。 | | `service_id` | 服务描述中的服务 id。 | | `uptime_seconds` | 服务启动以来的秒数。 | | `loaded_models` | 处于 `ready` 状态的模型数量。 | | `active_tasks` | 处于 `queued`、`loading`、`running` 或 `cancelling` 的任务数量。 | | `storage` | `ok` 或 `error`。 | `status` 为 `ready` 或 `degraded` 时返回 `200 OK`,否则返回 `503 Service Unavailable`。 健康接口只返回运行状态,**不得**返回模型路径、提示词、访问令牌等敏感信息。 --- # 本地安全要求 > LMSP 服务的安全要求,包括回环绑定、配对与 Bearer Token、权限范围、防范恶意网页的 Origin 与 Host 校验、局域网模式和日志规则。 Source: https://madeforagent.com/zh-cn/lmsp/1.0/security/ Language: zh-CN Specification: LMSP 1.0 (draft) Updated: 2026-11-08 ## 10.1 基线 {#baseline} “只监听 localhost”并不等于绝对安全:恶意网页仍可能尝试访问本机端口。因此 LMSP 组合使用多项措施。 - 服务默认**必须**绑定回环地址。除非启用了局域网模式([第 10.5 节](https://madeforagent.com/zh-cn/lmsp/1.0/security.md#lan)),否则**不得**监听 `0.0.0.0` 或任何其他非回环地址。 - 首次运行时,服务**应**生成高强度的随机服务密钥,并使用它(而不是任何内置的默认值)来签发和校验令牌。 - 对核心接口的每个请求都**必须**携带有效的 Bearer Token,[第 4.2 节](https://madeforagent.com/zh-cn/lmsp/1.0/endpoints.md#conventions)列出的引导接口除外。仅仅来自回环地址,永远不足以通过认证。 ## 10.2 配对与令牌 {#pairing} 客户端通过本机配对码获得属于自己的 Bearer Token。用户以服务提供的任意方式让服务显示配对码,客户端再提交该配对码: ```http POST /lmsp/v1/pair Content-Type: application/json ``` ```json { "client_name": "example-client", "pairing_code": "K7QD-4M2X", "scopes": ["inference"] } ``` | 请求字段 | 含义 | | --- | --- | | `client_name` | 客户端的显示名称,会展示给用户。 | | `pairing_code` | 用户从服务获得的配对码。 | | `scopes` | 可选,请求的权限范围列表。默认为 `["inference"]`。 | 服务以令牌作答。`token_type` 始终为 `Bearer`: ```json { "client_id": "cli_01J8MAT3W6X9B2D5F8H1K4N7QZ", "access_token": "lmsp_example_3f9a1c52e7d84b60", "token_type": "Bearer", "scopes": ["inference"] } ``` 此后客户端在每个请求中都发送 `Authorization: Bearer `。 - 配对码**必须**只能使用一次,并且**应**在 5 分钟内过期。它**应**至少包含 40 比特熵。服务**必须**对失败的尝试限速。错误或过期的配对码返回 `401` 和 `unauthorized`。 - 令牌按客户端相互独立,因此用户可以撤销某个客户端而不影响其他客户端。服务**应**允许用户列出并撤销已配对的客户端。 - 客户端**应**把令牌保存在操作系统钥匙串或凭据管理器中。服务**应**只保存每个令牌的哈希。 - 令牌缺失、无效或已被撤销时,返回 `401`、`unauthorized` 和 `WWW-Authenticate: Bearer` 响应头。令牌有效但缺少接口所需的权限范围时,返回 `403` 和 `forbidden`。 ## 10.3 权限范围 {#scopes} 权限被拆分为相互独立的范围。下载模型、删除模型和执行工具,永远不会附带在 inference 范围之内。 | 范围 | 允许的操作 | | --- | --- | | `inference` | 读取模型目录,创建响应和任务,读取并取消该客户端自己的任务。 | | `models:manage` | 加载和卸载模型。 | | `models:install` | 预留。下载和删除模型。 | | `tools:execute` | 预留。在服务端执行工具。 | 任务和已保存的响应属于创建它们的客户端。对于来自其他客户端的请求,服务**应**当作它们不存在来应答(`task_not_found` 或 `response_not_found`)。 ## 10.4 浏览器与网络防护 {#browser} - 服务默认**必须**拒绝跨域的浏览器请求,只允许明确配置的 `Origin` 值。带有其他 `Origin` 的请求返回 `403` 和 `forbidden`。 - 当某个来源被允许时,服务响应其 CORS 预检请求,并在 `Access-Control-Allow-Origin` 中给出确切的来源,绝不使用 `*`。 - 服务**必须**校验 `Host` 请求头,以防止 DNS Rebinding。服务只接受自己所服务的主机名:回环名称和地址,加上为局域网模式配置的名称。其他任何取值都返回 `403` 和 `forbidden`。 ## 10.5 局域网模式 {#lan} 局域网模式指监听回环地址之外的地址。 - 除非用户明确开启,服务**不得**启用局域网模式。 - 在局域网模式下,服务**必须**要求认证,并且**应**同时启用 TLS。除非用户明确允许,客户端**不应**通过明文 HTTP 向非回环地址发送令牌。 - 只有在局域网模式下,且用户也开启了“局域网发现”时,才允许 mDNS 广播([第 3.2 节](https://madeforagent.com/zh-cn/lmsp/1.0/discovery.md#mdns))。 ## 10.6 日志与隐私 {#logging} - 模型路径、日志、提示词和生成内容**不得**出现在 mDNS 广播中。 - 默认情况下,服务**应**只记录元数据(时间、接口、状态码、id、token 数量和耗时),**不应**记录完整的提示词和生成内容。 - 健康检查响应和错误详情**不得**包含模型路径、提示词、访问令牌等敏感信息。 --- # 持久化 > LMSP 服务如何保存自己的状态:嵌入式 SQLite 数据库、建议保留的表、任务与事件的事务规则、保留期,以及启动时的恢复。 Source: https://madeforagent.com/zh-cn/lmsp/1.0/persistence/ Language: zh-CN Specification: LMSP 1.0 (draft) Updated: 2026-11-08 ## 11.1 存储 {#storage} 服务**应**使用嵌入式 [SQLite](https://www.sqlite.org/) 数据库保存状态。建议的表如下: | 表 | 内容 | | --- | --- | | `service_config` | 服务的身份与配置。 | | `clients` | 已配对的客户端、令牌哈希和权限范围。 | | `models` | 模型元数据和内容摘要。 | | `responses` | 已保存的响应。 | | `tasks` | 异步任务。 | | `task_events` | 每个任务的事件日志,带序号。 | | `idempotency_keys` | 幂等键、请求摘要和结果。 | 模型文件**必须**放在数据库之外,数据库只保存元数据与内容摘要。 表名和表结构由实现自行选择。客户端能够观察到的、需要互操作的,是接下来两节所规定的行为。 ## 11.2 规则 {#rules} 无论服务使用何种存储,它都: - **必须**先持久化任务,再返回 `202 Accepted`。 - **必须**在同一个事务中更新事件序号和任务状态。 - **必须**使取消状态转换原子化,避免出现“已取消又完成”的竞争。 - **应**把已结束的任务和事件保留一段可配置的保留期。 - **不得**在清理日志时删除未结束的任务。 - **必须**在启动时扫描遗留的 `queued`、`loading`、`running` 和 `cancelling` 任务,并执行各自的重启策略([第 7.5 节](https://madeforagent.com/zh-cn/lmsp/1.0/tasks.md#restart))。 ## 11.3 启动时的恢复 {#recovery} 下表按 `on_restart` 策略列出遗留任务会被转入的状态: | 启动时发现的状态 | `fail` | `retry` | `resume` | | --- | --- | --- | --- | | `queued` | `failed` | `queued` | `queued` | | `loading` | `failed` | `queued` | `queued` | | `running` | `failed` | `queued` | `running`,从快照继续 | | `cancelling` | `cancelled` | `cancelled` | `cancelled` | 被转为 `failed` 的任务会得到错误码 `server_restarted` 和一个 `response.failed` 事件。被转为 `cancelled` 的任务会得到一个 `response.cancelled` 事件。新状态与对应的事件在同一个事务中写入([第 11.2 节](https://madeforagent.com/zh-cn/lmsp/1.0/persistence.md#rules))。 --- # 最小可用范围 > 首个 LMSP 实现的建议范围:构成可用闭环的十项内容,以及规范中可以推迟到后续版本的部分。 Source: https://madeforagent.com/zh-cn/lmsp/1.0/mvp/ Language: zh-CN Specification: LMSP 1.0 (draft) Updated: 2026-11-08 ## 12.1 第一版 {#scope} 第一版不必实现全部多模态功能、模型下载和实时语音。只要提供以下内容,就能形成可用的闭环: 1. 通过 `/.well-known/lmsp.json` 发现服务。 2. 可选的 `_lmsp._tcp.local.` 局域网广播。 3. `GET /lmsp/v1/models`。 4. `POST /lmsp/v1/responses`,同步和 SSE 流式都要支持。 5. 异步任务:创建、查询和取消,并使用 SQLite 持久化。 6. 用 `Last-Event-ID` 或 `after` 恢复断开的流。 7. Bearer Token、本机配对,以及 `Origin` 和 `Host` 防护。 8. 兼容 OpenAI 的 `/v1/models` 和 `/v1/chat/completions` 接口。 9. OpenAPI 3.2 契约文件,以及跨语言一致性测试。 10. 至少接入一个真实后端,并验证服务重启后任务能正确恢复。 这套设计的重点是:LMSP 提供完整能力,OpenAI 接口提供现成的兼容性,mDNS 与服务描述提供自动发现,SQLite 任务模型提供本地持久化。 ## 12.2 可以推迟的部分 {#later} 上面的清单没有提到的内容,都可以在后续版本中跟进,包括可选接口(已保存的响应,以及模型的加载和卸载)、OpenAI 的 `/v1/responses` 和 `/v1/embeddings` 接口、兼容 Ollama 的 API,以及文本之外的所有内容类型。 这个范围是通往首个可用版本的路径,而不是一致性等级。声称支持 LMSP 1.0 的服务,仍然要实现[第 4 章](https://madeforagent.com/zh-cn/lmsp/1.0/endpoints.md)中所有必需的接口,以及其他章节所描述的必需行为。