Compare commits

..

65 Commits

Author SHA1 Message Date
Ankur Sharma 4ae4c69c32 fix: ModuleNotFound error should be caught when dependencies of LocalEvalService are not installed
Once caught we publish the clarifying message to install the extra.

PiperOrigin-RevId: 785999033
2025-07-22 14:32:02 -07:00
Xiang (Sean) Zhou 32df937ebc chore: Update docstring for is_final_response method
PiperOrigin-RevId: 785983329
2025-07-22 13:50:30 -07:00
Yifan Wang 702e9bf556 chore: update adk web
PiperOrigin-RevId: 785972516
2025-07-22 13:22:34 -07:00
Ankur Sharma ff31f57dc9 fix: Move some API request and responses to DEBUG logging level
Please set --log_level to DEBUG, if you are interested in having those API request and responses in logs.

NOTE: Generally it is not recommended to have DEBUG log level for services that run in a production setting. It is our recommendation to only use DEBUG log level in a debug or development setting.
PiperOrigin-RevId: 785972338
2025-07-22 13:22:18 -07:00
Max Thormé d4f01afc15 fix(comment): fix comment to malicious user cannot obtain another user’s session
Merge https://github.com/google/adk-python/pull/1959

### What

Fix misleading comment.

```diff
- # Make sure a malicious user can obtain a session and events not belonging to them
+ # Make sure a malicious user **cannot** obtain a session or events not belonging to them
```

### Why

The previous wording contradicted the assertion `assert len(session_mismatch.events) == 0`, which verifies that a malicious user **cannot** access another user’s session or events.

### Testing plan

Docs-only change.

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/1959 from mthorme:fix-comment-session-mismatch b1f139af340bd240d40ed58f5dea3274c3a3bd83
PiperOrigin-RevId: 785908548
2025-07-22 10:33:57 -07:00
Shangjie Chen f46396fa98 chore: Allow httpoptions override in VertexAiSessionService
PiperOrigin-RevId: 785886180
2025-07-22 09:36:17 -07:00
Google Team Member 13ff009d34 fix: Handle non-json-serializable values in the execute_sql tool
This change takes cares of SQL results containing values that are not json serializable (e.g. datetime, bignumeric) by converting them to their string representation.

PiperOrigin-RevId: 785719997
2025-07-21 23:17:06 -07:00
Jacky Wu 6cc3d9ddd1 chore: follow-up #946 to allow session db kwargs in fast api entrance
Merge https://github.com/google/adk-python/pull/1042

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/1042 from Colstuwjx:chore/expose-database-args-in-fastapi 83a20cf400a8a712c57cb3574fdd654ab5d8d4d6
PiperOrigin-RevId: 785714547
2025-07-21 22:52:20 -07:00
Xiang (Sean) Zhou 083dcb4465 feat: Add ComputerUseToolset
PiperOrigin-RevId: 785646071
2025-07-21 18:08:35 -07:00
Xiang (Sean) Zhou b2c2f1bd33 chore: Add an a2a sample agent to demo running the a2a server via unvicorn command and run remote agent as root_agent
PiperOrigin-RevId: 785629271
2025-07-21 17:08:35 -07:00
Xiang (Sean) Zhou a77d68964a feat: Add an to_a2a util to convert adk agent to A2A ASGI application
Users can do :
```
a2a_app = to_a2a(root_agent)
```
then use below command to start a2a server:
```
uvicorn user_module:a2a_app --host localhost --port 8000
```

PiperOrigin-RevId: 785625048
2025-07-21 16:54:53 -07:00
Ankur Sharma d1f182e8e6 feat: Use LocalEvalService to run all evals in cli and web
We update both adk web run eval endpoint and adk eval cli to use the LocalService. The old method is marked as deprecated and will be removed in later PRs.

PiperOrigin-RevId: 785612708
2025-07-21 16:15:11 -07:00
Ariz Chang 0e173d7363 feat: Add camel case converter for agents
PiperOrigin-RevId: 785602954
2025-07-21 15:45:24 -07:00
Xiang (Sean) Zhou 18f5bea411 feat: Add agent card builder
PiperOrigin-RevId: 785565665
2025-07-21 13:59:02 -07:00
Jianfeng Zeng f2caf2eeca fix: Add buffer to the write file option
PiperOrigin-RevId: 785546915
2025-07-21 13:08:29 -07:00
Liang Wu dfee06ac06 feat(config): support input/output schema by fully-qualified code reference
PiperOrigin-RevId: 785541326
2025-07-21 12:53:13 -07:00
Wei Sun (Jack) 2aab1cf98e fix: allows current sub-agent to finish execution before exiting the loop agent due to a sub-agent's escalation. This commit also disables the summarization for exit_loop tool
Fixes #423

Related to #1670

- This avoids the `GeneratorExit` error thrown, which would crash OTel metric collection and cause `Failed to detach context` error.
- This also allows all function  calls are processed when exit_loop is called together with other tools in the same LLmResponse.

A sample agent for testing:

```
from google.adk import Agent
from google.adk.agents.loop_agent import LoopAgent
from google.adk.tools.exit_loop_tool import exit_loop

worker_1 = Agent(
    name='worker_1',
    description='Worker 1',
    instruction="""\
Just say job #1 is done.

If job #1 is said to be done. Call exit_loop tool.""",
    tools=[exit_loop],
)

worker_2 = Agent(
    name='worker_2',
    description='Worker 2',
    instruction="""\
Just say job #2 is done.

If job #2 is said to be done. Call exit_loop tool.""",
    tools=[exit_loop],
)

work_agent = LoopAgent(
    name='work_agent',
    description='Do all work.',
    sub_agents=[worker_1, worker_2],
    max_iterations=5,
)

root_agent = Agent(
    model='gemini-2.0-flash',
    name='hello_world_agent',
    description='hello world agent that can roll a  check prime',
    instruction="""Hand off works to sub agents.""",
    sub_agents=[work_agent],
)
```
PiperOrigin-RevId: 785538101
2025-07-21 12:43:19 -07:00
Xuan Yang 96a0d4b02c chore: undo the workaround which bypassed the setup-uv bug
Bug https://github.com/astral-sh/setup-uv/issues/489 has been fixed.

PiperOrigin-RevId: 785529944
2025-07-21 12:20:02 -07:00
Ankur Sharma 65cb6d6bf3 fix: Check that mean_score is a valid float value
In some cases, Vertex AI evaluation returns nan values for the metrics. That was not handled correctly.

PiperOrigin-RevId: 785514456
2025-07-21 11:36:55 -07:00
Ankur Sharma fc85348f91 fix: add utf-8 encoding to file based reads and writes of eval data
This will help manage encoding consistency between reads and writes.

PiperOrigin-RevId: 785503746
2025-07-21 11:09:45 -07:00
Xiang (Sean) Zhou 9467720919 chore: Remove unused import from hello world sample agent
PiperOrigin-RevId: 785501232
2025-07-21 11:03:03 -07:00
Xiang (Sean) Zhou ee3918e34e chore: Update the error message when A2A is not supported for the installed python version
PiperOrigin-RevId: 785490858
2025-07-21 10:37:33 -07:00
Vasilii Novikov 81e0d4083f feat: make GCS artifact service async under the hood
Merge https://github.com/google/adk-python/pull/1347

Fixes issue https://github.com/google/adk-python/issues/1346

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/1347 from condorcet:async_gcs_artifact_storage 3efee5a923b08af606aa5525e31816e0bc7b865d
PiperOrigin-RevId: 785488472
2025-07-21 10:31:41 -07:00
Dana K. Williams 2486349268 docs: Fix missing toolbox-core dependency and improve installation guide
Merge https://github.com/google/adk-python/pull/1195

## Summary
Updated the Toolbox Agent documentation to address a critical missing dependency that prevents the agent from running successfully.

## Changes Made
- **Added missing dependency**: Documented that `toolbox-core` must be installed via `pip install toolbox-core`
- **Improved documentation structure**: Added clear section numbering and better organization
- **Enhanced readability**: Fixed grammar, capitalization, and formatting throughout
- **Added Prerequisites section**: Set clear expectations before installation begins
- **Clarified optional steps**: Made it clearer when database creation can be skipped

## Problem Solved
The original documentation was missing a crucial step - installing the `toolbox-core` package. Without this dependency, users encounter an `ImportError: No module named 'toolbox-core'` when trying to use the `ToolboxToolset` class in ADK. This fix ensures users can successfully set up and run the agent without encountering import errors.

## Testing
- Verified the installation steps work correctly with the added dependency
- Confirmed the agent runs successfully after following the updated documentation

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/1195 from designcomputer:patch-1 b90c71fe95aa09a3dca069e91f14791f557ab2e3
PiperOrigin-RevId: 785487495
2025-07-21 10:29:33 -07:00
Xiang (Sean) Zhou 3643b4ae19 feat: Allow toolset to process llm_request before tools returned by it
PiperOrigin-RevId: 785480813
2025-07-21 10:11:40 -07:00
Xiang (Sean) Zhou cec400ada3 chore: Autoformat history_management agent codes
PiperOrigin-RevId: 785003632
2025-07-19 17:51:54 -07:00
Google Team Member d2461ecccb refactor: only import some module when needed and add follow_symlink=True when serve static files
PiperOrigin-RevId: 784956132
2025-07-19 12:54:05 -07:00
Calvin Giles ffe2bdbe4c feat: Add support for vertex gemini model optimizer
Merge https://github.com/google/adk-python/pull/1130

This enables the use of the `model-optimizer-*` family of models in vertex, as per the [documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/vertex-ai-model-optimizer#using-vertex-ai-model-optimizer).

To use this, ensure your location is set to `global` and pass a model optimizer model to an agent:

```python
root_agent = Agent(
    model="model-optimizer-exp-04-09",
    name="fast_and_slow_agent",
    instruction="Answer any question the user gives you - easy or hard.",
    generate_content_config=types.GenerateContentConfig(
        temperature=0.01,
        model_selection_config=ModelSelectionConfig(
            feature_selection_preference=FeatureSelectionPreference.BALANCED
            # Options: PRIORITIZE_QUALITY, BALANCED, PRIORITIZE_COST
        )
    ),
)
```
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/1130 from calvingiles:feat-model-optimizer 1a76bfa22420edb07d83415dcea6dd0114084e8e
PiperOrigin-RevId: 784921913
2025-07-19 09:05:17 -07:00
Calvin Giles 67284fc466 feat: History Management Sample
Merge https://github.com/google/adk-python/pull/891

This creates a sample relating to discussion #826 - how to manage context windows.

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/891 from calvingiles:history-management-sample 2827817bea8d96ea0eee7e3ba84c14bd1fe4286d
PiperOrigin-RevId: 784920438
2025-07-19 08:57:11 -07:00
Hangfei Lin 0ec69d05a4 feat: Enhance LangchainTool to accept more forms of functions
Now the LangchainTool can wrap:

* Langchain StructuredTool (sync and async).
* Langchain @Tool (sync and async).

This enhance the flexibility for user and enables async functionalities.

PiperOrigin-RevId: 784728061
2025-07-18 15:56:17 -07:00
Hangfei Lin f1e0bc0b18 chore: remove pr-commit-check
With this mode, the PR commit can be squashed before copybara. So we can delete the pr-commit-check.yml.

PiperOrigin-RevId: 784665025
2025-07-18 12:26:46 -07:00
Liang Wu 5edc493da9 chore: allow "from google.adk.tools import AgentTool"
PiperOrigin-RevId: 784606041
2025-07-18 09:30:40 -07:00
Copybara-Service 3568c9291d Merge pull request #547 from jeffreyrubi:fix/missing-path-level-parameters
PiperOrigin-RevId: 784415343
2025-07-17 20:27:46 -07:00
Copybara-Service d0a330cd15 Merge pull request #2020 from thiagosalvatore:main
PiperOrigin-RevId: 784393522
2025-07-17 18:50:46 -07:00
seanzhou1023 637fa410d8 Merge branch 'main' into main 2025-07-17 18:23:32 -07:00
Sean Zhou 6f016609e8 fix: support path level parameters for open_api_spec_parser
autoformat the changes
2025-07-17 18:09:40 -07:00
Hangfei Lin bb1b1c695f Merge branch 'main' into fix/missing-path-level-parameters 2025-07-17 17:46:27 -07:00
Wei Sun (Jack) bb4ff2cc3d chore: fixes formatting in adk_project_overview_and_architecture.md
PiperOrigin-RevId: 784371131
2025-07-17 17:21:14 -07:00
Wei Sun (Jack) b1f4aebb25 chore: lint fixings in vertex_ai_memory_bank.py
* Adds type hints;
* Switches to lazy evaluation in logs.

PiperOrigin-RevId: 784353566
2025-07-17 16:21:49 -07:00
Shangjie Chen 2e778049d0 feat: Support passing fully qualified agent engine resource name when constructing session service and memory service
Resolves https://github.com/google/adk-python/issues/1760

PiperOrigin-RevId: 784353411
2025-07-17 16:19:55 -07:00
Ankur Sharma 36e45cdab3 feat: Enable FinalResponseMatchV2 metric as an experiment
PiperOrigin-RevId: 784346859
2025-07-17 15:59:47 -07:00
Xuan Yang 35de210d4e chore: add a script to upload ADK docs for the ADK Answering Agent
PiperOrigin-RevId: 784343501
2025-07-17 15:49:44 -07:00
Xuan Yang bbe1c9dc66 fix: specify version into the uv installation action to bypass the setup-uv bug
https://github.com/astral-sh/setup-uv/issues/489

PiperOrigin-RevId: 784339143
2025-07-17 15:35:15 -07:00
Thiago Salvatore 9ad4350c88 Merge branch 'main' of github.com:thiagosalvatore/adk-python 2025-07-17 18:21:59 -03:00
Thiago Salvatore 2f8bb91e6b add docstring 2025-07-17 18:21:40 -03:00
Thiago Salvatore 3f01c8c999 Merge branch 'main' into main 2025-07-17 18:20:37 -03:00
Thiago Salvatore b5850e0757 reformat file structure 2025-07-17 18:17:09 -03:00
Thiago Salvatore 53df35ee58 fix(schema to dict): fix serialization of tools with nested schema 2025-07-17 18:16:17 -03:00
Xuan Yang 6e68c2d7f3 chore: update ADK Triage Agent to set issue type
It will change bug report to "Bug" type and feature request to "Feature" type.

PiperOrigin-RevId: 784280674
2025-07-17 12:51:41 -07:00
Xuan Yang 8ada46a18e chore: remove the redundant service.bucket setting in mock_gcs_artifact_service
PiperOrigin-RevId: 784253138
2025-07-17 11:29:32 -07:00
Xuan Yang 1c4c887bec fix: use the same word extractor for query and event contents in InMemoryMemoryService
PiperOrigin-RevId: 784236637
2025-07-17 10:43:01 -07:00
Xuan Yang 78697aa6af chore: add to_session method in StorageSession for readability
PiperOrigin-RevId: 784232578
2025-07-17 10:32:18 -07:00
Ankur Sharma b17d8b6e36 fix: Raise NotFoundError in list_eval_sets function when app_name doesn't exist
PiperOrigin-RevId: 784216832
2025-07-17 09:53:26 -07:00
Xiang (Sean) Zhou 377b5a9b78 fix: Add response schema for agent tool function declaration even when it's return None
PiperOrigin-RevId: 784216811
2025-07-17 09:51:34 -07:00
Xiang (Sean) Zhou 33ac8380ad fix: Set response schema for function that returns None
PiperOrigin-RevId: 784053725
2025-07-16 23:59:45 -07:00
Jianfeng Zeng 31fa5d91bd feat: Update builder/save endpoint to accept files as input. Write the agent yaml file to the agent folder
PiperOrigin-RevId: 783963934
2025-07-16 17:55:55 -07:00
Xuan Yang b705b35977 fix: update documentation owner's Github username for the ADK Triage Agent
PiperOrigin-RevId: 783951115
2025-07-16 17:10:25 -07:00
Shangjie Chen eccb484985 chore: Update label name for bot triaged issues
Also assign agent engine related issue to yeesian as poc

PiperOrigin-RevId: 783941809
2025-07-16 16:39:25 -07:00
Hangfei Lin aabfde56e4 Merge branch 'main' into fix/missing-path-level-parameters 2025-05-30 11:33:24 -07:00
Jeffrey Mak f2b9e72f82 Merge branch 'main' into fix/missing-path-level-parameters 2025-05-17 16:57:00 -04:00
Jeffrey Mak 3263a97999 Merge branch 'main' into fix/missing-path-level-parameters 2025-05-14 10:57:06 -04:00
Jeffrey Mak 36866935a3 Merge branch 'main' into fix/missing-path-level-parameters 2025-05-09 19:21:46 -04:00
kmak de238eeab1 fix: unittest for path-level parameters case 2025-05-09 19:20:59 -04:00
Jeffrey Mak 84579cab56 Merge branch 'main' into fix/missing-path-level-parameters 2025-05-04 08:51:14 -04:00
kmak 34b001292a fix: support path level parameters for open_api_spec_parser 2025-05-04 08:50:02 -04:00
94 changed files with 8045 additions and 782 deletions
-62
View File
@@ -1,62 +0,0 @@
# .github/workflows/pr-commit-check.yml
# This GitHub Action workflow checks if a pull request has more than one commit.
# If it does, it fails the check and instructs the user to squash their commits.
name: 'PR Commit Check'
# This workflow runs on pull request events.
# It's configured to run on any pull request that is opened or synchronized (new commits pushed).
on:
pull_request:
types: [opened, synchronize]
# Defines the jobs that will run as part of the workflow.
jobs:
check-commit-count:
# The type of runner that the job will run on. 'ubuntu-latest' is a good default.
runs-on: ubuntu-latest
# The steps that will be executed as part of the job.
steps:
# Step 1: Check out the code
# This action checks out your repository under $GITHUB_WORKSPACE, so your workflow can access it.
- name: Checkout Code
uses: actions/checkout@v4
with:
# We need to fetch all commits to accurately count them.
# '0' means fetch all history for all branches and tags.
fetch-depth: 0
# Step 2: Count the commits in the pull request
# This step runs a script to get the number of commits in the PR.
- name: Count Commits
id: count_commits
# We use `git rev-list --count` to count the commits.
# ${{ github.event.pull_request.base.sha }} is the commit SHA of the base branch.
# ${{ github.event.pull_request.head.sha }} is the commit SHA of the head branch (the PR branch).
# The '..' syntax gives us the list of commits in the head branch that are not in the base branch.
# The output of the command (the count) is stored in a step output variable named 'count'.
run: |
count=$(git rev-list --count ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }})
echo "commit_count=$count" >> $GITHUB_OUTPUT
# Step 3: Check if the commit count is greater than 1
# This step uses the output from the previous step to decide whether to pass or fail.
- name: Check Commit Count
# This step only runs if the 'commit_count' output from the 'count_commits' step is greater than 1.
if: steps.count_commits.outputs.commit_count > 1
# If the condition is met, the workflow will exit with a failure status.
run: |
echo "This pull request has ${{ steps.count_commits.outputs.commit_count }} commits."
echo "Please squash them into a single commit before merging."
echo "You can use git rebase -i HEAD~N"
echo "...where N is the number of commits you want to squash together. The PR check conveniently tells you this number! For example, if the check says you have 3 commits, you would run: git rebase -i HEAD~3."
echo "Because you have rewritten the commit history, you must use the --force flag to update the pull request: git push --force"
exit 1
# Step 4: Success message
# This step runs if the commit count is not greater than 1 (i.e., it's 1).
- name: Success
if: steps.count_commits.outputs.commit_count <= 1
run: |
echo "This pull request has a single commit. Great job!"
@@ -28,20 +28,22 @@ Google Agent Development Kit (ADK) for Python
Adhere to this structure for compatibility with ADK tooling.
my_adk_project/ \
└── src/ \
└── my_app/ \
├── agents/ \
├── my_agent/ \
```
my_adk_project/
└── src/
└── my_app/
├── agents/
│ ├── my_agent/
│ │ ├── __init__.py # Must contain: from. import agent \
│ │ └── agent.py # Must contain: root_agent = Agent(...) \
│ └── another_agent/ \
│ ├── __init__.py \
│ └── another_agent/
│ ├── __init__.py
│ └── agent.py\
```
agent.py: Must define the agent and assign it to a variable named root_agent. This is how ADK's tools find it.
__init__.py: In each agent directory, it must contain from. import agent to make the agent discoverable.
`__init__.py`: In each agent directory, it must contain from. import agent to make the agent discoverable.
## Local Development & Debugging
@@ -108,4 +110,3 @@ Test Cases: Create JSON files with input and a reference (expected tool calls an
Metrics: tool_trajectory_avg_score (does it use tools correctly?) and response_match_score (is the final answer good?).
Run via: adk web (UI), pytest (for CI/CD), or adk eval (CLI).
+123
View File
@@ -0,0 +1,123 @@
# A2A Root Sample Agent
This sample demonstrates how to use a **remote Agent-to-Agent (A2A) agent as the root agent** in the Agent Development Kit (ADK). This is a simplified approach where the main agent is actually a remote A2A service, also showcasing how to run remote agents using uvicorn command.
## Overview
The A2A Root sample consists of:
- **Root Agent** (`agent.py`): A remote A2A agent proxy as root agent that talks to a remote a2a agent running on a separate server
- **Remote Hello World Agent** (`remote_a2a/hello_world/agent.py`): The actual agent implementation that handles dice rolling and prime number checking running on remote server
## Architecture
```
┌─────────────────┐ ┌────────────────────┐
│ Root Agent │───▶│ Remote Hello │
│ (RemoteA2aAgent)│ │ World Agent │
│ (localhost:8000)│ │ (localhost:8001) │
└─────────────────┘ └────────────────────┘
```
## Key Features
### 1. **Remote A2A as Root Agent**
- The `root_agent` is a `RemoteA2aAgent` that connects to a remote A2A service
- Demonstrates how to use remote agents as the primary agent instead of local agents
- Shows the flexibility of the A2A architecture for distributed agent deployment
### 2. **Uvicorn Server Deployment**
- The remote agent is served using uvicorn, a lightweight ASGI server
- Demonstrates a simple way to deploy A2A agents without using the ADK CLI
- Shows how to expose A2A agents as standalone web services
### 3. **Agent Functionality**
- **Dice Rolling**: Can roll dice with configurable number of sides
- **Prime Number Checking**: Can check if numbers are prime
- **State Management**: Maintains roll history in tool context
- **Parallel Tool Execution**: Can use multiple tools in parallel
### 4. **Simple Deployment Pattern**
- Uses the `to_a2a()` utility to convert a standard ADK agent to an A2A service
- Minimal configuration required for remote agent deployment
## Setup and Usage
### Prerequisites
1. **Start the Remote A2A Agent server**:
```bash
# Start the remote agent using uvicorn
uvicorn contributing.samples.a2a_root.remote_a2a.hello_world.agent:a2a_app --host localhost --port 8001
```
2. **Run the Main Agent**:
```bash
# In a separate terminal, run the adk web server
adk web contributing/samples/
```
### Example Interactions
Once both services are running, you can interact with the root agent:
**Simple Dice Rolling:**
```
User: Roll a 6-sided die
Bot: I rolled a 4 for you.
```
**Prime Number Checking:**
```
User: Is 7 a prime number?
Bot: Yes, 7 is a prime number.
```
**Combined Operations:**
```
User: Roll a 10-sided die and check if it's prime
Bot: I rolled an 8 for you.
Bot: 8 is not a prime number.
```
**Multiple Rolls with Prime Checking:**
```
User: Roll a die 3 times and check which results are prime
Bot: I rolled a 3 for you.
Bot: I rolled a 7 for you.
Bot: I rolled a 4 for you.
Bot: 3, 7 are prime numbers.
```
## Code Structure
### Root Agent (`agent.py`)
- **`root_agent`**: A `RemoteA2aAgent` that connects to the remote A2A service
- **Agent Card URL**: Points to the well-known agent card endpoint on the remote server
### Remote Hello World Agent (`remote_a2a/hello_world/agent.py`)
- **`roll_die(sides: int)`**: Function tool for rolling dice with state management
- **`check_prime(nums: list[int])`**: Async function for prime number checking
- **`root_agent`**: The main agent with comprehensive instructions
- **`a2a_app`**: The A2A application created using `to_a2a()` utility
## Troubleshooting
**Connection Issues:**
- Ensure the uvicorn server is running on port 8001
- Check that no firewall is blocking localhost connections
- Verify the agent card URL in the root agent configuration
- Check uvicorn logs for any startup errors
**Agent Not Responding:**
- Check the uvicorn server logs for errors
- Verify the agent instructions are clear and unambiguous
- Ensure the A2A app is properly configured with the correct port
**Uvicorn Issues:**
- Make sure the module path is correct: `contributing.samples.a2a_root.remote_a2a.hello_world.agent:a2a_app`
- Check that all dependencies are installed
+24
View File
@@ -0,0 +1,24 @@
# Copyright 2025 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 a2a.utils.constants import AGENT_CARD_WELL_KNOWN_PATH
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
root_agent = RemoteA2aAgent(
name="hello_world_agent",
description=(
"Helpful assistant that can roll dice and check if numbers are prime."
),
agent_card=f"http://localhost:8001/{AGENT_CARD_WELL_KNOWN_PATH}",
)
@@ -0,0 +1,15 @@
# Copyright 2025 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,111 @@
# Copyright 2025 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.
import random
from google.adk import Agent
from google.adk.a2a.utils.agent_to_a2a import to_a2a
from google.adk.tools.tool_context import ToolContext
from google.genai import types
def roll_die(sides: int, tool_context: ToolContext) -> int:
"""Roll a die and return the rolled result.
Args:
sides: The integer number of sides the die has.
tool_context: the tool context
Returns:
An integer of the result of rolling the die.
"""
result = random.randint(1, sides)
if not 'rolls' in tool_context.state:
tool_context.state['rolls'] = []
tool_context.state['rolls'] = tool_context.state['rolls'] + [result]
return result
async def check_prime(nums: list[int]) -> str:
"""Check if a given list of numbers are prime.
Args:
nums: The list of numbers to check.
Returns:
A str indicating which number is prime.
"""
primes = set()
for number in nums:
number = int(number)
if number <= 1:
continue
is_prime = True
for i in range(2, int(number**0.5) + 1):
if number % i == 0:
is_prime = False
break
if is_prime:
primes.add(number)
return (
'No prime numbers found.'
if not primes
else f"{', '.join(str(num) for num in primes)} are prime numbers."
)
root_agent = Agent(
model='gemini-2.0-flash',
name='hello_world_agent',
description=(
'hello world agent that can roll a dice of 8 sides and check prime'
' numbers.'
),
instruction="""
You roll dice and answer questions about the outcome of the dice rolls.
You can roll dice of different sizes.
You can use multiple tools in parallel by calling functions in parallel(in one request and in one round).
It is ok to discuss previous dice roles, and comment on the dice rolls.
When you are asked to roll a die, you must call the roll_die tool with the number of sides. Be sure to pass in an integer. Do not pass in a string.
You should never roll a die on your own.
When checking prime numbers, call the check_prime tool with a list of integers. Be sure to pass in a list of integers. You should never pass in a string.
You should not check prime numbers before calling the tool.
When you are asked to roll a die and check prime numbers, you should always make the following two function calls:
1. You should first call the roll_die tool to get a roll. Wait for the function response before calling the check_prime tool.
2. After you get the function response from roll_die tool, you should call the check_prime tool with the roll_die result.
2.1 If user asks you to check primes based on previous rolls, make sure you include the previous rolls in the list.
3. When you respond, you must include the roll_die result from step 1.
You should always perform the previous 3 steps when asking for a roll and checking prime numbers.
You should not rely on the previous history on prime results.
""",
tools=[
roll_die,
check_prime,
],
# planner=BuiltInPlanner(
# thinking_config=types.ThinkingConfig(
# include_thoughts=True,
# ),
# ),
generate_content_config=types.GenerateContentConfig(
safety_settings=[
types.SafetySetting( # avoid false alarm about rolling dice.
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold=types.HarmBlockThreshold.OFF,
),
]
),
)
a2a_app = to_a2a(root_agent, port=8001)
@@ -2,7 +2,11 @@
The ADK Answering Agent is a Python-based agent designed to help answer questions in GitHub discussions for the `google/adk-python` repository. It uses a large language model to analyze open discussions, retrieve information from document store, generate response, and post a comment in the github discussion.
This agent can be operated in three distinct modes: an interactive mode for local use, a batch script mode for oncall use, or as a fully automated GitHub Actions workflow (TBD).
This agent can be operated in three distinct modes:
- An interactive mode for local use.
- A batch script mode for oncall use.
- A fully automated GitHub Actions workflow (TBD).
---
@@ -50,6 +54,15 @@ The `main.py` is reserved for the Github Workflow. The detailed setup for the au
---
## Update the Knowledge Base
The `upload_docs_to_vertex_ai_search.py` is a script to upload ADK related docs to Vertex AI Search datastore to update the knowledge base. It can be executed with the following command in your terminal:
```bash
export PYTHONPATH=contributing/samples # If not already exported
python -m adk_answering_agent.upload_docs_to_vertex_ai_search
```
## Setup and Configuration
Whether running in interactive or workflow mode, the agent requires the following setup.
@@ -59,7 +72,7 @@ The agent requires the following Python libraries.
```bash
pip install --upgrade pip
pip install google-adk requests
pip install google-adk
```
The agent also requires gcloud login:
@@ -68,6 +81,12 @@ The agent also requires gcloud login:
gcloud auth application-default login
```
The upload script requires the following additional Python libraries.
```bash
pip install google-cloud-storage google-cloud-discoveryengine
```
### Environment Variables
The following environment variables are required for the agent to connect to the necessary services.
@@ -75,9 +94,15 @@ The following environment variables are required for the agent to connect to the
* `GOOGLE_GENAI_USE_VERTEXAI=TRUE`: **(Required)** Use Google Vertex AI for the authentication.
* `GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID`: **(Required)** The Google Cloud project ID.
* `GOOGLE_CLOUD_LOCATION=LOCATION`: **(Required)** The Google Cloud region.
* `VERTEXAI_DATASTORE_ID=YOUR_DATASTORE_ID`: **(Required)** The Vertex AI datastore ID for the document store (i.e. knowledge base).
* `VERTEXAI_DATASTORE_ID=YOUR_DATASTORE_ID`: **(Required)** The full Vertex AI datastore ID for the document store (i.e. knowledge base), with the format of `projects/{project_number}/locations/{location}/collections/{collection}/dataStores/{datastore_id}`.
* `OWNER`: The GitHub organization or username that owns the repository (e.g., `google`). Needed for both modes.
* `REPO`: The name of the GitHub repository (e.g., `adk-python`). Needed for both modes.
* `INTERACTIVE`: Controls the agent's interaction mode. For the automated workflow, this is set to `0`. For interactive mode, it should be set to `1` or left unset.
The following environment variables are required to upload the docs to update the knowledge base.
* `GCS_BUCKET_NAME=YOUR_GCS_BUCKET_NAME`: **(Required)** The name of the GCS bucket to store the documents.
* `ADK_DOCS_ROOT_PATH=YOUR_ADK_DOCS_ROOT_PATH`: **(Required)** Path to the root of the downloaded adk-docs repo.
* `ADK_PYTHON_ROOT_PATH=YOUR_ADK_PYTHON_ROOT_PATH`: **(Required)** Path to the root of the downloaded adk-python repo.
For local execution in interactive mode, you can place these variables in a `.env` file in the project's root directory. For the GitHub workflow, they should be configured as repository secrets.
@@ -29,9 +29,14 @@ VERTEXAI_DATASTORE_ID = os.getenv("VERTEXAI_DATASTORE_ID")
if not VERTEXAI_DATASTORE_ID:
raise ValueError("VERTEXAI_DATASTORE_ID environment variable not set")
GOOGLE_CLOUD_PROJECT = os.getenv("GOOGLE_CLOUD_PROJECT")
GCS_BUCKET_NAME = os.getenv("GCS_BUCKET_NAME")
ADK_DOCS_ROOT_PATH = os.getenv("ADK_DOCS_ROOT_PATH")
ADK_PYTHON_ROOT_PATH = os.getenv("ADK_PYTHON_ROOT_PATH")
OWNER = os.getenv("OWNER", "google")
REPO = os.getenv("REPO", "adk-python")
BOT_RESPONSE_LABEL = os.getenv("BOT_RESPONSE_LABEL", "bot_responded")
BOT_RESPONSE_LABEL = os.getenv("BOT_RESPONSE_LABEL", "bot responded")
DISCUSSION_NUMBER = os.getenv("DISCUSSION_NUMBER")
IS_INTERACTIVE = os.getenv("INTERACTIVE", "1").lower() in ["true", "1"]
@@ -0,0 +1,222 @@
# Copyright 2025 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.
import os
import sys
from adk_answering_agent.settings import ADK_DOCS_ROOT_PATH
from adk_answering_agent.settings import ADK_PYTHON_ROOT_PATH
from adk_answering_agent.settings import GCS_BUCKET_NAME
from adk_answering_agent.settings import GOOGLE_CLOUD_PROJECT
from adk_answering_agent.settings import VERTEXAI_DATASTORE_ID
from google.api_core.exceptions import GoogleAPICallError
from google.cloud import discoveryengine_v1beta as discoveryengine
from google.cloud import storage
import markdown
GCS_PREFIX_TO_ROOT_PATH = {
"adk-docs": ADK_DOCS_ROOT_PATH,
"adk-python": ADK_PYTHON_ROOT_PATH,
}
def cleanup_gcs_prefix(project_id: str, bucket_name: str, prefix: str) -> bool:
"""Delete all the objects with the given prefix in the bucket."""
print(f"Start cleaning up GCS: gs://{bucket_name}/{prefix}...")
try:
storage_client = storage.Client(project=project_id)
bucket = storage_client.bucket(bucket_name)
blobs = list(bucket.list_blobs(prefix=prefix))
if not blobs:
print("GCS target location is already empty, no need to clean up.")
return True
bucket.delete_blobs(blobs)
print(f"Successfully deleted {len(blobs)} objects.")
return True
except GoogleAPICallError as e:
print(f"[ERROR] Failed to clean up GCS: {e}", file=sys.stderr)
return False
def upload_directory_to_gcs(
source_directory: str, project_id: str, bucket_name: str, prefix: str
) -> bool:
"""Upload the whole directory into GCS."""
print(
f"Start uploading directory {source_directory} to GCS:"
f" gs://{bucket_name}/{prefix}..."
)
if not os.path.isdir(source_directory):
print(f"[Error] {source_directory} is not a directory or does not exist.")
return False
storage_client = storage.Client(project=project_id)
bucket = storage_client.bucket(bucket_name)
file_count = 0
for root, dirs, files in os.walk(source_directory):
# Modify the 'dirs' list in-place to prevent os.walk from descending
# into hidden directories.
dirs[:] = [d for d in dirs if not d.startswith(".")]
# Keep only .md and .py files.
files = [f for f in files if f.endswith(".md") or f.endswith(".py")]
for filename in files:
local_path = os.path.join(root, filename)
relative_path = os.path.relpath(local_path, source_directory)
gcs_path = os.path.join(prefix, relative_path)
try:
content_type = None
if filename.lower().endswith(".md"):
# Vertex AI search doesn't recognize text/markdown,
# convert it to html and use text/html instead
content_type = "text/html"
with open(local_path, "r", encoding="utf-8") as f:
md_content = f.read()
html_content = markdown.markdown(
md_content, output_format="html5", encoding="utf-8"
)
if not html_content:
print(" - Skipped empty file: " + local_path)
continue
gcs_path = gcs_path.removesuffix(".md") + ".html"
bucket.blob(gcs_path).upload_from_string(
html_content, content_type=content_type
)
else: # Python files
bucket.blob(gcs_path).upload_from_filename(
local_path, content_type=content_type
)
type_msg = (
f"(type {content_type})" if content_type else "(type auto-detect)"
)
print(
f" - Uploaded {type_msg}: {local_path} ->"
f" gs://{bucket_name}/{gcs_path}"
)
file_count += 1
except GoogleAPICallError as e:
print(
f"[ERROR] Error uploading file {local_path}: {e}", file=sys.stderr
)
return False
print(f"Sucessfully uploaded {file_count} files to GCS.")
return True
def import_from_gcs_to_vertex_ai(
full_datastore_id: str,
gcs_bucket: str,
) -> bool:
"""Triggers a bulk import task from a GCS folder to Vertex AI Search."""
print(f"Triggering FULL SYNC import from gs://{gcs_bucket}/**...")
try:
client = discoveryengine.DocumentServiceClient()
gcs_uri = f"gs://{gcs_bucket}/**"
request = discoveryengine.ImportDocumentsRequest(
# parent has the format of
# "projects/{project_number}/locations/{location}/collections/{collection}/dataStores/{datastore_id}/branches/default_branch"
parent=full_datastore_id + "/branches/default_branch",
# Specify the GCS source and use "content" for unstructed data.
gcs_source=discoveryengine.GcsSource(
input_uris=[gcs_uri], data_schema="content"
),
reconciliation_mode=discoveryengine.ImportDocumentsRequest.ReconciliationMode.FULL,
)
operation = client.import_documents(request=request)
print(
"Successfully started full sync import operation."
f"Operation Name: {operation.operation.name}"
)
return True
except GoogleAPICallError as e:
print(f"[ERROR] Error triggering import: {e}", file=sys.stderr)
return False
def main():
# Check required environment variables.
if not GOOGLE_CLOUD_PROJECT:
print(
"[ERROR] GOOGLE_CLOUD_PROJECT environment variable not set. Exiting...",
file=sys.stderr,
)
return 1
if not GCS_BUCKET_NAME:
print(
"[ERROR] GCS_BUCKET_NAME environment variable not set. Exiting...",
file=sys.stderr,
)
return 1
if not VERTEXAI_DATASTORE_ID:
print(
"[ERROR] VERTEXAI_DATASTORE_ID environment variable not set."
" Exiting...",
file=sys.stderr,
)
return 1
if not ADK_DOCS_ROOT_PATH:
print(
"[ERROR] ADK_DOCS_ROOT_PATH environment variable not set. Exiting...",
file=sys.stderr,
)
return 1
if not ADK_PYTHON_ROOT_PATH:
print(
"[ERROR] ADK_PYTHON_ROOT_PATH environment variable not set. Exiting...",
file=sys.stderr,
)
return 1
for gcs_prefix in GCS_PREFIX_TO_ROOT_PATH:
# 1. Cleanup the GSC for a clean start.
if not cleanup_gcs_prefix(
GOOGLE_CLOUD_PROJECT, GCS_BUCKET_NAME, gcs_prefix
):
print("[ERROR] Failed to clean up GCS. Exiting...", file=sys.stderr)
return 1
# 2. Upload the docs to GCS.
if not upload_directory_to_gcs(
GCS_PREFIX_TO_ROOT_PATH[gcs_prefix],
GOOGLE_CLOUD_PROJECT,
GCS_BUCKET_NAME,
gcs_prefix,
):
print("[ERROR] Failed to upload docs to GCS. Exiting...", file=sys.stderr)
return 1
# 3. Import the docs from GCS to Vertex AI Search.
if not import_from_gcs_to_vertex_ai(VERTEXAI_DATASTORE_ID, GCS_BUCKET_NAME):
print(
"[ERROR] Failed to import docs from GCS to Vertex AI Search."
" Exiting...",
file=sys.stderr,
)
return 1
print("--- Sync task has been successfully initiated ---")
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -21,12 +21,14 @@ from adk_triaging_agent.settings import OWNER
from adk_triaging_agent.settings import REPO
from adk_triaging_agent.utils import error_response
from adk_triaging_agent.utils import get_request
from adk_triaging_agent.utils import patch_request
from adk_triaging_agent.utils import post_request
from google.adk import Agent
import requests
LABEL_TO_OWNER = {
"documentation": "polong",
"agent engine": "yeesian",
"documentation": "polong-lin",
"services": "DeanChensj",
"question": "",
"tools": "seanzhou1023",
@@ -136,6 +138,30 @@ def add_label_and_owner_to_issue(
}
def change_issue_type(issue_number: int, issue_type: str) -> dict[str, Any]:
"""Change the issue type of the given issue number.
Args:
issue_number: issue number of the Github issue, in string foramt.
issue_type: issue type to assign
Returns:
The the status of this request, with the applied issue type when successful.
"""
print(
f"Attempting to change issue type '{issue_type}' to issue #{issue_number}"
)
url = f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/issues/{issue_number}"
payload = {"type": issue_type}
try:
response = patch_request(url, payload)
except requests.exceptions.RequestException as e:
return error_response(f"Error: {e}")
return {"status": "success", "message": response, "issue_type": issue_type}
root_agent = Agent(
model="gemini-2.5-pro",
name="adk_triaging_assistant",
@@ -143,6 +169,7 @@ root_agent = Agent(
instruction=f"""
You are a triaging bot for the Github {REPO} repo with the owner {OWNER}. You will help get issues, and recommend a label.
IMPORTANT: {APPROVAL_INSTRUCTION}
Here are the rules for labeling:
- If the user is asking about documentation-related questions, label it with "documentation".
- If it's about session, memory services, label it with "services"
@@ -154,14 +181,24 @@ root_agent = Agent(
- If it's about model support(non-Gemini, like Litellm, Ollama, OpenAI models), label it with "models".
- If it's about tracing, label it with "tracing".
- If it's agent orchestration, agent definition, label it with "core".
- If it's about agent engine, label it with "agent engine".
- If you can't find a appropriate labels for the issue, follow the previous instruction that starts with "IMPORTANT:".
Call the `add_label_and_owner_to_issue` tool to label the issue, which will also assign the issue to the owner of the label.
After you label the issue, call the `change_issue_type` tool to change the issue type:
- If the issue is a bug report, change the issue type to "Bug".
- If the issue is a feature request, change the issue type to "Feature".
- Otherwise, **do not change the issue type**.
Present the followings in an easy to read format highlighting issue number and your label.
- the issue summary in a few sentence
- your label recommendation and justification
- the owner of the label if you assign the issue to an owner
""",
tools=[list_unlabeled_issues, add_label_and_owner_to_issue],
tools=[
list_unlabeled_issues,
add_label_and_owner_to_issue,
change_issue_type,
],
)
@@ -26,7 +26,7 @@ if not GITHUB_TOKEN:
OWNER = os.getenv("OWNER", "google")
REPO = os.getenv("REPO", "adk-python")
BOT_LABEL = os.getenv("BOT_LABEL", "bot_triaged")
BOT_LABEL = os.getenv("BOT_LABEL", "bot triaged")
EVENT_NAME = os.getenv("EVENT_NAME")
ISSUE_NUMBER = os.getenv("ISSUE_NUMBER")
ISSUE_TITLE = os.getenv("ISSUE_TITLE")
@@ -39,6 +39,12 @@ def post_request(url: str, payload: Any) -> dict[str, Any]:
return response.json()
def patch_request(url: str, payload: Any) -> dict[str, Any]:
response = requests.patch(url, headers=headers, json=payload, timeout=60)
response.raise_for_status()
return response.json()
def error_response(error_message: str) -> dict[str, Any]:
return {"status": "error", "message": error_message}
@@ -15,8 +15,6 @@
import random
from google.adk import Agent
from google.adk.planners import BuiltInPlanner
from google.adk.planners import PlanReActPlanner
from google.adk.tools.tool_context import ToolContext
from google.genai import types
+15
View File
@@ -0,0 +1,15 @@
# Copyright 2025 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
+116
View File
@@ -0,0 +1,116 @@
# Copyright 2025 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.
import random
from google.adk import Agent
from google.adk.agents.callback_context import CallbackContext
from google.adk.models import LlmRequest
from google.adk.tools.tool_context import ToolContext
def roll_die(sides: int, tool_context: ToolContext) -> int:
"""Roll a die and return the rolled result.
Args:
sides: The integer number of sides the die has.
Returns:
An integer of the result of rolling the die.
"""
result = random.randint(1, sides)
if not 'rolls' in tool_context.state:
tool_context.state['rolls'] = []
tool_context.state['rolls'] = tool_context.state['rolls'] + [result]
return result
async def check_prime(nums: list[int]) -> str:
"""Check if a given list of numbers are prime.
Args:
nums: The list of numbers to check.
Returns:
A str indicating which number is prime.
"""
primes = set()
for number in nums:
number = int(number)
if number <= 1:
continue
is_prime = True
for i in range(2, int(number**0.5) + 1):
if number % i == 0:
is_prime = False
break
if is_prime:
primes.add(number)
return (
'No prime numbers found.'
if not primes
else f"{', '.join(str(num) for num in primes)} are prime numbers."
)
def create_slice_history_callback(n_recent_turns):
async def before_model_callback(
callback_context: CallbackContext, llm_request: LlmRequest
):
if n_recent_turns < 1:
return
user_indexes = [
i
for i, content in enumerate(llm_request.contents)
if content.role == 'user'
]
if n_recent_turns > len(user_indexes):
return
suffix_idx = user_indexes[-n_recent_turns]
llm_request.contents = llm_request.contents[suffix_idx:]
return before_model_callback
root_agent = Agent(
model='gemini-2.0-flash',
name='short_history_agent',
description=(
'an agent that maintains only the last turn in its context window.'
' numbers.'
),
instruction="""
You roll dice and answer questions about the outcome of the dice rolls.
You can roll dice of different sizes.
You can use multiple tools in parallel by calling functions in parallel(in one request and in one round).
It is ok to discuss previous dice roles, and comment on the dice rolls.
When you are asked to roll a die, you must call the roll_die tool with the number of sides. Be sure to pass in an integer. Do not pass in a string.
You should never roll a die on your own.
When checking prime numbers, call the check_prime tool with a list of integers. Be sure to pass in a list of integers. You should never pass in a string.
You should not check prime numbers before calling the tool.
When you are asked to roll a die and check prime numbers, you should always make the following two function calls:
1. You should first call the roll_die tool to get a roll. Wait for the function response before calling the check_prime tool.
2. After you get the function response from roll_die tool, you should call the check_prime tool with the roll_die result.
2.1 If user asks you to check primes based on previous rolls, make sure you include the previous rolls in the list.
3. When you respond, you must include the roll_die result from step 1.
You should always perform the previous 3 steps when asking for a roll and checking prime numbers.
You should not rely on the previous history on prime results.
""",
tools=[roll_die, check_prime],
before_model_callback=create_slice_history_callback(n_recent_turns=2),
)
+80
View File
@@ -0,0 +1,80 @@
# Copyright 2025 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.
import asyncio
import time
import warnings
import agent
from dotenv import load_dotenv
from google.adk import Runner
from google.adk.artifacts import InMemoryArtifactService
from google.adk.cli.utils import logs
from google.adk.sessions import InMemorySessionService
from google.adk.sessions import Session
from google.genai import types
load_dotenv(override=True)
warnings.filterwarnings('ignore', category=UserWarning)
logs.log_to_tmp_folder()
async def main():
app_name = 'my_app'
user_id_1 = 'user1'
session_service = InMemorySessionService()
artifact_service = InMemoryArtifactService()
runner = Runner(
app_name=app_name,
agent=agent.root_agent,
artifact_service=artifact_service,
session_service=session_service,
)
session_11 = await session_service.create_session(
app_name=app_name, user_id=user_id_1
)
async def run_prompt(session: Session, new_message: str):
content = types.Content(
role='user', parts=[types.Part.from_text(text=new_message)]
)
print('** User says:', content.model_dump(exclude_none=True))
async for event in runner.run_async(
user_id=user_id_1,
session_id=session.id,
new_message=content,
):
if event.content.parts and event.content.parts[0].text:
print(f'** {event.author}: {event.content.parts[0].text}')
start_time = time.time()
print('Start time:', start_time)
print('------------------------------------')
await run_prompt(session_11, 'Hi')
await run_prompt(session_11, 'Roll a die with 100 sides')
await run_prompt(session_11, 'Roll a die again with 100 sides.')
await run_prompt(session_11, 'What numbers did I got?')
print(
await artifact_service.list_artifact_keys(
app_name=app_name, user_id=user_id_1, session_id=session_11.id
)
)
end_time = time.time()
print('------------------------------------')
print('End time:', end_time)
print('Total time:', end_time - start_time)
if __name__ == '__main__':
asyncio.run(main())
@@ -17,20 +17,31 @@ This agent aims to test the Langchain tool with Langchain's StructuredTool
"""
from google.adk.agents import Agent
from google.adk.tools.langchain_tool import LangchainTool
from langchain.tools import tool
from langchain_core.tools.structured import StructuredTool
from pydantic import BaseModel
def add(x, y) -> int:
async def add(x, y) -> int:
return x + y
@tool
def minus(x, y) -> int:
return x - y
class AddSchema(BaseModel):
x: int
y: int
test_langchain_tool = StructuredTool.from_function(
class MinusSchema(BaseModel):
x: int
y: int
test_langchain_add_tool = StructuredTool.from_function(
add,
name="add",
description="Adds two numbers",
@@ -45,5 +56,8 @@ root_agent = Agent(
"You are a helpful assistant for user questions, you have access to a"
" tool that adds two numbers."
),
tools=[LangchainTool(tool=test_langchain_tool)],
tools=[
LangchainTool(tool=test_langchain_add_tool),
LangchainTool(tool=minus),
],
)
+39 -18
View File
@@ -1,11 +1,18 @@
# Toolbox Agent
This agent is utilizing [mcp toolbox for database](https://googleapis.github.io/genai-toolbox/getting-started/introduction/) to assist end user based on the informaton stored in database.
Follow below steps to run this agent
This agent utilizes [MCP toolbox for database](https://googleapis.github.io/genai-toolbox/getting-started/introduction/) to assist end users based on information stored in a database.
# Install toolbox
Follow the steps below to run this agent.
* Run below command:
## Prerequisites
Before starting, ensure you have Python installed on your system.
## Installation Steps
### 1. Install Toolbox
Run the following command to download and install the toolbox:
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
@@ -13,20 +20,29 @@ curl -O https://storage.googleapis.com/genai-toolbox/v0.5.0/$OS/toolbox
chmod +x toolbox
```
# install SQLite
### 2. Install SQLite
* install sqlite from https://sqlite.org/
Install SQLite from [https://sqlite.org/](https://sqlite.org/)
### 3. Install Required Python Dependencies
# Create DB (optional. The db instance is already attached in the folder)
**Important**: The ADK's `ToolboxToolset` class requires the `toolbox-core` package, which is not automatically installed with the ADK. Install it using:
* Run below command:
```bash
pip install toolbox-core
```
### 4. Create Database (Optional)
*Note: A database instance is already included in the project folder. Skip this step if you want to use the existing database.*
To create a new database:
```bash
sqlite3 tool_box.db
```
* Run below SQL:
Run the following SQL commands to set up the hotels table:
```sql
CREATE TABLE hotels(
@@ -39,7 +55,6 @@ CREATE TABLE hotels(
booked BIT NOT NULL
);
INSERT INTO hotels(id, name, location, price_tier, checkin_date, checkout_date, booked)
VALUES
(1, 'Hilton Basel', 'Basel', 'Luxury', '2024-04-22', '2024-04-20', 0),
@@ -54,21 +69,27 @@ VALUES
(10, 'Comfort Inn Bern', 'Bern', 'Midscale', '2024-04-04', '2024-04-16', 0);
```
# create tools configurations
### 5. Create Tools Configuration
* Create a yaml file named "tools.yaml", see its contents in the agent folder.
Create a YAML file named `tools.yaml`. See the contents in the agent folder for reference.
# start toolbox server
### 6. Start Toolbox Server
* Run below commands in the agent folder
Run the following command in the agent folder:
```bash
toolbox --tools-file "tools.yaml"
```
# start ADK web UI
The server will start at `http://127.0.0.1:5000` by default.
# send user query
### 7. Start ADK Web UI
* query 1: what can you do for me ?
* query 2: could you let know the information about "Hilton Basel" hotel ?
Follow the ADK documentation to start the web user interface.
## Testing the Agent
Once everything is set up, you can test the agent with these sample queries:
- **Query 1**: "What can you do for me?"
- **Query 2**: "Could you let me know the information about 'Hilton Basel' hotel?"
+1
View File
@@ -105,6 +105,7 @@ test = [
"pytest-mock>=3.14.0",
"pytest-xdist>=3.6.1",
"pytest>=8.3.4",
"python-multipart>=0.0.9",
# go/keep-sorted end
]
@@ -22,8 +22,7 @@ try:
except ImportError as e:
if sys.version_info < (3, 10):
raise ImportError(
'A2A Tool requires Python 3.10 or above. Please upgrade your Python'
' version.'
'A2A requires Python 3.10 or above. Please upgrade your Python version.'
) from e
else:
raise e

Some files were not shown because too many files have changed in this diff Show More