已合并
feat: A2A网关接入实现 - VersatileA2AGateway适配器 #377
feat: A2A网关接入实现 - VersatileA2AGateway适配器 #377
已合并
xiongxing创建于 7月13日
xiongxing成员
7月13日

What type of PR is this?

/kind

Self-checklist:(请自检,在[ ]内打上x,我们将检视你的完成情况,否则会导致pr无法合入)

likedislike
Pull Request已成功合入, 合并人@曹玉哲
(感谢 xiongxing 的贡献)
atomgit-bot
atomgit-bot
7月13日 评论:

变更摘要

此 PR 为 versatile_adapter 新增了 A2A 网关协议适配模式。核心是新增 VersatileA2AGateway 类(继承自 VersatileProxy),将 VA 内部统一入参转换为 A2A 1.0 JSON-RPC SendStreamingMessage 请求,解析 SSE 响应中的 taskArtifactUpdate / taskStatusUpdate 事件,并映射为 VA 统一的 AdapterEvent。通过环境变量 VA_WORKFLOW_ADAPTER_TYPE 可在网关模式(a2a_gateway)与直连低码工作流模式(workflow)之间切换。

主要改动

  • 新增 VersatileA2AGateway 适配器:实现 _build_url、_build_headers、_build_request_body、_process_chunk、_on_stream_end 等钩子方法,完成 A2A 1.0 协议请求构造、SSE 响应解析(artifact 累积/状态终态处理),并将结果按低码工作流统一格式输出 {"event":"message","data":{"text":"…"}} 帧。
  • VersatileProxy 基类增强:VersatileStreamCtx 新增 artifact_texts、last_artifact_id、input_required 三个字段以支持 A2A 网关的 artifact 累积与状态跟踪;dispatch_stream 签名扩展 trace_id / agent_id 参数并存储入口上下文;_log_request 对 token/authorization 等敏感 header 脱敏。
  • 路由层支持 a2a_gateway 类型切换:VersatileAdapterRunner 读取环境变量 VA_WORKFLOW_ADAPTER_TYPE(默认 a2a_gateway),_match_workflow 据此匹配对应类型的 adapter;新增 _merge_a2a_gateway_defaults 函数合并 YAML 中 a2a_gateway_defaults 默认配置。
  • 配置与环境变量扩展:Settings 新增 versatile_workflow_adapter_type 字段(映射 VA_WORKFLOW_ADAPTER_TYPE 环境变量);YAML 配置示例新增 a2a_gateway_defaults 默认段和 type: a2a_gateway 的适配器条目,支持 a2a_gateway_base、agent_card_name、token 等专属字段。
  • 日志与上下文透传增强:response_wrapper.py 和 remote_agent_handler.py 增加关键事件调试日志;A2aVersatileExecutor 在 runner_kw 中透传 trace_id 和 agent_id。
likedislike
不准确?
atomgit-bot
atomgit-bot
7月13日 评论:

代码审查

审查总结

共审查 11 个变更文件,发现问题 3 个:

优先级 数量 说明
P2 2 _extract_text_from_status_message 缺少 parts 非列表防御;_on_stream_end 对空字符串 execution_result 真值误判导致不产出终态事件
P3 1 VersatileAdapterRunner 通过 os.getenv 直接读取路由模式,与 Pydantic Settings 不一致

逐文件审查结果:

  • applications/a2a_service/common/response_wrapper.py — 无问题(仅添加 debug 日志)
  • applications/a2a_service/orchestrator/handlers/remote_agent_handler.py — 无问题(仅添加 info 日志)
  • applications/versatile_adapter/.env.example — 无问题
  • applications/versatile_adapter/a2a_facade/executor.py — 无问题(新增 trace_id/agent_id 透传 + 日志)
  • applications/versatile_adapter/adapters/versatile_a2a_gateway.py — 2 个 P2 问题(见上方)
  • applications/versatile_adapter/adapters/versatile_proxy.py — 无问题(签名扩展 + 敏感头脱敏均为改进)
  • applications/versatile_adapter/config.py — 无问题
  • applications/versatile_adapter/dispatcher/runner.py — 1 个 P3 问题(见上方)
  • applications/versatile_adapter/tests/conftest.py — 无问题
  • applications/versatile_adapter/tests/test_versatile_a2a_gateway.py — 无问题
  • applications/versatile_adapter/versatile_proxy.yaml.example — 无问题

整体风险评估: 低至中等。两个 P2 问题均位于新增的 VersatileA2AGateway 适配器中,属于边界情况的防御缺失,不会在正常 A2A 协议交互中触发。核心主流程(A2A Gateway 正常 SSE 解析、路由匹配、事件映射)设计合理,测试覆盖充分。建议优先修复两个 P2 问题后再合入。

类型 数量
🔴 阻塞 0
🟡 建议 5

💬 仅评论

likedislike
不准确?
openJiuwen-bot成员
7月13日 评论:

欢迎来到 openJiuwen 社区

Hey @xiongxing , 感谢你对社区的贡献.

机器人使用手册

有关指令的使用,可以点击 此处 查看详情。开发人员可以在每个PR或Issue下方评论特定指令来触发机器人任务。

likedislike
OopenJiuwen-bot成员
7月13日 添加了label:openJiuwen-cla/yes
openJiuwen-bot成员
7月13日 评论:
任务名称 结果 日志操作
静态检查 ❌FAILED 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 N/A N/A
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck {{ruff_codecheck_result}} {{ruff_codecheck_link_text}}
likedislike
OopenJiuwen-bot成员
7月13日 添加了label:ci-failed
atomgit-bot
atomgit-bot
7月13日 评论:

AtomGit AI 助手使用指南

在 PR 评论中使用命令即可触发。支持在普通评论和代码行评论中使用。

功能 命令 说明
代码审查 /ai review 检查代码质量、潜在问题、安全风险
PR 摘要 /ai summary 生成 PR 变更内容的结构化摘要
代码解释 /ai explain 解释代码变更的逻辑和意图
自由提问 @atomgit-bot <问题> 询问关于本 PR 的任何问题
帮助 /ai help 显示此帮助信息

免责声明

AI 助手可能存在误判,请结合自身判断。可以对评论点 👍 或 👎 帮助我们改进。

likedislike
不准确?
atomgit-bot
atomgit-bot7月13日进行代码检视1
applications/versatile_adapter/a2a_facade/executor.py
@@ -80,0 +78,4 @@
78+ f"[A2aVA] 收到请求: conv_id={context.context_id}, task_id={context.task_id}, "
79+ f"body={str(runner_kw.get('body', {}))[:200]}, headers={runner_kw.get('headers', {})}, "
80+ f"trace_id={runner_kw.get('trace_id', '')}, agent_id={runner_kw.get('agent_id', '')}"
81+ )
atomgit-bot
atomgit-bot7月13日评论:

🟡 Medium Priority

executor.py 第 77-81 行新增的 logger.info(...) 将完整的 runner_kw.get('headers', {}) 以 INFO 级别写入日志,未对 token、authorization、cookie 等敏感 header 做任何脱敏处理。body 字段有 [:200] 截断,但 headers 完全没有截断或脱敏。

对比 versatile_proxy.py 中 _log_request 使用 _SENSITIVE_HEADERS 集合对 curl 命令中的敏感 header 做 *** 替换,且请求头日志使用 DEBUG 级别。此处以 INFO 级别(生产环境通常开启)记录完整 headers,若上游 a2a_service 传入的 headers 中包含 [REDACTED]、session cookie 等,将直接写入日志文件。

风险:日志被非授权人员/系统访问时泄露凭据。

建议:将日志级别降为 DEBUG,或对 headers 做脱敏处理(过滤 token/authorization/cookie 等敏感 key),或只记录 header keys 而非 values。

likedislike
不准确?
atomgit-bot
atomgit-bot7月13日进行代码检视1
applications/versatile_adapter/adapters/versatile_a2a_gateway.py
@@ -0,0 +328,4 @@
328+ if not message or not isinstance(message, dict):
329+ return ""
330+ parts = message.get("parts", [])
331+ return "".join(
atomgit-bot
atomgit-bot7月13日评论:

🟡 Medium Priority

在 VersatileA2AGateway._extract_text_from_status_message 方法中(第 330-334 行),parts = message.get("parts", []) 获得的 parts 可能不是列表(例如上游返回 {"parts": {"unexpected": "dict"}}),后续的生成器表达式 for part in parts 将抛出 TypeError: 'dict' object is not iterable。

相比之下,_handle_artifact_update 方法(第 218 行)已对此做了防御:if not isinstance(parts, list): parts = []。同样的防护应在 _extract_text_from_status_message 中添加,保持一致性并避免运行时崩溃。

触发条件:A2A Gateway 返回的 taskStatusUpdate.message.parts 字段为非列表值(如 dict、字符串)。

建议:在迭代 parts 前增加 isinstance(parts, list) 检查,与 _handle_artifact_update 保持一致。

改动建议
329
+ parts = message.get("parts", [])
330
+ if not isinstance(parts, list):
331
+ parts = []
329
332
  return "".join(
应用建议
likedislike
不准确?
atomgit-bot
atomgit-bot7月13日进行代码检视1
applications/versatile_adapter/adapters/versatile_a2a_gateway.py
@@ -0,0 +179,4 @@
179+ logger.info(f"[VersatileA2AGateway] INPUT_REQUIRED, conv_id={self._conv_id}")
180+ return [AdapterEvent(execution_input_required=ExecutionInputRequiredContent())]
181+ 
182+ if ctx.is_failed or ctx.execution_result:
atomgit-bot
atomgit-bot7月13日评论:

🟡 Medium Priority

VersatileA2AGateway._on_stream_end 第 182 行使用真值判断 ctx.execution_result 来决定是否产出 execution_completed 终态事件。但 _handle_artifact_update 在处理非文本 parts(如纯文件 parts)时,仍会将空字符串 "" 存入 ctx.artifact_texts(第 230 行),并设置 ctx.last_artifact_id(第 231 行)。当 TASK_STATE_COMPLETED 到来时,_handle_status_update 会从 artifact_texts 中取出空字符串赋给 ctx.execution_result(第 259 行)。

此时 ctx.is_failed or ctx.execution_result → False or "" → ""(falsy),_on_stream_end 返回 [],不产出 execution_completed。executor 的 for 循环结束后触发兜底逻辑(executor.py 第 133 行),以 result=None 完成任务——丢失了原本可能从 status.message 降级提取到的有效文本。

触发条件:A2A Gateway 返回的 artifact 中 parts 只有非 text 类型(如 {"type":"file", ...}),同时 TASK_STATE_COMPLETED 的 message 中包含可用于降级提取的文本。

建议:将 ctx.execution_result 的真值检查改为 ctx.execution_result is not None,以区分"已设置空结果"与"未设置结果"两种语义。ctx.execution_result 初始为 None(VersatileStreamCtx 第 36 行),只有被明确赋值后才应视为有结果。

改动建议
180
- if ctx.is_failed or ctx.execution_result:
180
+ if ctx.is_failed or ctx.execution_result is not None:
应用建议
likedislike
不准确?
atomgit-bot
atomgit-bot7月13日进行代码检视1
applications/versatile_adapter/versatile_proxy.yaml.example
@@ -52,0 +66,4 @@
66+ Content-Type: "application/json"
67+ forward_header_whitelist:
68+ - x-b3-traceid
69+ - x-b3-spanid
atomgit-bot
atomgit-bot7月13日评论:

🟡 Medium Priority

在 a2a_gateway_defaults 的 forward_header_whitelist(第 69 行)中包含了 x-b3-spanid。父类 VersatileProxy._build_headers(versatile_proxy.py 第 79 行)会按白名单原样转发该头(带原始 key x-b3-spanid 和原始值)。而子类 VersatileA2AGateway._build_headers(versatile_a2a_gateway.py 第 90 行)在 x-b3-parentspanid 存在时会通过 uuid.uuid4().hex[:16] 重新生成 X-B3-SpanId。HTTP 头部是大小写不敏感的(RFC 7230),因此最终请求中会同时存在 x-b3-spanid(转发的上游 span-id)和 X-B3-SpanId(新生成的 span-id),两个值不同,导致下游无法确定哪个是正确的 span-id,破坏分布式追踪语义。

证据链:

  1. YAML 第 69 行:- x-b3-spanid 在白名单中
  2. versatile_proxy.py 第 79 行:父类按原始 key 转发匹配白名单的 header
  3. versatile_a2a_gateway.py 第 90 行:子类生成新 X-B3-SpanId = uuid.uuid4().hex[:16]
  4. 两者同时出现在同一次 HTTP 请求中,值不同,HTTP 大小写不敏感

修复方向:从 a2a_gateway_defaults.forward_header_whitelist 中移除 x-b3-spanid(span-id 应由网关重新生成,不应透传上游值)。

建议:从 a2a_gateway_defaults.forward_header_whitelist 中移除 - x-b3-spanid 这一行。SpanId 由 A2A Gateway 的 _build_headers 重新生成(versatile_a2a_gateway.py:90),不应透传上游的 span-id。

likedislike
不准确?
atomgit-bot
atomgit-bot7月13日进行代码检视1
applications/versatile_adapter/adapters/versatile_a2a_gateway.py
@@ -0,0 +179,4 @@
179+ logger.info(f"[VersatileA2AGateway] INPUT_REQUIRED, conv_id={self._conv_id}")
180+ return [AdapterEvent(execution_input_required=ExecutionInputRequiredContent())]
181+ 
182+ if ctx.is_failed or ctx.execution_result:
atomgit-bot
atomgit-bot7月13日评论:

🟡 Medium Priority

_on_stream_end 第 182 行的条件 if ctx.is_failed or ctx.execution_result: 在 ctx.execution_result 为空字符串 "" 时求值为 False or "" → ""(falsy),导致整个方法返回空列表 []。

触发条件:A2A Gateway 返回 TASK_STATE_COMPLETED 但无 artifact 且 status message 也为空,或 artifact 累积的文本恰为空串。此时 ctx.completed=True、ctx.is_failed=False、ctx.execution_result=""。

后果:适配器层不产出任何终态事件,完全依赖 executor 的 if not terminal_sent 兜底逻辑发送 result=None 的 completed 事件。虽然 executor 有兜底,但适配器自身的契约被打破(completed 状态不产生事件),且当 executor 兜底逻辑未来变更时可能引入回归。

修复方向:在条件中增加对 ctx.completed 的判断,确保 completed 状态下始终产出 ExecutionCompletedContent 事件。

建议:将条件改为 if ctx.is_failed or ctx.execution_result or ctx.completed:,或在条件体内明确处理 completed-but-no-result 情况,确保产出 ExecutionCompletedContent(is_failed=False, result="", error_message="") 事件。

likedislike
不准确?
Xxiongxing成员
7月14日 推送  1 个提交:d8cfc2a7-fix: 静态检查问题修复 - 命名规范、参数过多、protected-access、staticmethod误报
openJiuwen-bot成员7月14日进行代码检视1
applications/versatile_adapter/adapters/versatile_proxy.py
@@ -102,1 +108,1 @@
108+ async def dispatch_stream( # pylint: disable=too-many-arguments
102109 self,
openJiuwen-bot7月14日评论:

此条代码评论区间+108至109

【openlibing.ci】识别到代码检查告警抑制注释,匹配工具:pylint,请Committer检视其合理性。

likedislike
openJiuwen-bot成员7月14日进行代码检视1
applications/versatile_adapter/dispatcher/runner.py
@@ -165,0 +196,1 @@
196+ async def run_async( # pylint: disable=too-many-arguments
197+ self, target: dict, headers: dict, params: dict,
openJiuwen-bot7月14日评论:

此条代码评论区间+196至+197

【openlibing.ci】识别到代码检查告警抑制注释,匹配工具:pylint,请Committer检视其合理性。

likedislike
openJiuwen-bot成员7月14日进行代码检视1
applications/versatile_adapter/tests/test_versatile_a2a_gateway.py
@@ -0,0 +2,2 @@
2+"""VersatileA2AGateway 单元测试。"""
3+# pylint: disable=protected-access,add-staticmethod-or-classmethod-decorator
4+import json
openJiuwen-bot7月14日评论:

此条代码评论区间+2至+4

【openlibing.ci】识别到代码检查告警抑制注释,匹配工具:pylint,请Committer检视其合理性。

likedislike
OopenJiuwen-bot成员
7月14日 添加了label:stat/needs-squash
OopenJiuwen-bot成员
7月14日 删除了label:ci-failed
openJiuwen-bot成员
7月14日 评论:
任务名称 结果 日志操作
静态检查 ❌FAILED 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 N/A N/A
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck {{ruff_codecheck_result}} {{ruff_codecheck_link_text}}
likedislike
OopenJiuwen-bot成员
7月14日 添加了label:ci-failed
xiongxing成员
7月14日 评论:

ci-pipeline

likedislike
OopenJiuwen-bot成员
7月14日 删除了label:ci-failed
openJiuwen-bot成员
7月14日 评论:
任务名称 结果 日志操作
静态检查 ❌FAILED 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 N/A N/A
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck {{ruff_codecheck_result}} {{ruff_codecheck_link_text}}
likedislike
OopenJiuwen-bot成员
7月14日 添加了label:ci-failed
Xxiongxing成员
7月14日 推送  1 个提交:24a878c7-refactor: 移除VA侧冗余的agent_id参数链路
openJiuwen-bot成员
7月14日 评论:

CLA 签署成功

xiongxing, 感谢您的代码贡献。 所有的代码贡献者都已完成了 CLA 签署。 👍

likedislike
xiongxing成员
7月14日 评论:

ci-pipeline

likedislike
OopenJiuwen-bot成员
7月14日 删除了label:ci-failed
openJiuwen-bot成员
7月14日 评论:
任务名称 结果 日志操作
静态检查 ✅SUCCESS 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 N/A N/A
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck {{ruff_codecheck_result}} {{ruff_codecheck_link_text}}
likedislike
OopenJiuwen-bot成员
7月14日 添加了label:ci-successful
OopenJiuwen-bot成员
7月14日 删除了label:ci-successful
openJiuwen-bot成员
7月14日 评论:
任务名称 结果 日志操作
静态检查 ✅SUCCESS 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 N/A N/A
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck {{ruff_codecheck_result}} {{ruff_codecheck_link_text}}
likedislike
OopenJiuwen-bot成员
7月14日 添加了label:ci-successful
此处折叠了9条事件消息 查看更多
OopenJiuwen-bot成员
7月14日 删除了label:ci-successful