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 agent to check issue format and content for bugs and feature requests
This agent will pose a comment to ask for more information according to the template if necessary. PiperOrigin-RevId: 775742256
This commit is contained in:
committed by
Copybara-Service
parent
5306ddad4d
commit
738d1a8b84
@@ -31,5 +31,8 @@ If applicable, add screenshots to help explain your problem.
|
||||
- Python version(python -V):
|
||||
- ADK version(pip show google-adk):
|
||||
|
||||
**Model Information:**
|
||||
For example, which model is being used.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
@@ -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,241 @@
|
||||
# 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 pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from adk_issue_formatting_agent.settings import GITHUB_BASE_URL
|
||||
from adk_issue_formatting_agent.settings import IS_INTERACTIVE
|
||||
from adk_issue_formatting_agent.settings import OWNER
|
||||
from adk_issue_formatting_agent.settings import REPO
|
||||
from adk_issue_formatting_agent.utils import error_response
|
||||
from adk_issue_formatting_agent.utils import get_request
|
||||
from adk_issue_formatting_agent.utils import post_request
|
||||
from adk_issue_formatting_agent.utils import read_file
|
||||
from google.adk import Agent
|
||||
import requests
|
||||
|
||||
BUG_REPORT_TEMPLATE = read_file(
|
||||
Path(__file__).parent / "../../../../.github/ISSUE_TEMPLATE/bug_report.md"
|
||||
)
|
||||
FREATURE_REQUEST_TEMPLATE = read_file(
|
||||
Path(__file__).parent
|
||||
/ "../../../../.github/ISSUE_TEMPLATE/feature_request.md"
|
||||
)
|
||||
|
||||
APPROVAL_INSTRUCTION = (
|
||||
"**Do not** wait or ask for user approval or confirmation for adding the"
|
||||
" comment."
|
||||
)
|
||||
if IS_INTERACTIVE:
|
||||
APPROVAL_INSTRUCTION = (
|
||||
"Ask for user approval or confirmation for adding the comment."
|
||||
)
|
||||
|
||||
|
||||
def list_open_issues(issue_count: int) -> dict[str, Any]:
|
||||
"""List most recent `issue_count` numer of open issues in the repo.
|
||||
|
||||
Args:
|
||||
issue_count: number of issues to return
|
||||
|
||||
Returns:
|
||||
The status of this request, with a list of issues when successful.
|
||||
"""
|
||||
url = f"{GITHUB_BASE_URL}/search/issues"
|
||||
query = f"repo:{OWNER}/{REPO} is:open is:issue"
|
||||
params = {
|
||||
"q": query,
|
||||
"sort": "created",
|
||||
"order": "desc",
|
||||
"per_page": issue_count,
|
||||
"page": 1,
|
||||
}
|
||||
|
||||
try:
|
||||
response = get_request(url, params)
|
||||
except requests.exceptions.RequestException as e:
|
||||
return error_response(f"Error: {e}")
|
||||
issues = response.get("items", None)
|
||||
return {"status": "success", "issues": issues}
|
||||
|
||||
|
||||
def get_issue(issue_number: int) -> dict[str, Any]:
|
||||
"""Get the details of the specified issue number.
|
||||
|
||||
Args:
|
||||
issue_number: issue number of the Github issue.
|
||||
|
||||
Returns:
|
||||
The status of this request, with the issue details when successful.
|
||||
"""
|
||||
url = f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/issues/{issue_number}"
|
||||
try:
|
||||
response = get_request(url)
|
||||
except requests.exceptions.RequestException as e:
|
||||
return error_response(f"Error: {e}")
|
||||
return {"status": "success", "issue": response}
|
||||
|
||||
|
||||
def add_comment_to_issue(issue_number: int, comment: str) -> dict[str, any]:
|
||||
"""Add the specified comment to the given issue number.
|
||||
|
||||
Args:
|
||||
issue_number: issue number of the Github issue
|
||||
comment: comment to add
|
||||
|
||||
Returns:
|
||||
The the status of this request, with the applied comment when successful.
|
||||
"""
|
||||
print(f"Attempting to add comment '{comment}' to issue #{issue_number}")
|
||||
url = f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/issues/{issue_number}/comments"
|
||||
payload = {"body": comment}
|
||||
|
||||
try:
|
||||
response = post_request(url, payload)
|
||||
except requests.exceptions.RequestException as e:
|
||||
return error_response(f"Error: {e}")
|
||||
return {
|
||||
"status": "success",
|
||||
"added_comment": response,
|
||||
}
|
||||
|
||||
|
||||
def list_comments_on_issue(issue_number: int) -> dict[str, any]:
|
||||
"""List all comments on the given issue number.
|
||||
|
||||
Args:
|
||||
issue_number: issue number of the Github issue
|
||||
|
||||
Returns:
|
||||
The the status of this request, with the list of comments when successful.
|
||||
"""
|
||||
print(f"Attempting to list comments on issue #{issue_number}")
|
||||
url = f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/issues/{issue_number}/comments"
|
||||
|
||||
try:
|
||||
response = get_request(url)
|
||||
except requests.exceptions.RequestException as e:
|
||||
return error_response(f"Error: {e}")
|
||||
return {"status": "success", "comments": response}
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-2.5-pro",
|
||||
name="adk_issue_formatting_assistant",
|
||||
description="Check ADK issue format and content.",
|
||||
instruction=f"""
|
||||
# 1. IDENTITY
|
||||
You are an AI assistant designed to help maintain the quality and consistency of issues in our GitHub repository.
|
||||
Your primary role is to act as a "GitHub Issue Format Validator." You will analyze new and existing **open** issues
|
||||
to ensure they contain all the necessary information as required by our templates. You are helpful, polite,
|
||||
and precise in your feedback.
|
||||
|
||||
# 2. CONTEXT & RESOURCES
|
||||
* **Repository:** You are operating on the GitHub repository `{OWNER}/{REPO}`.
|
||||
* **Bug Report Template:** (`{BUG_REPORT_TEMPLATE}`)
|
||||
* **Feature Request Template:** (`{FREATURE_REQUEST_TEMPLATE}`)
|
||||
|
||||
# 3. CORE MISSION
|
||||
Your goal is to check if a GitHub issue, identified as either a "bug" or a "feature request,"
|
||||
contains all the information required by the corresponding template. If it does not, your job is
|
||||
to post a single, helpful comment asking the original author to provide the missing information.
|
||||
{APPROVAL_INSTRUCTION}
|
||||
|
||||
**IMPORTANT NOTE:**
|
||||
* You add one comment at most each time you are invoked.
|
||||
* Don't proceed to other issues which are not the target issues.
|
||||
* Don't take any action on closed issues.
|
||||
|
||||
# 4. BEHAVIORAL RULES & LOGIC
|
||||
|
||||
## Step 1: Identify Issue Type & Applicability
|
||||
|
||||
Your first task is to determine if the issue is a valid target for validation.
|
||||
|
||||
1. **Assess Content Intent:** You must perform a quick semantic check of the issue's title, body, and comments.
|
||||
If you determine the issue's content is fundamentally *not* a bug report or a feature request
|
||||
(for example, it is a general question, a request for help, or a discussion prompt), then you must ignore it.
|
||||
2. **Exit Condition:** If the issue does not clearly fall into the categories of "bug" or "feature request"
|
||||
based on both its labels and its content, **take no action**.
|
||||
|
||||
## Step 2: Analyze the Issue Content
|
||||
|
||||
If you have determined the issue is a valid bug or feature request, your analysis depends on whether it has comments.
|
||||
|
||||
**Scenario A: Issue has NO comments**
|
||||
1. Read the main body of the issue.
|
||||
2. Compare the content of the issue body against the required headings/sections in the relevant template (Bug or Feature).
|
||||
3. Check for the presence of content under each heading. A heading with no content below it is considered incomplete.
|
||||
4. If one or more sections are missing or empty, proceed to Step 3.
|
||||
5. If all sections are filled out, your task is complete. Do nothing.
|
||||
|
||||
**Scenario B: Issue HAS one or more comments**
|
||||
1. First, analyze the main issue body to see which sections of the template are filled out.
|
||||
2. Next, read through **all** the comments in chronological order.
|
||||
3. As you read the comments, check if the information provided in them satisfies any of the template sections that were missing from the original issue body.
|
||||
4. After analyzing the body and all comments, determine if any required sections from the template *still* remain unaddressed.
|
||||
5. If one or more sections are still missing information, proceed to Step 3.
|
||||
6. If the issue body and comments *collectively* provide all the required information, your task is complete. Do nothing.
|
||||
|
||||
## Step 3: Formulate and Post a Comment (If Necessary)
|
||||
|
||||
If you determined in Step 2 that information is missing, you must post a **single comment** on the issue.
|
||||
|
||||
Please include a bolded note in your comment that this comment was added by an ADK agent.
|
||||
|
||||
**Comment Guidelines:**
|
||||
* **Be Polite and Helpful:** Start with a friendly tone.
|
||||
* **Be Specific:** Clearly list only the sections from the template that are still missing. Do not list sections that have already been filled out.
|
||||
* **Address the Author:** Mention the issue author by their username (e.g., `@username`).
|
||||
* **Provide Context:** Explain *why* the information is needed (e.g., "to help us reproduce the bug" or "to better understand your request").
|
||||
* **Do not be repetitive:** If you have already commented on an issue asking for information, do not comment again unless new information has been added and it's still incomplete.
|
||||
|
||||
**Example Comment for a Bug Report:**
|
||||
> **Response from ADK Agent**
|
||||
>
|
||||
> Hello @[issue-author-username], thank you for submitting this issue!
|
||||
>
|
||||
> To help us investigate and resolve this bug effectively, could you please provide the missing details for the following sections of our bug report template:
|
||||
>
|
||||
> * **To Reproduce:** (Please provide the specific steps required to reproduce the behavior)
|
||||
> * **Desktop (please complete the following information):** (Please provide OS, Python version, and ADK version)
|
||||
>
|
||||
> This information will give us the context we need to move forward. Thanks!
|
||||
|
||||
**Example Comment for a Feature Request:**
|
||||
> **Response from ADK Agent**
|
||||
>
|
||||
> Hi @[issue-author-username], thanks for this great suggestion!
|
||||
>
|
||||
> To help our team better understand and evaluate your feature request, could you please provide a bit more information on the following section:
|
||||
>
|
||||
> * **Is your feature request related to a problem? Please describe.**
|
||||
>
|
||||
> We look forward to hearing more about your idea!
|
||||
|
||||
# 5. FINAL INSTRUCTION
|
||||
|
||||
Execute this process for the given GitHub issue. Your final output should either be **[NO ACTION]**
|
||||
if the issue is complete or invalid, or **[POST COMMENT]** followed by the exact text of the comment you will post.
|
||||
|
||||
Please include your justification for your decision in your output.
|
||||
""",
|
||||
tools={
|
||||
list_open_issues,
|
||||
get_issue,
|
||||
add_comment_to_issue,
|
||||
list_comments_on_issue,
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
# 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
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
GITHUB_BASE_URL = "https://api.github.com"
|
||||
|
||||
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
|
||||
if not GITHUB_TOKEN:
|
||||
raise ValueError("GITHUB_TOKEN environment variable not set")
|
||||
|
||||
OWNER = os.getenv("OWNER", "google")
|
||||
REPO = os.getenv("REPO", "adk-python")
|
||||
EVENT_NAME = os.getenv("EVENT_NAME")
|
||||
ISSUE_NUMBER = os.getenv("ISSUE_NUMBER")
|
||||
ISSUE_COUNT_TO_PROCESS = os.getenv("ISSUE_COUNT_TO_PROCESS")
|
||||
|
||||
IS_INTERACTIVE = os.environ.get("INTERACTIVE", "1").lower() in ["true", "1"]
|
||||
@@ -0,0 +1,53 @@
|
||||
# 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 typing import Any
|
||||
|
||||
from adk_issue_formatting_agent.settings import GITHUB_TOKEN
|
||||
import requests
|
||||
|
||||
headers = {
|
||||
"Authorization": f"token {GITHUB_TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
}
|
||||
|
||||
|
||||
def get_request(
|
||||
url: str, params: dict[str, Any] | None = None
|
||||
) -> dict[str, Any]:
|
||||
if params is None:
|
||||
params = {}
|
||||
response = requests.get(url, headers=headers, params=params, timeout=60)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def post_request(url: str, payload: Any) -> dict[str, Any]:
|
||||
response = requests.post(url, headers=headers, json=payload, timeout=60)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def error_response(error_message: str) -> dict[str, Any]:
|
||||
return {"status": "error", "message": error_message}
|
||||
|
||||
|
||||
def read_file(file_path: str) -> str:
|
||||
"""Read the content of the given file."""
|
||||
try:
|
||||
with open(file_path, "r") as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File not found: {file_path}.")
|
||||
return ""
|
||||
Reference in New Issue
Block a user