更新 platform-llm 公共工具协议文档

补充 Chat、Responses、Anthropic 三协议工具支持矩阵

说明流式文本回调、slot 聚合与 Responses completed-only 恢复

补充工具参数与 Deserialize、StreamUnavailable、EmptyResponse 边界

同步后端架构与开发运维文档
This commit is contained in:
2026-07-27 03:06:11 +00:00
parent 9ba2893371
commit 6477f0b79e
3 changed files with 67 additions and 19 deletions
@@ -244,6 +244,23 @@ npm run check:server-rs-ddd
- LLM:通用 LLM 门面继续使用 `GENARRATIVE_LLM_*``platform-llm` 文本请求默认走 Responses,旧 `/api/llm/chat/completions` 代理和少数旧运行态聊天显式保留 Chat Completions 兼容协议;创意 Agent `gpt-5` Responses / Chat Completions 文本链路已于 2026-06 从 APIMart 迁移到 VectorEngine,使用 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 构造 OpenAI-compatible client`api-server` 会把未带 `/v1` 的 VectorEngine base URL 规范化到 `/v1` 后请求 `/responses``APIMART_BASE_URL` / `APIMART_API_KEY` 只作为历史残留,不再作为创意 Agent gpt-5 客户端来源;后续排障时优先确认 VectorEngine `/v1/models``/v1/chat/completions``/v1/responses` 可用性。
- LLM:通用 LLM 门面继续使用 `GENARRATIVE_LLM_*`;创意 Agent `gpt-5.4-mini` Chat Completions 文本链路已于 2026-06 从 APIMart 迁移到 VectorEngine,使用 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 构造 OpenAI-compatible client`api-server` 会把未带 `/v1` 的 VectorEngine base URL 规范化到 `/v1` 后请求 `/chat/completions`。通用 `/api/llm/chat/completions` 代理使用 `GENARRATIVE_LLM_PROVIDER=openai-compatible``GENARRATIVE_LLM_BASE_URL=https://api.vectorengine.cn/v1``GENARRATIVE_LLM_MODEL=gpt-5.4-mini`;未单独配置 `GENARRATIVE_LLM_API_KEY` 时可复用 `VECTOR_ENGINE_API_KEY``APIMART_BASE_URL` / `APIMART_API_KEY` 只作为历史残留,不再作为创意 Agent gpt-5.4-mini 客户端来源;后续排障时优先确认 VectorEngine `/v1/models``/v1/chat/completions``/v1/responses` 可用性。
### `platform-llm` 公共能力与三协议工具契约
`platform-llm` 的统一公共抽象为 `LlmRunRequest` / `LlmRunResponse``OpenAiChat``OpenAiResponses``Anthropic` 三种 API kind 都支持原生 function tools;工具调用统一从最终 `LlmRunResponse.tool_calls` 返回,不能再按 Anthropic 与否切换到提示词驱动的 text JSON wrapper。
| API kind | 请求工具形态 | `tool_choice` | 非流式响应 | 流式事件与 slot |
| --- | --- | --- | --- | --- |
| `OpenAiChat` | `tools[].type=function`,函数字段为 `name` / `description` / `parameters` / `strict` | `"auto"` / `"required"` | `choices[0].message.tool_calls` | `delta.tool_calls[].index` |
| `OpenAiResponses` | `tools[].type=function`,函数字段为 `name` / `description` / `parameters` / `strict` | `"auto"` / `"required"` | `output[].type=function_call` | `output_index``response.completed.response.output[]` 可作为 completed-only 兜底 |
| `Anthropic` | 顶层 `tools[]``name` / `description` / `input_schema`,无 `function` 包装层和 `strict` | `{ "type": "auto" }` / `{ "type": "any" }``Required → any` | `content[].type=tool_use``input` 序列化为 `arguments` | content block `index``content_block_start` + `input_json_delta` |
流式 `on_delta` 只发送文本增量、累计文本和完成原因,工具调用不进入回调。平台层按协议 slot 聚合并行工具片段:Chat 使用 `delta.tool_calls[].index`Responses 使用 `output_index`Anthropic 使用 content block `index`。Responses 的 `function_call_arguments.done``response.completed` 中的完整 arguments 覆盖此前分片;completed-only 恢复以 `response.output[]` 数组下标作为 slot。该聚合只负责解析,不表示工具执行并发。
流式收尾时,缺少工具 id / name、或非空 arguments 不是完整 JSON,均返回 `Deserialize`;空 arguments 默认归一为 `{}`。这只是 JSON 语法完整性检查,不是按工具 `parameters` 执行 JSON Schema 校验。非流式 Anthropic 缺失 `tool_use.input` 时也归一为 `{}`;其它协议的非流式 arguments 仍按上游字段解析。
错误边界固定如下:`StreamUnavailable` 只表示流式响应已给出 `tool_use` / `tool_calls` 完成原因但没有聚合出任何工具 slot,供调用方回退非流式;`EmptyResponse` 表示最终文本和工具调用都为空,纯工具响应合法;`Deserialize` 覆盖 JSON / SSE / UTF-8 解析失败、缺少 `choices[0]`、流式工具身份缺失和流式参数不完整。Anthropic 仍不支持 `web_search`、图片内容和纯 system 消息,必须至少有一条非 system 文本消息。
- 图片生成:VectorEngine `gpt-image-2` 图片 provider 归属 `platform-image`,密钥只在后端环境变量中;`api-server` 内的 `openai_image_generation.rs` 只是兼容调用面和外部失败审计桥接,不再承载 provider 协议实现。实际外部生成运行记录统一落 `tracking_event``event_key = external_generation_run`metadata 记录开始 / 结束时间、耗时、状态、成功标记、失败原因、provider task id 和结果摘要,不再写回过时的 `ai_task`。DashScope 只按仍在使用的历史能力单独处理,不作为 GPT-image-2 兜底。VectorEngine `/v1/images/generations``/v1/images/edits` 上游 POST 使用 `libcurl` 发送;`reqwest` 只保留给参考图 URL 下载和响应中图片 URL 下载。`/v1/images/edits` 的 multipart 参考图必须作为 libcurl 文件上传 part 发送,字段名为 `image`,实现上使用 `Form::buffer(file_name, bytes)` 并设置 `Content-Type`;不能只用 `contents(...).filename(...)`,否则上游会把请求转码为缺少图片并返回 `image is required``request_send` 阶段的 curl timeout / connect error 按可重试传输错误处理,最多尝试 5 次,并使用指数退避加短抖动;排障时优先看 `attempt``max_attempts``retry_delay_ms``reference_image_bytes_total``request_params`,不要把 `SendRequest` 当成上游业务错误。
- 抠图输入以私有 OSS 作为内存生命周期边界:生成原图和角色动作抽取帧上传时消费图片字节所有权,上传完成后不保留原图缓冲;手动去背景直接解析并校验已有 OSS object key,不下载原图。BgFilter 必须为 object key 签发 600 秒 GET URL 并通过 multipart `image_url` 提交,不用 `file` 重传;flat 链路进入阿里云 fallback 时由 `platform-matting` URL 接口单独下载并上传 `AuthorizeFileUpload` 临时对象,在推理前释放下载缓冲,继续 fallback 到本地键色时再单独下载一次原图,本地产出后释放本次原图下载缓冲。签名 URL 不得写入日志、审计或持久化。
- 阿里云通用抠图的非上海地域输入不得使用 `viapiutils/GetOssStsToken`、固定 `viapi-customer-temp` 或 OSS V1 PUT。`platform-matting` 必须按官方新版 SDK Advance 协议调用 `AuthorizeFileUpload`,使用动态返回的单对象 Policy 执行 multipart POST,再把临时上海 OSS URL 交给 `SegmentCommonImage`;输入归一化、结果下载与原尺寸 Alpha 回贴继续留在同一适配器内。该协议仍上传图片字节,不等同于阿里云服务端直接抓取任意公网 URL,也不改变上层 BgFilter → 阿里云 → 本地降级顺序。
@@ -649,7 +649,9 @@ OpenTelemetry 现阶段默认开启 OTLP traces / metrics / logs,但本地日
结构化创作 / RPG 的 Responses JSON 链路默认不打开 `web_search`;本地和生产如需联网增强,必须显式配置 `GENARRATIVE_RPG_LLM_WEB_SEARCH_ENABLED=true``GENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED=true`。如果上游未开通工具,Responses 可能先吐自然语言再返回 `ToolNotOpen`,这类报错应按工具不可用排查,不要先当成 JSON 解析 bug。
`platform-llm` 文本请求默认使用 Responses 协议;需要接旧 OpenAI Chat Completions 兼容网关时,调用方必须显式选择 Chat Completions。AI 游戏创作独立 App 是客户端,不读取 `.env`;发布 App 启动时会在 Tauri 应用配置目录生成 `game-creator.config.json`,主窗口“配置”面板读写该运行时文件,真实密钥和本机覆盖项写入该文件,仓库内 `apps/ai-game-creator-shell/game-creator.config.json` 只作为默认模板,开发 CLI 无 AppHandle 时才回退读取仓库旁边的 gitignored 覆盖文件。LLM 维度由 `llm.apiKind` 控制,默认 `openai_responses`,可设为 `openai_chat` 接旧 Chat Completions 兼容网关,或 `anthropic` 接 Anthropic Messages。
`platform-llm` 请求默认使用 Responses 协议;需要接旧 OpenAI Chat Completions 兼容网关时,调用方必须显式选择 Chat Completions。三种协议(`openai_chat``openai_responses``anthropic`)都使用原生 function tools,并统一从最终 `LlmRunResponse.tool_calls` 读取工具调用;流式 `on_delta` 只发送文本,不能把工具参数当作文本增量转发。Anthropic 工具请求使用 `input_schema``Required` 使用对象形态 `{ "type": "any" }`Anthropic 当前不支持 `web_search`、图片内容和纯 system 消息。AI 游戏创作独立 App 是客户端,不读取 `.env`;发布 App 启动时会在 Tauri 应用配置目录生成 `game-creator.config.json`,主窗口“配置”面板读写该运行时文件,真实密钥和本机覆盖项写入该文件,仓库内 `apps/ai-game-creator-shell/game-creator.config.json` 只作为默认模板,开发 CLI 无 AppHandle 时才回退读取仓库旁边的 gitignored 覆盖文件。LLM 维度由 `llm.apiKind` 控制,默认 `openai_responses`,可设为 `openai_chat` 接旧 Chat Completions 兼容网关,或 `anthropic` 接 Anthropic Messages。
流式工具片段按协议 slot 聚合,Responses 允许从 `response.completed.response.output[]` 做 completed-only 工具恢复。收尾时空参数默认 `{}`,非空参数必须是完整 JSON;解析失败、流式工具缺少身份或参数截断属于 `Deserialize`。流式已声明工具调用但没有聚合出工具 slot 属于 `StreamUnavailable`,由调用方决定是否回退非流式;文本和工具调用均为空才是 `EmptyResponse`
创意 Agent `gpt-5` 文本链路已从 APIMart 切到 VectorEngine`api-server` 读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 构造 OpenAI-compatible LLM client,并自动补齐 `/v1` 前缀用于 Responses 协议。排查或切换密钥后,可在本地运行:
创意 Agent `gpt-5.4-mini` 文本链路已从 APIMart 切到 VectorEngine`api-server` 读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 构造 OpenAI-compatible LLM client,并自动补齐 `/v1` 前缀后请求 `/chat/completions`。通用 `/api/llm/chat/completions` 代理使用 `GENARRATIVE_LLM_PROVIDER=openai-compatible``GENARRATIVE_LLM_BASE_URL=https://api.vectorengine.cn/v1``GENARRATIVE_LLM_MODEL=gpt-5.4-mini`,未单独配置 `GENARRATIVE_LLM_API_KEY` 时可复用 `VECTOR_ENGINE_API_KEY`。排查或切换密钥后,可在本地运行:
+47 -18
View File
@@ -1,31 +1,56 @@
# platform-llm 平台适配 crate
日期:`2026-04-21`
日期:`2026-07-27`
## 1. crate 职责
`platform-llm` 是 Rust 工作区里的大模型平台适配 crate,当前首版已经落地以下能力:
`platform-llm` 是 Rust 工作区里的大模型平台适配 crate,当前已经落地以下能力:
1. 统一 Ark / DashScope / Anthropic / 其他兼容网关的文本模型配置结构
2. 统一 OpenAI Chat Completions、OpenAI Responses 和 Anthropic Messages 文本请求、非流式响应与 SSE 流式增量解析
2. 统一 OpenAI Chat Completions、OpenAI Responses 和 Anthropic Messages 文本 / 原生 function tool 请求、非流式响应与 SSE 流式解析
3. 统一超时、连接失败、上游错误、空响应与重试策略
4. 为后续 `module-ai``module-story``module-npc``module-custom-world` 提供可直接复用的基础 client
## 2. 当前首版边界
## 2. 当前能力边界
当前实现只覆盖“文本 run”主链,不提前混入媒体生成和业务编排:
1. 对外抽象固定为 `LlmRunRequest` / `LlmRunResponse`,不再保留旧 `LlmTextRequest` / `LlmTextResponse` 类型。
2. 支持 `OpenAiChat``OpenAiResponses` 类 API kind JSON 请求与 SSE 增量响应
3. 支持 `Anthropic` API kind 的最小文本 Messages 请求、非流式响应与 SSE 文本增量解析Anthropic URL 默认在 base URL 后拼 `/v1/messages`,如果 base URL 已以 `/v1` 结尾则只拼 `/messages`
4. 当前 run 抽象只收敛通用文本结果、finish reason、response id 和 usage;上下文管理、后台执行、provider 原生工具等高级能力后续再按 capability 显式扩展,不把 Responses 语义硬编码进业务层
5. 支持按 provider 打标签,但不把业务 prompt、SSE 转发和模块状态写回本 crate。
6. `DashScope` 当前只通过“调用方显式提供兼容文本网关 base url”的方式接入,不复用图像 API
7. 角色动画、图片、视频、资产轮询仍留在后续 `platform-llm` / `platform-oss` / 业务模块任务里另行实现
2. `OpenAiChat``OpenAiResponses` `Anthropic`类 API kind 都支持 JSON 请求、非流式响应和 SSE 流式响应;默认 API kind 仍为 `OpenAiResponses`
3. 三类协议都使用统一的 `function_tools` / `tool_choice` 输入和 `LlmRunResponse.tool_calls` 输出。Anthropic 请求使用顶层 `tools[].input_schema` 与对象形态 `tool_choice`Anthropic URL 默认在 base URL 后拼 `/v1/messages`,如果 base URL 已以 `/v1` 结尾则只拼 `/messages`
4. Anthropic 当前仍不支持 `web_search`、图片内容和纯 system 消息;至少需要一条非 system 文本消息。角色动画、图片、视频、资产轮询仍留在其他平台适配和业务模块任务里
5. 流式 `on_delta` 只发送文本增量与完成原因;工具调用增量在 crate 内按 slot 聚合,完整调用只从最终 `LlmRunResponse.tool_calls` 读取。上下文管理、后台执行和业务状态写回本 crate。
6. 支持按 provider 打标签,但不把业务 prompt、SSE 转发和模块状态写回本 crate
7. `DashScope` 当前只通过“调用方显式提供兼容文本网关 base url”的方式接入,不复用图像 API
8. 角色动画、图片、视频、资产轮询仍留在后续 `platform-llm` / `platform-oss` / 业务模块任务里另行实现。
## 3. 核心导出
## 3. 三协议工具支持矩阵
首版对外导出以下公共类型:
| API kind | 请求工具形态 | `tool_choice` | 非流式工具响应 | 流式工具聚合 |
| --- | --- | --- | --- | --- |
| `OpenAiChat` | `tools[].type=function`,函数内为 `name` / `description` / `parameters` / `strict` | `"auto"` / `"required"` | `choices[0].message.tool_calls` | `delta.tool_calls[].index`;首片提供 id/name,后续拼接 arguments |
| `OpenAiResponses` | `tools[].type=function`,函数内为 `name` / `description` / `parameters` / `strict` | `"auto"` / `"required"` | `output[].type=function_call` | `output_index``output_item.added` 提供身份,`function_call_arguments.delta` 拼接,`.done` 覆盖完整参数 |
| `Anthropic` | 顶层 `tools[]``name` / `description` / `input_schema`,没有 `function` 包装层和 `strict` | `{ "type": "auto" }` / `{ "type": "any" }``Required` 映射为 `any` | `content[].type=tool_use``input` 序列化为 `arguments` | content block `index``content_block_start` 提供身份,`input_json_delta` 拼接参数 |
Responses 如果只发送 `response.completed`,解析器会从其中的 `response.output[]` 恢复 `function_call`;恢复时使用 output 数组下标作为 slot。三种协议的并行工具调用只在平台层做 slot 聚合,不代表工具会在平台层并发执行。
## 4. 流式与参数契约
1. `LlmStreamDelta` 只包含 `accumulated_text``delta_text``finish_reason`,工具调用不会进入 `on_delta`;纯工具响应允许 `text` 为空。
2. 工具片段按协议索引聚合:Chat 使用 `delta.tool_calls[].index`Responses 使用 `output_index`Anthropic 使用 content block `index`。Responses 的 `.done``response.completed` 完整 arguments 是权威值,可以覆盖之前的分片拼接。
3. 流结束固化工具调用时,缺少 id 或函数名返回 `Deserialize`;空参数默认保存为 `{}`;非空参数必须能反序列化为完整 JSON,截断或半截 JSON 不会交给业务层。这里是 JSON 语法完整性检查,不是针对 `parameters` 的 JSON Schema 业务校验。
4. 非流式 Anthropic `tool_use.input` 缺失时同样按 `{}` 归一;Chat / Responses 非流式响应的 arguments 仍以各自上游字段为准,调用方不能把缺失字段自动假定为统一 schema 校验通过。
## 5. 错误边界
1. `Deserialize`:上游 JSON、SSE 或 UTF-8 无法解析,Chat 非流式缺少 `choices[0]`,流式工具调用缺少 id / name,或流式工具参数不是完整 JSON。
2. `StreamUnavailable`:仅用于流式响应已声明 `tool_use` / `tool_calls`,但一个工具 slot 都没有聚合出来的协议兼容失败;调用方可以据此回退一次非流式请求,不能把已收到的解说文本当成最终回复。
3. `EmptyResponse`:最终文本为空且工具调用也为空。只有文本为空但存在有效工具调用时,响应才是合法的纯工具响应。
4. 流尾部出现 `Timeout``Connectivity``Transport``Deserialize` 时,只有已形成非空文本、完成原因且工具参数完整的响应才会保留;工具参数半截或没有可保留文本时继续返回错误。
## 6. 核心导出
当前对外导出以下公共类型:
1. `LlmProvider`
2. `LlmConfig`
@@ -34,12 +59,15 @@
5. `LlmRunRequest`
6. `LlmApiKind`
7. `LlmStreamDelta`
8. `LlmRunResponse`
9. `LlmTokenUsage`
10. `LlmClient`
11. `LlmError`
8. `LlmFunctionTool`
9. `LlmToolChoice`
10. `LlmToolCall`
11. `LlmRunResponse`
12. `LlmTokenUsage`
13. `LlmClient`
14. `LlmError`
## 4. 设计文档
## 7. 设计文档
## 当前文档入口
@@ -50,7 +78,8 @@
3. [../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md](../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md)
旧阶段设计文档不再作为实现依据。
## 5. 边界约束
## 8. 边界约束
1. `platform-llm` 只承接模型平台适配,不承接业务模块状态真相与业务规则。
2. 业务模块只能依赖这里的统一 client / DTO / 错误模型,不能再把上游请求细节散落回各 crate。