diff --git a/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md b/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md index e1e614870..178aea709 100644 --- a/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md +++ b/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md @@ -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 → 阿里云 → 本地降级顺序。 diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index cc1e1feb2..05a56eb83 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -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`。排查或切换密钥后,可在本地运行: diff --git a/server-rs/crates/platform-llm/README.md b/server-rs/crates/platform-llm/README.md index d02fbc08c..e4ce96635 100644 --- a/server-rs/crates/platform-llm/README.md +++ b/server-rs/crates/platform-llm/README.md @@ -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。