generative-ai 合同合规多智能体流水线深度解析:ADK RemoteA2aAgent 与 Go A2A 确定性策略引擎的跨语言编排
【免费下载链接】generative-aiSample code and notebooks for Generative AI on Google Cloud, with Gemini Enterprise Agent Platform项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai
本文以agents/adk/contract-compliance-pipeline/ARCHITECTURE.md为主线,拆解 generative-ai 仓库中合同合规(Contract Compliance)演示系统的可执行架构:浏览器驾驶舱如何通过 Python FastAPI 完成确定性字段抽取,再经 Google ADK 的RemoteA2aAgent通过 A2A JSON-RPCSendMessage把契约字段交给 Go 侧的确定性策略引擎完成审计裁决。读完本文,你将完整掌握该流水线的运行时拓扑、A2A 报文的请求/响应结构、五种策略检查规则的实现位置,以及三层信任边界的 fail-closed(失败即降级人工审核)设计。
一、架构核心思想:LLM 处理歧义,确定性代码执行硬策略
ARCHITECTURE.md 开宗明义地描述了当前仓库中“可执行”的架构形态——一个本地跨语言系统,由四个部分构成:
- 由 Python 在
/live-compliance/路径下提供的浏览器驾驶舱(Browser Cockpit); - 运行在
127.0.0.1:8000的 Python FastAPI 服务; - 运行在
:8888的 Go A2A 合规服务; - 通过 Go Agent Card 与 A2A JSON-RPC
SendMessage完成的 ADKRemoteA2aAgent交接(handoff)。
其中最关键的一句设计声明是:Go 服务在设计上是确定性的。它不是 LLM 智能体,而是执行需要可重复审计行为的策略阈值。这与同目录 README.md 中的核心观点一致:“LLMs are useful for ambiguity. Deterministic agents should enforce hard policy.”(LLM 适合处理歧义,确定性智能体应该执行硬策略)。如果政策规定供应商合同金额不得超过$500,000、期限不得超过 5 年、必须包含退出条款,那么这些检查就应该是可审计、可重复的——这正是 Go 侧checker.go的职责。
从仓库结构看,该演示被刻意拆成两个独立语言栈:Python 侧负责接入、抽取、会话状态与产物生成;Go 侧暴露 A2A Agent Card 并通过 JSON-RPC 执行同步、无 I/O 等待的策略校验。这种分工让演示在不依赖 Gemini API key 的前提下保持完全可复现(抽取夹具与策略检查均为确定性代码)。
二、运行时流程拓扑
ARCHITECTURE.md 用一张 mermaid 流程图定义了从浏览器到 Go 裁决再回到 UI 的完整数据流:
注意浏览器节点发出的载荷包含三类信息:样例合同文本 + 活动策略值 + 模拟器状态(sample text + policy + simulator state)。这意味着 UI 侧的策略覆盖值会真实地进入 A2A 数据部分并影响 Go 侧裁决,而不是装饰性的展示项。
三、实时请求路径的十步拆解
ARCHITECTURE.md 将一次完整的合规审计请求归纳为 10 个步骤,每一步都能在仓库源码中找到对应实现:
- 浏览器从
sample-contracts/中选择内置合同夹具(例如 standard-vendor-agreement.pdf)。 - 浏览器通过
/api/compliance/sample-contracts/{filename}获取夹具文本。 - 浏览器将文本、活动策略值、模拟器设置 POST 到
/api/compliance/upload。 - Python 在 tools.py 中用确定性解析抽取合同字段。
- Python 分类风险等级并构建 A2A 数据载荷。
- Python 在 fast_api_app.py 中创建一个聚焦的
RemoteA2aAgent。 - ADK 从
GO_AGENT_CARD_URL解析 Go Agent Card。 - ADK 向 Go 服务发送 A2A JSON-RPC
SendMessage。 - Go 解码 data part、执行确定性策略检查,返回已完成的 A2A Task。
- Python 保存案件状态、生成 HTML 产物,并返回 UI 可见的载荷。
3.1 Python 侧的确定性抽取
步骤 4 的实现是extract_contract_details_from_text(filename, text)(tools.py)。它并非调用 LLM,而是用正则表达式提取结构化字段:
- 金额:
r"\$[\d,]+(?:\.\d{2})?"; - 日期:英文月份 + 日 + 年(如
June 1, 2026),转换为 ISO 格式2026-06-01; - 当事方:匹配
between ... ("Contractor") and ... ("Client")结构; - 期限:
r"(\d+)\s+years?"; - 责任条款段落:截取
LIMITATION OF LIABILITY:到INSURANCE:之间的文本用于判定“无限责任”措辞。
对三份内置样例合同,模块还维护了SAMPLE_CONTRACT_DETAILS映射(tools.py#L127-L164),保证在文本缺失时也能回退到与样例一致的结构化字段——这是演示可复现性的另一道保险。
3.2 ADK RemoteA2aAgent 交接的实现细节
步骤 6~8 集中在invoke_go_compliance_service(...)(fast_api_app.py#L165-L242)中,几个值得注意的实现点:
- Agent Card URL 可配置:
GO_AGENT_CARD_URL环境变量默认指向http://localhost:8888/.well-known/agent.json(fast_api_app.py#L77-L80);_go_jsonrpc_url()会把/.well-known/agent.json后缀剥离,得到 JSON-RPC 根 URL。 - 请求拦截器注入任务元数据:
RemoteA2aAgent通过A2aRemoteAgentConfig挂载RequestInterceptor(before_request=add_task_metadata),在每次请求前把task_id写入request_metadata,保证 Go 侧能按案件 ID 归档任务。 - DataPart 的 GenAI 编码桥接:
_a2a_data_part_as_genai_part()先把a2a.types.DataPart序列化为 JSON,再包裹进 ADK 的A2A_DATA_PART_START_TAG/A2A_DATA_PART_END_TAG标签并封装为genai_types.Part的 inline blob——因为RemoteA2aAgent接受的是 GenAI 格式的Content,ADK 内部会将其转回 A2A DataPart 发出。 - 事件流回收原始报文:
runner.run_async(...)的每个事件里,ADK 会把真实的 A2A 请求/响应放在event.custom_metadata的a2a:request与a2a:response键中。演示的“Agent Exchange”面板展示的就是这两份原始报文,而非伪造的日志。
3.3 Go 侧的 JSON-RPC 分发
Go 服务端只有一个 JSON-RPC 入口HandleJSONRPC(task_handler.go#L131-L181),按 method 名分发:
| Method | 处理函数 | 说明 |
|---|---|---|
message/send/SendMessage | handleMessageSend | 当前 A2A 报文形态(Live 演示走这里) |
tasks/send | handleTasksSend | 旧版形态,保留给老客户端 |
tasks/get/GetTask | handleTasksGet | 按任务 ID 从内存 map 查询历史任务 |
这与 ARCHITECTURE.md “Go 服务处理当前SendMessage外加遗留tasks/send和tasks/get” 的描述完全对应。validateMessage(task_handler.go#L214-L254)的执行顺序是:extractContractPayload(找 data part)→unpackContractPayload(拆策略覆盖与合同字段)→decodeContractDetails(绑定到ContractDetails结构体)→ 同步调用compliance.CheckCompliance→ 构造已完成 Task → 写入内存任务表供tasks/get查询。源码注释明确写道:“Run compliance checks synchronously — pure computation, no I/O wait needed.”
tasks/get的存储是进程内map[string]*Task(task_handler.go#L105-L109),重启即失——从源码结构看这是一个演示级的轻量实现,而非持久化任务系统。
四、A2A 报文形态(Payload Shape)
4.1 请求信封
Python 在build_go_message_payload(...)(fast_api_app.py#L102-L119)中构建 UI 可见的请求信封,其完整形状如下(来自 ARCHITECTURE.md):
{ "jsonrpc": "2.0", "id": "case-{case_id}", "method": "SendMessage", "params": { "metadata": { "task_id": "{case_id}" }, "message": { "messageId": "case-{case_id}-request", "taskId": "{case_id}", "role": "ROLE_USER", "parts": [ { "data": { "schema_version": "contract-compliance.a2a.v1", "case_id": "{case_id}", "contract": { "contract_value": 250000.0, "contractor_name": "ACME CLOUD SOLUTIONS", "insurance_coverage": 2000000.0, "liability_limit": "$1,000,000.00", "term_length_years": 2, "auto_renewal": false, "has_termination_clause": true }, "policy": { "max_contract_value": 500000.0, "required_insurance_minimum": 1000000.0, "max_term_years": 5, "required_termination_clause": true, "prohibited_clauses": ["unlimited liability", "auto-renewal > 3yr"] } }, "mediaType": "application/json" } ] } } }其中schema_version: "contract-compliance.a2a.v1"是双方约定的数据部分版本标识;contract字段与 Go 侧ContractDetails结构体(checker.go#L21-L32)的 JSON tag 一一对应;policy字段可选——当且仅当 UI 提交了策略覆盖值时才会出现。
Go 侧对policy覆盖的处理在unpackContractPayload(task_handler.go#L271-L298):若载荷中存在policy键,则decodePolicy将其反序列化为compliance.Policy并整体替换进程默认策略;否则回落到启动时由InitPolicies加载的策略。此外它还会按contract→contract_details→ 平铺直传的顺序兼容三种历史载荷布局。这解释了为什么 ARCHITECTURE.md 强调 Python “只调用已配置的 Go Agent Card URL”,而策略灵活性完全通过报文内的 data part 传递。
4.2 响应:已完成 A2A Task 中的裁决
Go 返回一个已完成(completed)的 A2A Task,其状态消息携带 data part:
{ "passed": false, "violations": [ "Contract value $850000.00 exceeds company framework limit of $500000.00" ], "verdict_timestamp": "2026-06-03T00:00:00Z" }从completedTask(task_handler.go#L337-L368)源码可以看到响应 Task 的精确形态:role: "ROLE_AGENT"、state: "TASK_STATE_COMPLETED",裁决数据被封装成mediaType: "application/json"的 data part 放进status.message.parts。Python 侧的extract_verdict_from_go_response(fast_api_app.py#L122-L135)正是沿着result.status.message.parts逐 part 查找含data键的部分来还原裁决;找不到则抛出RuntimeError("Go compliance service returned no verdict data"),触发后文的 fail-closed 路径。
五、确定性策略引擎:checker.go 的六项审计
ARCHITECTURE.md 把策略检查定位在checker.go。源码中CheckCompliance(checker.go#L56-L107)按固定顺序执行以下检查,任一失败即追加到violations列表,最终passed = len(violations) == 0:
- 合同金额审计:
contract_value > max_contract_value→ “Contract value $%.2f exceeds company framework limit of $%.2f”; - 禁止条款(无限责任):当策略声明
unlimited liability时,若liability_limit文本(小写化后)包含unlimited或waived→ 违规; - 禁止条款(长周期自动续约):当策略声明
auto-renewal > 3yr且auto_renewal == true && term_length_years > 3→ 违规; - 保险下限:
insurance_coverage < required_insurance_minimum→ 违规; - 期限上限:
term_length_years > max_term_years→ 违规; - 退出条款:
required_termination_clause == true且has_termination_clause == false→ 违规。
verdict_timestamp使用time.Now().UTC().Format(time.RFC3339)生成(checker.go#L105),保证裁决带可追溯的时间戳。
默认策略阈值定义在 default_policy.json:
{ "max_contract_value": 500000.0, "prohibited_clauses": ["unlimited liability", "auto-renewal > 3yr"], "required_insurance_minimum": 1000000.0, "max_term_years": 5, "required_termination_clause": true }Go 服务通过命令行参数加载该文件,见 main.go#L18-L32:-port默认8888,-policy默认internal/policies/default_policy.json。若策略文件加载失败,InitPolicies(task_handler.go#L112-L126)会打印警告并回退到一套硬编码默认值,与上述 JSON 完全一致。路由注册顺序也有讲究:mux先注册/.well-known/agent.json再注册/,确保 Agent Card 端点优先于 JSON-RPC 根路由;绑定地址由HOST环境变量控制,默认0.0.0.0以适配容器部署。
Agent Card 本身由 card.go 的GetCard()构造:名称Security Compliance Validator、版本1.0.0、唯一接口为JSONRPC绑定(协议版本1.0),声明contract_compliance_check技能(tags: compliance / contract / validation),输入输出模式均为application/json。其 URL 可通过AGENT_URL环境变量覆盖,默认http://localhost:8888。
六、状态机与三态可见结果
ARCHITECTURE.md 将实时驾驶舱映射出的三种可见结果定义如下,必须原样继承:
| Outcome | Trigger |
|---|---|
APPROVED | Go 返回passed: true。 |
REVIEW_READY | Go 返回passed: false且带策略违规。 |
MANUAL_REVIEW | Go 不可用,或模拟器模式为Crashed (503)。 |
文档同时指出:state_schema.py 中更丰富的枚举仍保留着完整 ADK 参考路径所用的中间状态,但驾驶舱在健康路径下一次 API 调用即完成。源码印证了这一点——ComplianceStep枚举(state_schema.py#L18-L36)定义了 7 个状态:INGESTED → EXTRACTED → COMPLIANCE_PENDING → COMPLIANCE_COMPLETE → APPROVED / REVIEW_READY,外加超时/失败分支的MANUAL_REVIEW。而upload_contract_file在一次请求内同步走完抽取、交接、裁决并直接落到APPROVED或REVIEW_READY(fast_api_app.py#L500-L504),中间态只存在于agent.py的SequentialAgent参考实现中。
6.1 Fail-closed:交接失败如何降级
“Go 不可用”这一触发条件在源码中对应两层机制:
- 真实故障:
invoke_go_compliance_service抛出的ConnectionError、TimeoutError、urllib.error.URLError、RuntimeError被统一捕获,案件被置为ComplianceStep.MANUAL_REVIEW,并写入带 fail-safe 语义的裁决(“SYSTEM TIMEOUT... Document routed for legal manager manual verification”),trace 中追加resilience_fallback_gatespan(fast_api_app.py#L527-L552); - 模拟器注入:当表单字段
simulated_server_state == "crashed"时,代码主动raise ConnectionError("Simulated Go compliance service 503 failure")(fast_api_app.py#L478-L480),走与真实故障完全相同的路径——这就是 ARCHITECTURE.md 中“模拟器模式为Crashed (503)触发MANUAL_REVIEW”的底层实现。
也就是说该演示的降级策略是fail-closed:远端合规裁决不可得时,合同绝不自动放行,而是路由给人工审核。这一设计在合规场景下比 fail-open 更合理。
七、信任边界(Trust Boundaries)
ARCHITECTURE.md 按三个主体划定了信任边界,以下内容完整保留:
浏览器(Browser):
- 选择内置样例合同;
- 发送策略覆盖值;
- 从不直接调用 Go 服务。
Python 服务:
- 强制文件扩展名与 5MB 上传限制;
- 拒绝二进制 PDF 上传;内置的
.pdf文件其实是文本夹具; - 以 basename 与根边界检查解析样例与产物路径;
- 只调用已配置的 Go Agent Card URL;
- Go 交接失败时 fail-closed 到
MANUAL_REVIEW。
Go 服务:
- 在
/.well-known/agent.json提供 Agent Card; - 接受 JSON-RPC POST 请求;
- 处理当前
SendMessage及遗留tasks/send、tasks/get; - 应用来自
default_policy.json或请求策略覆盖的确定性策略规则。
这些边界在源码中的对应证据:upload_contract_file白名单校验扩展名(.pdf/.txt/.md)、将文件名改写为 UUID 防注入、abspath前缀做路径穿越检查、5MB 尺寸上限,并对以%PDF魔数开头的真正二进制 PDF 直接返回 400(fast_api_app.py#L383-L411);_secure_resolve_path(tools.py#L36-L55)则对..、反斜杠与绝对路径做 fail-close 拦截。Go 侧只接受 POST(task_handler.go#L134-L137),且策略来源仅限于启动参数文件与请求载荷内嵌的policy字段两个入口。
八、完整 ADK 参考路径与 Live 路径的分工
ARCHITECTURE.md 的 Key Files 表把python-extraction-agent/app/agent.py标注为“更完整的 ADKSequentialAgent参考”。其模块 docstring 明确了层级结构(agent.py#L15-L30):
SequentialAgent (coordinator) ├── Agent: extractor_agent — 解析合同,抽取关键法务字段(Gemini 模型) ├── RemoteA2aAgent: compliance_agent — 经 A2A 把字段发给 Go 合规服务 └── Agent: report_agent — 生成最终审计报告该参考路径中extractor_agent使用 Gemini 模型(而非正则)做抽取,需要 Google Cloud 凭证;而 Live 驾驶舱路径刻意选择“确定性抽取 + 一次聚焦RemoteA2aAgent调用”,在稳定可复现的前提下真实演练当前 A2ASendMessage协议。理解两条路径的分工,是避免误读本模块的关键:浏览器演示跑的是fast_api_app.py,agent.py仅作架构参考。
九、本地运行与验证方式
结合 README.md 的快速开始说明,验证上述架构的行为如下(路径均相对于agents/adk/contract-compliance-pipeline/):
# 终端 1:启动 Go A2A 合规智能体 cd go-compliance-agent go run cmd/server/main.go # 终端 2:启动 Python FastAPI 驾驶舱 cd python-extraction-agent uv sync uv run uvicorn app.fast_api_app:app --host 127.0.0.1 --port 8000打开http://127.0.0.1:8000/live-compliance/。该路径不需要 Gemini API key。也可用 Docker Compose 一键拉起(docker-compose.yml):
docker-compose up --build验证点建议按架构文档逐项核对:
- 手动冒烟:
curl http://127.0.0.1:8888/.well-known/agent.json应返回Security Compliance Validator的 Agent Card JSON; - Agent Exchange 面板出现
SendMessage报文,且 A2A 载荷包含jsonrpc: "2.0"; - Go 裁决在 UI 呈现,生成合规证书产物可渲染(产物端点
/api/compliance/cases/{case_id}/artifacts/{artifact_id}由 live_compliance.py 的artifact_response提供,产物写入local_artifacts/compliance/{case_id}且经过路径边界检查); - 将模拟器切换到
Crashed (503)后再运行,案件结果应为MANUAL_REVIEW。
单元测试覆盖:Python 侧uv run pytest tests/unit -v,Go 侧go test -v ./...(包含 checker_test.go 与 task_handler_test.go)。
十、关键文件索引
下表完整继承 ARCHITECTURE.md 的 Key Files 清单(路径已转换为仓库根相对路径):
| 文件 | 角色 |
|---|---|
| python-extraction-agent/app/static/live-compliance/index.html | 浏览器驾驶舱。 |
| python-extraction-agent/app/fast_api_app.py | API 路由、ADK 交接、案件响应。 |
| python-extraction-agent/app/tools.py | 确定性抽取与风险分类。 |
| python-extraction-agent/app/live_compliance.py | 案件状态、事件流、产物生成。 |
| python-extraction-agent/app/agent.py | 更完整的 ADKSequentialAgent参考。 |
| python-extraction-agent/app/state_schema.py | 案件状态机枚举。 |
| go-compliance-agent/internal/agentcard/card.go | Agent Card。 |
| go-compliance-agent/internal/handler/task_handler.go | A2A JSON-RPC 处理器。 |
| go-compliance-agent/internal/compliance/checker.go | 确定性策略检查器。 |
| go-compliance-agent/internal/policies/default_policy.json | 默认策略阈值。 |
| go-compliance-agent/cmd/server/main.go | Go 服务入口与路由注册。 |
十一、小结
这套合同合规流水线给出的工程范式可归纳为三点:其一,按可审计性划分智能体职责——抽取环节允许不确定性(演示中甚至用正则替代 LLM 换取可复现性),策略裁决环节则交由确定性代码;其二,用 A2A 协议做跨语言粘合剂——Agent Card 负责发现,JSON-RPCSendMessage负责调用,结构化 DataPart(schema_version+contract+ 可选policy)负责跨语言契约,策略覆盖通过载荷内嵌而非配置通道传递;其三,以 fail-closed 作为合规系统的底线语义——任何交接失败都收敛到MANUAL_REVIEW,绝不静默放行。若你需要在自己的 ADK 项目中编排非 LLM 的远端智能体,这套 “Agent Card 发现 + DataPart 载荷 + 请求拦截器注入任务元数据 + 内存会话 Runner” 的组合,是一个可以直接对照 fast_api_app.py 逐步复现的最小参考实现。
【免费下载链接】generative-aiSample code and notebooks for Generative AI on Google Cloud, with Gemini Enterprise Agent Platform项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考