You've already forked adk-python
mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
Merge branch 'main' into fix_graph
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"contextFileName": "AGENTS.md"
|
||||
}
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
|
||||
# Use grep -L to find files that DO NOT contain the pattern.
|
||||
# This command will output a list of non-compliant files.
|
||||
FILES_MISSING_IMPORT=$(grep -L 'from __future__ import annotations' $CHANGED_FILES)
|
||||
FILES_MISSING_IMPORT=$(grep -L 'from __future__ import annotations' $CHANGED_FILES || true)
|
||||
|
||||
# Check if the list of non-compliant files is empty
|
||||
if [ -z "$FILES_MISSING_IMPORT" ]; then
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# .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!"
|
||||
@@ -40,4 +40,5 @@ jobs:
|
||||
ISSUE_TITLE: ${{ github.event.issue.title }}
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
ISSUE_COUNT_TO_PROCESS: '3' # Process 3 issues at a time on schedule
|
||||
run: python contributing/samples/adk_triaging_agent/main.py
|
||||
PYTHONPATH: contributing/samples
|
||||
run: python -m adk_triaging_agent.main
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
# Gemini CLI / Gemini Code Assist Context
|
||||
|
||||
This document provides context for the Gemini CLI and Gemini Code Assist to understand the project and assist with development.
|
||||
|
||||
## Project Overview
|
||||
|
||||
The Agent Development Kit (ADK) is an open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control. While optimized for Gemini and the Google ecosystem, ADK is model-agnostic, deployment-agnostic, and is built for compatibility with other frameworks. ADK was designed to make agent development feel more like software development, to make it easier for developers to create, deploy, and orchestrate agentic architectures that range from simple tasks to complex workflows.
|
||||
|
||||
## ADK: Style Guides
|
||||
|
||||
### Python Style Guide
|
||||
|
||||
The project follows the Google Python Style Guide. Key conventions are enforced using `pylint` with the provided `pylintrc` configuration file. Here are some of the key style points:
|
||||
|
||||
* **Indentation**: 2 spaces.
|
||||
* **Line Length**: Maximum 80 characters.
|
||||
* **Naming Conventions**:
|
||||
* `function_and_variable_names`: `snake_case`
|
||||
* `ClassNames`: `CamelCase`
|
||||
* `CONSTANTS`: `UPPERCASE_SNAKE_CASE`
|
||||
* **Docstrings**: Required for all public modules, functions, classes, and methods.
|
||||
* **Imports**: Organized and sorted.
|
||||
* **Error Handling**: Specific exceptions should be caught, not general ones like `Exception`.
|
||||
|
||||
### Autoformat
|
||||
|
||||
We have autoformat.sh to help solve import organize and formatting issues.
|
||||
|
||||
```bash
|
||||
# Run in open_source_workspace/
|
||||
$ ./autoformat.sh
|
||||
```
|
||||
|
||||
### In ADK source
|
||||
|
||||
Below styles applies to the ADK source code (under `src/` folder of the Github.
|
||||
repo).
|
||||
|
||||
#### Use relative imports
|
||||
|
||||
```python
|
||||
# DO
|
||||
from ..agents.llm_agent import LlmAgent
|
||||
|
||||
# DON'T
|
||||
from google.adk.agents.llm_agent import LlmAgent
|
||||
```
|
||||
|
||||
#### Import from module, not from `__init__.py`
|
||||
|
||||
```python
|
||||
# DO
|
||||
from ..agents.llm_agent import LlmAgent
|
||||
|
||||
# DON'T
|
||||
from ..agents import LlmAgent # import from agents/__init__.py
|
||||
```
|
||||
|
||||
#### Always do `from __future__ import annotations`
|
||||
|
||||
```python
|
||||
# DO THIS, right after the open-source header.
|
||||
from __future__ import annotations
|
||||
```
|
||||
|
||||
Like below:
|
||||
|
||||
```python
|
||||
# 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 __future__ import annotations
|
||||
|
||||
# ... the rest of the file.
|
||||
```
|
||||
|
||||
This allows us to forward-reference a class without quotes.
|
||||
|
||||
Check out go/pep563 for details.
|
||||
|
||||
### In ADK tests
|
||||
|
||||
#### Use absolute imports
|
||||
|
||||
In tests, we use `google.adk` same as how our users uses.
|
||||
|
||||
```python
|
||||
# DO
|
||||
from google.adk.agents.llm_agent import LlmAgent
|
||||
|
||||
# DON'T
|
||||
from ..agents.llm_agent import LlmAgent
|
||||
```
|
||||
|
||||
## ADK: Local testing
|
||||
|
||||
### Unit tests
|
||||
|
||||
Run below command:
|
||||
|
||||
```bash
|
||||
$ pytest tests/unittests
|
||||
```
|
||||
|
||||
## Docstring and comments
|
||||
|
||||
### Comments - Explaining the Why, Not the What
|
||||
Philosophy: Well-written code should be largely self-documenting. Comments
|
||||
serve a different purpose: they should explain the complex algorithms,
|
||||
non-obvious business logic, or the rationale behind a particular implementation
|
||||
choice—the things the code cannot express on its own. Avoid comments that
|
||||
merely restate what the code does (e.g., # increment i above i += 1).
|
||||
|
||||
Style: Comments should be written as complete sentences. Block comments must
|
||||
begin with a # followed by a single space.
|
||||
|
||||
## Versioning
|
||||
ADK adherence to Semantic Versioning 2.0.0
|
||||
|
||||
Core Principle: The adk-python project strictly adheres to the Semantic
|
||||
Versioning 2.0.0 specification. All release versions will follow the
|
||||
MAJOR.MINOR.PATCH format.
|
||||
|
||||
### Breaking Change
|
||||
|
||||
A breaking change is any modification that introduces backward-incompatible
|
||||
changes to the public API. In the context of the ADK, this means a change that
|
||||
could force a developer using the framework to alter their existing code to
|
||||
upgrade to the new version. The public API is not limited to just the Python
|
||||
function and class signatures; it also encompasses data schemas for stored
|
||||
information (like evaluation datasets), the command-line interface (CLI),
|
||||
and the data format used for server communications.
|
||||
|
||||
### Public API Surface Definition
|
||||
|
||||
The "public API" of ADK is a broad contract that extends beyond its Python
|
||||
function signatures. A breaking change in any of the following areas can
|
||||
disrupt user workflows and the wider ecosystem of agents and tools built with
|
||||
ADK. The analysis of the breaking changes introduced in v1.0.0 demonstrates the
|
||||
expansive nature of this contract. For the purposes of versioning, the ADK
|
||||
Public API Surface is defined as:
|
||||
|
||||
- All public classes, methods, and functions in the google.adk namespace.
|
||||
|
||||
- The names, required parameters, and expected behavior of all built-in Tools
|
||||
(e.g., google_search, BuiltInCodeExecutor).
|
||||
|
||||
- The structure and schema of persisted data, including Session data, Memory,
|
||||
and Evaluation datasets.
|
||||
|
||||
- The JSON request/response format of the ADK API server(FastAPI server)
|
||||
used by adk web, including field casing conventions.
|
||||
|
||||
- The command-line interface (CLI) commands, arguments, and flags (e.g., adk deploy).
|
||||
|
||||
- The expected file structure for agent definitions that are loaded by the
|
||||
framework (e.g., the agent.py convention).
|
||||
|
||||
#### Checklist for Breaking Changes:
|
||||
|
||||
The following changes are considered breaking and necessitate a MAJOR version
|
||||
bump.
|
||||
|
||||
- API Signature Change: Renaming, removing, or altering the required parameters
|
||||
of any public class, method, or function (e.g., the removal of the list_events
|
||||
method from BaseSessionService).
|
||||
|
||||
- Architectural Shift: A fundamental change to a core component's behavior
|
||||
(e.g., making all service methods async, which requires consumers to use await).
|
||||
|
||||
- Data Schema Change: A non-additive change to a persisted data schema that
|
||||
renders old data unreadable or invalid (e.g., the redesign of the
|
||||
MemoryService and evaluation dataset schemas).
|
||||
|
||||
- Tool Interface Change: Renaming a built-in tool, changing its required
|
||||
parameters, or altering its fundamental purpose (e.g., replacing
|
||||
BuiltInCodeExecutionTool with BuiltInCodeExecutor and moving it from the tools
|
||||
parameter to the code_executor parameter of an Agent).
|
||||
|
||||
- Configuration Change: Altering the required structure of configuration files
|
||||
or agent definition files that the framework loads (e.g., the simplification
|
||||
of the agent.py structure for MCPToolset).
|
||||
|
||||
- Wire Format Change: Modifying the data format for API server interactions
|
||||
(e.g., the switch from snake_case to camelCase for all JSON payloads).
|
||||
|
||||
- Dependency Removal: Removing support for a previously integrated third-party
|
||||
library or tool type.
|
||||
+4
-3
@@ -49,6 +49,7 @@ This project follows
|
||||
|
||||
## Requirement for PRs
|
||||
|
||||
- Each PR should only have one commit. Please squash it if there are multiple PRs.
|
||||
- All PRs, other than small documentation or typo fixes, should have a Issue assoicated. If not, please create one.
|
||||
- Small, focused PRs. Keep changes minimal—one concern per PR.
|
||||
- For bug fixes or features, please provide logs or screenshot after the fix is applied to help reviewers better understand the fix.
|
||||
@@ -147,11 +148,11 @@ For any changes that impact user-facing documentation (guides, API reference, tu
|
||||
pytest ./tests/unittests
|
||||
```
|
||||
|
||||
NOTE: for accurately repro test failure, only include `test` and `eval` as
|
||||
extra dependencies.
|
||||
NOTE: for accurate repro of test failure, only include `test`, `eval` and
|
||||
`a2a` as extra dependencies.
|
||||
|
||||
```shell
|
||||
uv sync --extra test --extra eval
|
||||
uv sync --extra test --extra eval --extra a2a
|
||||
pytest ./tests/unittests
|
||||
```
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ Agent Development Kit (ADK) is a flexible and modular framework for developing a
|
||||
|
||||
For remote agent-to-agent communication, ADK integrates with the
|
||||
[A2A protocol](https://github.com/google-a2a/A2A/).
|
||||
See this [example](https://github.com/google-a2a/a2a-samples/tree/main/samples/python/agents/google_adk)
|
||||
See this [example](https://github.com/a2aproject/a2a-samples/tree/main/samples/python/agents)
|
||||
for how they can work together.
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
build_llms_txt.py – produce llms.txt and llms-full.txt
|
||||
– skips ```java``` blocks
|
||||
– README can be next to docs/ or inside docs/
|
||||
– includes Python API reference from HTML files
|
||||
– includes adk-python repository README
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
RE_JAVA = re.compile(r"```java[ \t\r\n][\s\S]*?```", re.I | re.M)
|
||||
RE_SNIPPET = re.compile(r"^(\s*)--8<--\s+\"([^\"]+?)(?::([^\"]+))?\"$", re.M)
|
||||
|
||||
|
||||
def fetch_adk_python_readme() -> str:
|
||||
"""Fetch README content from adk-python repository"""
|
||||
try:
|
||||
url = "https://raw.githubusercontent.com/google/adk-python/main/README.md"
|
||||
with urllib.request.urlopen(url) as response:
|
||||
return response.read().decode("utf-8")
|
||||
except (urllib.error.URLError, urllib.error.HTTPError) as e:
|
||||
print(f"Warning: Could not fetch adk-python README: {e}")
|
||||
return ""
|
||||
|
||||
|
||||
def strip_java(md: str) -> str:
|
||||
return RE_JAVA.sub("", md)
|
||||
|
||||
|
||||
def first_heading(md: str) -> str | None:
|
||||
for line in md.splitlines():
|
||||
if line.startswith("#"):
|
||||
return line.lstrip("#").strip()
|
||||
return None
|
||||
|
||||
|
||||
def md_to_text(md: str) -> str:
|
||||
import bs4
|
||||
import markdown
|
||||
|
||||
html = markdown.markdown(
|
||||
md, extensions=["fenced_code", "tables", "attr_list"]
|
||||
)
|
||||
return bs4.BeautifulSoup(html, "html.parser").get_text("\n")
|
||||
|
||||
|
||||
def html_to_text(html_file: Path) -> str:
|
||||
"""Extract text content from HTML files (for Python API reference)"""
|
||||
import bs4
|
||||
|
||||
try:
|
||||
html_content = html_file.read_text(encoding="utf-8")
|
||||
soup = bs4.BeautifulSoup(html_content, "html.parser")
|
||||
|
||||
# Remove script and style elements
|
||||
for script in soup(["script", "style"]):
|
||||
script.decompose()
|
||||
|
||||
# Get text and clean it up
|
||||
text = soup.get_text()
|
||||
lines = (line.strip() for line in text.splitlines())
|
||||
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
|
||||
text = "\n".join(chunk for chunk in chunks if chunk)
|
||||
|
||||
return text
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not process {html_file}: {e}")
|
||||
return ""
|
||||
|
||||
|
||||
def count_tokens(text: str, model: str = "cl100k_base") -> int:
|
||||
try:
|
||||
import tiktoken
|
||||
|
||||
return len(tiktoken.get_encoding(model).encode(text))
|
||||
except Exception:
|
||||
return len(text.split())
|
||||
|
||||
|
||||
def expand_code_snippets(content: str, project_root: Path) -> str:
|
||||
"""
|
||||
Expands code snippets marked with --8<-- "path/to/file.py" or
|
||||
--8<-- "path/to/file.py:section_name" into the content.
|
||||
"""
|
||||
|
||||
def replace_snippet(match):
|
||||
indent = match.group(1) # Capture leading spaces
|
||||
snippet_path_str = match.group(
|
||||
2
|
||||
) # Capture the file path (e.g., "examples/python/snippets/file.py")
|
||||
section_name = match.group(
|
||||
3
|
||||
) # Capture the section name if present (e.g., "init")
|
||||
snippet_full_path = (
|
||||
project_root / snippet_path_str
|
||||
) # Changed from base_path to project_root
|
||||
|
||||
# If not found in project root, try adk-docs directory
|
||||
if not snippet_full_path.exists():
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
adk_docs_path = script_dir / "adk-docs" / snippet_path_str
|
||||
if adk_docs_path.exists():
|
||||
snippet_full_path = adk_docs_path
|
||||
|
||||
if snippet_full_path.exists():
|
||||
try:
|
||||
file_content = snippet_full_path.read_text(encoding="utf-8")
|
||||
if section_name:
|
||||
# Extract content based on section markers
|
||||
# Handle both single and double hash markers with optional spacing
|
||||
start_marker_patterns = [
|
||||
f"# --8<-- [start:{section_name.strip()}]",
|
||||
f"## --8<-- [start:{section_name.strip()}]",
|
||||
]
|
||||
end_marker_patterns = [
|
||||
f"# --8<-- [end:{section_name.strip()}]",
|
||||
f"## --8<-- [end:{section_name.strip()}]",
|
||||
f"## --8<-- [end:{section_name.strip()}]", # Handle extra space
|
||||
]
|
||||
|
||||
start_index = -1
|
||||
end_index = -1
|
||||
|
||||
# Find start marker
|
||||
for pattern in start_marker_patterns:
|
||||
start_index = file_content.find(pattern)
|
||||
if start_index != -1:
|
||||
start_marker = pattern
|
||||
break
|
||||
|
||||
# Find end marker
|
||||
for pattern in end_marker_patterns:
|
||||
end_index = file_content.find(pattern)
|
||||
if end_index != -1:
|
||||
break
|
||||
|
||||
if start_index != -1 and end_index != -1 and start_index < end_index:
|
||||
# Adjust start_index to begin immediately after the start_marker
|
||||
start_of_code = start_index + len(start_marker)
|
||||
temp_content = file_content[start_of_code:end_index]
|
||||
lines = temp_content.splitlines(keepends=True)
|
||||
extracted_lines = []
|
||||
for line in lines:
|
||||
if (
|
||||
not line.strip().startswith("# --8<--")
|
||||
and not line.strip().startswith("## --8<--")
|
||||
and line.strip() != ""
|
||||
):
|
||||
extracted_lines.append(line)
|
||||
extracted_content = "".join(extracted_lines).strip("\n")
|
||||
|
||||
return textwrap.indent(extracted_content, indent)
|
||||
else:
|
||||
print(
|
||||
f"Warning: Section '{section_name}' not found or markers"
|
||||
f" malformed in {snippet_full_path}"
|
||||
)
|
||||
return match.group(0)
|
||||
else:
|
||||
# Read entire file if no section name
|
||||
return textwrap.indent(file_content, indent)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read snippet file {snippet_full_path}: {e}")
|
||||
return match.group(0)
|
||||
else:
|
||||
print(f"Warning: Snippet file not found: {snippet_full_path}")
|
||||
return match.group(0)
|
||||
|
||||
expanded_content = RE_SNIPPET.sub(replace_snippet, content)
|
||||
return expanded_content
|
||||
|
||||
|
||||
# ---------- index (llms.txt) ----------
|
||||
def build_index(docs: Path) -> str:
|
||||
# Locate README
|
||||
for cand in (docs / "README.md", docs.parent / "README.md"):
|
||||
if cand.exists():
|
||||
readme = cand.read_text(encoding="utf-8")
|
||||
break
|
||||
else:
|
||||
sys.exit("README.md not found in docs/ or its parent")
|
||||
|
||||
title = first_heading(readme) or "Documentation"
|
||||
summary = md_to_text(readme).split("\n\n")[0]
|
||||
lines = [f"# {title}", "", f"> {summary}", ""]
|
||||
|
||||
# Add adk-python repository README content
|
||||
adk_readme = fetch_adk_python_readme()
|
||||
if adk_readme:
|
||||
lines.append("## ADK Python Repository")
|
||||
lines.append("")
|
||||
# Include the full README content, properly formatted
|
||||
adk_text = md_to_text(strip_java(adk_readme))
|
||||
lines.append(adk_text)
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"**Source:** [adk-python"
|
||||
f" repository](https://github.com/google/adk-python)"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
primary: List[Tuple[str, str]] = []
|
||||
secondary: List[Tuple[str, str]] = []
|
||||
|
||||
# Process Markdown files
|
||||
for md in sorted(docs.rglob("*.md")):
|
||||
# Skip Java API reference files
|
||||
if "api-reference" in md.parts and "java" in md.parts:
|
||||
continue
|
||||
|
||||
rel = md.relative_to(docs)
|
||||
# Construct the correct GitHub URL for the Markdown file
|
||||
url = f"https://github.com/google/adk-docs/blob/main/docs/{rel}".replace(
|
||||
" ", "%20"
|
||||
)
|
||||
h = first_heading(strip_java(md.read_text(encoding="utf-8"))) or rel.stem
|
||||
(
|
||||
secondary
|
||||
if "sample" in rel.parts or "tutorial" in rel.parts
|
||||
else primary
|
||||
).append((h, url))
|
||||
|
||||
# Add Python API reference
|
||||
python_api_dir = docs / "api-reference" / "python"
|
||||
if python_api_dir.exists():
|
||||
primary.append((
|
||||
"Python API Reference",
|
||||
"https://github.com/google/adk-docs/blob/main/docs/api-reference/python/",
|
||||
))
|
||||
|
||||
def emit(name: str, items: List[Tuple[str, str]]):
|
||||
nonlocal lines
|
||||
if items:
|
||||
lines.append(f"## {name}")
|
||||
lines += [f"- [{h}]({u})" for h, u in items]
|
||||
lines.append("")
|
||||
|
||||
emit("Documentation", primary)
|
||||
emit("Optional", secondary)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# ---------- full corpus ----------
|
||||
def build_full(docs: Path) -> str:
|
||||
out = []
|
||||
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
project_root = script_dir.parents[2] # Correct project root
|
||||
print(f"DEBUG: Project Root: {project_root}")
|
||||
print(f"DEBUG: Docs Dir: {docs}")
|
||||
|
||||
# Add adk-python repository README content at the beginning
|
||||
adk_readme = fetch_adk_python_readme()
|
||||
if adk_readme:
|
||||
# Expand snippets in README if any
|
||||
expanded_adk_readme = expand_code_snippets(
|
||||
strip_java(adk_readme), project_root
|
||||
) # Pass project_root
|
||||
out.append("# ADK Python Repository")
|
||||
out.append("")
|
||||
out.append(expanded_adk_readme) # Use expanded content
|
||||
out.append("")
|
||||
out.append("---")
|
||||
out.append("")
|
||||
|
||||
# Process Markdown files
|
||||
for md in sorted(docs.rglob("*.md")):
|
||||
# Skip Java API reference files
|
||||
if "api-reference" in md.parts and "java" in md.parts:
|
||||
continue
|
||||
|
||||
md_content = md.read_text(encoding="utf-8")
|
||||
print(f"DEBUG: Processing markdown file: {md.relative_to(docs)}")
|
||||
expanded_md_content = expand_code_snippets(
|
||||
strip_java(md_content), project_root
|
||||
) # Changed back to project_root
|
||||
out.append(expanded_md_content) # Use expanded content
|
||||
|
||||
# Process Python API reference HTML files
|
||||
python_api_dir = docs / "api-reference" / "python"
|
||||
if python_api_dir.exists():
|
||||
# Add a separator and header for Python API reference
|
||||
out.append("\n\n# Python API Reference\n")
|
||||
|
||||
# Process main HTML files (skip static assets and generated files)
|
||||
html_files = [
|
||||
python_api_dir / "index.html",
|
||||
python_api_dir / "google-adk.html",
|
||||
python_api_dir / "genindex.html",
|
||||
python_api_dir / "py-modindex.html",
|
||||
]
|
||||
|
||||
for html_file in html_files:
|
||||
if html_file.exists():
|
||||
text = html_to_text(html_file)
|
||||
if text.strip():
|
||||
out.append(f"\n## {html_file.stem}\n")
|
||||
out.append(text)
|
||||
|
||||
return "\n\n".join(out)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(
|
||||
description="Generate llms.txt / llms-full.txt",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
ap.add_argument("--docs-dir", required=True, type=Path)
|
||||
ap.add_argument("--out-root", default=Path("."), type=Path)
|
||||
ap.add_argument("--index-limit", type=int, default=50_000)
|
||||
ap.add_argument("--full-limit", type=int, default=500_000)
|
||||
args = ap.parse_args()
|
||||
|
||||
idx, full = build_index(args.docs_dir), build_full(args.docs_dir)
|
||||
if (tok := count_tokens(idx)) > args.index_limit:
|
||||
sys.exit(f"Index too big: {tok:,}")
|
||||
if (tok := count_tokens(full)) > args.full_limit:
|
||||
sys.exit(f"Full text too big: {tok:,}")
|
||||
|
||||
(args.out_root / "llms.txt").write_text(idx, encoding="utf-8")
|
||||
(args.out_root / "llms-full.txt").write_text(full, encoding="utf-8")
|
||||
print("✅ Generated llms.txt and llms-full.txt successfully")
|
||||
print(f"llms.txt tokens: {count_tokens(idx)}")
|
||||
print(f"llms-full.txt tokens: {count_tokens(full)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,183 @@
|
||||
# A2A OAuth Authentication Sample Agent
|
||||
|
||||
This sample demonstrates the **Agent-to-Agent (A2A)** architecture with **OAuth Authentication** workflows in the Agent Development Kit (ADK). The sample implements a multi-agent system where a remote agent can surface OAuth authentication requests to the local agent, which then guides the end user through the OAuth flow before returning the authentication credentials to the remote agent for API access.
|
||||
|
||||
## Overview
|
||||
|
||||
The A2A OAuth Authentication sample consists of:
|
||||
|
||||
- **Root Agent** (`root_agent`): The main orchestrator that handles user requests and delegates tasks to specialized agents
|
||||
- **YouTube Search Agent** (`youtube_search_agent`): A local agent that handles YouTube video searches using LangChain tools
|
||||
- **BigQuery Agent** (`bigquery_agent`): A remote A2A agent that manages BigQuery operations and requires OAuth authentication for Google Cloud access
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌────────────────────┐ ┌──────────────────┐
|
||||
│ End User │───▶│ Root Agent │───▶│ BigQuery Agent │
|
||||
│ (OAuth Flow) │ │ (Local) │ │ (Remote A2A) │
|
||||
│ │ │ │ │ (localhost:8001) │
|
||||
│ OAuth UI │◀───│ │◀───│ OAuth Request │
|
||||
└─────────────────┘ └────────────────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. **Multi-Agent Architecture**
|
||||
- Root agent coordinates between local YouTube search and remote BigQuery operations
|
||||
- Demonstrates hybrid local/remote agent workflows
|
||||
- Seamless task delegation based on user request types
|
||||
|
||||
### 2. **OAuth Authentication Workflow**
|
||||
- Remote BigQuery agent surfaces OAuth authentication requests to the root agent
|
||||
- Root agent guides end users through Google OAuth flow for BigQuery access
|
||||
- Secure token exchange between agents for authenticated API calls
|
||||
|
||||
### 3. **Google Cloud Integration**
|
||||
- BigQuery toolset with comprehensive dataset and table management capabilities
|
||||
- OAuth-protected access to user's Google Cloud BigQuery resources
|
||||
- Support for listing, creating, and managing datasets and tables
|
||||
|
||||
### 4. **LangChain Tool Integration**
|
||||
- YouTube search functionality using LangChain community tools
|
||||
- Demonstrates integration of third-party tools in agent workflows
|
||||
|
||||
## Setup and Usage
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Set up OAuth Credentials**:
|
||||
```bash
|
||||
export OAUTH_CLIENT_ID=your_google_oauth_client_id
|
||||
export OAUTH_CLIENT_SECRET=your_google_oauth_client_secret
|
||||
```
|
||||
|
||||
2. **Start the Remote BigQuery Agent server**:
|
||||
```bash
|
||||
# Start the remote a2a server that serves the BigQuery agent on port 8001
|
||||
adk api_server --a2a --port 8001 contributing/samples/a2a_auth/remote_a2a
|
||||
```
|
||||
|
||||
3. **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:
|
||||
|
||||
**YouTube Search (No Authentication Required):**
|
||||
```
|
||||
User: Search for 3 Taylor Swift music videos
|
||||
Agent: I'll help you search for Taylor Swift music videos on YouTube.
|
||||
[Agent delegates to YouTube Search Agent]
|
||||
Agent: I found 3 Taylor Swift music videos:
|
||||
1. "Anti-Hero" - Official Music Video
|
||||
2. "Shake It Off" - Official Music Video
|
||||
3. "Blank Space" - Official Music Video
|
||||
```
|
||||
|
||||
**BigQuery Operations (OAuth Required):**
|
||||
```
|
||||
User: List my BigQuery datasets
|
||||
Agent: I'll help you access your BigQuery datasets. This requires authentication with your Google account.
|
||||
[Agent delegates to BigQuery Agent]
|
||||
Agent: To access your BigQuery data, please complete the OAuth authentication.
|
||||
[OAuth flow initiated - user redirected to Google authentication]
|
||||
User: [Completes OAuth flow in browser]
|
||||
Agent: Authentication successful! Here are your BigQuery datasets:
|
||||
- dataset_1: Customer Analytics
|
||||
- dataset_2: Sales Data
|
||||
- dataset_3: Marketing Metrics
|
||||
```
|
||||
|
||||
**Dataset Management:**
|
||||
```
|
||||
User: Show me details for my Customer Analytics dataset
|
||||
Agent: I'll get the details for your Customer Analytics dataset.
|
||||
[Using existing OAuth token]
|
||||
Agent: Customer Analytics Dataset Details:
|
||||
- Created: 2024-01-15
|
||||
- Location: US
|
||||
- Tables: 5
|
||||
- Description: Customer behavior and analytics data
|
||||
```
|
||||
|
||||
## Code Structure
|
||||
|
||||
### Main Agent (`agent.py`)
|
||||
|
||||
- **`youtube_search_agent`**: Local agent with LangChain YouTube search tool
|
||||
- **`bigquery_agent`**: Remote A2A agent configuration for BigQuery operations
|
||||
- **`root_agent`**: Main orchestrator with task delegation logic
|
||||
|
||||
### Remote BigQuery Agent (`remote_a2a/bigquery_agent/`)
|
||||
|
||||
- **`agent.py`**: Implementation of the BigQuery agent with OAuth toolset
|
||||
- **`agent.json`**: Agent card of the A2A agent
|
||||
- **`BigQueryToolset`**: OAuth-enabled tools for BigQuery dataset and table management
|
||||
|
||||
## OAuth Authentication Workflow
|
||||
|
||||
The OAuth authentication process follows this pattern:
|
||||
|
||||
1. **Initial Request**: User requests BigQuery operation through root agent
|
||||
2. **Delegation**: Root agent delegates to remote BigQuery agent
|
||||
3. **Auth Check**: BigQuery agent checks for valid OAuth token
|
||||
4. **Auth Request**: If no token, agent surfaces OAuth request to root agent
|
||||
5. **User OAuth**: Root agent guides user through Google OAuth flow
|
||||
6. **Token Exchange**: Root agent sends OAuth token to BigQuery agent
|
||||
7. **API Call**: BigQuery agent uses token to make authenticated API calls
|
||||
8. **Result Return**: BigQuery agent returns results through root agent to user
|
||||
|
||||
## Supported BigQuery Operations
|
||||
|
||||
The BigQuery agent supports the following operations:
|
||||
|
||||
### Dataset Operations:
|
||||
- **List Datasets**: `bigquery_datasets_list` - Get all user's datasets
|
||||
- **Get Dataset**: `bigquery_datasets_get` - Get specific dataset details
|
||||
- **Create Dataset**: `bigquery_datasets_insert` - Create new dataset
|
||||
|
||||
### Table Operations:
|
||||
- **List Tables**: `bigquery_tables_list` - Get tables in a dataset
|
||||
- **Get Table**: `bigquery_tables_get` - Get specific table details
|
||||
- **Create Table**: `bigquery_tables_insert` - Create new table in dataset
|
||||
|
||||
## Extending the Sample
|
||||
|
||||
You can extend this sample by:
|
||||
|
||||
- Adding more Google Cloud services (Cloud Storage, Compute Engine, etc.)
|
||||
- Implementing token refresh and expiration handling
|
||||
- Adding role-based access control for different BigQuery operations
|
||||
- Creating OAuth flows for other providers (Microsoft, Facebook, etc.)
|
||||
- Adding audit logging for authentication events
|
||||
- Implementing multi-tenant OAuth token management
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Connection Issues:**
|
||||
- Ensure the local ADK web server is running on port 8000
|
||||
- Ensure the remote A2A server is running on port 8001
|
||||
- Check that no firewall is blocking localhost connections
|
||||
- Verify the agent.json URL matches the running A2A server
|
||||
|
||||
**OAuth Issues:**
|
||||
- Verify OAuth client ID and secret are correctly set in .env file
|
||||
- Ensure OAuth redirect URIs are properly configured in Google Cloud Console
|
||||
- Check that the OAuth scopes include BigQuery access permissions
|
||||
- Verify the user has access to the BigQuery projects/datasets
|
||||
|
||||
**BigQuery Access Issues:**
|
||||
- Ensure the authenticated user has BigQuery permissions
|
||||
- Check that the Google Cloud project has BigQuery API enabled
|
||||
- Verify dataset and table names are correct and accessible
|
||||
- Check for quota limits on BigQuery API calls
|
||||
|
||||
**Agent Communication Issues:**
|
||||
- Check the logs for both the local ADK web server and remote A2A server
|
||||
- Verify OAuth tokens are properly passed between agents
|
||||
- Ensure agent instructions are clear about authentication requirements
|
||||
Executable → Regular
@@ -0,0 +1,62 @@
|
||||
# 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.adk.agents import Agent
|
||||
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
|
||||
from google.adk.tools.langchain_tool import LangchainTool
|
||||
from langchain_community.tools import YouTubeSearchTool
|
||||
|
||||
# Instantiate the tool
|
||||
langchain_yt_tool = YouTubeSearchTool()
|
||||
|
||||
# Wrap the tool in the LangchainTool class from ADK
|
||||
adk_yt_tool = LangchainTool(
|
||||
tool=langchain_yt_tool,
|
||||
)
|
||||
|
||||
youtube_search_agent = Agent(
|
||||
name="youtube_search_agent",
|
||||
model="gemini-2.0-flash", # Replace with the actual model name
|
||||
instruction="""
|
||||
Ask customer to provide singer name, and the number of videos to search.
|
||||
""",
|
||||
description="Help customer to search for a video on Youtube.",
|
||||
tools=[adk_yt_tool],
|
||||
output_key="youtube_search_output",
|
||||
)
|
||||
|
||||
bigquery_agent = RemoteA2aAgent(
|
||||
name="bigquery_agent",
|
||||
description="Help customer to manage notion workspace.",
|
||||
agent_card=(
|
||||
"http://localhost:8001/a2a/bigquery_agent/.well-known/agent.json"
|
||||
),
|
||||
)
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-2.0-flash",
|
||||
name="root_agent",
|
||||
instruction="""
|
||||
You are a helpful assistant that can help search youtube videos, look up BigQuery datasets and tables.
|
||||
You delegate youtube search tasks to the youtube_search_agent.
|
||||
You delegate BigQuery tasks to the bigquery_agent.
|
||||
Always clarify the results before proceeding.
|
||||
""",
|
||||
global_instruction=(
|
||||
"You are a helpful assistant that can help search youtube videos, look"
|
||||
" up BigQuery datasets and tables."
|
||||
),
|
||||
sub_agents=[youtube_search_agent, bigquery_agent],
|
||||
)
|
||||
@@ -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,29 @@
|
||||
{
|
||||
"capabilities": {},
|
||||
"defaultInputModes": ["text/plain"],
|
||||
"defaultOutputModes": ["application/json"],
|
||||
"description": "A Google BigQuery agent that helps manage users' data on Google BigQuery. Can list, get, and create datasets, as well as manage tables within datasets. Supports OAuth authentication for secure access to BigQuery resources.",
|
||||
"name": "bigquery_agent",
|
||||
"skills": [
|
||||
{
|
||||
"id": "dataset_management",
|
||||
"name": "Dataset Management",
|
||||
"description": "List, get details, and create BigQuery datasets",
|
||||
"tags": ["bigquery", "datasets", "google-cloud"]
|
||||
},
|
||||
{
|
||||
"id": "table_management",
|
||||
"name": "Table Management",
|
||||
"description": "List, get details, and create BigQuery tables within datasets",
|
||||
"tags": ["bigquery", "tables", "google-cloud"]
|
||||
},
|
||||
{
|
||||
"id": "oauth_authentication",
|
||||
"name": "OAuth Authentication",
|
||||
"description": "Secure authentication with Google BigQuery using OAuth",
|
||||
"tags": ["authentication", "oauth", "security"]
|
||||
}
|
||||
],
|
||||
"url": "http://localhost:8000/a2a/bigquery_agent",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
# 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.tools.google_api_tool import BigQueryToolset
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
# Access the variable
|
||||
oauth_client_id = os.getenv("OAUTH_CLIENT_ID")
|
||||
oauth_client_secret = os.getenv("OAUTH_CLIENT_SECRET")
|
||||
tools_to_expose = [
|
||||
"bigquery_datasets_list",
|
||||
"bigquery_datasets_get",
|
||||
"bigquery_datasets_insert",
|
||||
"bigquery_tables_list",
|
||||
"bigquery_tables_get",
|
||||
"bigquery_tables_insert",
|
||||
]
|
||||
bigquery_toolset = BigQueryToolset(
|
||||
client_id=oauth_client_id,
|
||||
client_secret=oauth_client_secret,
|
||||
tool_filter=tools_to_expose,
|
||||
)
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-2.0-flash",
|
||||
name="bigquery_agent",
|
||||
instruction="""
|
||||
You are a helpful Google BigQuery agent that help to manage users' data on Google BigQuery.
|
||||
Use the provided tools to conduct various operations on users' data in Google BigQuery.
|
||||
|
||||
Scenario 1:
|
||||
The user wants to query their biguqery datasets
|
||||
Use bigquery_datasets_list to query user's datasets
|
||||
|
||||
Scenario 2:
|
||||
The user wants to query the details of a specific dataset
|
||||
Use bigquery_datasets_get to get a dataset's details
|
||||
|
||||
Scenario 3:
|
||||
The user wants to create a new dataset
|
||||
Use bigquery_datasets_insert to create a new dataset
|
||||
|
||||
Scenario 4:
|
||||
The user wants to query their tables in a specific dataset
|
||||
Use bigquery_tables_list to list all tables in a dataset
|
||||
|
||||
Scenario 5:
|
||||
The user wants to query the details of a specific table
|
||||
Use bigquery_tables_get to get a table's details
|
||||
|
||||
Scenario 6:
|
||||
The user wants to insert a new table into a dataset
|
||||
Use bigquery_tables_insert to insert a new table into a dataset
|
||||
|
||||
Current user:
|
||||
<User>
|
||||
{userInfo?}
|
||||
</User>
|
||||
""",
|
||||
tools=[bigquery_toolset],
|
||||
)
|
||||
@@ -0,0 +1,120 @@
|
||||
# A2A Basic Sample Agent
|
||||
|
||||
This sample demonstrates the **Agent-to-Agent (A2A)** architecture in the Agent Development Kit (ADK), showcasing how multiple agents can work together to handle complex tasks. The sample implements an agent that can roll dice and check if numbers are prime.
|
||||
|
||||
## Overview
|
||||
|
||||
The A2A Basic sample consists of:
|
||||
|
||||
- **Root Agent** (`root_agent`): The main orchestrator that delegates tasks to specialized sub-agents
|
||||
- **Roll Agent** (`roll_agent`): A local sub-agent that handles dice rolling operations
|
||||
- **Prime Agent** (`prime_agent`): A remote A2A agent that checks if numbers are prime, this agent is running on a separate A2A server
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────┐ ┌────────────────────┐
|
||||
│ Root Agent │───▶│ Roll Agent │ │ Remote Prime │
|
||||
│ (Local) │ │ (Local) │ │ Agent │
|
||||
│ │ │ │ │ (localhost:8001) │
|
||||
│ │───▶│ │◀───│ │
|
||||
└─────────────────┘ └──────────────────┘ └────────────────────┘
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. **Local Sub-Agent Integration**
|
||||
- The `roll_agent` demonstrates how to create and integrate local sub-agents
|
||||
- Handles dice rolling with configurable number of sides
|
||||
- Uses a simple function tool (`roll_die`) for random number generation
|
||||
|
||||
### 2. **Remote A2A Agent Integration**
|
||||
- The `prime_agent` shows how to connect to remote agent services
|
||||
- Communicates with a separate service via HTTP at `http://localhost:8001/a2a/check_prime_agent`
|
||||
- Demonstrates cross-service agent communication
|
||||
|
||||
### 3. **Agent Orchestration**
|
||||
- The root agent intelligently delegates tasks based on user requests
|
||||
- Can chain operations (e.g., "roll a die and check if it's prime")
|
||||
- Provides clear workflow coordination between multiple agents
|
||||
|
||||
### 4. **Example Tool Integration**
|
||||
- Includes an `ExampleTool` with sample interactions for context
|
||||
- Helps the agent understand expected behavior patterns
|
||||
|
||||
## Setup and Usage
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Start the Remote Prime Agent server**:
|
||||
```bash
|
||||
# Start the remote a2a server that serves the check prime agent on port 8001
|
||||
adk api_server --a2a --port 8001 contributing/samples/a2a_basic/remote_a2a
|
||||
```
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
## Code Structure
|
||||
|
||||
### Main Agent (`agent.py`)
|
||||
|
||||
- **`roll_die(sides: int)`**: Function tool for rolling dice
|
||||
- **`roll_agent`**: Local agent specialized in dice rolling
|
||||
- **`prime_agent`**: Remote A2A agent configuration
|
||||
- **`root_agent`**: Main orchestrator with delegation logic
|
||||
|
||||
### Remote Prime Agent (`remote_a2a/check_prime_agent/`)
|
||||
|
||||
- **`agent.py`**: Implementation of the prime checking service
|
||||
- **`agent.json`**: Agent card of the A2A agent
|
||||
- **`check_prime(nums: list[int])`**: Prime number checking algorithm
|
||||
|
||||
|
||||
## Extending the Sample
|
||||
|
||||
You can extend this sample by:
|
||||
|
||||
- Adding more mathematical operations (factorization, square roots, etc.)
|
||||
- Creating additional remote agent
|
||||
- Implementing more complex delegation logic
|
||||
- Adding persistent state management
|
||||
- Integrating with external APIs or databases
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Connection Issues:**
|
||||
- Ensure the local ADK web server is running on port 8000
|
||||
- Ensure the remote A2A server is running on port 8001
|
||||
- Check that no firewall is blocking localhost connections
|
||||
- Verify the agent.json URL matches the running A2A server
|
||||
|
||||
**Agent Not Responding:**
|
||||
- Check the logs for both the local ADK web server on port 8000 and remote A2A server on port 8001
|
||||
- Verify the agent instructions are clear and unambiguous
|
||||
Executable
+15
@@ -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
|
||||
Executable
+120
@@ -0,0 +1,120 @@
|
||||
# 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.agents import Agent
|
||||
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
|
||||
from google.adk.tools.example_tool import ExampleTool
|
||||
from google.genai import types
|
||||
|
||||
|
||||
# --- Roll Die Sub-Agent ---
|
||||
def roll_die(sides: int) -> int:
|
||||
"""Roll a die and return the rolled result."""
|
||||
return random.randint(1, sides)
|
||||
|
||||
|
||||
roll_agent = Agent(
|
||||
name="roll_agent",
|
||||
description="Handles rolling dice of different sizes.",
|
||||
instruction="""
|
||||
You are responsible for rolling dice based on the user's request.
|
||||
When asked to roll a die, you must call the roll_die tool with the number of sides as an integer.
|
||||
""",
|
||||
tools=[roll_die],
|
||||
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,
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
example_tool = ExampleTool([
|
||||
{
|
||||
"input": {
|
||||
"role": "user",
|
||||
"parts": [{"text": "Roll a 6-sided die."}],
|
||||
},
|
||||
"output": [
|
||||
{"role": "model", "parts": [{"text": "I rolled a 4 for you."}]}
|
||||
],
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"role": "user",
|
||||
"parts": [{"text": "Is 7 a prime number?"}],
|
||||
},
|
||||
"output": [{
|
||||
"role": "model",
|
||||
"parts": [{"text": "Yes, 7 is a prime number."}],
|
||||
}],
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"role": "user",
|
||||
"parts": [{"text": "Roll a 10-sided die and check if it's prime."}],
|
||||
},
|
||||
"output": [
|
||||
{
|
||||
"role": "model",
|
||||
"parts": [{"text": "I rolled an 8 for you."}],
|
||||
},
|
||||
{
|
||||
"role": "model",
|
||||
"parts": [{"text": "8 is not a prime number."}],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
prime_agent = RemoteA2aAgent(
|
||||
name="prime_agent",
|
||||
description="Agent that handles checking if numbers are prime.",
|
||||
agent_card=(
|
||||
"http://localhost:8001/a2a/check_prime_agent/.well-known/agent.json"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-1.5-flash",
|
||||
name="root_agent",
|
||||
instruction="""
|
||||
You are a helpful assistant that can roll dice and check if numbers are prime.
|
||||
You delegate rolling dice tasks to the roll_agent and prime checking tasks to the prime_agent.
|
||||
Follow these steps:
|
||||
1. If the user asks to roll a die, delegate to the roll_agent.
|
||||
2. If the user asks to check primes, delegate to the prime_agent.
|
||||
3. If the user asks to roll a die and then check if the result is prime, call roll_agent first, then pass the result to prime_agent.
|
||||
Always clarify the results before proceeding.
|
||||
""",
|
||||
global_instruction=(
|
||||
"You are DicePrimeBot, ready to roll dice and check prime numbers."
|
||||
),
|
||||
sub_agents=[roll_agent, prime_agent],
|
||||
tools=[example_tool],
|
||||
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,
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -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,17 @@
|
||||
{
|
||||
"capabilities": {},
|
||||
"defaultInputModes": ["text/plain"],
|
||||
"defaultOutputModes": ["application/json"],
|
||||
"description": "An agent specialized in checking whether numbers are prime. It can efficiently determine the primality of individual numbers or lists of numbers.",
|
||||
"name": "check_prime_agent",
|
||||
"skills": [
|
||||
{
|
||||
"id": "prime_checking",
|
||||
"name": "Prime Number Checking",
|
||||
"description": "Check if numbers in a list are prime using efficient mathematical algorithms",
|
||||
"tags": ["mathematical", "computation", "prime", "numbers"]
|
||||
}
|
||||
],
|
||||
"url": "http://localhost:8001/a2a/check_prime_agent",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
# 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.tools.tool_context import ToolContext
|
||||
from google.genai import types
|
||||
|
||||
|
||||
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='check_prime_agent',
|
||||
description='check prime agent that can check whether numbers are prime.',
|
||||
instruction="""
|
||||
You check whether numbers are prime.
|
||||
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 rely on the previous history on prime results.
|
||||
""",
|
||||
tools=[
|
||||
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,
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user