From 679d543f8e84a8290064a72319477c45084cb303 Mon Sep 17 00:00:00 2001 From: Xuan Yang Date: Wed, 19 Nov 2025 16:26:23 -0800 Subject: [PATCH] docs: Update ADK triaging agent to only triage planned issues It also enables the ADK triaging agent to run periodically on planned but not triaged issues. Co-authored-by: Xuan Yang PiperOrigin-RevId: 834489103 --- .github/workflows/triage.yml | 7 +++- .../samples/adk_triaging_agent/agent.py | 37 ++++++++++++++++ .../samples/adk_triaging_agent/main.py | 42 +++++++++++++------ 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 57e729e9..0d310cee 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -2,11 +2,16 @@ name: ADK Issue Triaging Agent on: issues: - types: [opened, reopened] + types: [labeled] + schedule: + # Run every 6 hours to triage planned but not triaged issues + - cron: '0 */6 * * *' jobs: agent-triage-issues: runs-on: ubuntu-latest + # Only run if labeled with "planned" or if it's a scheduled run + if: github.event_name == 'schedule' || github.event.label.name == 'planned' permissions: issues: write contents: read diff --git a/contributing/samples/adk_triaging_agent/agent.py b/contributing/samples/adk_triaging_agent/agent.py index 4ffcc352..9504e72d 100644 --- a/contributing/samples/adk_triaging_agent/agent.py +++ b/contributing/samples/adk_triaging_agent/agent.py @@ -110,6 +110,42 @@ def list_unlabeled_issues(issue_count: int) -> dict[str, Any]: return {"status": "success", "issues": unlabeled_issues} +def list_planned_untriaged_issues(issue_count: int) -> dict[str, Any]: + """List planned issues without component labels (e.g., core, tools, etc.). + + Args: + issue_count: number of issues to return + + Returns: + The status of this request, with a list of issues when successful. + """ + url = f"{GITHUB_BASE_URL}/search/issues" + query = f"repo:{OWNER}/{REPO} is:open is:issue label:planned" + params = { + "q": query, + "sort": "created", + "order": "desc", + "per_page": issue_count, + "page": 1, + } + + try: + response = get_request(url, params) + except requests.exceptions.RequestException as e: + return error_response(f"Error: {e}") + issues = response.get("items", []) + + # Filter out issues that already have component labels + component_labels = set(LABEL_TO_OWNER.keys()) + untriaged_issues = [] + for issue in issues: + issue_labels = {label["name"] for label in issue.get("labels", [])} + # If the issue only has "planned" but no component labels, it's untriaged + if not (issue_labels & component_labels): + untriaged_issues.append(issue) + return {"status": "success", "issues": untriaged_issues} + + def add_label_and_owner_to_issue( issue_number: int, label: str ) -> dict[str, Any]: @@ -241,6 +277,7 @@ root_agent = Agent( """, tools=[ list_unlabeled_issues, + list_planned_untriaged_issues, add_label_and_owner_to_issue, change_issue_type, ], diff --git a/contributing/samples/adk_triaging_agent/main.py b/contributing/samples/adk_triaging_agent/main.py index 317f5893..f608a696 100644 --- a/contributing/samples/adk_triaging_agent/main.py +++ b/contributing/samples/adk_triaging_agent/main.py @@ -16,6 +16,7 @@ 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 @@ -37,21 +38,32 @@ USER_ID = "adk_triage_user" async def fetch_specific_issue_details(issue_number: int): - """Fetches details for a single issue if it's unlabelled.""" + """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) - if not issue_data.get("labels", None): - print(f"Issue #{issue_number} is unlabelled. Proceeding.") + labels = issue_data.get("labels", []) + label_names = {label["name"] for label in labels} + + # Check if issue has "planned" label but no component labels + component_labels = set(LABEL_TO_OWNER.keys()) + has_planned = "planned" in label_names + has_component = bool(label_names & component_labels) + + if has_planned and not has_component: + print(f"Issue #{issue_number} is planned but not triaged. Proceeding.") return { "number": issue_data["number"], "title": issue_data["title"], "body": issue_data.get("body", ""), } else: - print(f"Issue #{issue_number} is already labelled. Skipping.") + print( + f"Issue #{issue_number} is already triaged or doesn't have" + " 'planned' label. Skipping." + ) return None except requests.exceptions.RequestException as e: print(f"Error fetching issue #{issue_number}: {e}") @@ -108,26 +120,32 @@ async def main(): specific_issue = await fetch_specific_issue_details(issue_number) if specific_issue is None: print( - f"No unlabelled issue details found for #{issue_number} or an error" - " occurred. Skipping agent interaction." + 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"] prompt = ( - f"A new GitHub issue #{issue_number} has been opened or" - f' reopened. Title: "{issue_title}"\nBody:' + f"A GitHub issue #{issue_number} has been labeled as 'planned'." + f' Title: "{issue_title}"\nBody:' f' "{issue_body}"\n\nBased on the rules, recommend an' - " appropriate label and its justification." - " Then, use the 'add_label_to_issue' tool to apply the label " - "directly to this issue. Only label it, do not" + " appropriate component label and its justification." + " Then, use the 'add_label_and_owner_to_issue' tool to apply the" + " label directly to this issue. Only label it, do not" " process any other issues." ) 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 triage the most recent {issue_count} issues." + prompt = ( + "Please use the 'list_planned_untriaged_issues' tool to find the" + f" most recent {issue_count} planned issues that haven't been" + " triaged yet (i.e., issues with 'planned' label but no component" + " labels like 'core', 'tools', etc.). Then triage each of them by" + " applying appropriate component labels." + ) response = await call_agent_async(runner, USER_ID, session.id, prompt) print(f"<<<< Agent Final Output: {response}\n")