chore: import upstream snapshot with attribution
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:13 +08:00
commit ec2b666284
2231 changed files with 491535 additions and 0 deletions
@@ -0,0 +1,195 @@
# A2A Human-in-the-Loop Sample Agent
This sample demonstrates the **Agent-to-Agent (A2A)** architecture with **Human-in-the-Loop** workflows in the Agent Development Kit (ADK). The sample implements a reimbursement processing agent that automatically handles small expenses while requiring remote agent to process for larger amounts. The remote agent will require a human approval for large amounts, thus surface this request to local agent and human interacting with local agent can approve the request.
## Overview
The A2A Human-in-the-Loop sample consists of:
- **Root Agent** (`root_agent`): The main reimbursement agent that handles expense requests and delegates approval to remote Approval Agent for large amounts
- **Approval Agent** (`approval_agent`): A remote A2A agent that handles the human approval process via long-running tools (which implements asynchronous approval workflows that can pause execution and wait for human input), this agent is running on a separate A2A server
## Architecture
```
┌─────────────────┐ ┌────────────────────┐ ┌──────────────────┐
│ Human Manager │───▶│ Root Agent │───▶│ Approval Agent │
│ (External) │ │ (Local) │ │ (Remote A2A) │
│ │ │ │ │ (localhost:8001) │
│ Approval UI │◀───│ │◀───│ │
└─────────────────┘ └────────────────────┘ └──────────────────┘
```
## Key Features
### 1. **Automated Decision Making**
- Automatically approves reimbursements under $100
- Uses business logic to determine when human intervention is required
- Provides immediate responses for simple cases
### 2. **Human-in-the-Loop Workflow**
- Seamlessly escalates high-value requests (>$100) to remote approval agent
- Remote approval agent uses long-running tools to surface approval requests back to the root agent
- Human managers interact directly with the root agent to approve/reject requests
### 3. **Long-Running Tool Integration**
- Demonstrates `LongRunningFunctionTool` for asynchronous operations
- Shows how to handle pending states and external updates
- Implements proper tool response handling for delayed approvals
### 4. **Remote A2A Agent Communication**
- The approval agent runs as a separate service that processes approval workflows
- Communicates via HTTP at `http://localhost:8001/a2a/human_in_loop`
- Surfaces approval requests back to the root agent for human interaction
## Setup and Usage
### Prerequisites
1. **Start the Remote Approval Agent server**:
```bash
# Start the remote a2a server that serves the human-in-the-loop approval agent on port 8001
adk api_server --a2a --port 8001 contributing/samples/a2a/a2a_human_in_loop/remote_a2a
```
1. **Run the Main Agent**:
```bash
# In a separate terminal, run the adk web server
adk web contributing/samples/a2a
```
### Example Interactions
Once both services are running, you can interact with the root agent through the approval workflow:
**Automatic Approval (Under $100):**
```
User: Please reimburse $50 for meals
Agent: I'll process your reimbursement request for $50 for meals. Since this amount is under $100, I can approve it automatically.
Agent: ✅ Reimbursement approved and processed: $50 for meals
```
**Human Approval Required (Over $100):**
```
User: Please reimburse $200 for conference travel
Agent: I'll process your reimbursement request for $200 for conference travel. Since this amount exceeds $100, I need to get manager approval.
Agent: 🔄 Request submitted for approval (Ticket: reimbursement-ticket-001). Please wait for manager review.
[Human manager approves the pending request from the ADK Web UI]
Agent: ✅ Great news! Your reimbursement has been approved by the manager. Processing $200 for conference travel.
```
> **Approving from the ADK Web UI:** The approval is a *long-running tool* call
> that runs on the remote approval agent. The pending call is surfaced in the
> Web UI as a function call awaiting a response. To approve (or reject), hover
> over the pending `ask_for_approval` function response in the UI and use
> **"Send another response"** to send back an updated response such as
> `{"status": "approved", "ticketId": "reimbursement-ticket-001"}`. Simply
> typing "I approve" as a chat message will **not** resume the pending request,
> because the framework needs a `FunctionResponse` that carries the same call
> `id` to resume the long-running tool.
>
> For this resume to be routed back to the remote approval agent (rather than
> restarting at the root agent), the sample is exposed as an `App` with
> `ResumabilityConfig(is_resumable=True)` in `agent.py`.
## Code Structure
### Main Agent (`agent.py`)
- **`reimburse(purpose: str, amount: float)`**: Function tool for processing reimbursements
- **`approval_agent`**: Remote A2A agent configuration for human approval workflows
- **`root_agent`**: Main reimbursement agent with automatic/manual approval logic
### Remote Approval Agent (`remote_a2a/human_in_loop/`)
- **`agent.py`**: Implementation of the approval agent with long-running tools
- **`agent.json`**: Agent card of the A2A agent
- **`ask_for_approval()`**: Long-running tool that handles approval requests
## Long-Running Tool Workflow
The human-in-the-loop process follows this pattern:
1. **Initial Call**: Root agent delegates approval request to remote approval agent for amounts >$100
1. **Pending Response**: Remote approval agent returns immediate response with `status: "pending"` and ticket ID and surface the approval request to root agent
1. **Agent Acknowledgment**: Root agent informs user about pending approval status
1. **Human Interaction**: Human manager interacts with root agent to review and approve/reject the request
1. **Updated Response**: Root agent receives updated tool response with approval decision and send it to remote agent
1. **Final Action**: Remote agent processes the approval and completes the reimbursement and send the result to root_agent
## Extending the Sample
You can extend this sample by:
- Adding more complex approval hierarchies (multiple approval levels)
- Implementing different approval rules based on expense categories
- Creating additional remote agent for budget checking or policy validation
- Adding notification systems for approval status updates
- Integrating with external approval systems or databases
- Implementing approval timeouts and escalation procedures
## Deployment to Other Environments
When deploying the remote approval A2A agent to different environments (e.g., Cloud Run, different hosts/ports), you **must** update the `url` field in the agent card JSON file:
### Local Development
```json
{
"url": "http://localhost:8001/a2a/human_in_loop",
...
}
```
### Cloud Run Example
```json
{
"url": "https://your-approval-service-abc123-uc.a.run.app/a2a/human_in_loop",
...
}
```
### Custom Host/Port Example
```json
{
"url": "https://your-domain.com:9000/a2a/human_in_loop",
...
}
```
**Important:** The `url` field in `remote_a2a/human_in_loop/agent.json` must point to the actual RPC endpoint where your remote approval A2A agent is deployed and accessible.
## Troubleshooting
**Connection Issues:**
- Ensure the local ADK web server is running on port 8000
- Ensure the remote A2A server is running on port 8001
- Check that no firewall is blocking localhost connections
- **Verify the `url` field in `remote_a2a/human_in_loop/agent.json` matches the actual deployed location of your remote A2A server**
- Verify the agent card URL passed to RemoteA2AAgent constructor matches the running A2A server
**Agent Not Responding:**
- Check the logs for both the local ADK web server on port 8000 and remote A2A server on port 8001
- Verify the agent instructions are clear and unambiguous
- Ensure long-running tool responses are properly formatted with matching IDs
- **Double-check that the RPC URL in the agent.json file is correct and accessible**
**Approval Workflow Issues:**
- Verify that updated tool responses use the same `id` and `name` as the original function call
- Check that the approval status is correctly updated in the tool response
- Ensure the human approval process is properly simulated or integrated
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
@@ -0,0 +1,68 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from google.adk.agents.llm_agent import Agent
from google.adk.agents.remote_a2a_agent import AGENT_CARD_WELL_KNOWN_PATH
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
from google.adk.apps import App
from google.adk.apps import ResumabilityConfig
from google.genai import types
def reimburse(purpose: str, amount: float) -> str:
"""Reimburse the amount of money to the employee."""
return {
'status': 'ok',
}
approval_agent = RemoteA2aAgent(
name='approval_agent',
description='Help approve the reimburse if the amount is greater than 100.',
agent_card=(
f'http://localhost:8001/a2a/human_in_loop{AGENT_CARD_WELL_KNOWN_PATH}'
),
)
root_agent = Agent(
name='reimbursement_agent',
instruction="""
You are an agent whose job is to handle the reimbursement process for
the employees. If the amount is less than $100, you will automatically
approve the reimbursement. And call reimburse() to reimburse the amount to the employee.
If the amount is greater than $100. You will hand over the request to
approval_agent to handle the reimburse.
""",
tools=[reimburse],
sub_agents=[approval_agent],
generate_content_config=types.GenerateContentConfig(temperature=0.1),
)
# The human-in-the-loop approval runs as a long-running tool on the remote
# approval_agent. When the manager approves (or rejects) the request, the ADK
# Web UI sends back a FunctionResponse for that pending long-running call. For
# the next turn to be routed back to the (remote) approval_agent so it can
# resume the paused tool instead of restarting at the root reimbursement_agent,
# the app must be resumable. Without this, the confirmation is delivered to the
# root agent, which has no pending call, and nothing happens (see issue #5871).
app = App(
name='a2a_human_in_loop',
root_agent=root_agent,
resumability_config=ResumabilityConfig(
is_resumable=True,
),
)
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
@@ -0,0 +1,45 @@
{
"capabilities": {},
"defaultInputModes": [
"text/plain"
],
"defaultOutputModes": [
"application/json"
],
"description": "A reimbursement agent that handles employee expense reimbursement requests. Automatically approves amounts under $100 and requires manager approval for larger amounts using long-running tools for human-in-the-loop workflows.",
"name": "reimbursement_agent",
"skills": [
{
"id": "automatic_reimbursement",
"name": "Automatic Reimbursement",
"description": "Automatically process and approve reimbursements under $100",
"tags": [
"reimbursement",
"automation",
"finance"
]
},
{
"id": "approval_workflow",
"name": "Approval Workflow",
"description": "Request manager approval for reimbursements over $100 using long-running tools",
"tags": [
"approval",
"workflow",
"human-in-loop"
]
},
{
"id": "expense_processing",
"name": "Expense Processing",
"description": "Process employee expense claims and handle reimbursement logic",
"tags": [
"expenses",
"processing",
"employee-services"
]
}
],
"url": "http://localhost:8001/a2a/human_in_loop",
"version": "1.0.0"
}
@@ -0,0 +1,55 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Any
from google.adk import Agent
from google.adk.tools.long_running_tool import LongRunningFunctionTool
from google.adk.tools.tool_context import ToolContext
from google.genai import types
def reimburse(purpose: str, amount: float) -> str:
"""Reimburse the amount of money to the employee."""
return {
'status': 'ok',
}
def ask_for_approval(
purpose: str, amount: float, tool_context: ToolContext
) -> dict[str, Any]:
"""Ask for approval for the reimbursement."""
return {
'status': 'pending',
'amount': amount,
'ticketId': 'reimbursement-ticket-001',
}
root_agent = Agent(
name='reimbursement_agent',
instruction="""
You are an agent whose job is to handle the reimbursement process for
the employees. If the amount is less than $100, you will automatically
approve the reimbursement.
If the amount is greater than $100, you will
ask for approval from the manager. If the manager approves, you will
call reimburse() to reimburse the amount to the employee. If the manager
rejects, you will inform the employee of the rejection.
""",
tools=[reimburse, LongRunningFunctionTool(func=ask_for_approval)],
generate_content_config=types.GenerateContentConfig(temperature=0.1),
)