Files
suzmii 736a1b6ac6
Project CI / Repository checks (pull_request) Successful in 2m36s
Project CI / Frontend tests (pull_request) Successful in 3m15s
Project CI / Backend tests (pull_request) Successful in 7m35s
Project CI / Native shell tests (pull_request) Failing after 7m43s
接入 LLM Router 累计额度结算
按 Router used_quota 累计值与首次基线结算泥点
新增原子 checkpoint 事务及 llm_router_consume 钱包流水
同步额度查询校验、前端展示、生成绑定和技术文档
2026-09-06 00:36:31 +08:00
..
2026-09-06 00:36:31 +08:00

platform-llm 平台适配 crate

更新:2026-08-06

1. crate 职责

platform-llm 是 Rust 工作区里的大模型平台适配 crate,当前已经落地以下能力:

  1. 统一 Ark / DashScope / Anthropic / 其他兼容网关的文本模型配置结构
  2. 统一 OpenAI Chat Completions、OpenAI Responses 和 Anthropic Messages 的文本 / 原生 function tool 请求、非流式响应与 SSE 流式解析
  3. 统一超时、连接失败、上游错误、空响应与重试策略
  4. 为后续 module-aimodule-storymodule-npcmodule-custom-world 提供可直接复用的基础 client

2. 当前能力边界

当前实现只覆盖“文本 run”主链,不提前混入媒体生成和业务编排:

  1. 对外抽象固定为 LlmRunRequest / LlmRunResponse,不再保留旧 LlmTextRequest / LlmTextResponse 类型。
  2. OpenAiChatOpenAiResponsesAnthropic 三类 API kind 都支持 JSON 请求、非流式响应和 SSE 流式响应;默认 API kind 仍为 OpenAiResponses
  3. 三类协议都使用统一的 function_tools / tool_choice 输入和 LlmRunResponse.tool_calls 输出。Anthropic 请求使用顶层 tools[].input_schema 与对象形态 tool_choiceAnthropic 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. 三协议工具支持矩阵

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_indexoutput_item.added 提供身份,function_call_arguments.delta 拼接,.done 覆盖完整参数
Anthropic 顶层 tools[]name / description / input_schema,没有 function 包装层;只有 endpoint/model 配置显式声明支持且 schema / 请求复杂度满足 Anthropic 当前边界时才发送 strict: true。strict 传输 schema 会剥离不支持的约束,调用方原 schema 保持不变;最后一项带 ephemeral cache breakpoint { "type": "auto" } / { "type": "any" }Required 映射为 any content[].type=tool_useinput 序列化为 arguments content block indexcontent_block_start 提供身份,input_json_delta 拼接参数;message_start/message_delta 合并 cache/input/output usage

Responses 如果只发送 response.completedresponse.incomplete,解析器会从其中的 response.output[] 恢复 function_call;恢复时使用 output 数组下标作为 slot。response.incomplete 表示上游没有完成本轮生成:其中的工具调用即使参数是完整 JSON 也返回 Deserialize,纯正文则保留为可用的降级结果。三种协议的并行工具调用只在平台层做 slot 聚合,不代表工具会在平台层并发执行。

4. 流式与参数契约

  1. LlmRunRequest.max_output_tokens 是协议中立的生成预算,包含可见输出与 Provider 可能使用的隐藏 reasoning token,不包含输入 token,也不保证可见正文长度。Responses 映射为 max_output_tokensAnthropic 映射为 max_tokensChat 由 LlmConfig.openai_chat_token_budget_field 显式映射为当前 max_completion_tokens 或兼容网关旧字段 max_tokens,每次只发送一个。默认保留 legacy,已验证支持新字段的 endpoint 必须显式 opt-in;禁止按模型名猜测或收到 400 后自动重放。
  2. LlmStreamDelta 只包含 accumulated_textdelta_textfinish_reason,工具调用不会进入 on_delta;纯工具响应允许 text 为空。
  3. 工具片段按协议索引聚合:Chat 使用 delta.tool_calls[].indexResponses 使用 output_indexAnthropic 使用 content block index。Responses 的 .doneresponse.completedresponse.incomplete 中的完整 arguments 是权威值,可以覆盖之前的分片拼接。
  4. 流结束固化工具调用时,缺少 id 或函数名返回 Deserialize;空参数默认保存为 {};非空参数必须能反序列化为完整 JSON,截断或半截 JSON 不会交给业务层。这里是 JSON 语法完整性检查,不是针对 parameters 的 JSON Schema 业务校验。
  5. 非流式工具调用采用不同的参数边界:缺失或空白 arguments 统一归一为 {}Chat / Responses 的非空畸形 arguments 不在平台层做 JSON 校验、修复或静默丢弃,而是保留参数内容(仅按统一归一策略去除首尾空白),连同 call id 和函数名交给调用方的 repair 循环。Anthropic tool_use.input 缺失时同样按 {} 归一;调用方不能把非流式参数自动假定为统一 schema 校验通过。

5. 错误边界

  1. Deserialize:上游 JSON、SSE 或 UTF-8 无法解析,Chat 非流式缺少 choices[0],流式工具调用缺少 id / name,或流式工具参数不是完整 JSON。
  2. StreamUnavailable:仅用于流式响应已声明 tool_use / tool_calls,但一个工具 slot 都没有聚合出来的协议兼容失败;调用方可以据此回退一次非流式请求,不能把已收到的解说文本当成最终回复。
  3. EmptyResponse:最终文本为空且工具调用也为空。只有文本为空但存在有效工具调用时,响应才是合法的纯工具响应。
  4. 流尾部出现 TimeoutConnectivityTransportDeserialize 时,只有已形成非空文本或至少一个工具调用、观察到协议完成信号、已有完成原因且工具参数完整的响应才会保留;纯工具响应即使 text 为空也可以保留。工具参数半截或既没有文本也没有工具调用时继续返回错误。

6. 核心导出

当前对外导出以下公共类型:

  1. LlmProvider
  2. LlmConfig
  3. OpenAiChatTokenBudgetField
  4. LlmMessageRole
  5. LlmMessage
  6. LlmRunRequest
  7. LlmApiKind
  8. LlmStreamDelta
  9. LlmFunctionTool
  10. LlmToolChoice
  11. LlmToolCall
  12. LlmRunResponse
  13. LlmTokenUsage
  14. LlmClient
  15. LlmError

7. 设计文档

当前文档入口

当前长期工程口径已融合到:

  1. ../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md
  2. ../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md
  3. ../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md

旧阶段设计文档不再作为实现依据。

8. 边界约束

  1. platform-llm 只承接模型平台适配,不承接业务模块状态真相与业务规则。
  2. 业务模块只能依赖这里的统一 client / DTO / 错误模型,不能再把上游请求细节散落回各 crate。
  3. api-server 后续如果需要做 REST/SSE façade,只允许在协议层调用 platform-llm,不能复制一份私有实现。

9. 验收证据边界

  1. cargo test -p platform-llm 的确定性用例把 checked-in SSE fixture 交给 parser,验证归一后的文本、工具调用、slot 聚合、Responses 仅有 completed / incomplete 终态事件时的恢复、参数 JSON 完整性和错误边界;同时包含 tests/live_stream_tool_calls.rs 中不依赖外部 Provider 的 parse_api_kind 本地解析测试。fixture 可以来源于真实端点抓包,但测试不保存原始 SSE,也不逐事件与端点报文比较,因此不能证明抓包转录无偏差。
  2. tests/live_stream_tool_calls.rs 同时包含默认执行的 parse_api_kind 确定性测试,以及 1 个默认 #[ignore] 的真实端点工具调用 smoke。PLATFORM_LLM_LIVE_API_KIND 支持 openai_responsesopenai_chatanthropic,未设置或空白时默认 openai_responses,未知非空值会直接使验收失败。真实 smoke 只验证最终归一结果中的工具名、id 和完整参数 JSON;文本增量字符数仅用于打印观测,工具调用不进入 on_delta,也没有原始 SSE 录制或逐事件对比能力。
  3. 因此验收应分别称为“固定 SSE fixture parser 覆盖及本地解析单元测试”和“真实端点归一工具调用 smoke”,不能把后者描述为原始 SSE fidelity 或转录一致性证明。