eb63f44271
b1ef45fbd 把“arguments 非空必须是完整 JSON”同时应用到流式和非流式,
越界了。两者的“参数不完整”语义不同:流式意味着流被截断,是传输层事实;
非流式的外层 body 已经完整,参数半截只说明模型输出有问题,属于内容层事实。
App 早有更好的处理——工具计划格式修复循环把畸形响应回灌给模型重写,比硬
报错再重跑整轮 Provider 有效得多。平台层拦下来会丢掉修复所需的 call id、
函数名和原始参数,导致 background_agent_runtime_repairs_malformed_native_
function_arguments 直接以 kind=deserialize 失败,第二次 repair 请求不再发出。
按流式与非流式区分该校验。b1ef45fbd 的其余部分不变:禁止静默丢弃、id 与
函数名必填、空参数归一为空对象、三协议共用一份策略。
原用例反转为断言原样透传,并补一条成对的流式用例锁住流式仍然报错。
platform-llm 平台适配 crate
日期:2026-07-27
1. crate 职责
platform-llm 是 Rust 工作区里的大模型平台适配 crate,当前已经落地以下能力:
- 统一 Ark / DashScope / Anthropic / 其他兼容网关的文本模型配置结构
- 统一 OpenAI Chat Completions、OpenAI Responses 和 Anthropic Messages 的文本 / 原生 function tool 请求、非流式响应与 SSE 流式解析
- 统一超时、连接失败、上游错误、空响应与重试策略
- 为后续
module-ai、module-story、module-npc、module-custom-world提供可直接复用的基础 client
2. 当前能力边界
当前实现只覆盖“文本 run”主链,不提前混入媒体生成和业务编排:
- 对外抽象固定为
LlmRunRequest/LlmRunResponse,不再保留旧LlmTextRequest/LlmTextResponse类型。 OpenAiChat、OpenAiResponses与Anthropic三类 API kind 都支持 JSON 请求、非流式响应和 SSE 流式响应;默认 API kind 仍为OpenAiResponses。- 三类协议都使用统一的
function_tools/tool_choice输入和LlmRunResponse.tool_calls输出。Anthropic 请求使用顶层tools[].input_schema与对象形态tool_choice;Anthropic URL 默认在 base URL 后拼/v1/messages,如果 base URL 已以/v1结尾则只拼/messages。 - Anthropic 当前仍不支持
web_search、图片内容和纯 system 消息;至少需要一条非 system 文本消息。角色动画、图片、视频、资产轮询仍留在其他平台适配和业务模块任务里。 - 流式
on_delta只发送文本增量与完成原因;工具调用增量在 crate 内按 slot 聚合,完整调用只从最终LlmRunResponse.tool_calls读取。上下文管理、后台执行和业务状态不写回本 crate。 - 支持按 provider 打标签,但不把业务 prompt、SSE 转发和模块状态写回本 crate。
DashScope当前只通过“调用方显式提供兼容文本网关 base url”的方式接入,不复用图像 API。- 角色动画、图片、视频、资产轮询仍留在后续
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_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. 流式与参数契约
LlmStreamDelta只包含accumulated_text、delta_text和finish_reason,工具调用不会进入on_delta;纯工具响应允许text为空。- 工具片段按协议索引聚合:Chat 使用
delta.tool_calls[].index,Responses 使用output_index,Anthropic 使用 content blockindex。Responses 的.done和response.completed完整 arguments 是权威值,可以覆盖之前的分片拼接。 - 流结束固化工具调用时,缺少 id 或函数名返回
Deserialize;空参数默认保存为{};非空参数必须能反序列化为完整 JSON,截断或半截 JSON 不会交给业务层。这里是 JSON 语法完整性检查,不是针对parameters的 JSON Schema 业务校验。 - 非流式 Anthropic
tool_use.input缺失时同样按{}归一;Chat / Responses 非流式响应的 arguments 仍以各自上游字段为准,调用方不能把缺失字段自动假定为统一 schema 校验通过。
5. 错误边界
Deserialize:上游 JSON、SSE 或 UTF-8 无法解析,Chat 非流式缺少choices[0],流式工具调用缺少 id / name,或流式工具参数不是完整 JSON。StreamUnavailable:仅用于流式响应已声明tool_use/tool_calls,但一个工具 slot 都没有聚合出来的协议兼容失败;调用方可以据此回退一次非流式请求,不能把已收到的解说文本当成最终回复。EmptyResponse:最终文本为空且工具调用也为空。只有文本为空但存在有效工具调用时,响应才是合法的纯工具响应。- 流尾部出现
Timeout、Connectivity、Transport或Deserialize时,只有已形成非空文本、完成原因且工具参数完整的响应才会保留;工具参数半截或没有可保留文本时继续返回错误。
6. 核心导出
当前对外导出以下公共类型:
LlmProviderLlmConfigLlmMessageRoleLlmMessageLlmRunRequestLlmApiKindLlmStreamDeltaLlmFunctionToolLlmToolChoiceLlmToolCallLlmRunResponseLlmTokenUsageLlmClientLlmError
7. 设计文档
当前文档入口
当前长期工程口径已融合到:
- ../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md
- ../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md
- ../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md
旧阶段设计文档不再作为实现依据。
8. 边界约束
platform-llm只承接模型平台适配,不承接业务模块状态真相与业务规则。- 业务模块只能依赖这里的统一 client / DTO / 错误模型,不能再把上游请求细节散落回各 crate。
api-server后续如果需要做 REST/SSE façade,只允许在协议层调用platform-llm,不能复制一份私有实现。
9. 验收证据边界
cargo test -p platform-llm的确定性用例把 checked-in SSE fixture 交给 parser,验证归一后的文本、工具调用、slot 聚合、Responses completed-only 恢复、参数 JSON 完整性和错误边界。fixture 可以来源于真实端点抓包,但测试不保存原始 SSE,也不逐事件与端点报文比较,因此不能证明抓包转录无偏差。tests/live_stream_tool_calls.rs是默认#[ignore]的真实端点工具调用 smoke。它只验证最终归一结果中的工具名、id 和完整参数 JSON;文本增量字符数仅用于打印观测,工具调用不进入on_delta,也没有原始 SSE 录制或逐事件对比能力。- 因此验收应分别称为“固定 SSE fixture parser 覆盖”和“真实端点归一工具调用 smoke”,不能把后者描述为原始 SSE fidelity 或转录一致性证明。