Skip to content

Commit 3aef9a1

Browse files
xuanyang15copybara-github
authored andcommitted
docs: Update ADK issue triaging agent to add component label before planned
Co-authored-by: Xuan Yang <xygoogle@google.com> PiperOrigin-RevId: 839391092
1 parent 7740113 commit 3aef9a1

4 files changed

Lines changed: 174 additions & 63 deletions

File tree

.github/workflows/triage.yml

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@ name: ADK Issue Triaging Agent
22

33
on:
44
issues:
5-
types: [labeled]
5+
types: [opened, labeled]
66
schedule:
7-
# Run every 6 hours to triage planned but not triaged issues
7+
# Run every 6 hours to triage untriaged issues
88
- cron: '0 */6 * * *'
99

1010
jobs:
1111
agent-triage-issues:
1212
runs-on: ubuntu-latest
13-
# Only run if labeled with "planned" or if it's a scheduled run
14-
if: github.event_name == 'schedule' || github.event.label.name == 'planned'
13+
# Run for:
14+
# - Scheduled runs (batch processing)
15+
# - New issues (need component labeling)
16+
# - Issues labeled with "planned" (need owner assignment)
17+
if: >-
18+
github.event_name == 'schedule' ||
19+
github.event.action == 'opened' ||
20+
github.event.label.name == 'planned'
1521
permissions:
1622
issues: write
1723
contents: read
@@ -35,8 +41,8 @@ jobs:
3541
GITHUB_TOKEN: ${{ secrets.ADK_TRIAGE_AGENT }}
3642
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
3743
GOOGLE_GENAI_USE_VERTEXAI: 0
38-
OWNER: 'google'
39-
REPO: 'adk-python'
44+
OWNER: ${{ github.repository_owner }}
45+
REPO: ${{ github.event.repository.name }}
4046
INTERACTIVE: 0
4147
EVENT_NAME: ${{ github.event_name }} # 'issues', 'schedule', etc.
4248
ISSUE_NUMBER: ${{ github.event.issue.number }}

contributing/samples/adk_triaging_agent/README.md

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,39 @@
11
# ADK Issue Triaging Assistant
22

3-
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 new and unlabelled issues, recommend appropriate labels based on a predefined set of rules, and apply them.
3+
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.
44

55
This agent can be operated in two distinct modes: an interactive mode for local use or as a fully automated GitHub Actions workflow.
66

77
---
88

9+
## Triaging Workflow
10+
11+
The agent performs different actions based on the issue state:
12+
13+
| Condition | Actions |
14+
|-----------|---------|
15+
| Issue without component label | Add component label + Set issue type (Bug/Feature) |
16+
| Issue with "planned" label but no assignee | Assign owner based on component label |
17+
| Issue with "planned" label AND no component label | Add component label + Set type + Assign owner |
18+
19+
### Component Labels
20+
The agent can assign the following component labels, each mapped to an owner:
21+
- `core`, `tools`, `mcp`, `eval`, `live`, `models`, `tracing`, `web`, `services`, `documentation`, `question`, `agent engine`, `a2a`, `bq`
22+
23+
### Issue Types
24+
Based on the issue content, the agent will set the issue type to:
25+
- **Bug**: For bug reports
26+
- **Feature**: For feature requests
27+
28+
---
29+
930
## Interactive Mode
1031

1132
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.
1233

1334
### Features
1435
* **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 applying a label to a GitHub issue.
36+
* **User Approval**: In interactive mode, the agent is instructed to ask for your confirmation before applying labels or assigning owners.
1637

1738
### Running in Interactive Mode
1839
To run the agent in interactive mode, first set the required environment variables. Then, execute the following command in your terminal:
@@ -31,12 +52,19 @@ For automated, hands-off issue triaging, the agent can be integrated directly in
3152
### Workflow Triggers
3253
The GitHub workflow is configured to run on specific triggers:
3354

34-
1. **Issue Events**: The workflow executes automatically whenever a new issue is `opened` or an existing one is `reopened`.
55+
1. **New Issues (`opened`)**: When a new issue is created, the agent adds an appropriate component label and sets the issue type.
56+
57+
2. **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.
58+
59+
3. **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).
3560

36-
2. **Scheduled Runs**: The workflow also runs on a recurring schedule (every 6 hours) to process any unlabelled issues that may have been missed.
61+
### Automated Actions
62+
When running as part of the GitHub workflow, the agent operates non-interactively:
63+
- **Component Labeling**: Automatically applies the most appropriate component label
64+
- **Issue Type Setting**: Sets the issue type to Bug or Feature based on content
65+
- **Owner Assignment**: Only assigns owners for issues marked as "planned"
3766

38-
### Automated Labeling
39-
When running as part of the GitHub workflow, the agent operates non-interactively. It identifies the best label and applies it directly without requiring user approval. This behavior is configured by setting the `INTERACTIVE` environment variable to `0` in the workflow file.
67+
This behavior is configured by setting the `INTERACTIVE` environment variable to `0` in the workflow file.
4068

4169
### Workflow Configuration
4270
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.
@@ -60,8 +88,8 @@ The following environment variables are required for the agent to connect to the
6088

6189
* `GITHUB_TOKEN`: **(Required)** A GitHub Personal Access Token with `issues:write` permissions. Needed for both interactive and workflow modes.
6290
* `GOOGLE_API_KEY`: **(Required)** Your API key for the Gemini API. Needed for both interactive and workflow modes.
63-
* `OWNER`: The GitHub organization or username that owns the repository (e.g., `google`). Needed for both modes.
64-
* `REPO`: The name of the GitHub repository (e.g., `adk-python`). Needed for both modes.
91+
* `OWNER`: The GitHub organization or username that owns the repository (e.g., `google`). In the workflow, this is automatically set from the repository context.
92+
* `REPO`: The name of the GitHub repository (e.g., `adk-python`). In the workflow, this is automatically set from the repository context.
6593
* `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.
6694

6795
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.

contributing/samples/adk_triaging_agent/agent.py

Lines changed: 89 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -78,22 +78,27 @@
7878
APPROVAL_INSTRUCTION = "Only label them when the user approves the labeling!"
7979

8080

81-
def list_planned_untriaged_issues(issue_count: int) -> dict[str, Any]:
82-
"""List planned issues without component labels (e.g., core, tools, etc.).
81+
def list_untriaged_issues(issue_count: int) -> dict[str, Any]:
82+
"""List open issues that need triaging.
83+
84+
Returns issues that need any of the following actions:
85+
1. Issues without component labels (need labeling + type setting)
86+
2. Issues with 'planned' label but no assignee (need owner assignment)
8387
8488
Args:
8589
issue_count: number of issues to return
8690
8791
Returns:
8892
The status of this request, with a list of issues when successful.
93+
Each issue includes flags indicating what actions are needed.
8994
"""
9095
url = f"{GITHUB_BASE_URL}/search/issues"
91-
query = f"repo:{OWNER}/{REPO} is:open is:issue label:planned"
96+
query = f"repo:{OWNER}/{REPO} is:open is:issue"
9297
params = {
9398
"q": query,
9499
"sort": "created",
95100
"order": "desc",
96-
"per_page": issue_count,
101+
"per_page": 100, # Fetch more to filter
97102
"page": 1,
98103
}
99104

@@ -103,29 +108,46 @@ def list_planned_untriaged_issues(issue_count: int) -> dict[str, Any]:
103108
return error_response(f"Error: {e}")
104109
issues = response.get("items", [])
105110

106-
# Filter out issues that already have component labels
107111
component_labels = set(LABEL_TO_OWNER.keys())
108112
untriaged_issues = []
109113
for issue in issues:
110114
issue_labels = {label["name"] for label in issue.get("labels", [])}
111-
# If the issue only has "planned" but no component labels, it's untriaged
112-
if not (issue_labels & component_labels):
115+
assignees = issue.get("assignees", [])
116+
117+
existing_component_labels = issue_labels & component_labels
118+
has_component = bool(existing_component_labels)
119+
has_planned = "planned" in issue_labels
120+
121+
# Determine what actions are needed
122+
needs_component_label = not has_component
123+
needs_owner = has_planned and not assignees
124+
125+
# Include issue if it needs any action
126+
if needs_component_label or needs_owner:
127+
issue["has_planned_label"] = has_planned
128+
issue["has_component_label"] = has_component
129+
issue["existing_component_label"] = (
130+
list(existing_component_labels)[0]
131+
if existing_component_labels
132+
else None
133+
)
134+
issue["needs_component_label"] = needs_component_label
135+
issue["needs_owner"] = needs_owner
113136
untriaged_issues.append(issue)
137+
if len(untriaged_issues) >= issue_count:
138+
break
114139
return {"status": "success", "issues": untriaged_issues}
115140

116141

117-
def add_label_and_owner_to_issue(
118-
issue_number: int, label: str
119-
) -> dict[str, Any]:
120-
"""Add the specified label and owner to the given issue number.
142+
def add_label_to_issue(issue_number: int, label: str) -> dict[str, Any]:
143+
"""Add the specified component label to the given issue number.
121144
122145
Args:
123146
issue_number: issue number of the GitHub issue.
124147
label: label to assign
125148
126149
Returns:
127-
The the status of this request, with the applied label and assigned owner
128-
when successful.
150+
The status of this request, with the applied label when successful.
129151
"""
130152
print(f"Attempting to add label '{label}' to issue #{issue_number}")
131153
if label not in LABEL_TO_OWNER:
@@ -143,15 +165,38 @@ def add_label_and_owner_to_issue(
143165
except requests.exceptions.RequestException as e:
144166
return error_response(f"Error: {e}")
145167

168+
return {
169+
"status": "success",
170+
"message": response,
171+
"applied_label": label,
172+
}
173+
174+
175+
def add_owner_to_issue(issue_number: int, label: str) -> dict[str, Any]:
176+
"""Assign an owner to the issue based on the component label.
177+
178+
This should only be called for issues that have the 'planned' label.
179+
180+
Args:
181+
issue_number: issue number of the GitHub issue.
182+
label: component label that determines the owner to assign
183+
184+
Returns:
185+
The status of this request, with the assigned owner when successful.
186+
"""
187+
print(
188+
f"Attempting to assign owner for label '{label}' to issue #{issue_number}"
189+
)
190+
if label not in LABEL_TO_OWNER:
191+
return error_response(
192+
f"Error: Label '{label}' is not a valid component label."
193+
)
194+
146195
owner = LABEL_TO_OWNER.get(label, None)
147196
if not owner:
148197
return {
149198
"status": "warning",
150-
"message": (
151-
f"{response}\n\nLabel '{label}' does not have an owner. Will not"
152-
" assign."
153-
),
154-
"applied_label": label,
199+
"message": f"Label '{label}' does not have an owner. Will not assign.",
155200
}
156201

157202
assignee_url = (
@@ -167,7 +212,6 @@ def add_label_and_owner_to_issue(
167212
return {
168213
"status": "success",
169214
"message": response,
170-
"applied_label": label,
171215
"assigned_owner": owner,
172216
}
173217

@@ -223,29 +267,46 @@ def change_issue_type(issue_number: int, issue_type: str) -> dict[str, Any]:
223267
- If it's about BigQuery integrations, label it with "bq".
224268
- If you can't find an appropriate labels for the issue, follow the previous instruction that starts with "IMPORTANT:".
225269
226-
Call the `add_label_and_owner_to_issue` tool to label the issue, which will also assign the issue to the owner of the label.
270+
## Triaging Workflow
271+
272+
Each issue will have flags indicating what actions are needed:
273+
- `needs_component_label`: true if the issue needs a component label
274+
- `needs_owner`: true if the issue needs an owner assigned (has 'planned' label but no assignee)
275+
276+
For each issue, perform ONLY the required actions based on the flags:
277+
278+
1. **If `needs_component_label` is true**:
279+
- Use `add_label_to_issue` to add the appropriate component label
280+
- Use `change_issue_type` to set the issue type:
281+
- Bug report → "Bug"
282+
- Feature request → "Feature"
283+
- Otherwise → do not change the issue type
284+
285+
2. **If `needs_owner` is true**:
286+
- Use `add_owner_to_issue` to assign an owner based on the component label
287+
- Note: If the issue already has a component label (`has_component_label: true`), use that existing label to determine the owner
227288
228-
After you label the issue, call the `change_issue_type` tool to change the issue type:
229-
- If the issue is a bug report, change the issue type to "Bug".
230-
- If the issue is a feature request, change the issue type to "Feature".
231-
- Otherwise, **do not change the issue type**.
289+
Do NOT add a component label if `needs_component_label` is false.
290+
Do NOT assign an owner if `needs_owner` is false.
232291
233292
Response quality requirements:
234293
- Summarize the issue in your own words without leaving template
235294
placeholders (never output text like "[fill in later]").
236295
- Justify the chosen label with a short explanation referencing the issue
237296
details.
238-
- Mention the assigned owner when a label maps to one.
297+
- Mention the assigned owner only when you actually assign one (i.e., when
298+
the issue has the 'planned' label).
239299
- If no label is applied, clearly state why.
240300
241301
Present the following in an easy to read format highlighting issue number and your label.
242302
- the issue summary in a few sentence
243303
- your label recommendation and justification
244-
- the owner of the label if you assign the issue to an owner
304+
- the owner of the label if you assign the issue to an owner (only for planned issues)
245305
""",
246306
tools=[
247-
list_planned_untriaged_issues,
248-
add_label_and_owner_to_issue,
307+
list_untriaged_issues,
308+
add_label_to_issue,
309+
add_owner_to_issue,
249310
change_issue_type,
250311
],
251312
)

contributing/samples/adk_triaging_agent/main.py

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,41 @@ async def fetch_specific_issue_details(issue_number: int):
4646
issue_data = get_request(url)
4747
labels = issue_data.get("labels", [])
4848
label_names = {label["name"] for label in labels}
49+
assignees = issue_data.get("assignees", [])
4950

50-
# Check if issue has "planned" label but no component labels
51+
# Check issue state
5152
component_labels = set(LABEL_TO_OWNER.keys())
5253
has_planned = "planned" in label_names
53-
has_component = bool(label_names & component_labels)
54+
existing_component_labels = label_names & component_labels
55+
has_component = bool(existing_component_labels)
56+
has_assignee = len(assignees) > 0
5457

55-
if has_planned and not has_component:
56-
print(f"Issue #{issue_number} is planned but not triaged. Proceeding.")
58+
# Determine what actions are needed
59+
needs_component_label = not has_component
60+
needs_owner = has_planned and not has_assignee
61+
62+
if needs_component_label or needs_owner:
63+
print(
64+
f"Issue #{issue_number} needs triaging. "
65+
f"needs_component_label={needs_component_label}, "
66+
f"needs_owner={needs_owner}"
67+
)
5768
return {
5869
"number": issue_data["number"],
5970
"title": issue_data["title"],
6071
"body": issue_data.get("body", ""),
72+
"has_planned_label": has_planned,
73+
"has_component_label": has_component,
74+
"existing_component_label": (
75+
list(existing_component_labels)[0]
76+
if existing_component_labels
77+
else None
78+
),
79+
"needs_component_label": needs_component_label,
80+
"needs_owner": needs_owner,
6181
}
6282
else:
63-
print(
64-
f"Issue #{issue_number} is already triaged or doesn't have"
65-
" 'planned' label. Skipping."
66-
)
83+
print(f"Issue #{issue_number} is already fully triaged. Skipping.")
6784
return None
6885
except requests.exceptions.RequestException as e:
6986
print(f"Error fetching issue #{issue_number}: {e}")
@@ -127,25 +144,24 @@ async def main():
127144

128145
issue_title = ISSUE_TITLE or specific_issue["title"]
129146
issue_body = ISSUE_BODY or specific_issue["body"]
147+
needs_component_label = specific_issue.get("needs_component_label", True)
148+
needs_owner = specific_issue.get("needs_owner", False)
149+
existing_component_label = specific_issue.get("existing_component_label")
150+
130151
prompt = (
131-
f"A GitHub issue #{issue_number} has been labeled as 'planned'."
132-
f' Title: "{issue_title}"\nBody:'
133-
f' "{issue_body}"\n\nBased on the rules, recommend an'
134-
" appropriate component label and its justification."
135-
" Then, use the 'add_label_and_owner_to_issue' tool to apply the"
136-
" label directly to this issue. Only label it, do not"
137-
" process any other issues."
152+
f"Triage GitHub issue #{issue_number}.\n\n"
153+
f'Title: "{issue_title}"\n'
154+
f'Body: "{issue_body}"\n\n'
155+
f"Issue state: needs_component_label={needs_component_label}, "
156+
f"needs_owner={needs_owner}, "
157+
f"existing_component_label={existing_component_label}"
138158
)
139159
else:
140160
print(f"EVENT: Processing batch of issues (event: {EVENT_NAME}).")
141161
issue_count = parse_number_string(ISSUE_COUNT_TO_PROCESS, default_value=3)
142162
prompt = (
143-
"Please use the 'list_planned_untriaged_issues' tool to find the"
144-
f" most recent {issue_count} planned issues that haven't been"
145-
" triaged yet (i.e., issues with 'planned' label but no component"
146-
" labels like 'core', 'tools', etc.). Then triage each of them by"
147-
" applying appropriate component labels. If you cannot find any planned"
148-
" issues, please don't try to triage any issues."
163+
f"Please use 'list_untriaged_issues' to find {issue_count} issues that"
164+
" need triaging, then triage each one according to your instructions."
149165
)
150166

151167
response = await call_agent_async(runner, USER_ID, session.id, prompt)

0 commit comments

Comments
 (0)