diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml deleted file mode 100644 index ed0f8514..00000000 --- a/.github/workflows/triage.yml +++ /dev/null @@ -1,68 +0,0 @@ -# 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. - -name: ADK Issue Triaging Agent - -on: - issues: - types: [opened, labeled] - schedule: - # Run every 6 hours to triage untriaged issues - - cron: '0 */6 * * *' - -jobs: - agent-triage-issues: - runs-on: ubuntu-latest - # Run for: - # - Scheduled runs (batch processing) - # - New issues (need component labeling) - # - Issues labeled with "planned" (need owner assignment) - if: >- - github.repository == 'google/adk-python' && ( - github.event_name == 'schedule' || - github.event.action == 'opened' - ) - permissions: - issues: write - contents: read - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.11' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install requests google-adk - - - name: Run Triaging Script - env: - GITHUB_TOKEN: ${{ secrets.ADK_TRIAGE_AGENT }} - GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} - GOOGLE_GENAI_USE_VERTEXAI: 0 - OWNER: ${{ github.repository_owner }} - REPO: ${{ github.event.repository.name }} - INTERACTIVE: 0 - EVENT_NAME: ${{ github.event_name }} # 'issues', 'schedule', etc. - ISSUE_NUMBER: ${{ github.event.issue.number }} - ISSUE_TITLE: ${{ github.event.issue.title }} - ISSUE_BODY: ${{ github.event.issue.body }} - ISSUE_COUNT_TO_PROCESS: '3' # Process 3 issues at a time on schedule - PYTHONPATH: contributing/samples/adk_team - run: python -m adk_triaging_agent.main diff --git a/contributing/samples/adk_team/adk_triaging_agent/README.md b/contributing/samples/adk_team/adk_triaging_agent/README.md deleted file mode 100644 index cb0fe3a8..00000000 --- a/contributing/samples/adk_team/adk_triaging_agent/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# ADK Issue Triaging Assistant - -The ADK Issue Triaging Assistant is a Python-based agent designed to help manage and triage GitHub issues for the `google/adk-python` repository. It uses a large language model to analyze issues, recommend appropriate component labels, set issue types, and assign owners based on predefined rules. - -This agent can be operated in two distinct modes: an interactive mode for local use or as a fully automated GitHub Actions workflow. - -______________________________________________________________________ - -## Triaging Workflow - -The agent performs different actions based on the issue state: - -| Condition | Actions | -| ------------------------------------------------- | -------------------------------------------------- | -| Issue without component label | Add component label + Set issue type (Bug/Feature) | -| Issue with "planned" label but no assignee | Assign owner based on component label | -| Issue with "planned" label AND no component label | Add component label + Set type + Assign owner | - -### Component Labels - -The agent can assign the following component labels, each mapped to an owner: - -- `a2a`, `agent engine`, `auth`, `bq`, `core`, `documentation`, `eval`, `live`, `mcp`, `models`, `services`, `tools`, `tracing`, `web`, `workflow` - -### Issue Types - -Based on the issue content, the agent will set the issue type to: - -- **Bug**: For bug reports -- **Feature**: For feature requests - -______________________________________________________________________ - -## Interactive Mode - -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. - -### 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 applying labels or assigning owners. - -### Running in Interactive Mode - -To run the agent in interactive mode, first set the required environment variables. Then, execute the following command in your terminal: - -```bash -adk web -``` - -This will start a local server and provide a URL to access the agent's web interface in your browser. - -______________________________________________________________________ - -## GitHub Workflow Mode - -For automated, hands-off issue triaging, the agent can be integrated directly into your repository's CI/CD pipeline using a GitHub Actions workflow. - -### Workflow Triggers - -The GitHub workflow is configured to run on specific triggers: - -1. **New Issues (`opened`)**: When a new issue is created, the agent adds an appropriate component label and sets the issue type. - -1. **Planned Label Added (`labeled` with "planned")**: When an issue is labeled as "planned", the agent assigns an owner based on the component label. If the issue doesn't have a component label yet, the agent will also add one. - -1. **Scheduled Runs**: The workflow runs every 6 hours to process any issues that need triaging (either missing component labels or missing assignees for "planned" issues). - -### Automated Actions - -When running as part of the GitHub workflow, the agent operates non-interactively: - -- **Component Labeling**: Automatically applies the most appropriate component label -- **Issue Type Setting**: Sets the issue type to Bug or Feature based on content -- **Owner Assignment**: Only assigns owners for issues marked as "planned" - -This behavior is configured by setting the `INTERACTIVE` environment variable to `0` in the workflow file. - -### Workflow Configuration - -The workflow is defined in a YAML file (`.github/workflows/triage.yml`). This file contains the steps to check out the code, set up the Python environment, install dependencies, and run the triaging script with the necessary environment variables and secrets. - -______________________________________________________________________ - -## Setup and Configuration - -Whether running in interactive or workflow mode, the agent requires the following setup. - -### Dependencies - -The agent requires the following Python libraries. - -```bash -pip install --upgrade pip -pip install google-adk requests -``` - -### 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. Needed for both interactive and workflow modes. -- `GOOGLE_API_KEY`: **(Required)** Your API key for the Gemini API. Needed for both interactive and workflow modes. -- `OWNER`: The GitHub organization or username that owns the repository (e.g., `google`). In the workflow, this is automatically set from the repository context. -- `REPO`: The name of the GitHub repository (e.g., `adk-python`). In the workflow, this is automatically set from the repository context. -- `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. - -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. diff --git a/contributing/samples/adk_team/adk_triaging_agent/__init__.py b/contributing/samples/adk_team/adk_triaging_agent/__init__.py deleted file mode 100755 index 4015e47d..00000000 --- a/contributing/samples/adk_team/adk_triaging_agent/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2026 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 diff --git a/contributing/samples/adk_team/adk_triaging_agent/main.py b/contributing/samples/adk_team/adk_triaging_agent/main.py deleted file mode 100644 index fcdac832..00000000 --- a/contributing/samples/adk_team/adk_triaging_agent/main.py +++ /dev/null @@ -1,185 +0,0 @@ -# Copyright 2026 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 time - -from adk_triaging_agent import agent -from adk_triaging_agent.agent import LABEL_TO_OWNER -from adk_triaging_agent.settings import EVENT_NAME -from adk_triaging_agent.settings import GITHUB_BASE_URL -from adk_triaging_agent.settings import ISSUE_BODY -from adk_triaging_agent.settings import ISSUE_COUNT_TO_PROCESS -from adk_triaging_agent.settings import ISSUE_NUMBER -from adk_triaging_agent.settings import ISSUE_TITLE -from adk_triaging_agent.settings import OWNER -from adk_triaging_agent.settings import REPO -from adk_triaging_agent.utils import get_request -from adk_triaging_agent.utils import parse_number_string -from google.adk.agents.run_config import RunConfig -from google.adk.runners import InMemoryRunner -from google.adk.runners import Runner -from google.genai import types -import requests - -APP_NAME = "adk_triage_app" -USER_ID = "adk_triage_user" - - -async def fetch_specific_issue_details(issue_number: int): - """Fetches details for a single issue if it needs triaging.""" - url = f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/issues/{issue_number}" - print(f"Fetching details for specific issue: {url}") - - try: - issue_data = get_request(url) - labels = issue_data.get("labels", []) - label_names = {label["name"] for label in labels} - assignees = issue_data.get("assignees", []) - - # Check issue state - component_labels = set(LABEL_TO_OWNER.keys()) - has_planned = "planned" in label_names - existing_component_labels = label_names & component_labels - has_component = bool(existing_component_labels) - has_assignee = len(assignees) > 0 - - # Determine what actions are needed - needs_component_label = not has_component - needs_owner = not has_assignee - - if needs_component_label or needs_owner: - print( - f"Issue #{issue_number} needs triaging. " - f"needs_component_label={needs_component_label}, " - f"needs_owner={needs_owner}" - ) - return { - "number": issue_data["number"], - "title": issue_data["title"], - "body": issue_data.get("body", ""), - "has_planned_label": has_planned, - "has_component_label": has_component, - "existing_component_label": ( - list(existing_component_labels)[0] - if existing_component_labels - else None - ), - "needs_component_label": needs_component_label, - "needs_owner": needs_owner, - } - else: - print(f"Issue #{issue_number} is already fully triaged. Skipping.") - return None - except requests.exceptions.RequestException as e: - print(f"Error fetching issue #{issue_number}: {e}") - if hasattr(e, "response") and e.response is not None: - print(f"Response content: {e.response.text}") - return None - - -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 - and hasattr(event.content.parts[0], "text") - and event.content.parts[0].text - ): - print(f"** {event.author} (ADK): {event.content.parts[0].text}") - if event.author == agent.root_agent.name: - final_response_text += event.content.parts[0].text - - return final_response_text - - -async def main(): - runner = InMemoryRunner( - agent=agent.root_agent, - app_name=APP_NAME, - ) - session = await runner.session_service.create_session( - user_id=USER_ID, - app_name=APP_NAME, - ) - - if EVENT_NAME == "issues" and ISSUE_NUMBER: - print(f"EVENT: Processing specific issue due to '{EVENT_NAME}' event.") - issue_number = parse_number_string(ISSUE_NUMBER) - if not issue_number: - print(f"Error: Invalid issue number received: {ISSUE_NUMBER}.") - return - - specific_issue = await fetch_specific_issue_details(issue_number) - if specific_issue is None: - print( - f"No issue details found for #{issue_number} that needs triaging," - " or an error occurred. Skipping agent interaction." - ) - return - - issue_title = ISSUE_TITLE or specific_issue["title"] - issue_body = ISSUE_BODY or specific_issue["body"] - needs_component_label = specific_issue.get("needs_component_label", True) - needs_owner = specific_issue.get("needs_owner", False) - existing_component_label = specific_issue.get("existing_component_label") - - prompt = ( - f"Triage GitHub issue #{issue_number}.\n\n" - f'Title: "{issue_title}"\n' - f'Body: "{issue_body}"\n\n' - f"Issue state: needs_component_label={needs_component_label}, " - f"needs_owner={needs_owner}, " - f"existing_component_label={existing_component_label}" - ) - else: - print(f"EVENT: Processing batch of issues (event: {EVENT_NAME}).") - issue_count = parse_number_string(ISSUE_COUNT_TO_PROCESS, default_value=3) - prompt = ( - f"Please use 'list_untriaged_issues' to find {issue_count} issues that" - " need triaging, then triage each one according to your instructions." - ) - - response = await call_agent_async(runner, USER_ID, session.id, prompt) - print(f"<<<< Agent Final Output: {response}\n") - - -if __name__ == "__main__": - start_time = time.time() - print( - f"Start triaging {OWNER}/{REPO} issues 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_team/adk_triaging_agent/settings.py b/contributing/samples/adk_team/adk_triaging_agent/settings.py deleted file mode 100644 index fdc8b4e0..00000000 --- a/contributing/samples/adk_team/adk_triaging_agent/settings.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2026 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_TITLE = os.getenv("ISSUE_TITLE") -ISSUE_BODY = os.getenv("ISSUE_BODY") -ISSUE_COUNT_TO_PROCESS = os.getenv("ISSUE_COUNT_TO_PROCESS") - -IS_INTERACTIVE = os.environ.get("INTERACTIVE", "1").lower() in ["true", "1"] diff --git a/contributing/samples/adk_team/adk_triaging_agent/utils.py b/contributing/samples/adk_team/adk_triaging_agent/utils.py deleted file mode 100644 index 8c5aa9b1..00000000 --- a/contributing/samples/adk_team/adk_triaging_agent/utils.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2026 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_triaging_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 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() - - -def error_response(error_message: str) -> dict[str, Any]: - return {"status": "error", "message": error_message} - - -def parse_number_string(number_str: str, default_value: int = 0) -> int: - """Parse a number from the given string.""" - try: - return int(number_str) - except ValueError: - print( - f"Warning: Invalid number string: {number_str}. Defaulting to" - f" {default_value}." - ) - return default_value