feat: support ai gateway yaml import and export in ui and e2e flows - #3146
Conversation
cszmzh
commented
Aug 10, 2026
- 支持 AI 网关资源 YAML 的页面导入导出及 e2e 导入。
wklken
left a comment
There was a problem hiding this comment.
PR #3146 合并 Review 报告(Codex + Claude 双模型汇总)
- 标题:feat: support ai gateway yaml import and export in ui and e2e flows
- 作者:cszmzh (Zach)
- 分支:
ft_ai_gateway_yaml_import_export→master - 规模:13 个文件,+333 / -93(diff 638 行)
- 重心:3 份 OpenAPI schema JSON(各 48 行)、
biz/openapi/openapi.py(54 行)、v2 sync 测试(112 行)
本报告由
github-pr-monitor双模型(Codex gpt-5.6-sol + Claude Opus 5)独立 review 后汇总。两个模型结论高度一致。
汇总结论
合并建议:merge after fixes(修复后合并)
两个 reviewer 均确认存在一处硬阻塞(Critical):parsers.py:178 的 Python 3 语法错误导致模块无法 import,资源文档导入功能整体不可用。另有若干 High / Medium / Low 级别问题需处理后合并或跟进。
Critical Issues(合并前必须修复)
[C1] parsers.py:178 使用 Python 2 异常语法,模块无法 import 🔴
src/dashboard/apigateway/apigateway/biz/resource_doc/importer/parsers.py:178
try:
openapi_manager.parse_schema()
except ResolutionError, ValueError: # ← SyntaxError
raise SchemaValidationError("") from Noneexcept A, B: 是 Python 2 语法,Python 3 必须用括号:except (ResolutionError, ValueError):。这是模块级语法错误,任何 import 该模块的代码都会立即抛 SyntaxError,不限于某个运行时分支。该模块承载 OpenAPIParser / 资源文档导入解析,属于导入链路主路径,会导致资源文档导入整体不可用,且 pytest 在收集阶段即失败。Claude 已用 ast.parse 实测确认。
修复:
except (ResolutionError, ValueError) as err:
raise SchemaValidationError(str(err)) from None该语法错误说明本 PR 未执行
ruff/ pytest。按src/dashboard/AGENTS.md的 Post-Implementation Requirements,合并前必须补跑:
uv run make edition-ee && uv run make lint-check及相关 pytest 目标。
High Issues(应该修复)
[H1] ResourceDataImportSLZ.to_internal_value 绕过 DRF Mapping 守卫,非法入参从 400 退化为 500
serializers.py:721
def to_internal_value(self, data):
# 兼容资源导入页面将模型代理 API 的空后端配置提交为 {} 的场景。
if data.get("kind") == ResourceKindEnum.AI.value and data.get("backend_config") == {}:
data = {**data, "backend_config": None}
return super().to_internal_value(data)覆写在调用 super().to_internal_value(data) 之前就执行 data.get(...),绕过了 DRF Serializer.to_internal_value 首行的 if not isinstance(data, Mapping): raise ValidationError(...) 守卫。该 SLZ 通过 ResourceImportInputSLZ.import_resources = ListField(child=ResourceDataImportSLZ()) 使用,客户端传入 {"import_resources": ["foo"]} 或 [[1, 2]] 时 data 为 str/list,直接抛 AttributeError: 'str' object has no attribute 'get' → 未捕获 → HTTP 500(正确行为应为 400)。同一路径被 ResourceImportDocPreviewInputSLZ.review_resource(serializers.py:775)复用。
修复:先判断类型再兼容处理:
def to_internal_value(self, data):
if (
isinstance(data, Mapping)
and data.get("kind") == ResourceKindEnum.AI.value
and data.get("backend_config") == {}
):
data = {**data, "backend_config": None}
return super().to_internal_value(data)[H2] 本 PR 唯一改动语义的文档导入路径(parsers.py)零测试覆盖
parsers.py 从 validate() 切换为 validate_schema() + parse_schema() 两段式调用,是本 PR 语义变化最大的一处(校验范围从"schema + 业务校验"收窄为"仅 schema"),但新增测试全部集中在 v2 sync / resource import / resource_version 导出,没有任何一条覆盖 OpenAPIParser._parse。这正是 C1 语法错误能被提交的直接原因。建议至少补两条用例:schema 非法时 SchemaValidationError 的 message 非空且包含 json_path;$ref 解析失败时走 ResolutionError 分支。
Medium Issues(风险较低,建议处理)
[M1] 文档导入由 validate() 收窄为 validate_schema(),业务校验缺失
parsers.py:172 由 openapi_manager.validate() 改为 openapi_manager.validate_schema()。对照 biz/openapi/openapi.py,validate() = validate_schema() + ResourceImportValidator.validate()(承载 _validate_kind、资源数量上限等规则)。改动后文档导入不再拒绝业务层非法资源。逻辑上可能合理(文档导入只需 schema 合法,避免 AI 资源因后端未创建而无法生成文档),但属于行为变更,diff 无注释、PR 描述无说明。建议在 _parse 处补一行注释说明"文档导入仅需 schema 合法,业务校验由资源导入链路负责",避免后续被误当回归改回。
[M2] SchemaValidationError("") 丢弃底层错误信息
parsers.py:179 把 ResolutionError / ValueError 的原因完全丢掉,且 from None 抑制异常链,用户与日志都拿不到线索。$ref 无法解析是 YAML 导入最常见失败之一。修 C1 时一并带上 str(err)。
[M3] BaseParser.get_resources 对 backend is None 缺少防御
biz/openapi/parser.py:59-63 改动后 kind == ai 时不再补默认 backend,backend 可能为 None;而 parser.py:79 无条件调用 backend.get("name", ...)。当前被远端 schema(ai 分支 required: ["kind", "backend"])屏蔽,不可从 HTTP 触发,故未升级为 High,但属纯靠 schema 维持的隐式不变量——任何未先跑 schema 的新调用方都会 AttributeError。建议显式处理:
backend = extension_resource.get("backend")
if kind != ResourceKindEnum.AI.value:
backend = backend or {...}
elif backend is None:
raise ValueError(_("模型代理资源必须指定 backend"))(抛 ValueError 可被 openapi.py:43 的 except (ResolutionError, ValueError) 收敛,与既有风格一致。)
[M4] AI 资源"空后端配置"存在 {} 与 None 两种表示,契约不自闭合
serializers.py:849 把 check 接口输出改成 {"name": ..., "config": None},而 to_internal_value 又把提交上来的 backend_config == {} 归一成 None。导出用 None、导入额外容忍 {}。新增测试本身暴露了这一点(喂回 import 前必须手工 pop("backend") 并把 backend_config 写成 {})。建议二选一:让前端统一提交 None 并删除兼容代码;或在注释中标注这是过渡期兼容并记录清理条件(关联 issue / 前端版本)。
Low Issues(轻微改进建议)
- [L1]
service/resource_version/openapi_export.py:142的responses兜底用if "responses" not in operation,未覆盖responses: {}空 dict 情况。建议改为if not operation.get("responses"):。 - [L2]
_validate_refs在validate()+parse()流程中被重复执行(validate_schema与parse_schema各调一次),行为正确(幂等),属已知冗余。 - [L3]
data/apidocs/zh/sync_resources.md与v2_sync_resources.md新增的 AI 网关资源 YAML 说明内容完全一致,后续需同步维护。
Dismissed Findings(已驳回,不计入问题)
oneOf会破坏省略kind/kind: ai的校验:不成立。逐分支推演,kind缺失恰好命中分支 2(非 name-only 普通资源),kind: ai必然落空于分支 2(其enum=["standard"]),两分支互斥,与文档"使用kind: standard或省略kind"一致。backend: {name: "default"}(仅 name)的普通资源会被 schema 拒:确实会拒,但改动前的allOf[anyOf[...]]对同一输入同样拒绝,属既有行为而非本 PR 回归。to_internal_value里data = {**data, ...}会污染调用方入参:不成立,浅拷贝新建 dict,写法正确。- 英文文档
data/apidocs/en/未同步更新:不成立,data/apidocs/下只有zh子目录,无英文副本需同步。
双模型一致性
| Issue | Codex | Claude | Verdict |
|---|---|---|---|
C1 parsers.py:178 语法错误 |
是 | 是(ast.parse 实测) | 真实问题(硬阻塞) |
H1 to_internal_value 类型守卫缺失 |
— | 是 | 真实问题(Claude 检出,建议补测) |
H2 parsers.py 零测试覆盖 |
是 | 是 | 真实问题 |
| M1 业务校验缺失 | — | 是 | 需人工确认(疑似有意为之) |
| M2 错误信息被丢弃 | — | 是 | 真实问题 |
| M3 backend None 缺防御 | — | 是 | 潜在问题 |
M4 {}/None 双表示 |
— | 是 | 设计取舍 |
| L1-L3 | — | 是 | 轻微 |
合并建议
merge after fixes(修复后合并)
方向良好:oneOf 把"AI 资源 backend 仅含 name"与"普通资源 backend 必须含请求配置"表达为两个互斥分支,比原 allOf[anyOf[...]] 清晰;三份 schema(2.0/3.0/3.1)改动完全一致无漏改;validate/validate_schema 与 parse/parse_schema 的拆分让文档导入不必承担资源落库校验,分层符合 AGENTS.md 职责划分;中文文档同步补充了 AI 资源 YAML 说明。
阻塞项(必须修后合并):
- C1 —
except ResolutionError, ValueError:硬语法错误,模块无法 import,资源文档导入不可用,pytest 收集即失败。 - H1 —
to_internal_value绕过 DRF Mapping 守卫,非法入参从 400 退化为 500。
建议一并处理:M2(顺手在 C1 修复中带 str(err))、M3(显式拒绝 backend is None)、H2(为 OpenAPIParser._parse 补 2 条用例)。
由于 C1 存在可确定本 PR 未执行 lint 与测试,修复后请务必按
src/dashboard/AGENTS.md补跑uv run make edition-ee && uv run make lint-check及相关 pytest 目标。
[from openclaw-internal]
|
C1 / H1 / H2 / M1 / M2 / M4 fix |