diff --git a/.github/workflows/analyze-releases-for-adk-docs-updates.yml b/.github/workflows/analyze-releases-for-adk-docs-updates.yml new file mode 100644 index 00000000..086acc96 --- /dev/null +++ b/.github/workflows/analyze-releases-for-adk-docs-updates.yml @@ -0,0 +1,41 @@ +name: Analyze New Release for ADK Docs Updates + +on: + # Runs on every new release. + release: + types: [published] + # Manual trigger for testing and retrying. + workflow_dispatch: + +jobs: + analyze-new-release-for-adk-docs-updates: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install requests google-adk + + - name: Run Analyzing Script + env: + GITHUB_TOKEN: ${{ secrets.ADK_TRIAGE_AGENT }} + GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + GOOGLE_GENAI_USE_VERTEXAI: 0 + DOC_OWNER: 'google' + CODE_OWNER: 'google' + DOC_REPO: 'adk-docs' + CODE_REPO: 'adk-python' + INTERACTIVE: 0 + PYTHONPATH: contributing/samples/adk_documentation + run: python -m adk_release_analyzer.main diff --git a/contributing/samples/adk_documentation/adk_release_analyzer/README.md b/contributing/samples/adk_documentation/adk_release_analyzer/README.md new file mode 100644 index 00000000..198c7aa6 --- /dev/null +++ b/contributing/samples/adk_documentation/adk_release_analyzer/README.md @@ -0,0 +1,100 @@ +# ADK Release Analyzer Agent + +The ADK Release Analyzer Agent is a Python-based agent designed to help keep +documentation up-to-date with code changes. It analyzes the differences between +two releases of the `google/adk-python` repository, identifies required updates +in the `google/adk-docs` repository, and automatically generates a GitHub issue +with detailed instructions for documentation changes. + +This agent can be operated in two distinct modes: + +* an interactive mode for local use +* a fully automated mode for integration into workflows. + +--- + +## Interactive Mode + +This mode allows you to run the agent locally to review its recommendations in +real-time before any changes are made. + +### Features + +* **Web Interface**: The agent's interactive mode can be rendered in a web +browser using the ADK's `adk web` command. +* **User Approval**: In interactive mode, the agent is instructed to ask for +your confirmation before creating an issue on GitHub with the documentation +update instructions. +* **Question & Answer**: You ask questions about the releases and code changes. +The agent will provide answers based on related information. + +### Running in Interactive Mode +To run the agent in interactive mode, first set the required environment +variables, ensuring `INTERACTIVE` is set to `1` or is unset. Then, execute the +following command in your terminal: + +```bash +adk web contributing/samples/adk_documentation +``` + +This will start a local server and provide a URL to access the agent's web +interface in your browser. + +--- + +## Automated Mode + +For automated, hands-off analysis, the agent can be run as a script (`main.py`), +for example as part of a CI/CD pipeline. The workflow is configured in +`.github/workflows/analyze-releases-for-adk-docs-updates.yml` and automatically +checks the most recent two releases for docs updates. + +### Workflow Triggers +The GitHub workflow is configured to run on specific triggers: + +- **Release Events**: The workflow executes automatically whenever a new release +is `published`. + +- **Manual Dispatch**: The workflow also runs when manually triggered for +testing and retrying. + +### Automated Issue Creation + +When running in automated mode, the agent operates non-interactively. It creates +a GitHub issue with the documentation update instructions directly without +requiring user approval. This behavior is configured by setting the +`INTERACTIVE` environment variable to `0`. + +--- + +## Setup and Configuration + +Whether running in interactive or automated mode, the agent requires the +following setup. + +### Dependencies + +The agent requires the following Python libraries. + +```bash +pip install --upgrade pip +pip install google-adk +``` + +### Environment Variables + +The following environment variables are required for the agent to connect to +the necessary services. + +* `GITHUB_TOKEN`: **(Required)** A GitHub Personal Access Token with issues:write permissions for the documentation repository. +* `GOOGLE_API_KEY`: **(Required)** Your API key for the Gemini API. +* `DOC_OWNER`: The GitHub organization or username that owns the documentation repository (defaults to `google`). +* `CODE_OWNER`: The GitHub organization or username that owns the code repository (defaults to `google`). +* `DOC_REPO`: The name of the documentation repository (defaults to `adk-docs`). +* `CODE_REPO`: The name of the code repository (defaults to `adk-python`). +* `LOCAL_REPOS_DIR_PATH`: The local directory to clone the repositories into (defaults to `/tmp`). +* `INTERACTIVE`: Controls the agent's interaction mode. Set to 1 for interactive mode (default), and 0 for automated mode. + +For local execution, you can place these variables in a `.env` file in the +project's root directory. For automated workflows, they should be configured as +environment variables or secrets. \ No newline at end of file diff --git a/contributing/samples/adk_documentation/adk_release_analyzer/agent.py b/contributing/samples/adk_documentation/adk_release_analyzer/agent.py index f419a1da..3a7a8119 100644 --- a/contributing/samples/adk_documentation/adk_release_analyzer/agent.py +++ b/contributing/samples/adk_documentation/adk_release_analyzer/agent.py @@ -91,7 +91,8 @@ root_agent = Agent( Explanation of why this change is necessary. **Reference**: - Reference to the code change. + Reference to the code change (e.g. https://github.com/google/adk-python/commit/b3b70035c432670a5f0b5cdd1e9467f43b80495c). + Reference to the code file (e.g. src/google/adk/tools/spanner/metadata_tool.py). ``` - When referncing doc file, use the full relative path of the doc file in the ADK Docs repository (e.g. docs/sessions/memory.md). 9. Create or recommend to create a Github issue in the Github Repository {DOC_REPO} with the instructions using the `create_issue` tool. @@ -103,6 +104,7 @@ root_agent = Agent( - **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. + - **Reference:** For each recommended change, reference the code changes (i.e. links to the commits) **AND** the code files (i.e. relative paths to the code files in the codebase). - **Sorting:** Sort the recommended changes by the importance of the changes, from the most important to the least important. - Here are the importance groups: Feature changes > Bug fixes > Other changes. - Within each importance group, sort the changes by the number of files they affect. diff --git a/contributing/samples/adk_documentation/adk_release_analyzer/main.py b/contributing/samples/adk_documentation/adk_release_analyzer/main.py new file mode 100644 index 00000000..9bdf0d48 --- /dev/null +++ b/contributing/samples/adk_documentation/adk_release_analyzer/main.py @@ -0,0 +1,68 @@ +# 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 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 google.adk.cli.utils import logs +from google.adk.runners import InMemoryRunner + +APP_NAME = "adk_release_analyzer" +USER_ID = "adk_release_analyzer_user" + +logs.setup_adk_logger(level=logging.DEBUG) + + +async def main(): + 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, + "Please analyze the most recent two releases of ADK Python!", + ) + print(f"<<<< Agent Final Output: {response}\n") + + +if __name__ == "__main__": + start_time = time.time() + print( + f"Start analyzing {CODE_OWNER}/{CODE_REPO} releases for" + f" {DOC_OWNER}/{DOC_REPO} updates 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( + "Triaging 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") diff --git a/contributing/samples/adk_documentation/adk_release_analyzer/utils.py b/contributing/samples/adk_documentation/adk_release_analyzer/utils.py index 11a12236..d99e1434 100644 --- a/contributing/samples/adk_documentation/adk_release_analyzer/utils.py +++ b/contributing/samples/adk_documentation/adk_release_analyzer/utils.py @@ -17,6 +17,9 @@ from typing import Dict from typing import List from adk_release_analyzer.settings import GITHUB_TOKEN +from google.adk.agents.run_config import RunConfig +from google.adk.runners import Runner +from google.genai import types import requests HEADERS = { @@ -70,3 +73,26 @@ def patch_request(url: str, payload: Any) -> Dict[str, Any]: response = requests.patch(url, headers=HEADERS, json=payload, timeout=60) response.raise_for_status() return response.json() + + +async def call_agent_async( + runner: Runner, user_id: str, session_id: str, prompt: str +) -> str: + """Call the agent asynchronously with the user's prompt.""" + content = types.Content( + role="user", parts=[types.Part.from_text(text=prompt)] + ) + + final_response_text = "" + async for event in runner.run_async( + user_id=user_id, + session_id=session_id, + new_message=content, + run_config=RunConfig(save_input_blobs_as_artifacts=False), + ): + if event.content and event.content.parts: + if text := "".join(part.text or "" for part in event.content.parts): + if event.author != "user": + final_response_text += text + + return final_response_text