469 lines
16 KiB
Plaintext
469 lines
16 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "7fb27b941602401d91542211134fc71a",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Google ADK Example: Human Approval Workflow with AgentOps"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "acae54e37e7d407bbb7b55eff062a284",
|
|
"metadata": {},
|
|
"source": [
|
|
"This notebook demonstrates a complete human approval workflow using the Google ADK (Agent Development Kit), integrated with AgentOps for observability.\n",
|
|
"\n",
|
|
"**Key Features:**\n",
|
|
"- **Sequential Agent Processing:** The workflow uses multiple agents chained together to handle different stages of the approval process.\n",
|
|
"- **External Tool Integration:** An agent interacts with an external tool that simulates (or in this version, directly prompts for) human approval.\n",
|
|
"- **Session State Management:** Information is passed between agents and persisted using session state.\n",
|
|
"- **AgentOps Observability:** All agent actions, tool calls, and LLM interactions are traced and can be viewed in your AgentOps dashboard.\n",
|
|
"- **Interactive Human Input:** The approval step now requires direct input from the user."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "9a63283cbaf04dbcab1f6479b197f3a8",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 1. Setup and Dependencies"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8dd0d8092fe74a7c96281538738b07e2",
|
|
"metadata": {},
|
|
"source": [
|
|
"First, let's install the necessary libraries if they are not already present and import them."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "72eea5119410473aa328ad9291626812",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%pip install google-adk agentops python-dotenv nest_asyncio asyncio"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"id": "8edb47106e1a46a883d545849b8ab81b",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import json\n",
|
|
"import os\n",
|
|
"import asyncio\n",
|
|
"from google.adk.agents import LlmAgent, SequentialAgent\n",
|
|
"from google.adk.tools import FunctionTool\n",
|
|
"from google.adk.runners import Runner\n",
|
|
"from google.adk.sessions import InMemorySessionService\n",
|
|
"from google.genai import types\n",
|
|
"from pydantic import BaseModel, Field\n",
|
|
"import nest_asyncio\n",
|
|
"import agentops\n",
|
|
"from dotenv import load_dotenv"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "10185d26023b46108eb7d9f57d49d2b3",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 2. Configuration and Initialization"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8763a12b2bbd4a93a75aff182afb95dc",
|
|
"metadata": {},
|
|
"source": [
|
|
"Load environment variables (especially `AGENTOPS_API_KEY` and your Google API key for Gemini) and initialize AgentOps."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "7623eae2785240b9bd12b16a66d81610",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Load environment variables from .env file\n",
|
|
"load_dotenv()\n",
|
|
"nest_asyncio.apply()\n",
|
|
"AGENTOPS_API_KEY = os.getenv(\"AGENTOPS_API_KEY\") or \"your_agentops_api_key_here\"\n",
|
|
"# Initialize AgentOps - Just 2 lines!\n",
|
|
"agentops.init(AGENTOPS_API_KEY, trace_name=\"adk-human-approval-notebook\", auto_start_session=False)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "7cdc8c89c7104fffa095e18ddfef8986",
|
|
"metadata": {},
|
|
"source": [
|
|
"Define some constants for our application."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "b118ea5561624da68c537baed56e602f",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"APP_NAME = \"human_approval_app_notebook\"\n",
|
|
"USER_ID = \"test_user_notebook_123\"\n",
|
|
"SESSION_ID = \"approval_session_notebook_456\"\n",
|
|
"MODEL_NAME = \"gemini-1.5-flash\"\n",
|
|
"agentops.start_trace(trace_name=APP_NAME, tags=[\"google_adk\", \"notebook\"])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "938c804e27f84196a10c8828c723f798",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 3. Define Schemas"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "504fb2a444614c0babb325280ed9130a",
|
|
"metadata": {},
|
|
"source": [
|
|
"Pydantic models are used to define the structure of data for approval requests and decisions. This helps with validation and clarity."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"id": "59bbdb311c014d738909a11f9e486628",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class ApprovalRequest(BaseModel):\n",
|
|
" amount: float = Field(description=\"The amount requiring approval\")\n",
|
|
" reason: str = Field(description=\"The reason for the request\")\n",
|
|
"\n",
|
|
"\n",
|
|
"class ApprovalDecision(BaseModel):\n",
|
|
" decision: str = Field(description=\"The approval decision: 'approved' or 'rejected'\")\n",
|
|
" comments: str = Field(description=\"Additional comments from the approver\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b43b363d81ae4b689946ece5c682cd59",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 4. External Approval Tool (with Human Interaction)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8a65eabff63a45729fe45fb5ade58bdc",
|
|
"metadata": {},
|
|
"source": [
|
|
"This tool now directly prompts the user for an approval decision. In a real-world scenario, this might involve sending a notification to an approver and waiting for their response through a UI or API."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"id": "c3933fab20d04ec698c2621248eb3be0",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"async def external_approval_tool(amount: float, reason: str) -> str:\n",
|
|
" \"\"\"\n",
|
|
" Prompts for human approval and returns the decision as a JSON string.\n",
|
|
" \"\"\"\n",
|
|
" print(\"🔔 HUMAN APPROVAL REQUIRED:\")\n",
|
|
" print(f\" Amount: ${amount:,.2f}\")\n",
|
|
" print(f\" Reason: {reason}\")\n",
|
|
" decision = \"\"\n",
|
|
" while decision.lower() not in [\"approved\", \"rejected\"]:\n",
|
|
" decision = input(\" Enter decision (approved/rejected): \").strip().lower()\n",
|
|
" if decision.lower() not in [\"approved\", \"rejected\"]:\n",
|
|
" print(\" Invalid input. Please enter 'approved' or 'rejected'.\")\n",
|
|
" comments = input(\" Enter comments (optional): \").strip()\n",
|
|
" print(f\" Decision: {decision.upper()}\")\n",
|
|
" print(f\" Comments: {comments if comments else 'N/A'}\")\n",
|
|
" return json.dumps({\"decision\": decision, \"comments\": comments, \"amount\": amount, \"reason\": reason})\n",
|
|
"\n",
|
|
"\n",
|
|
"# Create the approval tool instance\n",
|
|
"approval_tool = FunctionTool(func=external_approval_tool)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "4dd4641cc4064e0191573fe9c69df29b",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 5. Define Agents"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8309879909854d7188b41380fd92a7c3",
|
|
"metadata": {},
|
|
"source": [
|
|
"We define three agents for our workflow:\n",
|
|
"1. **`PrepareApprovalAgent`**: Extracts details from the user's request.\n",
|
|
"2. **`RequestHumanApprovalAgent`**: Uses the `external_approval_tool` to get a decision.\n",
|
|
"3. **`ProcessDecisionAgent`**: Processes the decision and formulates a final response."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"id": "3ed186c9a28b402fb0bc4494df01f08d",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Agent 1: Prepare the approval request\n",
|
|
"prepare_request = LlmAgent(\n",
|
|
" model=MODEL_NAME,\n",
|
|
" name=\"PrepareApprovalAgent\",\n",
|
|
" description=\"Extracts and prepares approval request details from user input\",\n",
|
|
" instruction=\"\"\"You are an approval request preparation agent.\n",
|
|
" Your task:\n",
|
|
" 1. Extract the amount and reason from the user's request\n",
|
|
" 2. Store these values in the session state with keys 'approval_amount' and 'approval_reason'\n",
|
|
" 3. Validate that both amount and reason are provided\n",
|
|
" 4. Respond with a summary of what will be submitted for approval\n",
|
|
" If the user input is missing amount or reason, ask for clarification.\n",
|
|
" \"\"\",\n",
|
|
" output_key=\"request_prepared\",\n",
|
|
")\n",
|
|
"\n",
|
|
"# Agent 2: Request human approval using the tool\n",
|
|
"request_approval = LlmAgent(\n",
|
|
" model=MODEL_NAME,\n",
|
|
" name=\"RequestHumanApprovalAgent\",\n",
|
|
" description=\"Calls the external approval system with prepared request details\",\n",
|
|
" instruction=\"\"\"You are a human approval request agent.\n",
|
|
" Your task:\n",
|
|
" 1. Get the 'approval_amount' and 'approval_reason' from the session state\n",
|
|
" 2. Use the external_approval_tool with these values\n",
|
|
" 3. Store the approval decision in session state with key 'human_decision'\n",
|
|
" 4. Respond with the approval status\n",
|
|
" Always use the exact values from the session state for the tool call.\n",
|
|
" \"\"\",\n",
|
|
" tools=[approval_tool],\n",
|
|
" output_key=\"approval_requested\",\n",
|
|
")\n",
|
|
"\n",
|
|
"# Agent 3: Process the approval decision\n",
|
|
"process_decision = LlmAgent(\n",
|
|
" model=MODEL_NAME,\n",
|
|
" name=\"ProcessDecisionAgent\",\n",
|
|
" description=\"Processes the human approval decision and provides final response\",\n",
|
|
" instruction=\"\"\"You are a decision processing agent.\n",
|
|
" Your task:\n",
|
|
" 1. Check the 'human_decision' from session state\n",
|
|
" 2. Parse the approval decision JSON\n",
|
|
" 3. If approved: congratulate and provide next steps\n",
|
|
" 4. If rejected: explain the rejection and suggest alternatives\n",
|
|
" 5. Provide a clear, helpful final response to the user\n",
|
|
"\n",
|
|
" Be professional and helpful in your response.\n",
|
|
" \"\"\",\n",
|
|
" output_key=\"final_decision\",\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cb1e1581032b452c9409d6c6813c49d1",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 6. Create Sequential Workflow"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "379cbbc1e968416e875cc15c1202d7eb",
|
|
"metadata": {},
|
|
"source": [
|
|
"Combine the agents into a sequential workflow. The `SequentialAgent` ensures that the sub-agents are executed in the specified order."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"id": "277c27b1587741f2af2001be3712ef0d",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"approval_workflow = SequentialAgent(\n",
|
|
" name=\"HumanApprovalWorkflowNotebook\",\n",
|
|
" description=\"Complete workflow for processing approval requests with human oversight\",\n",
|
|
" sub_agents=[prepare_request, request_approval, process_decision],\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "db7b79bc585a40fcaf58bf750017e135",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 7. Session Management and Runner"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "916684f9a58a4a2aa5f864670399430d",
|
|
"metadata": {},
|
|
"source": [
|
|
"Set up an in-memory session service and the workflow runner."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"id": "1671c31a24314836a5b85d7ef7fbf015",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"session_service = InMemorySessionService()\n",
|
|
"# Create runner\n",
|
|
"workflow_runner = Runner(agent=approval_workflow, app_name=APP_NAME, session_service=session_service)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "33b0902fd34d4ace834912fa1002cf8e",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 8. Helper Function to Run Workflow"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "f6fa52606d8c4a75a9b52967216f8f3f",
|
|
"metadata": {},
|
|
"source": [
|
|
"This function encapsulates the logic to run the workflow for a given user request and session ID."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"id": "f5a1fa73e5044315a093ec459c9be902",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"async def run_approval_workflow_notebook(user_request: str, session_id: str):\n",
|
|
" \"\"\"Run the complete approval workflow with a user request in the notebook environment\"\"\"\n",
|
|
" print(f\"{'=' * 60}\")\n",
|
|
" print(f\" Starting Approval Workflow for Session: {session_id}\")\n",
|
|
" print(f\"{'=' * 60}\")\n",
|
|
" print(f\"User Request: {user_request}\")\n",
|
|
" # Create user message\n",
|
|
" user_content = types.Content(role=\"user\", parts=[types.Part(text=user_request)])\n",
|
|
" step_count = 0\n",
|
|
" final_response = \"No response received\"\n",
|
|
" # Run the workflow\n",
|
|
" async for event in workflow_runner.run_async(\n",
|
|
" user_id=USER_ID,\n",
|
|
" session_id=session_id,\n",
|
|
" new_message=user_content,\n",
|
|
" ):\n",
|
|
" if event.author and event.content:\n",
|
|
" step_count += 1\n",
|
|
" print(f\"📋 Step {step_count} - {event.author}:\")\n",
|
|
" if event.content.parts:\n",
|
|
" response_text = event.content.parts[0].text\n",
|
|
" print(f\" {response_text}\")\n",
|
|
" if event.is_final_response():\n",
|
|
" final_response = response_text\n",
|
|
" session = await session_service.get_session(\n",
|
|
" app_name=APP_NAME,\n",
|
|
" user_id=USER_ID,\n",
|
|
" session_id=session_id,\n",
|
|
" )\n",
|
|
" print(f\"{'=' * 60}\")\n",
|
|
" print(f\"📊 Workflow Complete - Session State ({session_id}):\")\n",
|
|
" print(f\"{'=' * 60}\")\n",
|
|
" for key, value in session.state.items():\n",
|
|
" print(f\" {key}: {value}\")\n",
|
|
" print(f\"🎯 Final Response: {final_response}\")\n",
|
|
" return final_response"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cdf66aed5cc84ca1b48e60bad68798a8",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 9. Main Execution Logic"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "28d3efd5258a48a79c179ea5c6759f01",
|
|
"metadata": {},
|
|
"source": [
|
|
"This cell contains the main logic to run the workflow with a few test cases. Each test case will run in its own session."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "3f9bc0b9dd2c44919cc8dcca39b469f8",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"async def main_notebook():\n",
|
|
" test_requests = [\n",
|
|
" \"I need approval for $750 for team lunch and celebrations\",\n",
|
|
" \"Please approve $3,000 for a conference ticket and travel expenses\",\n",
|
|
" \"I need $12,000 approved for critical software licenses renewal\",\n",
|
|
" ]\n",
|
|
" for i, request in enumerate(test_requests, 1):\n",
|
|
" current_session_id = f\"approval_session_notebook_{456 + i - 1}\"\n",
|
|
" # Create the session before running the workflow\n",
|
|
" await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=current_session_id)\n",
|
|
" print(f\"Created session: {current_session_id}\")\n",
|
|
" await run_approval_workflow_notebook(request, current_session_id)\n",
|
|
"\n",
|
|
"\n",
|
|
"try:\n",
|
|
" asyncio.run(main_notebook())\n",
|
|
" agentops.end_trace(end_state=\"Success\")\n",
|
|
"except Exception as e:\n",
|
|
" print(f\"Error: {e}\")\n",
|
|
" agentops.end_trace(end_state=\"Error\")"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": ".venv",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.11.12"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|