You've already forked adk-python
mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fda34ea9ff | |||
| 609b3a572c | |||
| 2a8ca06c3e | |||
| 3489bb598d | |||
| ed63cd86eb | |||
| 447798d573 | |||
| 958c18db2b | |||
| 83ba6419cd | |||
| d232e6216d | |||
| c7ce987676 | |||
| bf27f22a95 | |||
| 7fc09b2c64 | |||
| c4cd1802bf | |||
| 3cd4cd3ecf | |||
| 2a07eefc27 | |||
| d83ee1a6f2 | |||
| 3611e9fe09 | |||
| 97bf28bad6 | |||
| d6c6bb4b24 | |||
| 46282eeb0d | |||
| f4c7d788b0 | |||
| 15109c6879 | |||
| d40df2edf2 | |||
| a66f12273c | |||
| 5d2a876ca8 | |||
| 0c4f52699b | |||
| 618c824994 | |||
| 16d9696012 | |||
| 03fe909ada | |||
| b79400162d | |||
| c024ac5762 | |||
| 19ca61d241 | |||
| 7c2df7e4f6 | |||
| d43c80b718 | |||
| be0786ea88 | |||
| 6b89ceb49a | |||
| 694eca08e5 | |||
| 55cb36edfe | |||
| 74a331f1fb | |||
| 98c8a71d67 | |||
| b0fb3536e5 | |||
| 1a43cf0f60 | |||
| a380d70ac7 | |||
| a134ba6f7a | |||
| 7067c0319d | |||
| 756a326033 | |||
| 0e284f45ff | |||
| 8076b2294f | |||
| f1a92e9674 | |||
| 717993b14f | |||
| d212e50c10 | |||
| 79681e3513 | |||
| 33921d524f | |||
| 92c37496d3 | |||
| b9b2c3fb54 | |||
| b4c4f88c22 | |||
| 68de9adc18 | |||
| 0026edc1c9 | |||
| d3910e7b8c | |||
| 30e10c87c5 | |||
| fe618c3d93 | |||
| a63ea2735c | |||
| ff8a3c9b43 | |||
| a2263b1808 | |||
| e0851a1e57 | |||
| 09f30afe10 | |||
| 4542af5650 | |||
| 505d936007 | |||
| d7af2b2815 | |||
| 41b33d4a0a | |||
| 09cb128cf9 | |||
| b2a2b11776 | |||
| 1773cdab96 | |||
| 1807e73472 | |||
| 98f504cebe | |||
| 9928cafe32 | |||
| c5a0437745 | |||
| e060344e39 | |||
| 1561b0f15c | |||
| 62a543bd58 | |||
| cbdb5fc507 | |||
| 98727b4698 | |||
| 76700d790b | |||
| fe01103b74 | |||
| 53b14325ce | |||
| b299241b56 | |||
| 68f0fca832 | |||
| bb00c9519d | |||
| 00798af881 |
@@ -7,7 +7,7 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
** Please make sure you read the contribution guide and file the issues in the rigth place. **
|
||||
** Please make sure you read the contribution guide and file the issues in the right place. **
|
||||
[Contribution guide.](https://google.github.io/adk-docs/contributing-guide/)
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
@@ -7,7 +7,7 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
** Please make sure you read the contribution guide and file the issues in the rigth place. **
|
||||
** Please make sure you read the contribution guide and file the issues in the right place. **
|
||||
[Contribution guide.](https://google.github.io/adk-docs/contributing-guide/)
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# Changelog
|
||||
|
||||
## 1.1.1
|
||||
|
||||
### Features
|
||||
* Add BigQuery first-party tools. See [here](https://github.com/google/adk-python/commit/d6c6bb4b2489a8b7a4713e4747c30d6df0c07961) for more details.
|
||||
|
||||
|
||||
## 1.1.0
|
||||
|
||||
### Features
|
||||
|
||||
* Extract agent loading logic from fast_api.py to a separate AgentLoader class and support more agent definition folder/file structure.
|
||||
* Added audio play in web UI.
|
||||
* Added input transcription support for live/streaming.
|
||||
* Added support for storing eval run history locally in adk eval cli.
|
||||
* Image artifacts can now be clicked directly in chat message to view.
|
||||
* Left side panel can now be resized.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Avoid duplicating log in stderr.
|
||||
* Align event filtering and ordering logic.
|
||||
* Add handling for None param.annotation.
|
||||
* Fixed several minor bugs regarding eval tab in web UI.
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* Updates mypy config in pyproject.toml.
|
||||
* Add google search agent in samples.
|
||||
* Update filtered schema parameters for Gemini API.
|
||||
* Adds autoformat.sh for formatting codebase.
|
||||
|
||||
## 1.0.0
|
||||
|
||||
### âš BREAKING CHANGES
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
</h3>
|
||||
<h3 align="center">
|
||||
Important Links:
|
||||
<a href="https://google.github.io/adk-docs/">Docs</a> &
|
||||
<a href="https://github.com/google/adk-samples">Samples</a>.
|
||||
<a href="https://google.github.io/adk-docs/">Docs</a>,
|
||||
<a href="https://github.com/google/adk-samples">Samples</a>,
|
||||
<a href="https://github.com/google/adk-java">Java ADK</a> &
|
||||
<a href="https://github.com/google/adk-web">ADK Web</a>.
|
||||
</h3>
|
||||
</html>
|
||||
|
||||
@@ -38,6 +40,12 @@ Agent Development Kit (ADK) is a flexible and modular framework for developing a
|
||||
- **Deploy Anywhere**: Easily containerize and deploy agents on Cloud Run or
|
||||
scale seamlessly with Vertex AI Agent Engine.
|
||||
|
||||
## 🤖 Agent2Agent (A2A) Protocol and ADK Integration
|
||||
|
||||
For remote agent-to-agent communication, ADK integrates with the
|
||||
[A2A protocol](https://github.com/google/A2A/).
|
||||
See this [example](https://github.com/google/A2A/tree/main/samples/python/agents/google_adk)
|
||||
for how they can work together.
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
@@ -123,16 +131,9 @@ adk eval \
|
||||
samples_for_testing/hello_world/hello_world_eval_set_001.evalset.json
|
||||
```
|
||||
|
||||
## 🤖 A2A and ADK integration
|
||||
|
||||
For remote agent-to-agent communication, ADK integrates with the
|
||||
[A2A protocol](https://github.com/google/A2A/).
|
||||
See this [example](https://github.com/google/A2A/tree/main/samples/python/agents/google_adk)
|
||||
for how they can work together.
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions from the community! Whether it's bug reports, feature requests, documentation improvements, or code contributions, please see our
|
||||
We welcome contributions from the community! Whether it's bug reports, feature requests, documentation improvements, or code contributions, please see our
|
||||
- [General contribution guideline and flow](https://google.github.io/adk-docs/contributing-guide/#questions).
|
||||
- Then if you want to contribute code, please read [Code Contributing Guidelines](./CONTRIBUTING.md) to get started.
|
||||
|
||||
@@ -140,10 +141,6 @@ We welcome contributions from the community! Whether it's bug reports, feature r
|
||||
|
||||
This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Preview
|
||||
|
||||
This feature is subject to the "Pre-GA Offerings Terms" in the General Service Terms section of the [Service Specific Terms](https://cloud.google.com/terms/service-terms#1). Pre-GA features are available "as is" and might have limited support. For more information, see the [launch stage descriptions](https://cloud.google.com/products?hl=en#product-launch-stages).
|
||||
|
||||
---
|
||||
|
||||
*Happy Agent Building!*
|
||||
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
# 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.
|
||||
|
||||
# Autoformat ADK codebase.
|
||||
|
||||
if ! command -v isort &> /dev/null
|
||||
then
|
||||
echo "isort not found, refer to CONTRIBUTING.md to set up dev environment first."
|
||||
exit
|
||||
fi
|
||||
|
||||
if ! command -v pyink &> /dev/null
|
||||
then
|
||||
echo "pyink not found, refer to CONTRIBUTING.md to set up dev environment first."
|
||||
exit
|
||||
fi
|
||||
|
||||
echo '---------------------------------------'
|
||||
echo '| Organizing imports for src/...'
|
||||
echo '---------------------------------------'
|
||||
|
||||
isort src/
|
||||
echo 'All done! ✨ 🍰 ✨'
|
||||
|
||||
echo '---------------------------------------'
|
||||
echo '| Organizing imports for tests/...'
|
||||
echo '---------------------------------------'
|
||||
|
||||
isort tests/
|
||||
echo 'All done! ✨ 🍰 ✨'
|
||||
|
||||
echo '---------------------------------------'
|
||||
echo '| Auto-formatting src/...'
|
||||
echo '---------------------------------------'
|
||||
|
||||
find -L src/ -type f -name "*.py" -exec pyink --config pyproject.toml {} +
|
||||
|
||||
echo '---------------------------------------'
|
||||
echo '| Auto-formatting tests/...'
|
||||
echo '---------------------------------------'
|
||||
|
||||
find -L tests/ -type f -name "*.py" -exec pyink --config pyproject.toml {} +
|
||||
@@ -0,0 +1,83 @@
|
||||
# BigQuery Tools Sample
|
||||
|
||||
## Introduction
|
||||
|
||||
This sample agent demonstrates the BigQuery first-party tools in ADK,
|
||||
distributed via the `google.adk.tools.bigquery` module. These tools include:
|
||||
|
||||
1. `list_dataset_ids`
|
||||
|
||||
Fetches BigQuery dataset ids present in a GCP project.
|
||||
|
||||
1. `get_dataset_info`
|
||||
|
||||
Fetches metadata about a BigQuery dataset.
|
||||
|
||||
1. `list_table_ids`
|
||||
|
||||
Fetches table ids present in a BigQuery dataset.
|
||||
|
||||
1. `get_table_info`
|
||||
|
||||
Fetches metadata about a BigQuery table.
|
||||
|
||||
1. `execute_sql`
|
||||
|
||||
Runs a SQL query in BigQuery.
|
||||
|
||||
## How to use
|
||||
|
||||
Set up environment variables in your `.env` file for using
|
||||
[Google AI Studio](https://google.github.io/adk-docs/get-started/quickstart/#gemini---google-ai-studio)
|
||||
or
|
||||
[Google Cloud Vertex AI](https://google.github.io/adk-docs/get-started/quickstart/#gemini---google-cloud-vertex-ai)
|
||||
for the LLM service for your agent. For example, for using Google AI Studio you
|
||||
would set:
|
||||
|
||||
* GOOGLE_GENAI_USE_VERTEXAI=FALSE
|
||||
* GOOGLE_API_KEY={your api key}
|
||||
|
||||
### With Application Default Credentials
|
||||
|
||||
This mode is useful for quick development when the agent builder is the only
|
||||
user interacting with the agent. The tools are initialized with the default
|
||||
credentials present on the machine running the agent.
|
||||
|
||||
1. Create application default credentials on the machine where the agent would
|
||||
be running by following https://cloud.google.com/docs/authentication/provide-credentials-adc.
|
||||
|
||||
1. Set `RUN_WITH_ADC=True` in `agent.py` and run the agent
|
||||
|
||||
### With Interactive OAuth
|
||||
|
||||
1. Follow
|
||||
https://developers.google.com/identity/protocols/oauth2#1.-obtain-oauth-2.0-credentials-from-the-dynamic_data.setvar.console_name.
|
||||
to get your client id and client secret. Be sure to choose "web" as your client
|
||||
type.
|
||||
|
||||
1. Follow https://developers.google.com/workspace/guides/configure-oauth-consent to add scope "https://www.googleapis.com/auth/bigquery".
|
||||
|
||||
1. Follow https://developers.google.com/identity/protocols/oauth2/web-server#creatingcred to add http://localhost/dev-ui/ to "Authorized redirect URIs".
|
||||
|
||||
Note: localhost here is just a hostname that you use to access the dev ui,
|
||||
replace it with the actual hostname you use to access the dev ui.
|
||||
|
||||
1. For 1st run, allow popup for localhost in Chrome.
|
||||
|
||||
1. Configure your `.env` file to add two more variables before running the agent:
|
||||
|
||||
* OAUTH_CLIENT_ID={your client id}
|
||||
* OAUTH_CLIENT_SECRET={your client secret}
|
||||
|
||||
Note: don't create a separate .env, instead put it to the same .env file that
|
||||
stores your Vertex AI or Dev ML credentials
|
||||
|
||||
1. Set `RUN_WITH_ADC=False` in `agent.py` and run the agent
|
||||
|
||||
## Sample prompts
|
||||
|
||||
* which weather datasets exist in bigquery public data?
|
||||
* tell me more about noaa_lightning
|
||||
* which tables exist in the ml_datasets dataset?
|
||||
* show more details about the penguins table
|
||||
* compute penguins population per island.
|
||||
@@ -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,58 @@
|
||||
# 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
|
||||
|
||||
from google.adk.agents import llm_agent
|
||||
from google.adk.tools.bigquery import BigQueryCredentialsConfig
|
||||
from google.adk.tools.bigquery import BigQueryToolset
|
||||
import google.auth
|
||||
|
||||
|
||||
RUN_WITH_ADC = False
|
||||
|
||||
|
||||
if RUN_WITH_ADC:
|
||||
# Initialize the tools to use the application default credentials.
|
||||
application_default_credentials, _ = google.auth.default()
|
||||
credentials_config = BigQueryCredentialsConfig(
|
||||
credentials=application_default_credentials
|
||||
)
|
||||
else:
|
||||
# Initiaze the tools to do interactive OAuth
|
||||
# The environment variables OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET
|
||||
# must be set
|
||||
credentials_config = BigQueryCredentialsConfig(
|
||||
client_id=os.getenv("OAUTH_CLIENT_ID"),
|
||||
client_secret=os.getenv("OAUTH_CLIENT_SECRET"),
|
||||
scopes=["https://www.googleapis.com/auth/bigquery"],
|
||||
)
|
||||
|
||||
bigquery_toolset = BigQueryToolset(credentials_config=credentials_config)
|
||||
|
||||
# The variable name `root_agent` determines what your root agent is for the
|
||||
# debug CLI
|
||||
root_agent = llm_agent.Agent(
|
||||
model="gemini-2.0-flash",
|
||||
name="hello_agent",
|
||||
description=(
|
||||
"Agent to answer questions about BigQuery data and models and execute"
|
||||
" SQL queries."
|
||||
),
|
||||
instruction="""\
|
||||
You are a data science agent with access to several BigQuery tools.
|
||||
Make use of those tools to answer the user's questions.
|
||||
""",
|
||||
tools=[bigquery_toolset],
|
||||
)
|
||||
@@ -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,30 @@
|
||||
# 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 google.genai import Client
|
||||
|
||||
from google.adk import Agent
|
||||
from google.adk.tools import google_search
|
||||
|
||||
# Only Vertex AI supports image generation for now.
|
||||
client = Client()
|
||||
|
||||
root_agent = Agent(
|
||||
model='gemini-2.0-flash-001',
|
||||
name='root_agent',
|
||||
description="""an agent whose job it is to perform Google search queries and answer questions about the results.""",
|
||||
instruction="""You are an agent whose job is to perform Google search queries and answer questions about the results.
|
||||
""",
|
||||
tools=[google_search],
|
||||
)
|
||||
@@ -0,0 +1,43 @@
|
||||
# Agent with Long-Running Tools
|
||||
|
||||
This example demonstrates an agent using a long-running tool (`ask_for_approval`).
|
||||
|
||||
## Key Flow for Long-Running Tools
|
||||
|
||||
1. **Initial Call**: The agent calls the long-running tool (e.g., `ask_for_approval`).
|
||||
2. **Initial Tool Response**: The tool immediately returns an initial response, typically indicating a "pending" status and a way to track the request (e.g., a `ticket-id`). This is sent back to the agent as a `types.FunctionResponse` (usually processed internally by the runner and then influencing the agent's next turn).
|
||||
3. **Agent Acknowledges**: The agent processes this initial response and usually informs the user about the pending status.
|
||||
4. **External Process/Update**: The long-running task progresses externally (e.g., a human approves the request).
|
||||
5. **❗️Crucial Step: Provide Updated Tool Response❗️**:
|
||||
* Once the external process completes or updates, your application **must** construct a new `types.FunctionResponse`.
|
||||
* This response should use the **same `id` and `name`** as the original `FunctionCall` to the long-running tool.
|
||||
* The `response` field within this `types.FunctionResponse` should contain the *updated data* (e.g., `{'status': 'approved', ...}`).
|
||||
* Send this `types.FunctionResponse` back to the agent as a part within a new message using `role="user"`.
|
||||
|
||||
```python
|
||||
# Example: After external approval
|
||||
updated_tool_output_data = {
|
||||
"status": "approved",
|
||||
"ticket-id": ticket_id, # from original call
|
||||
# ... other relevant updated data
|
||||
}
|
||||
|
||||
updated_function_response_part = types.Part(
|
||||
function_response=types.FunctionResponse(
|
||||
id=long_running_function_call.id, # Original call ID
|
||||
name=long_running_function_call.name, # Original call name
|
||||
response=updated_tool_output_data,
|
||||
)
|
||||
)
|
||||
|
||||
# Send this back to the agent
|
||||
await runner.run_async(
|
||||
# ... session_id, user_id ...
|
||||
new_message=types.Content(
|
||||
parts=[updated_function_response_part], role="user"
|
||||
),
|
||||
)
|
||||
```
|
||||
6. **Agent Acts on Update**: The agent receives this message containing the `types.FunctionResponse` and, based on its instructions, proceeds with the next steps (e.g., calling another tool like `reimburse`).
|
||||
|
||||
**Why is this important?** The agent relies on receiving this subsequent `types.FunctionResponse` (provided in a message with `role="user"` containing the specific `Part`) to understand that the long-running task has concluded or its state has changed. Without it, the agent will remain unaware of the outcome of the pending task.
|
||||
@@ -22,14 +22,20 @@ from google.genai import types
|
||||
|
||||
def reimburse(purpose: str, amount: float) -> str:
|
||||
"""Reimburse the amount of money to the employee."""
|
||||
return {'status': 'ok'}
|
||||
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'}
|
||||
return {
|
||||
'status': 'pending',
|
||||
'amount': amount,
|
||||
'ticketId': 'reimbursement-ticket-001',
|
||||
}
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
# 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 agent
|
||||
from dotenv import load_dotenv
|
||||
from typing import Any
|
||||
from typing import Union
|
||||
from google.adk.agents import Agent
|
||||
from google.adk.events import Event
|
||||
from google.adk.runners import Runner
|
||||
from google.adk.tools import LongRunningFunctionTool
|
||||
from google.adk.sessions import InMemorySessionService
|
||||
from google.genai import types
|
||||
|
||||
import os
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
|
||||
from opentelemetry.sdk.trace import export
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
APP_NAME = "human_in_the_loop"
|
||||
USER_ID = "1234"
|
||||
SESSION_ID = "session1234"
|
||||
|
||||
session_service = InMemorySessionService()
|
||||
|
||||
|
||||
async def main():
|
||||
session = await session_service.create_session(
|
||||
app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID
|
||||
)
|
||||
runner = Runner(
|
||||
agent=agent.root_agent,
|
||||
app_name=APP_NAME,
|
||||
session_service=session_service,
|
||||
)
|
||||
|
||||
async def call_agent(query: str):
|
||||
content = types.Content(role="user", parts=[types.Part(text=query)])
|
||||
|
||||
print(f'>>> User Query: "{query}"')
|
||||
print("--- Running agent's initial turn ---")
|
||||
|
||||
events_async = runner.run_async(
|
||||
session_id=session.id, user_id=USER_ID, new_message=content
|
||||
)
|
||||
|
||||
long_running_function_call: Union[types.FunctionCall, None] = None
|
||||
initial_tool_response: Union[types.FunctionResponse, None] = None
|
||||
ticket_id: Union[str, None] = None
|
||||
|
||||
async for event in events_async:
|
||||
if event.content and event.content.parts:
|
||||
for i, part in enumerate(event.content.parts):
|
||||
if part.text:
|
||||
print(f" Part {i} [Text]: {part.text.strip()}")
|
||||
if part.function_call:
|
||||
print(
|
||||
f" Part {i} [FunctionCall]:"
|
||||
f" {part.function_call.name}({part.function_call.args}) ID:"
|
||||
f" {part.function_call.id}"
|
||||
)
|
||||
if not long_running_function_call and part.function_call.id in (
|
||||
event.long_running_tool_ids or []
|
||||
):
|
||||
long_running_function_call = part.function_call
|
||||
print(
|
||||
" (Captured as long_running_function_call for"
|
||||
f" '{part.function_call.name}')"
|
||||
)
|
||||
if part.function_response:
|
||||
print(
|
||||
f" Part {i} [FunctionResponse]: For"
|
||||
f" '{part.function_response.name}', ID:"
|
||||
f" {part.function_response.id}, Response:"
|
||||
f" {part.function_response.response}"
|
||||
)
|
||||
if (
|
||||
long_running_function_call
|
||||
and part.function_response.id == long_running_function_call.id
|
||||
):
|
||||
initial_tool_response = part.function_response
|
||||
if initial_tool_response.response:
|
||||
ticket_id = initial_tool_response.response.get("ticketId")
|
||||
print(
|
||||
" (Captured as initial_tool_response for"
|
||||
f" '{part.function_response.name}', Ticket ID: {ticket_id})"
|
||||
)
|
||||
|
||||
print("--- End of agent's initial turn ---\n")
|
||||
|
||||
if (
|
||||
long_running_function_call
|
||||
and initial_tool_response
|
||||
and initial_tool_response.response.get("status") == "pending"
|
||||
):
|
||||
print(f"--- Simulating external approval for ticket: {ticket_id} ---\n")
|
||||
|
||||
updated_tool_output_data = {
|
||||
"status": "approved",
|
||||
"ticketId": ticket_id,
|
||||
"approver_feedback": "Approved by manager at " + str(
|
||||
asyncio.get_event_loop().time()
|
||||
),
|
||||
}
|
||||
|
||||
updated_function_response_part = types.Part(
|
||||
function_response=types.FunctionResponse(
|
||||
id=long_running_function_call.id,
|
||||
name=long_running_function_call.name,
|
||||
response=updated_tool_output_data,
|
||||
)
|
||||
)
|
||||
|
||||
print(
|
||||
"--- Sending updated tool result to agent for call ID"
|
||||
f" {long_running_function_call.id}: {updated_tool_output_data} ---"
|
||||
)
|
||||
print("--- Running agent's turn AFTER receiving updated tool result ---")
|
||||
|
||||
async for event in runner.run_async(
|
||||
session_id=session.id,
|
||||
user_id=USER_ID,
|
||||
new_message=types.Content(
|
||||
parts=[updated_function_response_part], role="user"
|
||||
),
|
||||
):
|
||||
if event.content and event.content.parts:
|
||||
for i, part in enumerate(event.content.parts):
|
||||
if part.text:
|
||||
print(f" Part {i} [Text]: {part.text.strip()}")
|
||||
if part.function_call:
|
||||
print(
|
||||
f" Part {i} [FunctionCall]:"
|
||||
f" {part.function_call.name}({part.function_call.args}) ID:"
|
||||
f" {part.function_call.id}"
|
||||
)
|
||||
if part.function_response:
|
||||
print(
|
||||
f" Part {i} [FunctionResponse]: For"
|
||||
f" '{part.function_response.name}', ID:"
|
||||
f" {part.function_response.id}, Response:"
|
||||
f" {part.function_response.response}"
|
||||
)
|
||||
print("--- End of agent's turn AFTER receiving updated tool result ---")
|
||||
|
||||
elif long_running_function_call and not initial_tool_response:
|
||||
print(
|
||||
f"--- Long running function '{long_running_function_call.name}' was"
|
||||
" called, but its initial response was not captured. ---"
|
||||
)
|
||||
elif not long_running_function_call:
|
||||
print(
|
||||
"--- No long running function call was detected in the initial"
|
||||
" turn. ---"
|
||||
)
|
||||
|
||||
await call_agent("Please reimburse $50 for meals")
|
||||
print("=" * 70)
|
||||
await call_agent("Please reimburse $200 for conference travel")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
provider = TracerProvider()
|
||||
project_id = os.environ.get("GOOGLE_CLOUD_PROJECT")
|
||||
if not project_id:
|
||||
raise ValueError("GOOGLE_CLOUD_PROJECT environment variable is not set.")
|
||||
print("Tracing to project", project_id)
|
||||
processor = export.BatchSpanProcessor(
|
||||
CloudTraceSpanExporter(project_id=project_id)
|
||||
)
|
||||
provider.add_span_processor(processor)
|
||||
trace.set_tracer_provider(provider)
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
provider.force_flush()
|
||||
print("Done tracing to project", project_id)
|
||||
@@ -0,0 +1,75 @@
|
||||
# Application Integration Agent Sample with End-User Credentials
|
||||
|
||||
## Introduction
|
||||
|
||||
This sample demonstrates how to use the `ApplicationIntegrationToolset` within
|
||||
an ADK agent to interact with external applications using **end-user OAuth 2.0
|
||||
credentials**. Specifically, this agent (`agent.py`) is configured to interact
|
||||
with Google Calendar using a pre-configured Application Integration connection
|
||||
and authenticating as the end user.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Set up Integration Connection:**
|
||||
* You need an existing
|
||||
[Integration connection](https://cloud.google.com/integration-connectors/docs/overview)
|
||||
configured to interact with Google Calendar APIs. Follow the
|
||||
[documentation](https://google.github.io/adk-docs/tools/google-cloud-tools/#use-integration-connectors)
|
||||
to provision the Integration Connector in Google Cloud. You will need
|
||||
the `Connection Name`, `Project ID`, and `Location` of your connection.
|
||||
* Ensure the connection is configured to use Google Calendar (e.g., by
|
||||
enabling the `google-calendar-connector` or a similar connector).
|
||||
|
||||
2. **Configure OAuth 2.0 Client:**
|
||||
* You need an OAuth 2.0 Client ID and Client Secret that is authorized to
|
||||
access the required Google Calendar scopes (e.g.,
|
||||
`https://www.googleapis.com/auth/calendar.readonly`). You can create
|
||||
OAuth credentials in the Google Cloud Console under "APIs & Services"
|
||||
-> "Credentials".
|
||||
|
||||
3. **Configure Environment Variables:**
|
||||
* Create a `.env` file in the same directory as `agent.py` (or add to
|
||||
your existing one).
|
||||
* Add the following variables to the `.env` file, replacing the
|
||||
placeholder values with your actual connection details:
|
||||
|
||||
```dotenv
|
||||
CONNECTION_NAME=<YOUR_CALENDAR_CONNECTION_NAME>
|
||||
CONNECTION_PROJECT=<YOUR_GOOGLE_CLOUD_PROJECT_ID>
|
||||
CONNECTION_LOCATION=<YOUR_CONNECTION_LOCATION>
|
||||
CLIENT_ID=<YOUR_OAUTH_CLIENT_ID>
|
||||
CLIENT_SECRET=<YOUR_OAUTH_CLIENT_SECRET>
|
||||
```
|
||||
|
||||
## End-User Authentication (OAuth 2.0)
|
||||
|
||||
This agent utilizes the `AuthCredential` and `OAuth2Auth` classes from the ADK
|
||||
to handle authentication.
|
||||
* It defines an OAuth 2.0 scheme (`oauth2_scheme`) based on Google Cloud's
|
||||
OAuth endpoints and required scopes.
|
||||
* It uses the `CLIENT_ID` and `CLIENT_SECRET` from the environment variables
|
||||
(or hardcoded values in the sample) to configure `OAuth2Auth`.
|
||||
* This `AuthCredential` is passed to the `ApplicationIntegrationToolset`,
|
||||
enabling the tool to make authenticated API calls to Google Calendar on
|
||||
behalf of the user running the agent. The ADK framework will typically
|
||||
handle the OAuth flow (e.g., prompting the user for consent) when the tool
|
||||
is first invoked.
|
||||
|
||||
## How to Use
|
||||
|
||||
1. **Install Dependencies:** Ensure you have the necessary libraries installed
|
||||
(e.g., `google-adk`, `python-dotenv`).
|
||||
2. **Run the Agent:** Execute the agent script from your terminal:
|
||||
```bash
|
||||
python agent.py
|
||||
```
|
||||
3. **Interact:** Once the agent starts, you can interact with it. If it's the
|
||||
first time using the tool requiring OAuth, you might be prompted to go
|
||||
through the OAuth consent flow in your browser. After successful
|
||||
authentication, you can ask the agent to perform tasks.
|
||||
|
||||
## Sample Prompts
|
||||
|
||||
Here are some examples of how you can interact with the agent:
|
||||
|
||||
* `Can you list events from my primary calendar?`
|
||||
@@ -0,0 +1 @@
|
||||
from . import agent
|
||||
@@ -0,0 +1,95 @@
|
||||
# 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
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from google.adk import Agent
|
||||
from google.adk.auth import AuthCredential
|
||||
from google.adk.auth import AuthCredentialTypes
|
||||
from google.adk.auth import OAuth2Auth
|
||||
from google.adk.tools.application_integration_tool.application_integration_toolset import ApplicationIntegrationToolset
|
||||
from google.adk.tools.openapi_tool.auth.auth_helpers import dict_to_auth_scheme
|
||||
from google.genai import types
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
connection_name = os.getenv("CONNECTION_NAME")
|
||||
connection_project = os.getenv("CONNECTION_PROJECT")
|
||||
connection_location = os.getenv("CONNECTION_LOCATION")
|
||||
client_secret = os.getenv("CLIENT_SECRET")
|
||||
client_id = os.getenv("CLIENT_ID")
|
||||
|
||||
|
||||
oauth2_data_google_cloud = {
|
||||
"type": "oauth2",
|
||||
"flows": {
|
||||
"authorizationCode": {
|
||||
"authorizationUrl": "https://accounts.google.com/o/oauth2/auth",
|
||||
"tokenUrl": "https://oauth2.googleapis.com/token",
|
||||
"scopes": {
|
||||
"https://www.googleapis.com/auth/cloud-platform": (
|
||||
"View and manage your data across Google Cloud Platform"
|
||||
" services"
|
||||
),
|
||||
"https://www.googleapis.com/auth/calendar.readonly": (
|
||||
"View your calendars"
|
||||
),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
oauth2_scheme = dict_to_auth_scheme(oauth2_data_google_cloud)
|
||||
|
||||
auth_credential = AuthCredential(
|
||||
auth_type=AuthCredentialTypes.OAUTH2,
|
||||
oauth2=OAuth2Auth(
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
),
|
||||
)
|
||||
|
||||
calendar_tool = ApplicationIntegrationToolset(
|
||||
project=connection_project,
|
||||
location=connection_location,
|
||||
tool_name_prefix="calendar_tool",
|
||||
connection=connection_name,
|
||||
actions=["GET_calendars/%7BcalendarId%7D/events"],
|
||||
tool_instructions="""
|
||||
Use this tool to list events in a calendar. Get calendarId from the user and use it in tool as following example:
|
||||
connectorInputPayload: { "Path parameters": { "calendarId": "primary" } }. Follow the schema correctly. Note its "Path parameters" and not "Path_parameters".
|
||||
""",
|
||||
auth_scheme=oauth2_scheme,
|
||||
auth_credential=auth_credential,
|
||||
)
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-2.0-flash",
|
||||
name="data_processing_agent",
|
||||
description="Agent that can list events in a calendar.",
|
||||
instruction="""
|
||||
Helps you with calendar related tasks.
|
||||
""",
|
||||
tools=calendar_tool.get_tools(),
|
||||
generate_content_config=types.GenerateContentConfig(
|
||||
safety_settings=[
|
||||
types.SafetySetting(
|
||||
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
|
||||
threshold=types.HarmBlockThreshold.OFF,
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
This agent connects to a local MCP server via sse.
|
||||
|
||||
To run this agent, start the local MCP server first by :
|
||||
|
||||
```bash
|
||||
uv run filesystem_server.py
|
||||
```
|
||||
|
||||
@@ -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,58 @@
|
||||
# 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
|
||||
|
||||
from google.adk.agents.llm_agent import LlmAgent
|
||||
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPServerParams
|
||||
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
|
||||
from google.adk.tools.mcp_tool.mcp_toolset import SseServerParams
|
||||
|
||||
_allowed_path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
root_agent = LlmAgent(
|
||||
model='gemini-2.0-flash',
|
||||
name='enterprise_assistant',
|
||||
instruction=f"""\
|
||||
Help user accessing their file systems.
|
||||
|
||||
Allowed directory: {_allowed_path}
|
||||
""",
|
||||
tools=[
|
||||
MCPToolset(
|
||||
connection_params=StreamableHTTPServerParams(
|
||||
url='http://localhost:3000/mcp',
|
||||
),
|
||||
# don't want agent to do write operation
|
||||
# you can also do below
|
||||
# tool_filter=lambda tool, ctx=None: tool.name
|
||||
# not in [
|
||||
# 'write_file',
|
||||
# 'edit_file',
|
||||
# 'create_directory',
|
||||
# 'move_file',
|
||||
# ],
|
||||
tool_filter=[
|
||||
'read_file',
|
||||
'read_multiple_files',
|
||||
'list_directory',
|
||||
'directory_tree',
|
||||
'search_files',
|
||||
'get_file_info',
|
||||
'list_allowed_directories',
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
@@ -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 os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# Create an MCP server with a name
|
||||
mcp = FastMCP("Filesystem Server", host="localhost", port=3000)
|
||||
|
||||
|
||||
# Add a tool to read file contents
|
||||
@mcp.tool(description="Read contents of a file")
|
||||
def read_file(filepath: str) -> str:
|
||||
"""Read and return the contents of a file."""
|
||||
with open(filepath, "r") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
# Add a tool to list directory contents
|
||||
@mcp.tool(description="List contents of a directory")
|
||||
def list_directory(dirpath: str) -> list:
|
||||
"""List all files and directories in the given directory."""
|
||||
return os.listdir(dirpath)
|
||||
|
||||
|
||||
# Add a tool to get current working directory
|
||||
@mcp.tool(description="Get current working directory")
|
||||
def get_cwd() -> str:
|
||||
"""Return the current working directory."""
|
||||
return str(Path.cwd())
|
||||
|
||||
|
||||
# Graceful shutdown handler
|
||||
async def shutdown(signal, loop):
|
||||
"""Cleanup tasks tied to the service's shutdown."""
|
||||
print(f"\nReceived exit signal {signal.name}...")
|
||||
|
||||
# Get all running tasks
|
||||
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
|
||||
|
||||
# Cancel all tasks
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
|
||||
print(f"Cancelling {len(tasks)} outstanding tasks")
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
# Stop the loop
|
||||
loop.stop()
|
||||
print("Shutdown complete!")
|
||||
|
||||
|
||||
# Main entry point with graceful shutdown handling
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
# The MCP run function ultimately uses asyncio.run() internally
|
||||
mcp.run(transport="streamable-http")
|
||||
except KeyboardInterrupt:
|
||||
print("\nServer shutting down gracefully...")
|
||||
# The asyncio event loop has already been stopped by the KeyboardInterrupt
|
||||
print("Server has been shut down.")
|
||||
except Exception as e:
|
||||
print(f"Unexpected error: {e}")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
print("Thank you for using the Filesystem MCP Server!")
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user