Skip to content

Commit cbb1842

Browse files
Merge branch 'main' into add-generate-card-command
2 parents a0fad41 + e87a843 commit cbb1842

File tree

71 files changed

+4978
-1393
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+4978
-1393
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Mypy New Error Check
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
10+
jobs:
11+
mypy-diff:
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
python-version: ['3.10', '3.11', '3.12', '3.13',]
16+
steps:
17+
- name: Checkout code
18+
uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: ${{ matrix.python-version }}
26+
27+
- name: Install uv
28+
uses: astral-sh/setup-uv@v5
29+
30+
- name: Generate Baseline (Main)
31+
run: |
32+
# Switch to main branch to generate baseline
33+
git checkout origin/main
34+
35+
git checkout ${{ github.sha }} -- pyproject.toml
36+
37+
# Install dependencies for main
38+
uv venv .venv
39+
source .venv/bin/activate
40+
uv sync --all-extras
41+
42+
# Run mypy, filter for errors only, remove line numbers (file:123: -> file::), and sort
43+
# We ignore exit code (|| true) because we expect errors on main
44+
uv run mypy . | grep "error:" | sed 's/:\([0-9]\+\):/::/g' | sort > main_errors.txt || true
45+
46+
echo "Found $(wc -l < main_errors.txt) errors on main."
47+
48+
- name: Check PR Branch
49+
run: |
50+
# Switch back to the PR commit
51+
git checkout ${{ github.sha }}
52+
53+
# Re-sync dependencies in case the PR changed them
54+
source .venv/bin/activate
55+
uv sync --all-extras
56+
57+
# Run mypy on PR code, apply same processing
58+
uv run mypy . | grep "error:" | sed 's/:\([0-9]\+\):/::/g' | sort > pr_errors.txt || true
59+
60+
echo "Found $(wc -l < pr_errors.txt) errors on PR branch."
61+
62+
- name: Compare and Fail on New Errors
63+
run: |
64+
# 'comm -13' suppresses unique lines in file1 (main) and common lines,
65+
# leaving only lines unique to file2 (PR) -> The new errors.
66+
comm -13 main_errors.txt pr_errors.txt > new_errors.txt
67+
68+
if [ -s new_errors.txt ]; then
69+
echo "::error::The following NEW mypy errors were introduced:"
70+
cat new_errors.txt
71+
exit 1
72+
else
73+
echo "Great job! No new mypy errors introduced."
74+
fi

.github/workflows/mypy.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Mypy Type Check
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
mypy:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ['3.10', '3.11', '3.12', '3.13',]
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Install uv
20+
uses: astral-sh/setup-uv@v1
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: ${{ matrix.python-version }}
26+
27+
- name: Install dependencies
28+
run: uv sync --all-extras
29+
30+
- name: Run mypy
31+
32+
run: uv run mypy . --strict

contributing/samples/adk_documentation/adk_release_analyzer/agent.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,25 @@
5757
from google.adk.agents.loop_agent import LoopAgent
5858
from google.adk.agents.readonly_context import ReadonlyContext
5959
from google.adk.agents.sequential_agent import SequentialAgent
60+
from google.adk.models import Gemini
6061
from google.adk.tools.exit_loop_tool import exit_loop
6162
from google.adk.tools.tool_context import ToolContext
63+
from google.genai import types
64+
65+
# Retry configuration for handling API rate limits and overload
66+
_RETRY_OPTIONS = types.HttpRetryOptions(
67+
initial_delay=10,
68+
attempts=8,
69+
exp_base=2,
70+
max_delay=300,
71+
http_status_codes=[429, 503],
72+
)
73+
74+
# Use gemini-3-pro-preview for planning and summary (better quality)
75+
GEMINI_PRO_WITH_RETRY = Gemini(
76+
model="gemini-3-pro-preview",
77+
retry_options=_RETRY_OPTIONS,
78+
)
6279

6380
# Maximum number of files per analysis group to avoid context overflow
6481
MAX_FILES_PER_GROUP = 5
@@ -249,7 +266,7 @@ def get_release_context(tool_context: ToolContext) -> dict[str, Any]:
249266
# =============================================================================
250267

251268
planner_agent = Agent(
252-
model="gemini-2.5-pro",
269+
model=GEMINI_PRO_WITH_RETRY,
253270
name="release_planner",
254271
description=(
255272
"Plans the analysis by fetching release info and organizing files into"
@@ -272,12 +289,12 @@ def get_release_context(tool_context: ToolContext) -> dict[str, Any]:
272289
273290
3. Call `get_changed_files_summary` to get the list of changed files WITHOUT
274291
the full patches (to save context space).
275-
- **IMPORTANT**: Pass `local_repo_path="{LOCAL_REPOS_DIR_PATH}/{CODE_REPO}"`
276-
to use local git and avoid GitHub API's 300-file limit.
292+
- **IMPORTANT**: Pass these parameters:
293+
- `local_repo_path="{LOCAL_REPOS_DIR_PATH}/{CODE_REPO}"` to avoid 300-file limit
294+
- `path_filter="src/google/adk/"` to only get ADK source files (reduces token usage)
277295
278-
4. Filter and organize the files:
279-
- **INCLUDE** only files in `src/google/adk/` directory
280-
- **EXCLUDE** test files, `__init__.py`, and files outside src/
296+
4. Further filter the returned files:
297+
- **EXCLUDE** test files and `__init__.py` files
281298
- **IMPORTANT**: Do NOT exclude any file just because it has few changes.
282299
Even single-line changes to public APIs need documentation updates.
283300
- **PRIORITIZE** by importance:
@@ -423,7 +440,7 @@ def file_analyzer_instruction(readonly_context: ReadonlyContext) -> str:
423440

424441

425442
file_group_analyzer = Agent(
426-
model="gemini-2.5-pro",
443+
model=GEMINI_PRO_WITH_RETRY,
427444
name="file_group_analyzer",
428445
description=(
429446
"Analyzes a group of changed files and generates recommendations."
@@ -507,7 +524,7 @@ def summary_instruction(readonly_context: ReadonlyContext) -> str:
507524

508525

509526
summary_agent = Agent(
510-
model="gemini-2.5-pro",
527+
model=GEMINI_PRO_WITH_RETRY,
511528
name="summary_agent",
512529
description="Compiles recommendations and creates the GitHub issue.",
513530
instruction=summary_instruction,
@@ -542,7 +559,7 @@ def summary_instruction(readonly_context: ReadonlyContext) -> str:
542559
# =============================================================================
543560

544561
root_agent = Agent(
545-
model="gemini-2.5-pro",
562+
model=GEMINI_PRO_WITH_RETRY,
546563
name="adk_release_analyzer",
547564
description=(
548565
"Analyzes ADK Python releases and generates documentation update"

contributing/samples/adk_documentation/tools.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ def get_changed_files_summary(
605605
start_tag: str,
606606
end_tag: str,
607607
local_repo_path: Optional[str] = None,
608+
path_filter: Optional[str] = None,
608609
) -> Dict[str, Any]:
609610
"""Gets a summary of changed files between two releases without patches.
610611
@@ -620,14 +621,17 @@ def get_changed_files_summary(
620621
local_repo_path: Optional absolute path to local git repo. If provided
621622
and valid, uses git diff instead of GitHub API to get complete
622623
file list (avoids 300-file limit).
624+
path_filter: Optional path prefix to filter files. Only files whose
625+
path starts with this prefix will be included. Example:
626+
"src/google/adk/" to only include ADK source files.
623627
624628
Returns:
625629
A dictionary containing the status and a summary of changed files.
626630
"""
627631
# Use local git if valid path is provided (avoids GitHub API 300-file limit)
628632
if local_repo_path and os.path.isdir(os.path.join(local_repo_path, ".git")):
629633
return _get_changed_files_from_local_git(
630-
local_repo_path, start_tag, end_tag, repo_owner, repo_name
634+
local_repo_path, start_tag, end_tag, repo_owner, repo_name, path_filter
631635
)
632636

633637
# Fall back to GitHub API (limited to 300 files)
@@ -689,6 +693,7 @@ def _get_changed_files_from_local_git(
689693
end_tag: str,
690694
repo_owner: str,
691695
repo_name: str,
696+
path_filter: Optional[str] = None,
692697
) -> Dict[str, Any]:
693698
"""Gets changed files using local git commands (no file limit).
694699
@@ -698,6 +703,7 @@ def _get_changed_files_from_local_git(
698703
end_tag: The newer tag (head) for the comparison.
699704
repo_owner: Repository owner for compare URL.
700705
repo_name: Repository name for compare URL.
706+
path_filter: Optional path prefix to filter files.
701707
702708
Returns:
703709
A dictionary containing the status and a summary of changed files.
@@ -766,6 +772,10 @@ def _get_changed_files_from_local_git(
766772
status_code = parts[0][0] # First char is the status
767773
filename = parts[-1] # Last part is filename (handles renames)
768774

775+
# Apply path filter if specified
776+
if path_filter and not filename.startswith(path_filter):
777+
continue
778+
769779
stats = file_stats.get(
770780
filename,
771781
{
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# MCP Toolset OAuth Authentication Sample
2+
3+
This sample demonstrates the toolset authentication feature where OAuth credentials are required for both tool listing and tool calling.
4+
5+
## Overview
6+
7+
The toolset authentication flow works in two phases:
8+
9+
1. **Phase 1**: When the agent tries to get tools from the MCP server without credentials, the toolset signals "authentication required" and returns an auth request event.
10+
11+
2. **Phase 2**: After the user provides OAuth credentials, the agent can successfully list and call tools.
12+
13+
## Files
14+
15+
- `oauth_mcp_server.py` - MCP server that requires Bearer token authentication
16+
- `agent.py` - Agent configuration with OAuth-protected MCP toolset
17+
- `main.py` - Test script demonstrating the two-phase auth flow
18+
19+
## Running the Sample
20+
21+
1. Start the MCP server in one terminal:
22+
23+
```bash
24+
PYTHONPATH=src python contributing/samples/mcp_toolset_auth/oauth_mcp_server.py
25+
```
26+
27+
2. Run the test script in another terminal:
28+
29+
```bash
30+
PYTHONPATH=src python contributing/samples/mcp_toolset_auth/main.py
31+
```
32+
33+
## Expected Behavior
34+
35+
1. First invocation yields an `adk_request_credential` function call
36+
2. The credential ID is `_adk_toolset_auth_McpToolset` to indicate toolset auth
37+
3. After providing the access token, the agent can list and call tools
38+
39+
## Testing with ADK Web UI
40+
41+
You can also test with the ADK web UI:
42+
43+
```bash
44+
adk web contributing/samples/mcp_toolset_auth
45+
```
46+
47+
Note: The web UI will display the auth request and you'll need to manually provide credentials.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from . import agent
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Agent that uses MCP toolset requiring OAuth authentication.
16+
17+
This agent demonstrates the toolset authentication feature where OAuth
18+
credentials are required for both tool listing and tool calling.
19+
"""
20+
21+
from __future__ import annotations
22+
23+
from fastapi.openapi.models import OAuth2
24+
from fastapi.openapi.models import OAuthFlowAuthorizationCode
25+
from fastapi.openapi.models import OAuthFlows
26+
from google.adk.agents import LlmAgent
27+
from google.adk.auth.auth_credential import AuthCredential
28+
from google.adk.auth.auth_credential import AuthCredentialTypes
29+
from google.adk.auth.auth_credential import OAuth2Auth
30+
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
31+
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
32+
33+
# OAuth2 auth scheme with authorization code flow
34+
# This specifies the OAuth metadata needed for the full OAuth flow
35+
auth_scheme = OAuth2(
36+
flows=OAuthFlows(
37+
authorizationCode=OAuthFlowAuthorizationCode(
38+
authorizationUrl='https://example.com/oauth/authorize',
39+
tokenUrl='https://example.com/oauth/token',
40+
scopes={'read': 'Read access', 'write': 'Write access'},
41+
)
42+
)
43+
)
44+
45+
# OAuth credential with client credentials (used for token exchange)
46+
# In a real scenario, this would be used to obtain the access token
47+
auth_credential = AuthCredential(
48+
auth_type=AuthCredentialTypes.OAUTH2,
49+
oauth2=OAuth2Auth(
50+
client_id='test_client_id',
51+
client_secret='test_client_secret',
52+
),
53+
)
54+
55+
# Create the MCP toolset with OAuth authentication
56+
mcp_toolset = McpToolset(
57+
connection_params=StreamableHTTPConnectionParams(
58+
url='http://localhost:3001/mcp',
59+
),
60+
auth_scheme=auth_scheme,
61+
auth_credential=auth_credential,
62+
)
63+
64+
# Define the agent that uses the OAuth-protected MCP toolset
65+
root_agent = LlmAgent(
66+
model='gemini-2.0-flash',
67+
name='oauth_mcp_agent',
68+
instruction="""You are a helpful assistant that can access user information.
69+
70+
You have access to tools that require authentication:
71+
- get_user_profile: Get profile information for a specific user
72+
- list_users: List all available users
73+
74+
When the user asks about users, use these tools to help them.""",
75+
tools=[mcp_toolset],
76+
)

0 commit comments

Comments
 (0)