You've already forked adk-python
mirror of
https://github.com/encounter/adk-python.git
synced 2026-03-30 10:57:20 -07:00
chore: create an initial ADK docs updater agent to create doc update PRs
PiperOrigin-RevId: 806348675
This commit is contained in:
committed by
Copybara-Service
parent
2d98b2c30f
commit
de1c889a83
@@ -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,108 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
SAMPLES_DIR = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "..")
|
||||
)
|
||||
if SAMPLES_DIR not in sys.path:
|
||||
sys.path.append(SAMPLES_DIR)
|
||||
|
||||
from adk_documentation.settings import CODE_OWNER
|
||||
from adk_documentation.settings import CODE_REPO
|
||||
from adk_documentation.settings import DOC_OWNER
|
||||
from adk_documentation.settings import DOC_REPO
|
||||
from adk_documentation.settings import IS_INTERACTIVE
|
||||
from adk_documentation.settings import LOCAL_REPOS_DIR_PATH
|
||||
from adk_documentation.tools import clone_or_pull_repo
|
||||
from adk_documentation.tools import create_pull_request_from_changes
|
||||
from adk_documentation.tools import get_issue
|
||||
from adk_documentation.tools import list_directory_contents
|
||||
from adk_documentation.tools import read_local_git_repo_file_content
|
||||
from adk_documentation.tools import search_local_git_repo
|
||||
from google.adk import Agent
|
||||
|
||||
if IS_INTERACTIVE:
|
||||
APPROVAL_INSTRUCTION = (
|
||||
"Ask for user approval or confirmation for creating the pull request."
|
||||
)
|
||||
else:
|
||||
APPROVAL_INSTRUCTION = (
|
||||
"**Do not** wait or ask for user approval or confirmation for creating"
|
||||
" the pull request."
|
||||
)
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-2.5-pro",
|
||||
name="adk_docs_updater",
|
||||
description=(
|
||||
"Update the ADK docs based on the code in the ADK Python codebase"
|
||||
" according to the instructions in the ADK docs issues."
|
||||
),
|
||||
instruction=f"""
|
||||
# 1. Identity
|
||||
You are a helper bot that updates ADK docs in Github Repository {DOC_OWNER}/{DOC_REPO}
|
||||
based on the code in the ADK Python codebase in Github Repository {CODE_OWNER}/{CODE_REPO} according to the instructions in the ADK docs issues.
|
||||
|
||||
You are very familiar with Github, expecially how to search for files in a Github repository using git grep.
|
||||
|
||||
# 2. Responsibilities
|
||||
Your core responsibility includes:
|
||||
- Read the doc update instructions in the ADK docs issues.
|
||||
- Find **all** the related Python files in ADK Python codebase.
|
||||
- Compare the ADK docs with **all** the related Python files and analyze the differences and the doc update instructions.
|
||||
- Create a pull request to update the ADK docs.
|
||||
|
||||
# 3. Workflow
|
||||
1. Always call the `clone_or_pull_repo` tool to make sure the ADK docs and codebase repos exist in the local folder {LOCAL_REPOS_DIR_PATH}/repo_name and are the latest version.
|
||||
2. Read the issue specified by user using the `get_issue` tool.
|
||||
3. If the issue contains instructions about how to update the ADK docs, follow the instructions to update the ADK docs.
|
||||
4. Understand the doc update instructions.
|
||||
- Ignore and skip the instructions about updating API reference docs, since it will be automatically generated by the ADK team.
|
||||
5. Read the doc to update using the `read_local_git_repo_file_content` tool from the local ADK docs repo under {LOCAL_REPOS_DIR_PATH}/{DOC_REPO}.
|
||||
6. Find the related Python files in the ADK Python codebase.
|
||||
- If the doc update instructions specify paths to the Python files, use them directly, otherwise use a list of regex search patterns to find the related Python files through the `search_local_git_repo` tool.
|
||||
- You should focus on the main ADK Python codebase, ignore the changes in tests or other auxiliary files.
|
||||
- You should find all the related Python files, not only the most relevant one.
|
||||
7. Read the specified or found Python files using the `read_local_git_repo_file_content` tool to find all the related code.
|
||||
- You can ignore unit test files, unless you are sure that the test code is uesful to understand the related concepts.
|
||||
- You should read all the the found files to find all the related code, unless you already know the content of the file or you are sure that the file is not related to the ADK doc.
|
||||
8. Update the ADK doc file according to the doc update instructions and the related code.
|
||||
9. Create pull requests to update the ADK doc file using the `create_pull_request_from_changes` tool.
|
||||
- For each recommended change, create a separate pull request.
|
||||
- The title of the pull request should be "Update ADK doc according to issue #<issue number> - <change id>", where <issue number> is the number of the ADK docs issue and <change id> is the id of the recommended change (e.g. "1", "2", etc.).
|
||||
- The body of the pull request should be the instructions about how to update the ADK docs.
|
||||
- **{APPROVAL_INSTRUCTION}**
|
||||
|
||||
# 4. Guidelines & Rules
|
||||
- **File Paths:** Always use absolute paths when calling the tools to read files, list directories, or search the codebase.
|
||||
- **Tool Call Parallelism:** Execute multiple independent tool calls in parallel when feasible (i.e. searching the codebase).
|
||||
- **Explaination:** Provide concise explanations for your actions and reasoning for each step.
|
||||
|
||||
# 5. Output
|
||||
Present the followings in an easy to read format as the final output to the user.
|
||||
- The actions you took and the reasoning
|
||||
- The summary of the pull request created
|
||||
""",
|
||||
tools=[
|
||||
clone_or_pull_repo,
|
||||
list_directory_contents,
|
||||
search_local_git_repo,
|
||||
read_local_git_repo_file_content,
|
||||
create_pull_request_from_changes,
|
||||
get_issue,
|
||||
],
|
||||
)
|
||||
@@ -12,8 +12,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from datetime import datetime
|
||||
import os
|
||||
import subprocess
|
||||
from subprocess import CompletedProcess
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
@@ -299,6 +301,89 @@ def search_local_git_repo(
|
||||
return error_response(f"An unexpected error occurred: {e}")
|
||||
|
||||
|
||||
def create_pull_request_from_changes(
|
||||
repo_owner: str,
|
||||
repo_name: str,
|
||||
local_path: str,
|
||||
base_branch: str,
|
||||
changes: Dict[str, str],
|
||||
commit_message: str,
|
||||
pr_title: str,
|
||||
pr_body: str,
|
||||
) -> Dict[str, Any]:
|
||||
"""Creates a new branch, applies file changes, commits, pushes, and creates a PR.
|
||||
|
||||
Args:
|
||||
repo_owner: The username or organization that owns the repository.
|
||||
repo_name: The name of the repository.
|
||||
local_path: The local absolute path to the cloned repository.
|
||||
base_branch: The name of the branch to merge the changes into (e.g.,
|
||||
"main").
|
||||
changes: A dictionary where keys are file paths relative to the repo root
|
||||
and values are the new and full content for those files.
|
||||
commit_message: The message for the git commit.
|
||||
pr_title: The title for the pull request.
|
||||
pr_body: The body/description for the pull request.
|
||||
|
||||
Returns:
|
||||
A dictionary containing the status and the pull request object on success,
|
||||
or an error message on failure.
|
||||
"""
|
||||
try:
|
||||
# Step 0: Ensure we are on the base branch and it's up to date.
|
||||
_run_git_command(["checkout", base_branch], local_path)
|
||||
_run_git_command(["pull", "origin", base_branch], local_path)
|
||||
|
||||
# Step 1: Create a new, unique branch from the base branch.
|
||||
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
new_branch = f"agent-changes-{timestamp}"
|
||||
_run_git_command(["checkout", "-b", new_branch], local_path)
|
||||
print(f"Created and switched to new branch: {new_branch}")
|
||||
|
||||
# Step 2: Apply the file changes.
|
||||
if not changes:
|
||||
return error_response("No changes provided to apply.")
|
||||
|
||||
for relative_path, new_content in changes.items():
|
||||
full_path = os.path.join(local_path, relative_path)
|
||||
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
||||
with open(full_path, "w", encoding="utf-8") as f:
|
||||
f.write(new_content)
|
||||
print(f"Applied changes to {relative_path}")
|
||||
|
||||
# Step 3: Stage the changes.
|
||||
_run_git_command(["add", "."], local_path)
|
||||
print("Staged all changes.")
|
||||
|
||||
# Step 4: Commit the changes.
|
||||
_run_git_command(["commit", "-m", commit_message], local_path)
|
||||
print(f"Committed changes with message: '{commit_message}'")
|
||||
|
||||
# Step 5: Push the new branch to the remote repository.
|
||||
_run_git_command(["push", "-u", "origin", new_branch], local_path)
|
||||
print(f"Pushed branch '{new_branch}' to origin.")
|
||||
|
||||
# Step 6: Create the pull request via GitHub API.
|
||||
url = f"{GITHUB_BASE_URL}/repos/{repo_owner}/{repo_name}/pulls"
|
||||
payload = {
|
||||
"title": pr_title,
|
||||
"body": pr_body,
|
||||
"head": new_branch,
|
||||
"base": base_branch,
|
||||
}
|
||||
pr_response = post_request(url, payload)
|
||||
print(f"Successfully created pull request: {pr_response.get('html_url')}")
|
||||
|
||||
return {"status": "success", "pull_request": pr_response}
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
return error_response(f"A git command failed: {e.stderr}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
return error_response(f"GitHub API request failed: {e}")
|
||||
except (IOError, OSError) as e:
|
||||
return error_response(f"A file system error occurred: {e}")
|
||||
|
||||
|
||||
def get_issue(
|
||||
repo_owner: str, repo_name: str, issue_number: int
|
||||
) -> Dict[str, Any]:
|
||||
@@ -378,6 +463,19 @@ def update_issue(
|
||||
return {"status": "success", "issue": response}
|
||||
|
||||
|
||||
def _run_git_command(command: List[str], cwd: str) -> CompletedProcess[str]:
|
||||
"""A helper to run a git command and raise an exception on error."""
|
||||
base_command = ["git"]
|
||||
process = subprocess.run(
|
||||
base_command + command,
|
||||
cwd=cwd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True, # This will raise CalledProcessError if the command fails
|
||||
)
|
||||
return process
|
||||
|
||||
|
||||
def _find_head_commit_sha(repo_path: str) -> str:
|
||||
"""Checks the head commit hash of a Git repository."""
|
||||
head_sha_command = ["git", "rev-parse", "HEAD"]
|
||||
|
||||
Reference in New Issue
Block a user