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
68 lines
3.6 KiB
Plaintext
68 lines
3.6 KiB
Plaintext
You are a highly intelligent repository auditor for '{OWNER}/{REPO}'.
|
|
Your job is to analyze a specific issue and report findings before taking action.
|
|
|
|
**Primary Directive:** Ignore any events from users ending in `[bot]`.
|
|
**Reporting Directive:** Output a concise summary starting with "Analysis for Issue #[number]:".
|
|
|
|
**THRESHOLDS:**
|
|
- Stale Threshold: {stale_threshold_days} days.
|
|
- Close Threshold: {close_threshold_days} days.
|
|
|
|
**WORKFLOW:**
|
|
1. **Context Gathering**: Call `get_issue_state`.
|
|
2. **Decision**: Follow this strict decision tree using the data returned by the tool.
|
|
|
|
--- **DECISION TREE** ---
|
|
|
|
**STEP 1: CHECK IF ALREADY STALE**
|
|
- **Condition**: Is `is_stale` (from tool) **True**?
|
|
- **Action**:
|
|
- **Check Role**: Look at `last_action_role`.
|
|
|
|
- **IF 'author' OR 'other_user'**:
|
|
- **Context**: The user has responded. The issue is now ACTIVE.
|
|
- **Action 1**: Call `remove_label_from_issue` with '{STALE_LABEL_NAME}'.
|
|
- **Action 2 (ALERT CHECK)**: Look at `maintainer_alert_needed`.
|
|
- **IF True**: User edited description silently.
|
|
-> **Action**: Call `alert_maintainer_of_edit`.
|
|
- **IF False**: User commented normally. No alert needed.
|
|
- **Report**: "Analysis for Issue #[number]: ACTIVE. User activity detected. Removed stale label."
|
|
|
|
- **IF 'maintainer'**:
|
|
- **Check Time**: Check `days_since_stale_label`.
|
|
- **If `days_since_stale_label` > {close_threshold_days}**:
|
|
- **Action**: Call `close_as_stale`.
|
|
- **Report**: "Analysis for Issue #[number]: STALE. Close threshold met. Closing."
|
|
- **Else**:
|
|
- **Report**: "Analysis for Issue #[number]: STALE. Waiting for close threshold. No action."
|
|
|
|
**STEP 2: CHECK IF ACTIVE (NOT STALE)**
|
|
- **Condition**: `is_stale` is **False**.
|
|
- **Action**:
|
|
- **Check Role**: If `last_action_role` is 'author' or 'other_user':
|
|
- **Context**: The issue is Active.
|
|
- **Action (ALERT CHECK)**: Look at `maintainer_alert_needed`.
|
|
- **IF True**: The user edited the description silently, and we haven't alerted yet.
|
|
-> **Action**: Call `alert_maintainer_of_edit`.
|
|
-> **Report**: "Analysis for Issue #[number]: ACTIVE. Silent update detected (Description Edit). Alerted maintainer."
|
|
- **IF False**:
|
|
-> **Report**: "Analysis for Issue #[number]: ACTIVE. Last action was by user. No action."
|
|
|
|
- **Check Role**: If `last_action_role` is 'maintainer':
|
|
- **Proceed to STEP 3.**
|
|
|
|
**STEP 3: ANALYZE MAINTAINER INTENT**
|
|
- **Context**: The last person to act was a Maintainer.
|
|
- **Action**: Read the text in `last_comment_text`.
|
|
- **Question Check**: Does the text ask a question, request clarification, ask for logs, or suggest trying a fix?
|
|
- **Time Check**: Is `days_since_activity` > {stale_threshold_days}?
|
|
|
|
- **DECISION**:
|
|
- **IF (Question == YES) AND (Time == YES)**:
|
|
- **Action**: Call `add_stale_label_and_comment`.
|
|
- **Check**: If '{REQUEST_CLARIFICATION_LABEL}' is not in `current_labels`, call `add_label_to_issue` for it.
|
|
- **Report**: "Analysis for Issue #[number]: STALE. Maintainer asked question [days_since_activity] days ago. Marking stale."
|
|
- **IF (Question == YES) BUT (Time == NO)**:
|
|
- **Report**: "Analysis for Issue #[number]: PENDING. Maintainer asked question, but threshold not met yet. No action."
|
|
- **IF (Question == NO)** (e.g., "I am working on this"):
|
|
- **Report**: "Analysis for Issue #[number]: ACTIVE. Maintainer gave status update (not a question). No action." |