Skip to content

Commit d126b29

Browse files
Xuan Yangcopybara-github
authored andcommitted
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
1 parent 88e4faa commit d126b29

File tree

5 files changed

+345
-0
lines changed

5 files changed

+345
-0
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,8 @@ If applicable, add screenshots to help explain your problem.
3131
- Python version(python -V):
3232
- ADK version(pip show google-adk):
3333

34+
**Model Information:**
35+
For example, which model is being used.
36+
3437
**Additional context**
3538
Add any other context about the problem here.
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: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
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 pathlib import Path
16+
from typing import Any
17+
18+
from adk_issue_formatting_agent.settings import GITHUB_BASE_URL
19+
from adk_issue_formatting_agent.settings import IS_INTERACTIVE
20+
from adk_issue_formatting_agent.settings import OWNER
21+
from adk_issue_formatting_agent.settings import REPO
22+
from adk_issue_formatting_agent.utils import error_response
23+
from adk_issue_formatting_agent.utils import get_request
24+
from adk_issue_formatting_agent.utils import post_request
25+
from adk_issue_formatting_agent.utils import read_file
26+
from google.adk import Agent
27+
import requests
28+
29+
BUG_REPORT_TEMPLATE = read_file(
30+
Path(__file__).parent / "../../../../.github/ISSUE_TEMPLATE/bug_report.md"
31+
)
32+
FREATURE_REQUEST_TEMPLATE = read_file(
33+
Path(__file__).parent
34+
/ "../../../../.github/ISSUE_TEMPLATE/feature_request.md"
35+
)
36+
37+
APPROVAL_INSTRUCTION = (
38+
"**Do not** wait or ask for user approval or confirmation for adding the"
39+
" comment."
40+
)
41+
if IS_INTERACTIVE:
42+
APPROVAL_INSTRUCTION = (
43+
"Ask for user approval or confirmation for adding the comment."
44+
)
45+
46+
47+
def list_open_issues(issue_count: int) -> dict[str, Any]:
48+
"""List most recent `issue_count` numer of open issues in the repo.
49+
50+
Args:
51+
issue_count: number of issues to return
52+
53+
Returns:
54+
The status of this request, with a list of issues when successful.
55+
"""
56+
url = f"{GITHUB_BASE_URL}/search/issues"
57+
query = f"repo:{OWNER}/{REPO} is:open is:issue"
58+
params = {
59+
"q": query,
60+
"sort": "created",
61+
"order": "desc",
62+
"per_page": issue_count,
63+
"page": 1,
64+
}
65+
66+
try:
67+
response = get_request(url, params)
68+
except requests.exceptions.RequestException as e:
69+
return error_response(f"Error: {e}")
70+
issues = response.get("items", None)
71+
return {"status": "success", "issues": issues}
72+
73+
74+
def get_issue(issue_number: int) -> dict[str, Any]:
75+
"""Get the details of the specified issue number.
76+
77+
Args:
78+
issue_number: issue number of the Github issue.
79+
80+
Returns:
81+
The status of this request, with the issue details when successful.
82+
"""
83+
url = f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/issues/{issue_number}"
84+
try:
85+
response = get_request(url)
86+
except requests.exceptions.RequestException as e:
87+
return error_response(f"Error: {e}")
88+
return {"status": "success", "issue": response}
89+
90+
91+
def add_comment_to_issue(issue_number: int, comment: str) -> dict[str, any]:
92+
"""Add the specified comment to the given issue number.
93+
94+
Args:
95+
issue_number: issue number of the Github issue
96+
comment: comment to add
97+
98+
Returns:
99+
The the status of this request, with the applied comment when successful.
100+
"""
101+
print(f"Attempting to add comment '{comment}' to issue #{issue_number}")
102+
url = f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/issues/{issue_number}/comments"
103+
payload = {"body": comment}
104+
105+
try:
106+
response = post_request(url, payload)
107+
except requests.exceptions.RequestException as e:
108+
return error_response(f"Error: {e}")
109+
return {
110+
"status": "success",
111+
"added_comment": response,
112+
}
113+
114+
115+
def list_comments_on_issue(issue_number: int) -> dict[str, any]:
116+
"""List all comments on the given issue number.
117+
118+
Args:
119+
issue_number: issue number of the Github issue
120+
121+
Returns:
122+
The the status of this request, with the list of comments when successful.
123+
"""
124+
print(f"Attempting to list comments on issue #{issue_number}")
125+
url = f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/issues/{issue_number}/comments"
126+
127+
try:
128+
response = get_request(url)
129+
except requests.exceptions.RequestException as e:
130+
return error_response(f"Error: {e}")
131+
return {"status": "success", "comments": response}
132+
133+
134+
root_agent = Agent(
135+
model="gemini-2.5-pro",
136+
name="adk_issue_formatting_assistant",
137+
description="Check ADK issue format and content.",
138+
instruction=f"""
139+
# 1. IDENTITY
140+
You are an AI assistant designed to help maintain the quality and consistency of issues in our GitHub repository.
141+
Your primary role is to act as a "GitHub Issue Format Validator." You will analyze new and existing **open** issues
142+
to ensure they contain all the necessary information as required by our templates. You are helpful, polite,
143+
and precise in your feedback.
144+
145+
# 2. CONTEXT & RESOURCES
146+
* **Repository:** You are operating on the GitHub repository `{OWNER}/{REPO}`.
147+
* **Bug Report Template:** (`{BUG_REPORT_TEMPLATE}`)
148+
* **Feature Request Template:** (`{FREATURE_REQUEST_TEMPLATE}`)
149+
150+
# 3. CORE MISSION
151+
Your goal is to check if a GitHub issue, identified as either a "bug" or a "feature request,"
152+
contains all the information required by the corresponding template. If it does not, your job is
153+
to post a single, helpful comment asking the original author to provide the missing information.
154+
{APPROVAL_INSTRUCTION}
155+
156+
**IMPORTANT NOTE:**
157+
* You add one comment at most each time you are invoked.
158+
* Don't proceed to other issues which are not the target issues.
159+
* Don't take any action on closed issues.
160+
161+
# 4. BEHAVIORAL RULES & LOGIC
162+
163+
## Step 1: Identify Issue Type & Applicability
164+
165+
Your first task is to determine if the issue is a valid target for validation.
166+
167+
1. **Assess Content Intent:** You must perform a quick semantic check of the issue's title, body, and comments.
168+
If you determine the issue's content is fundamentally *not* a bug report or a feature request
169+
(for example, it is a general question, a request for help, or a discussion prompt), then you must ignore it.
170+
2. **Exit Condition:** If the issue does not clearly fall into the categories of "bug" or "feature request"
171+
based on both its labels and its content, **take no action**.
172+
173+
## Step 2: Analyze the Issue Content
174+
175+
If you have determined the issue is a valid bug or feature request, your analysis depends on whether it has comments.
176+
177+
**Scenario A: Issue has NO comments**
178+
1. Read the main body of the issue.
179+
2. Compare the content of the issue body against the required headings/sections in the relevant template (Bug or Feature).
180+
3. Check for the presence of content under each heading. A heading with no content below it is considered incomplete.
181+
4. If one or more sections are missing or empty, proceed to Step 3.
182+
5. If all sections are filled out, your task is complete. Do nothing.
183+
184+
**Scenario B: Issue HAS one or more comments**
185+
1. First, analyze the main issue body to see which sections of the template are filled out.
186+
2. Next, read through **all** the comments in chronological order.
187+
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.
188+
4. After analyzing the body and all comments, determine if any required sections from the template *still* remain unaddressed.
189+
5. If one or more sections are still missing information, proceed to Step 3.
190+
6. If the issue body and comments *collectively* provide all the required information, your task is complete. Do nothing.
191+
192+
## Step 3: Formulate and Post a Comment (If Necessary)
193+
194+
If you determined in Step 2 that information is missing, you must post a **single comment** on the issue.
195+
196+
Please include a bolded note in your comment that this comment was added by an ADK agent.
197+
198+
**Comment Guidelines:**
199+
* **Be Polite and Helpful:** Start with a friendly tone.
200+
* **Be Specific:** Clearly list only the sections from the template that are still missing. Do not list sections that have already been filled out.
201+
* **Address the Author:** Mention the issue author by their username (e.g., `@username`).
202+
* **Provide Context:** Explain *why* the information is needed (e.g., "to help us reproduce the bug" or "to better understand your request").
203+
* **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.
204+
205+
**Example Comment for a Bug Report:**
206+
> **Response from ADK Agent**
207+
>
208+
> Hello @[issue-author-username], thank you for submitting this issue!
209+
>
210+
> 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:
211+
>
212+
> * **To Reproduce:** (Please provide the specific steps required to reproduce the behavior)
213+
> * **Desktop (please complete the following information):** (Please provide OS, Python version, and ADK version)
214+
>
215+
> This information will give us the context we need to move forward. Thanks!
216+
217+
**Example Comment for a Feature Request:**
218+
> **Response from ADK Agent**
219+
>
220+
> Hi @[issue-author-username], thanks for this great suggestion!
221+
>
222+
> To help our team better understand and evaluate your feature request, could you please provide a bit more information on the following section:
223+
>
224+
> * **Is your feature request related to a problem? Please describe.**
225+
>
226+
> We look forward to hearing more about your idea!
227+
228+
# 5. FINAL INSTRUCTION
229+
230+
Execute this process for the given GitHub issue. Your final output should either be **[NO ACTION]**
231+
if the issue is complete or invalid, or **[POST COMMENT]** followed by the exact text of the comment you will post.
232+
233+
Please include your justification for your decision in your output.
234+
""",
235+
tools={
236+
list_open_issues,
237+
get_issue,
238+
add_comment_to_issue,
239+
list_comments_on_issue,
240+
},
241+
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
import os
16+
17+
from dotenv import load_dotenv
18+
19+
load_dotenv(override=True)
20+
21+
GITHUB_BASE_URL = "https://api.github.com"
22+
23+
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
24+
if not GITHUB_TOKEN:
25+
raise ValueError("GITHUB_TOKEN environment variable not set")
26+
27+
OWNER = os.getenv("OWNER", "google")
28+
REPO = os.getenv("REPO", "adk-python")
29+
EVENT_NAME = os.getenv("EVENT_NAME")
30+
ISSUE_NUMBER = os.getenv("ISSUE_NUMBER")
31+
ISSUE_COUNT_TO_PROCESS = os.getenv("ISSUE_COUNT_TO_PROCESS")
32+
33+
IS_INTERACTIVE = os.environ.get("INTERACTIVE", "1").lower() in ["true", "1"]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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 typing import Any
16+
17+
from adk_issue_formatting_agent.settings import GITHUB_TOKEN
18+
import requests
19+
20+
headers = {
21+
"Authorization": f"token {GITHUB_TOKEN}",
22+
"Accept": "application/vnd.github.v3+json",
23+
}
24+
25+
26+
def get_request(
27+
url: str, params: dict[str, Any] | None = None
28+
) -> dict[str, Any]:
29+
if params is None:
30+
params = {}
31+
response = requests.get(url, headers=headers, params=params, timeout=60)
32+
response.raise_for_status()
33+
return response.json()
34+
35+
36+
def post_request(url: str, payload: Any) -> dict[str, Any]:
37+
response = requests.post(url, headers=headers, json=payload, timeout=60)
38+
response.raise_for_status()
39+
return response.json()
40+
41+
42+
def error_response(error_message: str) -> dict[str, Any]:
43+
return {"status": "error", "message": error_message}
44+
45+
46+
def read_file(file_path: str) -> str:
47+
"""Read the content of the given file."""
48+
try:
49+
with open(file_path, "r") as f:
50+
return f.read()
51+
except FileNotFoundError:
52+
print(f"Error: File not found: {file_path}.")
53+
return ""

0 commit comments

Comments
 (0)