92425dfea2
## 背景 本 PR 修复了最新 master 基线上发现的 AGC 测试失败,包括 Direct Runtime Skill 契约、敏感信息脱敏、外部生成 5xx 结果未知、Provider transport 错误分类,以及本地 mock 请求体读取问题。主要集中在: - Direct Runtime 提示词和 Skill 索引契约未同步; - 浏览器诊断、委派回执和 Runtime 失败投影的敏感信息脱敏回归; - macOS 临时目录路径被误判为不安全链接祖先; - 流式和画布 mock 服务只读取请求头,导致请求体断言失败; - 本机 HTTP 代理将 loopback 连接关闭改写为 HTTP 502,导致 transport 错误分类错误; - 平台图片生成收到 5xx 时,无法正确区分确定拒绝和结果未知。 本 PR 不包含资源管理滚轮分页相关前端改动。 ## 修复内容 ### Direct Runtime 与 Skill 契约 - 补齐审核 Skill 索引及 manifest 指纹。 - 恢复 Direct Runtime 提示词相关测试。 - 保持系统提示词长度、审核索引范围和敏感信息边界不变。 ### 敏感信息投影 - 浏览器诊断在进入同线程修复前统一脱敏。 - 委派回执严格隔离敏感上下文。 - Runtime 失败投影不再泄漏命令行密码参数或旧计划明细。 ### 外部生成与 Provider 错误分类 - 平台图片生成提交收到 5xx 时统一返回“结果未知”。 - 保留本地恢复账本,禁止将 5xx 当作确定拒绝。 - `platform-llm` 对 `localhost`、IPv4/IPv6 loopback 地址禁用环境代理,避免代理伪造 502,恢复真实 transport/stream 错误分类。 ### 测试与 macOS 边界 - 流式 LLM、External Canvas mock 服务改为读取完整 HTTP 请求体。 - 允许 macOS `/var` 到 `/private/var` 的系统临时目录祖先链接。 - 继续拒绝用户项目中的链接祖先。 - macOS 下跳过不满足平台前提的大小写敏感路径和非 UTF-8 文件名测试。 ## 影响范围 - AGC Direct Runtime - Provider Runtime - External Canvas 生成恢复 - `platform-llm` loopback HTTP 客户端 - 相关 Rust 测试基础设施 不改变公网 API、SpacetimeDB schema、资源管理滚轮分页行为或外部生产 Provider 的代理策略。 ## 验证结果 - AGC Rust 完整测试:`2303 passed, 0 failed, 14 ignored` - `platform-llm` 测试:`132 passed, 0 failed` - `npm run check:encoding`:通过 - `git diff --check`:通过 ## 分支与提交 - 分支:`codex/fix-agc-runtime-test-regressions` - 基线:`origin/master` - 最新提交:`94c5443a2 修复AGC运行时测试与本地请求回归` Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/198 Co-authored-by: suzmii <suzmii@qq.com> Co-committed-by: suzmii <suzmii@qq.com>
platform-llm 平台适配 crate
更新:2026-08-06
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 包装层;只有 endpoint/model 配置显式声明支持且 schema / 请求复杂度满足 Anthropic 当前边界时才发送 strict: true。strict 传输 schema 会剥离不支持的约束,调用方原 schema 保持不变;最后一项带 ephemeral cache breakpoint |
{ "type": "auto" } / { "type": "any" };Required 映射为 any |
content[].type=tool_use,input 序列化为 arguments |
content block index;content_block_start 提供身份,input_json_delta 拼接参数;message_start/message_delta 合并 cache/input/output usage |
Responses 如果只发送 response.completed 或 response.incomplete,解析器会从其中的 response.output[] 恢复 function_call;恢复时使用 output 数组下标作为 slot。response.incomplete 表示上游没有完成本轮生成:其中的工具调用即使参数是完整 JSON 也返回 Deserialize,纯正文则保留为可用的降级结果。三种协议的并行工具调用只在平台层做 slot 聚合,不代表工具会在平台层并发执行。
4. 流式与参数契约
LlmRunRequest.max_output_tokens是协议中立的生成预算,包含可见输出与 Provider 可能使用的隐藏 reasoning token,不包含输入 token,也不保证可见正文长度。Responses 映射为max_output_tokens,Anthropic 映射为max_tokens;Chat 由LlmConfig.openai_chat_token_budget_field显式映射为当前max_completion_tokens或兼容网关旧字段max_tokens,每次只发送一个。默认保留 legacy,已验证支持新字段的 endpoint 必须显式 opt-in;禁止按模型名猜测或收到400后自动重放。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和response.incomplete中的完整 arguments 是权威值,可以覆盖之前的分片拼接。 - 流结束固化工具调用时,缺少 id 或函数名返回
Deserialize;空参数默认保存为{};非空参数必须能反序列化为完整 JSON,截断或半截 JSON 不会交给业务层。这里是 JSON 语法完整性检查,不是针对parameters的 JSON Schema 业务校验。 - 非流式工具调用采用不同的参数边界:缺失或空白
arguments统一归一为{};Chat / Responses 的非空畸形arguments不在平台层做 JSON 校验、修复或静默丢弃,而是保留参数内容(仅按统一归一策略去除首尾空白),连同 call id 和函数名交给调用方的 repair 循环。Anthropictool_use.input缺失时同样按{}归一;调用方不能把非流式参数自动假定为统一 schema 校验通过。
5. 错误边界
Deserialize:上游 JSON、SSE 或 UTF-8 无法解析,Chat 非流式缺少choices[0],流式工具调用缺少 id / name,或流式工具参数不是完整 JSON。StreamUnavailable:仅用于流式响应已声明tool_use/tool_calls,但一个工具 slot 都没有聚合出来的协议兼容失败;调用方可以据此回退一次非流式请求,不能把已收到的解说文本当成最终回复。EmptyResponse:最终文本为空且工具调用也为空。只有文本为空但存在有效工具调用时,响应才是合法的纯工具响应。- 流尾部出现
Timeout、Connectivity、Transport或Deserialize时,只有已形成非空文本或至少一个工具调用、观察到协议完成信号、已有完成原因且工具参数完整的响应才会保留;纯工具响应即使text为空也可以保留。工具参数半截或既没有文本也没有工具调用时继续返回错误。
6. 核心导出
当前对外导出以下公共类型:
LlmProviderLlmConfigOpenAiChatTokenBudgetFieldLlmMessageRoleLlmMessageLlmRunRequestLlmApiKindLlmStreamDeltaLlmFunctionToolLlmToolChoiceLlmToolCallLlmRunResponseLlmTokenUsageLlmClientLlmError
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 / incomplete 终态事件时的恢复、参数 JSON 完整性和错误边界;同时包含tests/live_stream_tool_calls.rs中不依赖外部 Provider 的parse_api_kind本地解析测试。fixture 可以来源于真实端点抓包,但测试不保存原始 SSE,也不逐事件与端点报文比较,因此不能证明抓包转录无偏差。tests/live_stream_tool_calls.rs同时包含默认执行的parse_api_kind确定性测试,以及 1 个默认#[ignore]的真实端点工具调用 smoke。PLATFORM_LLM_LIVE_API_KIND支持openai_responses、openai_chat和anthropic,未设置或空白时默认openai_responses,未知非空值会直接使验收失败。真实 smoke 只验证最终归一结果中的工具名、id 和完整参数 JSON;文本增量字符数仅用于打印观测,工具调用不进入on_delta,也没有原始 SSE 录制或逐事件对比能力。- 因此验收应分别称为“固定 SSE fixture parser 覆盖及本地解析单元测试”和“真实端点归一工具调用 smoke”,不能把后者描述为原始 SSE fidelity 或转录一致性证明。