mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
Merge https://github.com/google/adk-python/pull/3700 ### Description This PR refactors the `adk_stale_agent` to address `429 RESOURCE_EXHAUSTED` errors encountered during workflow execution. The previous implementation was inefficient in fetching issue history (using pagination over the REST API) and lacked server-side filtering, causing excessive API calls and huge token consumption that breached Gemini API quotas. The new implementation switches to a **GraphQL-first approach**, implements server-side filtering via the Search API, adds robust concurrency controls, and significantly improves code maintainability through modular refactoring. ### Root Cause of Failure The previous workflow failed with the following error due to passing too much context to the LLM and processing too many irrelevant issues: ```text google.genai.errors.ClientError: 429 RESOURCE_EXHAUSTED. Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_paid_tier_input_token_count ``` ### Key Changes #### 1. Optimization: REST → GraphQL (`agent.py`) * **Old:** Fetched issue comments and timeline events using multiple paginated REST API calls (`/timeline`). * **New:** Implemented `get_issue_state` using a single **GraphQL** query. This fetches comments, `userContentEdits`, and specific timeline events (Labels, Renames) in one network request. * **Refactoring:** The complex analysis logic has been decomposed into focused helper functions (_fetch_graphql_data, _build_history_timeline, _replay_history_to_find_state) for better readability and testing. * **Configurable:** Added GRAPHQL_COMMENT_LIMIT and GRAPHQL_TIMELINE_LIMIT settings to tune context depth * **Impact:** Drastically reduces the data payload size and eliminates multiple API round-trips, significantly lowering the token count sent to the LLM. #### 2. Optimization: Server-Side Filtering (`utils.py`) * **Old:** Fetched *all* open issues via REST and filtered them in Python memory. * **New:** Uses the GitHub Search API (`get_old_open_issue_numbers`) with `created:<DATE` syntax. * **Impact:** Only fetches issue numbers that actually meet the age threshold, preventing the agent from wasting cycles and tokens on brand-new issues. #### 3. Concurrency & Rate Limiting (`main.py` & `settings.py`) * **Old:** Sequential execution loop. * **New:** Implemented `asyncio.gather` with a configurable `CONCURRENCY_LIMIT` (set to 3). * **New:** Added `urllib3` retry strategies (exponential backoff) in `utils.py` to handle GitHub API rate limits (HTTP 429) gracefully. #### 4. Logic Improvements ("Ghost Edits") * **New Feature:** The agent now detects "Ghost Edits" (where an author updates the issue description without posting a new comment). * **Action:** If a silent edit is detected on a stale candidate, the agent now alerts maintainers instead of marking it stale, preventing false positives. ### File Comparison Summary | File | Change | | :--- | :--- | | `main.py` | Switched from `InMemoryRunner` loop to `asyncio` chunked processing. Added execution timing and API usage logging. | | `agent.py` | Replaced REST logic with GraphQL query. Added logic to handle silent body edits. Decomposed giant get_issue_state into helper functions with docstrings. Added _format_days helper. | | `utils.py` | Added `HTTPAdapter` with Retries. Added `get_old_open_issue_numbers` using Search API. | | `settings.py` | Removed `ISSUES_PER_RUN`; added configuration for CONCURRENCY_LIMIT, SLEEP_BETWEEN_CHUNKS, and GraphQL limits. | | `PROMPT_INSTRUCTIONS.txt` | Simplified decision tree; removed date calculation responsibility from LLM. | ### Verification The new logic minimizes token usage by offloading date calculations to Python and strictly limiting the context passed to the LLM to semantic intent analysis (e.g., "Is this a question?"). * **Metric Check:** The workflow now tracks API calls per issue to ensure we stay within limits. * **Safety:** Silent edits by users now correctly reset the "Stale" timer. * **Maintainability:** All complex logic is now isolated in typed helper functions with comprehensive docstrings. Co-authored-by: Xuan Yang <xygoogle@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/3700 from ryanaiagent:feat/improve-stale-agent 888064eff125ae74f7c3a9ad6c74f98de80243a2 PiperOrigin-RevId: 838885530
90 lines
4.7 KiB
Markdown
90 lines
4.7 KiB
Markdown
# ADK Stale Issue Auditor Agent
|
|
|
|
This directory contains an autonomous, **GraphQL-powered** agent designed to audit a GitHub repository for stale issues. It maintains repository hygiene by ensuring all open items are actionable and responsive.
|
|
|
|
Unlike traditional "Stale Bots" that only look at timestamps, this agent uses a **Unified History Trace** and an **LLM (Large Language Model)** to understand the *context* of a conversation. It distinguishes between a maintainer asking a question (stale candidate) vs. a maintainer providing a status update (active).
|
|
|
|
---
|
|
|
|
## Core Logic & Features
|
|
|
|
The agent operates as a "Repository Auditor," proactively scanning open issues using a high-efficiency decision tree.
|
|
|
|
### 1. Smart State Verification (GraphQL)
|
|
Instead of making multiple expensive API calls, the agent uses a single **GraphQL** query per issue to reconstruct the entire history of the conversation. It combines:
|
|
* **Comments**
|
|
* **Description/Body Edits** ("Ghost Edits")
|
|
* **Title Renames**
|
|
* **State Changes** (Reopens)
|
|
|
|
It sorts these events chronologically to determine the **Last Active Actor**.
|
|
|
|
### 2. The "Last Actor" Rule
|
|
The agent follows a precise logic flow based on who acted last:
|
|
|
|
* **If Author/User acted last:** The issue is **ACTIVE**.
|
|
* This includes comments, title changes, and *silent* description edits.
|
|
* **Action:** The agent immediately removes the `stale` label.
|
|
* **Silent Update Alert:** If the user edited the description but *did not* comment, the agent posts a specific alert: *"Notification: The author has updated the issue description..."* to ensure maintainers are notified (since GitHub does not trigger notifications for body edits).
|
|
* **Spam Prevention:** The agent checks if it has already alerted about a specific silent edit to avoid spamming the thread.
|
|
|
|
* **If Maintainer acted last:** The issue is **POTENTIALLY STALE**.
|
|
* The agent passes the text of the maintainer's last comment to the LLM.
|
|
|
|
### 3. Semantic Intent Analysis (LLM)
|
|
If the maintainer was the last person to speak, the LLM analyzes the comment text to determine intent:
|
|
* **Question/Request:** "Can you provide logs?" / "Please try v2.0."
|
|
* **Verdict:** **STALE** (Waiting on Author).
|
|
* **Action:** If the time threshold is met, the agent adds the `stale` label. It also checks for the `request clarification` label and adds it if missing.
|
|
* **Status Update:** "We are working on a fix." / "Added to backlog."
|
|
* **Verdict:** **ACTIVE** (Waiting on Maintainer).
|
|
* **Action:** No action taken. The issue remains open without stale labels.
|
|
|
|
### 4. Lifecycle Management
|
|
* **Marking Stale:** After `STALE_HOURS_THRESHOLD` (default: 7 days) of inactivity following a maintainer's question.
|
|
* **Closing:** After `CLOSE_HOURS_AFTER_STALE_THRESHOLD` (default: 7 days) of continued inactivity while marked stale.
|
|
|
|
---
|
|
|
|
## Performance & Safety
|
|
|
|
* **GraphQL Optimized:** Fetches comments, edits, labels, and timeline events in a single network request to minimize latency and API quota usage.
|
|
* **Search API Filtering:** Uses the GitHub Search API to pre-filter issues created recently, ensuring the bot doesn't waste cycles analyzing brand-new issues.
|
|
* **Rate Limit Aware:** Includes intelligent sleeping and retry logic (exponential backoff) to handle GitHub API rate limits (HTTP 429) gracefully.
|
|
* **Execution Metrics:** Logs the time taken and API calls consumed for every issue processed.
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
The agent is configured via environment variables, typically set as secrets in GitHub Actions.
|
|
|
|
### Required Secrets
|
|
|
|
| Secret Name | Description |
|
|
| :--- | :--- |
|
|
| `GITHUB_TOKEN` | A GitHub Personal Access Token (PAT) or Service Account Token with `repo` scope. |
|
|
| `GOOGLE_API_KEY` | An API key for the Google AI (Gemini) model used for reasoning. |
|
|
|
|
### Optional Configuration
|
|
|
|
These variables control the timing thresholds and model selection.
|
|
|
|
| Variable Name | Description | Default |
|
|
| :--- | :--- | :--- |
|
|
| `STALE_HOURS_THRESHOLD` | Hours of inactivity after a maintainer's question before marking as `stale`. | `168` (7 days) |
|
|
| `CLOSE_HOURS_AFTER_STALE_THRESHOLD` | Hours after being marked `stale` before the issue is closed. | `168` (7 days) |
|
|
| `LLM_MODEL_NAME`| The specific Gemini model version to use. | `gemini-2.5-flash` |
|
|
| `OWNER` | Repository owner (auto-detected in Actions). | (Environment dependent) |
|
|
| `REPO` | Repository name (auto-detected in Actions). | (Environment dependent) |
|
|
|
|
---
|
|
|
|
## Deployment
|
|
|
|
To deploy this agent, a GitHub Actions workflow file (`.github/workflows/stale-bot.yml`) is recommended.
|
|
|
|
### Directory Structure Note
|
|
Because this agent resides within the `adk-python` package structure, the workflow must ensure the script is executed correctly to handle imports.
|
|
|