You've already forked adk-python
mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ab87da592 | |||
| b53e6e3567 | |||
| c29d41f0d0 | |||
| 0c1f1fadeb | |||
| bb148002f8 | |||
| 4c00b86e33 | |||
| b3b31a9ffb | |||
| 463dcee58c | |||
| 4f07228f57 | |||
| c53c02f022 | |||
| 4e6b31a860 | |||
| afebb5227b | |||
| 168c724866 | |||
| 05e3f73451 | |||
| 23834e8a02 | |||
| de1c889a83 | |||
| 2d98b2c30f | |||
| 4cf00702a0 | |||
| a7f4e02dc7 | |||
| 908757b47f | |||
| 91528890db | |||
| f73ae6e101 | |||
| 9862b7b1e2 | |||
| ab69ef8de8 | |||
| 873551d7b9 | |||
| 921e5cb370 | |||
| f52608328e | |||
| c5613fb6f0 | |||
| 0bc2ee64e3 | |||
| f96f0ebd0d | |||
| 894bec794e | |||
| 3c433b7168 | |||
| 707c1a7bd4 |
@@ -14,11 +14,12 @@ assignees: ''
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Please share a minimal code and data to reproduce your problem.
|
||||
Steps to reproduce the behavior:
|
||||
1. Install '...'
|
||||
2. Run '....'
|
||||
3. Open '....'
|
||||
4. See error
|
||||
4. Provie error or stacktrace
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
@@ -27,12 +28,13 @@ A clear and concise description of what you expected to happen.
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- OS: [e.g. macOS, Linux, Windows]
|
||||
- Python version(python -V):
|
||||
- ADK version(pip show google-adk):
|
||||
|
||||
**Model Information:**
|
||||
For example, which model is being used.
|
||||
- Are you using LiteLLM: Yes/No
|
||||
- Which model is being used(e.g. gemini-2.5-pro)
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
@@ -12,6 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
@@ -46,9 +46,9 @@ jobs:
|
||||
REPO: 'adk-python'
|
||||
INTERACTIVE: 0
|
||||
PYTHONPATH: contributing/samples
|
||||
DISCUSSION_JSON: ${{ toJson(github.event.discussion) }}
|
||||
run: |
|
||||
# Replace single quotes with the sequence that allows them to be used in a single-quoted string
|
||||
# This replaces ' with '\'', which ends the current single-quoted string, adds an escaped single quote, then starts a new single-quoted string
|
||||
SAFE_JSON="${DISCUSSION_JSON//\'/\'\\\'\'}"
|
||||
python -m adk_answering_agent.main --discussion '$SAFE_JSON'
|
||||
# Write discussion data to temporary file to avoid secret masking issues
|
||||
cat > /tmp/discussion.json << 'EOF'
|
||||
${{ toJson(github.event.discussion) }}
|
||||
EOF
|
||||
python -m adk_answering_agent.main --discussion-file /tmp/discussion.json
|
||||
|
||||
@@ -17,6 +17,8 @@ Please refer to [ADK Project Overview and Architecture](https://github.com/googl
|
||||
- ADK live related configs are in [run_config.py](https://github.com/google/adk-python/blob/main/src/google/adk/agents/run_config.py).
|
||||
- ADK live under multi-agent scenario: we convert the audio into text. This text will be passed to next agent as context.
|
||||
- Most logics are in [base_llm_flow.py](https://github.com/google/adk-python/blob/main/src/google/adk/flows/llm_flows/base_llm_flow.py) and [gemini_llm_connection.py](https://github.com/google/adk-python/blob/main/src/google/adk/models/gemini_llm_connection.py).
|
||||
- Input transcription and output transcription should be added to session as Event.
|
||||
- User audio or model audio should be saved into artifacts with a reference in Event to it.
|
||||
- Tests are in [tests/unittests/streaming](https://github.com/google/adk-python/tree/main/tests/unittests/streaming).
|
||||
|
||||
## ADK: Style Guides
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [1.14.1](https://github.com/google/adk-python/compare/v1.14.0...v1.14.1) (2025-09-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fix logging issues with RemoteA2aAgent [0c1f1fa](https://github.com/google/adk-python/commit/0c1f1fadeb5a6357af9cad0eff5d5e7103fc88b0)
|
||||
|
||||
## [1.14.0](https://github.com/google/adk-python/compare/v1.13.0...v1.14.0) (2025-09-10)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -27,11 +27,13 @@ Agent Development Kit (ADK) is a flexible and modular framework for developing a
|
||||
|
||||
---
|
||||
|
||||
## ✨ What's new
|
||||
## 🔥 What's new
|
||||
|
||||
- **Agent Config**: Build agents without code. Check out the
|
||||
[Agent Config](https://google.github.io/adk-docs/agents/config/) feature.
|
||||
|
||||
- **Tool Confirmation**: A [tool confirmation flow(HITL)](https://google.github.io/adk-docs/tools/confirmation/) that can guard tool execution with explicit confirmation and custom input
|
||||
|
||||
## ✨ Key Features
|
||||
|
||||
- **Rich Tool Ecosystem**: Utilize pre-built tools, custom functions,
|
||||
@@ -94,7 +96,7 @@ from google.adk.tools import google_search
|
||||
|
||||
root_agent = Agent(
|
||||
name="search_assistant",
|
||||
model="gemini-2.0-flash", # Or your preferred Gemini model
|
||||
model="gemini-2.5-flash", # Or your preferred Gemini model
|
||||
instruction="You are a helpful assistant. Answer user questions using Google Search when needed.",
|
||||
description="An assistant that can search the web.",
|
||||
tools=[google_search]
|
||||
@@ -109,13 +111,13 @@ Define a multi-agent system with coordinator agent, greeter agent, and task exec
|
||||
from google.adk.agents import LlmAgent, BaseAgent
|
||||
|
||||
# Define individual agents
|
||||
greeter = LlmAgent(name="greeter", model="gemini-2.0-flash", ...)
|
||||
task_executor = LlmAgent(name="task_executor", model="gemini-2.0-flash", ...)
|
||||
greeter = LlmAgent(name="greeter", model="gemini-2.5-flash", ...)
|
||||
task_executor = LlmAgent(name="task_executor", model="gemini-2.5-flash", ...)
|
||||
|
||||
# Create parent agent and assign children via sub_agents
|
||||
coordinator = LlmAgent(
|
||||
name="Coordinator",
|
||||
model="gemini-2.0-flash",
|
||||
model="gemini-2.5-flash",
|
||||
description="I coordinate greetings and tasks.",
|
||||
sub_agents=[ # Assign sub_agents here
|
||||
greeter,
|
||||
|
||||
@@ -36,46 +36,77 @@ else:
|
||||
" comment."
|
||||
)
|
||||
|
||||
|
||||
root_agent = Agent(
|
||||
model="gemini-2.5-pro",
|
||||
name="adk_answering_agent",
|
||||
description="Answer questions about ADK repo.",
|
||||
instruction=f"""
|
||||
You are a helpful assistant that responds to questions from the GitHub repository `{OWNER}/{REPO}`
|
||||
based on information about Google ADK found in the document store. You can access the document store
|
||||
using the `VertexAiSearchTool`.
|
||||
You are a helpful assistant that responds to questions from the GitHub repository `{OWNER}/{REPO}`
|
||||
based on information about Google ADK found in the document store. You can access the document store
|
||||
using the `VertexAiSearchTool`.
|
||||
|
||||
When user specifies a discussion number, here are the steps:
|
||||
1. Use the `get_discussion_and_comments` tool to get the details of the discussion including the comments.
|
||||
2. Focus on the latest comment but reference all comments if needed to understand the context.
|
||||
* If there is no comment at all, just focus on the discussion title and body.
|
||||
3. If all the following conditions are met, try to add a comment to the discussion, otherwise, do not respond:
|
||||
* The discussion is not closed.
|
||||
* The latest comment is not from you or other agents (marked as "Response from XXX Agent").
|
||||
* The latest comment is asking a question or requesting information.
|
||||
4. Use the `VertexAiSearchTool` to find relevant information before answering.
|
||||
* If you need infromation about Gemini API, ask the `gemini_assistant` agent to provide the information and references.
|
||||
* You can call the `gemini_assistant` agent with multiple queries to find all the relevant information.
|
||||
5. If you can find relevant information, use the `add_comment_to_discussion` tool to add a comment to the discussion.
|
||||
6. If you post a comment, add the label {BOT_RESPONSE_LABEL} to the discussion using the `add_label_to_discussion` tool.
|
||||
Here are the steps to help answer GitHub discussions:
|
||||
|
||||
IMPORTANT:
|
||||
* {APPROVAL_INSTRUCTION}
|
||||
* Your response should be based on the information you found in the document store. Do not invent
|
||||
information that is not in the document store. Do not invent citations which are not in the document store.
|
||||
* **Be Objective**: your answer should be based on the facts you found in the document store, do not be misled by user's assumptions or user's understanding of ADK.
|
||||
* If you can't find the answer or information in the document store, **do not** respond.
|
||||
* Start with a short summary of your response in the comment as a TLDR, e.g. "**TLDR**: <your summary>".
|
||||
* Have a divider line between the TLDR and your detail response.
|
||||
* Do not respond to any other discussion except the one specified by the user.
|
||||
* Please include your justification for your decision in your output
|
||||
to the user who is telling with you.
|
||||
* If you uses citation from the document store, please provide a footnote
|
||||
referencing the source document format it as: "[1] publicly accessible HTTPS URL of the document".
|
||||
* You **should always** use the `convert_gcs_links_to_https` tool to convert GCS links (e.g. "gs://...") to HTTPS links.
|
||||
* **Do not** use the `convert_gcs_links_to_https` tool for non-GCS links.
|
||||
* Make sure the citation URL is valid. Otherwise do not list this specific citation.
|
||||
""",
|
||||
1. **Determine data source**:
|
||||
* If the user has provided complete discussion JSON data in the prompt,
|
||||
use that data directly.
|
||||
* If the user only provided a discussion number, use the
|
||||
`get_discussion_and_comments` tool to fetch the discussion details.
|
||||
|
||||
2. **Analyze the discussion**:
|
||||
* Focus on the latest comment but reference all comments if needed to
|
||||
understand the context.
|
||||
* If there is no comment at all, focus on the discussion title and body.
|
||||
|
||||
3. **Decide whether to respond**:
|
||||
* If all the following conditions are met, try to add a comment to the
|
||||
discussion, otherwise, do not respond:
|
||||
- The discussion is not closed.
|
||||
- The latest comment is not from you or other agents (marked as
|
||||
"Response from XXX Agent").
|
||||
- The discussion is asking a question or requesting information.
|
||||
- The discussion is about ADK or related topics.
|
||||
|
||||
4. **Research the answer**:
|
||||
* Use the `VertexAiSearchTool` to find relevant information before answering.
|
||||
* If you need information about Gemini API, ask the `gemini_assistant` agent
|
||||
to provide the information and references.
|
||||
* You can call the `gemini_assistant` agent with multiple queries to find
|
||||
all the relevant information.
|
||||
|
||||
5. **Post the response**:
|
||||
* If you can find relevant information, use the `add_comment_to_discussion`
|
||||
tool to add a comment to the discussion.
|
||||
* If you post a comment, add the label "{BOT_RESPONSE_LABEL}" to the discussion
|
||||
using the `add_label_to_discussion` tool.
|
||||
|
||||
IMPORTANT:
|
||||
* {APPROVAL_INSTRUCTION}
|
||||
* Your response should be based on the information you found in the document
|
||||
store. Do not invent information that is not in the document store. Do not
|
||||
invent citations which are not in the document store.
|
||||
* **Be Objective**: your answer should be based on the facts you found in the
|
||||
document store, do not be misled by user's assumptions or user's
|
||||
understanding of ADK.
|
||||
* If you can't find the answer or information in the document store,
|
||||
**do not** respond.
|
||||
* Start with a short summary of your response in the comment as a TLDR,
|
||||
e.g. "**TLDR**: <your summary>".
|
||||
* Have a divider line between the TLDR and your detail response.
|
||||
* Please include your justification for your decision in your output
|
||||
to the user who is telling with you.
|
||||
* If you use citation from the document store, please provide a footnote
|
||||
referencing the source document format it as: "[1] publicly accessible
|
||||
HTTPS URL of the document".
|
||||
* You **should always** use the `convert_gcs_links_to_https` tool to convert
|
||||
GCS links (e.g. "gs://...") to HTTPS links.
|
||||
* **Do not** use the `convert_gcs_links_to_https` tool for non-GCS links.
|
||||
* Make sure the citation URL is valid. Otherwise do not list this specific
|
||||
citation.
|
||||
* Do not respond to any other discussion except the one specified by the user.
|
||||
|
||||
""",
|
||||
tools=[
|
||||
VertexAiSearchTool(data_store_id=VERTEXAI_DATASTORE_ID),
|
||||
AgentTool(gemini_assistant_agent),
|
||||
|
||||
@@ -132,6 +132,13 @@ def process_arguments():
|
||||
help="Answer a discussion using provided JSON data from GitHub event.",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--discussion-file",
|
||||
type=str,
|
||||
metavar="FILE",
|
||||
help="Answer a discussion using JSON data from a file.",
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -155,9 +162,18 @@ async def main():
|
||||
)
|
||||
return
|
||||
discussion_numbers = [discussion_number]
|
||||
elif args.discussion:
|
||||
elif args.discussion or args.discussion_file:
|
||||
try:
|
||||
discussion_data = json.loads(args.discussion)
|
||||
# Load discussion data from either argument or file
|
||||
if args.discussion:
|
||||
discussion_data = json.loads(args.discussion)
|
||||
source_desc = "--discussion argument"
|
||||
else: # args.discussion_file
|
||||
with open(args.discussion_file, "r", encoding="utf-8") as f:
|
||||
discussion_data = json.load(f)
|
||||
source_desc = f"file {args.discussion_file}"
|
||||
|
||||
# Common validation and processing
|
||||
discussion_number = discussion_data.get("number")
|
||||
if not discussion_number:
|
||||
print("Error: Discussion JSON missing 'number' field.", file=sys.stderr)
|
||||
@@ -165,10 +181,12 @@ async def main():
|
||||
discussion_numbers = [discussion_number]
|
||||
# Store the discussion data for later use
|
||||
discussion_json_data = discussion_data
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File not found: {args.discussion_file}", file=sys.stderr)
|
||||
return
|
||||
except json.JSONDecodeError as e:
|
||||
print(
|
||||
f"Error: Invalid JSON in --discussion argument: {e}", file=sys.stderr
|
||||
)
|
||||
print(f"Error: Invalid JSON in {source_desc}: {e}", file=sys.stderr)
|
||||
return
|
||||
|
||||
print(f"Will try to answer discussions: {discussion_numbers}...")
|
||||
@@ -189,16 +207,14 @@ async def main():
|
||||
|
||||
# If we have discussion JSON data, include it in the prompt
|
||||
# to avoid API call
|
||||
if args.discussion and discussion_json_data:
|
||||
title = discussion_json_data.get("title", "No title")
|
||||
body = discussion_json_data.get("body", "No body")
|
||||
author = discussion_json_data.get("author", {}).get("login", "Unknown")
|
||||
if discussion_json_data:
|
||||
discussion_json_str = json.dumps(discussion_json_data, indent=2)
|
||||
prompt = (
|
||||
f"Please help answer this GitHub discussion #{discussion_number}:\n\n"
|
||||
f"Title: {title}\n\n"
|
||||
f"Author: {author}\n\n"
|
||||
f"Body: {body}\n\n"
|
||||
"Please provide a helpful response based on your knowledge of ADK."
|
||||
f"Please help answer this GitHub discussion #{discussion_number}."
|
||||
" Here is the complete discussion"
|
||||
f" data:\n\n```json\n{discussion_json_str}\n```\n\nPlease analyze"
|
||||
" this discussion and provide a helpful response based on your"
|
||||
" knowledge of ADK."
|
||||
)
|
||||
else:
|
||||
prompt = (
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# 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.
|
||||
@@ -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,109 @@
|
||||
# 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 and analyze the issue specified by user.
|
||||
- If user only specified the issue number, call the `get_issue` tool to get the issue details, otherwise use the issue details provided by user directly.
|
||||
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,
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,105 @@
|
||||
# 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 argparse
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
|
||||
from adk_documentation.adk_docs_updater import agent
|
||||
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.tools import get_issue
|
||||
from adk_documentation.utils import call_agent_async
|
||||
from google.adk.cli.utils import logs
|
||||
from google.adk.runners import InMemoryRunner
|
||||
|
||||
APP_NAME = "adk_docs_updater"
|
||||
USER_ID = "adk_docs_updater_user"
|
||||
|
||||
logs.setup_adk_logger(level=logging.DEBUG)
|
||||
|
||||
|
||||
def process_arguments():
|
||||
"""Parses command-line arguments."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="A script that creates pull requests to update ADK docs.",
|
||||
epilog=(
|
||||
"Example usage: \n"
|
||||
"\tpython -m adk_docs_updater.main --issue_number 123\n"
|
||||
),
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
)
|
||||
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
|
||||
group.add_argument(
|
||||
"--issue_number",
|
||||
type=int,
|
||||
metavar="NUM",
|
||||
help="Answer a specific issue number.",
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
async def main():
|
||||
args = process_arguments()
|
||||
if not args.issue_number:
|
||||
print("Please specify an issue number using --issue_number flag")
|
||||
return
|
||||
issue_number = args.issue_number
|
||||
|
||||
get_issue_response = get_issue(DOC_OWNER, DOC_REPO, issue_number)
|
||||
if get_issue_response["status"] != "success":
|
||||
print(f"Failed to get issue {issue_number}: {get_issue_response}\n")
|
||||
return
|
||||
issue = get_issue_response["issue"]
|
||||
|
||||
runner = InMemoryRunner(
|
||||
agent=agent.root_agent,
|
||||
app_name=APP_NAME,
|
||||
)
|
||||
session = await runner.session_service.create_session(
|
||||
app_name=APP_NAME,
|
||||
user_id=USER_ID,
|
||||
)
|
||||
|
||||
response = await call_agent_async(
|
||||
runner,
|
||||
USER_ID,
|
||||
session.id,
|
||||
f"Please update the ADK docs according to the following issue:\n{issue}",
|
||||
)
|
||||
print(f"<<<< Agent Final Output: {response}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_time = time.time()
|
||||
print(
|
||||
f"Start creating pull requests to update {DOC_OWNER}/{DOC_REPO} docs"
|
||||
f" according the {CODE_OWNER}/{CODE_REPO} at"
|
||||
f" {time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(start_time))}"
|
||||
)
|
||||
print("-" * 80)
|
||||
asyncio.run(main())
|
||||
print("-" * 80)
|
||||
end_time = time.time()
|
||||
print(
|
||||
"Updating finished at"
|
||||
f" {time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(end_time))}",
|
||||
)
|
||||
print("Total script execution time:", f"{end_time - start_time:.2f} seconds")
|
||||
@@ -11,3 +11,5 @@
|
||||
# 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
|
||||
|
||||
@@ -12,19 +12,28 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from adk_release_analyzer.settings import CODE_OWNER
|
||||
from adk_release_analyzer.settings import CODE_REPO
|
||||
from adk_release_analyzer.settings import DOC_OWNER
|
||||
from adk_release_analyzer.settings import DOC_REPO
|
||||
from adk_release_analyzer.settings import IS_INTERACTIVE
|
||||
from adk_release_analyzer.settings import LOCAL_REPOS_DIR_PATH
|
||||
from adk_release_analyzer.tools import clone_or_pull_repo
|
||||
from adk_release_analyzer.tools import create_issue
|
||||
from adk_release_analyzer.tools import get_changed_files_between_releases
|
||||
from adk_release_analyzer.tools import list_directory_contents
|
||||
from adk_release_analyzer.tools import list_releases
|
||||
from adk_release_analyzer.tools import read_local_git_repo_file_content
|
||||
from adk_release_analyzer.tools import search_local_git_repo
|
||||
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_issue
|
||||
from adk_documentation.tools import get_changed_files_between_releases
|
||||
from adk_documentation.tools import list_directory_contents
|
||||
from adk_documentation.tools import list_releases
|
||||
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:
|
||||
|
||||
@@ -16,12 +16,12 @@ import asyncio
|
||||
import logging
|
||||
import time
|
||||
|
||||
from adk_release_analyzer import agent
|
||||
from adk_release_analyzer.settings import CODE_OWNER
|
||||
from adk_release_analyzer.settings import CODE_REPO
|
||||
from adk_release_analyzer.settings import DOC_OWNER
|
||||
from adk_release_analyzer.settings import DOC_REPO
|
||||
from adk_release_analyzer.utils import call_agent_async
|
||||
from adk_documentation.adk_release_analyzer import agent
|
||||
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.utils import call_agent_async
|
||||
from google.adk.cli.utils import logs
|
||||
from google.adk.runners import InMemoryRunner
|
||||
|
||||
|
||||
+107
-9
@@ -12,19 +12,21 @@
|
||||
# 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
|
||||
from typing import Optional
|
||||
|
||||
from adk_release_analyzer.settings import GITHUB_BASE_URL
|
||||
from adk_release_analyzer.utils import error_response
|
||||
from adk_release_analyzer.utils import get_paginated_request
|
||||
from adk_release_analyzer.utils import get_request
|
||||
from adk_release_analyzer.utils import patch_request
|
||||
from adk_release_analyzer.utils import post_request
|
||||
from adk_documentation.settings import GITHUB_BASE_URL
|
||||
from adk_documentation.utils import error_response
|
||||
from adk_documentation.utils import get_paginated_request
|
||||
from adk_documentation.utils import get_request
|
||||
from adk_documentation.utils import patch_request
|
||||
from adk_documentation.utils import post_request
|
||||
import requests
|
||||
|
||||
|
||||
@@ -133,7 +135,7 @@ def clone_or_pull_repo(
|
||||
A dictionary indicating the status of the operation, output message, and
|
||||
the head commit hash.
|
||||
"""
|
||||
repo_url = f"https://github.com/{repo_owner}/{repo_name}.git"
|
||||
repo_url = f"git@github.com:{repo_owner}/{repo_name}.git"
|
||||
|
||||
try:
|
||||
# Check local path and decide to clone or pull
|
||||
@@ -246,8 +248,8 @@ def list_directory_contents(directory_path: str) -> Dict[str, Any]:
|
||||
def search_local_git_repo(
|
||||
directory_path: str,
|
||||
pattern: str,
|
||||
extensions: Optional[List[str]],
|
||||
ignored_dirs: Optional[List[str]],
|
||||
extensions: Optional[List[str]] = None,
|
||||
ignored_dirs: Optional[List[str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Searches a local Git repository for a pattern.
|
||||
|
||||
@@ -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"]
|
||||
+1
-1
@@ -16,7 +16,7 @@ from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
||||
from adk_release_analyzer.settings import GITHUB_TOKEN
|
||||
from adk_documentation.settings import GITHUB_TOKEN
|
||||
from google.adk.agents.run_config import RunConfig
|
||||
from google.adk.runners import Runner
|
||||
from google.genai import types
|
||||
@@ -62,7 +62,7 @@ bigtable_toolset = BigtableToolset(
|
||||
# The variable name `root_agent` determines what your root agent is for the
|
||||
# debug CLI
|
||||
root_agent = LlmAgent(
|
||||
model="gemini-1.5-flash",
|
||||
model="gemini-2.5-flash",
|
||||
name="bigtable_agent",
|
||||
description=(
|
||||
"Agent to answer questions about Bigtable database tables and"
|
||||
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user