Skip to content

Commit 798bbca

Browse files
xuanyang15copybara-github
authored andcommitted
chore: create an initial prototype agent to answer Github discussion questions
This agent will post a comment to answer questions or provide more information according to the knowledge base. PiperOrigin-RevId: 778183040
1 parent 2034fbb commit 798bbca

File tree

7 files changed

+645
-0
lines changed

7 files changed

+645
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# ADK Answering Agent
2+
3+
The ADK Answering Agent is a Python-based agent designed to help answer questions in GitHub discussions for the `google/adk-python` repository. It uses a large language model to analyze open discussions, retrieve information from document store, generate response, and post a comment in the github discussion.
4+
5+
This agent can be operated in three distinct modes: an interactive mode for local use, a batch script mode for oncall use, or as a fully automated GitHub Actions workflow (TBD).
6+
7+
---
8+
9+
## Interactive Mode
10+
11+
This mode allows you to run the agent locally to review its recommendations in real-time before any changes are made to your repository's issues.
12+
13+
### Features
14+
* **Web Interface**: The agent's interactive mode can be rendered in a web browser using the ADK's `adk web` command.
15+
* **User Approval**: In interactive mode, the agent is instructed to ask for your confirmation before posting a comment to a GitHub issue.
16+
* **Question & Answer**: You can ask ADK related questions, and the agent will provide answers based on its knowledge on ADK.
17+
18+
### Running in Interactive Mode
19+
To run the agent in interactive mode, first set the required environment variables. Then, execute the following command in your terminal:
20+
21+
```bash
22+
adk web
23+
```
24+
This will start a local server and provide a URL to access the agent's web interface in your browser.
25+
26+
---
27+
28+
## Batch Script Mode
29+
30+
The `answer_discussions.py` is created for ADK oncall team to batch process discussions.
31+
32+
### Features
33+
* **Batch Process**: Taken either a number as the count of the recent discussions or a list of discussion numbers, the script will invoke the agent to answer all the specified discussions in one single run.
34+
35+
### Running in Interactive Mode
36+
To run the agent in batch script mode, first set the required environment variables. Then, execute the following command in your terminal:
37+
38+
```bash
39+
export PYTHONPATH=contributing/samples
40+
python -m adk_answering_agent.answer_discussions --numbers 27 36 # Answer specified discussions
41+
```
42+
43+
Or `python -m adk_answering_agent.answer_discussions --recent 10` to answer the 10 most recent updated discussions.
44+
45+
---
46+
47+
## GitHub Workflow Mode
48+
49+
The `main.py` is reserved for the Github Workflow. The detailed setup for the automatic workflow is TBD.
50+
51+
---
52+
53+
## Setup and Configuration
54+
55+
Whether running in interactive or workflow mode, the agent requires the following setup.
56+
57+
### Dependencies
58+
The agent requires the following Python libraries.
59+
60+
```bash
61+
pip install --upgrade pip
62+
pip install google-adk requests
63+
```
64+
65+
The agent also requires gcloud login:
66+
67+
```bash
68+
gcloud auth application-default login
69+
```
70+
71+
### Environment Variables
72+
The following environment variables are required for the agent to connect to the necessary services.
73+
74+
* `GITHUB_TOKEN=YOUR_GITHUB_TOKEN`: **(Required)** A GitHub Personal Access Token with `issues:write` permissions. Needed for both interactive and workflow modes.
75+
* `GOOGLE_GENAI_USE_VERTEXAI=TRUE`: **(Required)** Use Google Vertex AI for the authentication.
76+
* `GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID`: **(Required)** The Google Cloud project ID.
77+
* `GOOGLE_CLOUD_LOCATION=LOCATION`: **(Required)** The Google Cloud region.
78+
* `VERTEXAI_DATASTORE_ID=YOUR_DATASTORE_ID`: **(Required)** The Vertex AI datastore ID for the document store (i.e. knowledge base).
79+
* `OWNER`: The GitHub organization or username that owns the repository (e.g., `google`). Needed for both modes.
80+
* `REPO`: The name of the GitHub repository (e.g., `adk-python`). Needed for both modes.
81+
* `INTERACTIVE`: Controls the agent's interaction mode. For the automated workflow, this is set to `0`. For interactive mode, it should be set to `1` or left unset.
82+
83+
For local execution in interactive mode, you can place these variables in a `.env` file in the project's root directory. For the GitHub workflow, they should be configured as repository secrets.
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: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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_answering_agent.settings import IS_INTERACTIVE
18+
from adk_answering_agent.settings import OWNER
19+
from adk_answering_agent.settings import REPO
20+
from adk_answering_agent.settings import VERTEXAI_DATASTORE_ID
21+
from adk_answering_agent.utils import error_response
22+
from adk_answering_agent.utils import run_graphql_query
23+
from google.adk.agents import Agent
24+
from google.adk.tools import VertexAiSearchTool
25+
import requests
26+
27+
if IS_INTERACTIVE:
28+
APPROVAL_INSTRUCTION = (
29+
"Ask for user approval or confirmation for adding the comment."
30+
)
31+
else:
32+
APPROVAL_INSTRUCTION = (
33+
"**Do not** wait or ask for user approval or confirmation for adding the"
34+
" comment."
35+
)
36+
37+
38+
def get_discussion_and_comments(discussion_number: int) -> dict[str, Any]:
39+
"""Fetches a discussion and its comments using the GitHub GraphQL API.
40+
41+
Args:
42+
discussion_number: The number of the GitHub discussion.
43+
44+
Returns:
45+
A dictionary with the request status and the discussion details.
46+
"""
47+
print(f"Attempting to get discussion #{discussion_number} and its comments")
48+
query = """
49+
query($owner: String!, $repo: String!, $discussionNumber: Int!) {
50+
repository(owner: $owner, name: $repo) {
51+
discussion(number: $discussionNumber) {
52+
id
53+
title
54+
body
55+
createdAt
56+
closed
57+
author {
58+
login
59+
}
60+
# For each comment, fetch the latest 100 comments.
61+
comments(last: 100) {
62+
nodes {
63+
id
64+
body
65+
createdAt
66+
author {
67+
login
68+
}
69+
# For each comment, fetch the latest 50 replies
70+
replies(last: 50) {
71+
nodes {
72+
id
73+
body
74+
createdAt
75+
author {
76+
login
77+
}
78+
}
79+
}
80+
}
81+
}
82+
}
83+
}
84+
}
85+
"""
86+
variables = {
87+
"owner": OWNER,
88+
"repo": REPO,
89+
"discussionNumber": discussion_number,
90+
}
91+
try:
92+
response = run_graphql_query(query, variables)
93+
if "errors" in response:
94+
return error_response(str(response["errors"]))
95+
discussion_data = (
96+
response.get("data", {}).get("repository", {}).get("discussion")
97+
)
98+
if not discussion_data:
99+
return error_response(f"Discussion #{discussion_number} not found.")
100+
return {"status": "success", "discussion": discussion_data}
101+
except requests.exceptions.RequestException as e:
102+
return error_response(str(e))
103+
104+
105+
def add_comment_to_discussion(
106+
discussion_id: str, comment_body: str
107+
) -> dict[str, Any]:
108+
"""Adds a comment to a specific discussion.
109+
110+
Args:
111+
discussion_id: The GraphQL node ID of the discussion.
112+
comment_body: The content of the comment in Markdown.
113+
114+
Returns:
115+
The status of the request and the new comment's details.
116+
"""
117+
print(f"Adding comment to discussion {discussion_id}")
118+
query = """
119+
mutation($discussionId: ID!, $body: String!) {
120+
addDiscussionComment(input: {discussionId: $discussionId, body: $body}) {
121+
comment {
122+
id
123+
body
124+
createdAt
125+
author {
126+
login
127+
}
128+
}
129+
}
130+
}
131+
"""
132+
variables = {"discussionId": discussion_id, "body": comment_body}
133+
try:
134+
response = run_graphql_query(query, variables)
135+
if "errors" in response:
136+
return error_response(str(response["errors"]))
137+
new_comment = (
138+
response.get("data", {}).get("addDiscussionComment", {}).get("comment")
139+
)
140+
return {"status": "success", "comment": new_comment}
141+
except requests.exceptions.RequestException as e:
142+
return error_response(str(e))
143+
144+
145+
root_agent = Agent(
146+
model="gemini-2.5-pro",
147+
name="adk_answering_agent",
148+
description="Answer questions about ADK repo.",
149+
instruction=f"""
150+
You are a helpful assistant that responds to questions from the GitHub repository `{OWNER}/{REPO}`
151+
based on information about Google ADK found in the document store. You can access the document store
152+
using the `VertexAiSearchTool`.
153+
154+
When user specifies a discussion number, here are the steps:
155+
1. Use the `get_discussion_and_comments` tool to get the details of the discussion including the comments.
156+
2. Focus on the latest comment but reference all comments if needed to understand the context.
157+
* If there is no comment at all, just focus on the discussion title and body.
158+
3. If all the following conditions are met, try to add a comment to the discussion, otherwise, do not respond:
159+
* The discussion is not closed.
160+
* The latest comment is not from you or other agents (marked as "Response from XXX Agent").
161+
* The latest comment is asking a question or requesting information.
162+
4. Use the `VertexAiSearchTool` to find relevant information before answering.
163+
164+
IMPORTANT:
165+
* {APPROVAL_INSTRUCTION}
166+
* Your response should be based on the information you found in the document store. Do not invent
167+
information that is not in the document store. Do not invent citations which are not in the document store.
168+
* If you can't find the answer or information in the document store, **do not** respond.
169+
* Include a bolded note (e.g. "Response from ADK Answering Agent") in your comment
170+
to indicate this comment was added by an ADK Answering Agent.
171+
* Have an empty line between the note and the rest of your response.
172+
* Inlclude a short summary of your response in the comment as a TLDR, e.g. "**TLDR**: <your summary>".
173+
* Have a divider line between the TLDR and your detail response.
174+
* Do not respond to any other discussion except the one specified by the user.
175+
* Please include your justification for your decision in your output
176+
to the user who is telling with you.
177+
* If you uses citation from the document store, please provide a footnote
178+
referencing the source document format it as: "[1] URL of the document".
179+
* Replace the "gs://prefix/" part, e.g. "gs://adk-qa-bucket/", to be "https://github.com/google/"
180+
* Add "blob/main/" after the repo name, e.g. "adk-python", "adk-docs", for example:
181+
* If the original URL is "gs://adk-qa-bucket/adk-python/src/google/adk/version.py",
182+
then the citation URL is "https://github.com/google/adk-python/blob/main/src/google/adk/version.py",
183+
* If the original URL is "gs://adk-qa-bucket/adk-docs/docs/index.md",
184+
then the citation URL is "https://github.com/google/adk-docs/blob/main/docs/index.md"
185+
* If the file is a html file, replace the ".html" to be ".md"
186+
""",
187+
tools=[
188+
VertexAiSearchTool(data_store_id=VERTEXAI_DATASTORE_ID),
189+
get_discussion_and_comments,
190+
add_comment_to_discussion,
191+
],
192+
)

0 commit comments

Comments
 (0)