Skip to content

Conversation

KangmoonSeo
Copy link
Contributor

@KangmoonSeo KangmoonSeo commented May 22, 2025

User description

Description

This PR implements 3 changes to properly exclude RateLimitError from retry logic in all AIHandler.chat_completion()

Changes

  1. Applied consistent @retry library:
    • All AIHandler classes now use from tenacity import retry instead of the built-in import retry library
      • OpenAIHandler at pr_agent/algo/ai_handlers/openai_ai_handler.py
      • LangChainOpenAIHandler at pr_agent/algo/ai_handlers/langchain_ai_handler.py
  2. Excluded RateLimitError from @retry conditions:
    • Previous APIError condition was absorbing RateLimitError instances, preventing retry breakpoint
    # AS-IS (Problematic)
    @retry(
        retry=retry_if_exception_type((openai.APIError, openai.APIConnectionError, openai.APITimeoutError)), # No retry on RateLimitError
        stop=stop_after_attempt(OPENAI_RETRIES)
    )
    def foo():
      raise openai.RateLimitError  # Oops... This gets retried because RateLimitError inherits from APIError :-(
    • So, I added retry_if_not_exception_type to include APIError while explicitly excluding RateLimitError
    # TO-BE
    @retry(
        retry=retry_if_exception_type(openai.APIError) & retry_if_not_exception_type(openai.RateLimitError),
        stop=stop_after_attempt(OPENAI_RETRIES),
    )
  3. Applied consistent exception handling pattern:

closes #1807


Test Results

AS-IS (Problematic)

  • image
  • RateLimitError incorrectly retried 5 times despite error-level logging

TO-BE (Fixed)

  1. RateLimitError scenario

    • image
    • RateLimitError now exits immediately after single attempt as intended
  2. APIError (non-RateLimit) scenario

    • image
    • Other APIError types correctly retry 5 times as expected
  3. Normal execution

    • image
    • Standard operation works as expected

PR Type

Bug fix, Enhancement


Description

  • Excludes RateLimitError from retry logic in all AIHandler chat_completion methods

    • Uses retry_if_not_exception_type(openai.RateLimitError) with tenacity for precise control
  • Standardizes retry and exception handling across OpenAI, LiteLLM, and LangChain handlers

    • Ensures consistent logging and error escalation patterns
  • Removes redundant or overly broad exception handling

    • Cleans up catch blocks and error messages for clarity

Changes walkthrough 📝

Relevant files
Bug fix
langchain_ai_handler.py
Refactor retry and error handling, exclude RateLimitError from retries

pr_agent/algo/ai_handlers/langchain_ai_handler.py

  • Switches to tenacity for retry logic
  • Excludes RateLimitError from retries using combined predicates
  • Refactors exception handling for clearer, consistent logging
  • Removes redundant exception types from retry and catch blocks
  • +15/-7   
    litellm_ai_handler.py
    Exclude RateLimitError from retries, unify error handling

    pr_agent/algo/ai_handlers/litellm_ai_handler.py

  • Updates retry logic to exclude RateLimitError using tenacity
  • Refactors exception handling for clarity and consistency
  • Removes redundant exception types from retry and catch blocks
  • +6/-6     
    openai_ai_handler.py
    Use tenacity, exclude RateLimitError from retry, refactor errors

    pr_agent/algo/ai_handlers/openai_ai_handler.py

  • Switches to tenacity for retry logic
  • Excludes RateLimitError from retries using combined predicates
  • Refactors exception handling for consistent logging and escalation
  • Removes redundant and overlapping exception handling
  • +13/-11 

    Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • Copy link
    Contributor

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    🎫 Ticket compliance analysis ✅

    1807 - PR Code Verified

    Compliant requirements:

    • Fix retry decorator in chat_completion to properly exclude RateLimitError
    • Expected behavior: RateLimitError should be raised immediately without retry attempts

    Requires further human verification:

    • Verify that the fix actually prevents RateLimitError from being retried in real-world scenarios
    ⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Exception Handling

    The exception handling code appears to be missing the actual exception variables in the catch blocks. The error and warning log messages reference an 'e' variable that isn't defined in the catch statement.

    except openai.RateLimitError as e:
        get_logger().error(f"Rate limit error during LLM inference: {e}")
        raise
    except openai.APIError as e:
        get_logger().warning(f"Error during LLM inference: {e}")
        raise
    except Exception as e:
        get_logger().warning(f"Unknown error during LLM inference: {e}")
        raise openai.APIError from e

    Copy link
    Contributor

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Include all retry-worthy exceptions

    The retry decorator is missing handling for openai.APITimeoutError and
    openai.APIConnectionError which were previously covered. These error types
    should be included in the retry logic to maintain the same level of resilience.

    pr_agent/algo/ai_handlers/langchain_ai_handler.py [39-42]

     @retry(
    -    retry=retry_if_exception_type(openai.APIError) & retry_if_not_exception_type(openai.RateLimitError),
    +    retry=(retry_if_exception_type((openai.APIError, openai.APITimeoutError, openai.APIConnectionError)) 
    +           & retry_if_not_exception_type(openai.RateLimitError)),
         stop=stop_after_attempt(OPENAI_RETRIES),
     )
    • Apply / Chat
    Suggestion importance[1-10]: 8

    __

    Why: The suggestion correctly points out that openai.APITimeoutError and openai.APIConnectionError were previously retried but are omitted in the new logic. Including them ensures the retry behavior remains robust against transient network and timeout errors, which is important for reliability.

    Medium
    Learned
    best practice
    Standardize logging format

    Use consistent f-string formatting for log messages as done in the error
    handling section. This improves readability and ensures consistent logging
    patterns throughout the codebase.

    pr_agent/algo/ai_handlers/openai_ai_handler.py [47-48]

    -get_logger().info("System: ", system)
    -get_logger().info("User: ", user)
    +get_logger().info(f"System: {system}")
    +get_logger().info(f"User: {user}")
    • Apply / Chat
    Suggestion importance[1-10]: 6

    __

    Why:
    Relevant best practice - Fix inconsistent error logging formats to maintain clarity and consistency in log messages

    Low
    • More
    • Author self-review: I have reviewed the PR code suggestions, and addressed the relevant ones.

    @mrT23
    Copy link
    Collaborator

    mrT23 commented May 24, 2025

    thanks @KangmoonSeo

    @mrT23 mrT23 merged commit 20b1a1f into qodo-ai:main May 24, 2025
    2 checks passed
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    RateLimitError incorrectly retried due to APIError inheritance in @retry decorator
    2 participants