Tutorial Image: LangGraph Tutorial: Rate Limiting Implementation - Unit 2.2 Exercise 3

LangGraph Tutorial: Rate Limiting Implementation - Unit 2.2 Exercise 3

Learn to implement rate limiting in LangGraph applications for controlling tool usage and ensuring system stability. This tutorial covers state management, usage tracking, and error handling to prevent resource abuse and maintain fair usage policies.

๐ŸŽฏ What You'll Learn Today

LangGraph Tutorial: Rate Limiting Implementation - Unit 2.2 Exercise 3

This tutorial is also available in Google Colab here or for download here

Joint Initiative: This tutorial is part of a collaboration between AI Product Engineer and the Nebius Academy. This tutorial demonstrates how to implement rate limiting in LangGraph applications to control tool usage and prevent abuse.

Key Concepts Covered

  1. State Management with TypedDict
  2. Rate Limit Tracking
  3. Usage Monitoring
  4. State Immutability Patterns
from typing import Annotated, TypedDict
!pip install langchain-core
!pip install langgraph
from langchain_core.messages import AIMessage, BaseMessage
from langgraph.graph.message import add_messages

Step 1: State Structure Definition

We define our state structure to track messages, tool usage, and rate limits.

Why This Matters

State structure is crucial because

  1. Enables tracking of tool usage across multiple interactions
  2. Provides a foundation for enforcing rate limits
  3. Maintains system stability through usage controls

Debug Tips

  1. State Initialization Issues:

    • Verify all dictionaries are properly initialized
    • Check for correct type annotations
    • Ensure rate_limits contains expected tools
class State(TypedDict):
    """State container with rate limiting capabilities.

    Attributes:
        messages: List of interaction messages
        tool_usage: Dictionary tracking number of times each tool is used
        rate_limits: Dictionary defining maximum uses allowed for each tool
    """

    messages: Annotated[list[BaseMessage], add_messages]
    tool_usage: dict[str, int]
    rate_limits: dict[str, int]

Step 2: Rate Limit Verification

Implement the logic to check if a tool has exceeded its rate limit.

Why This Matters

Rate limit checking is essential because

  1. Prevents abuse of expensive or limited resources
  2. Ensures fair system usage
  3. Protects external API quotas

Debug Tips

  1. Rate Checking Issues:

    • Add logging for limit checks
    • Verify default values for new tools
    • Check comparison logic
def check_rate_limit(state: State, tool_name: str) -> bool:
    """Check if tool has exceeded its rate limit.

    Args:
        state: Current application state
        tool_name: Name of the tool to check

    Returns:
        bool: True if tool hasn't exceeded its limit, False otherwise
    """
    usage = state["tool_usage"].get(tool_name, 0)
    limit = state["rate_limits"].get(tool_name, float("inf"))
    return usage < limit

Step 3: Usage Tracking Implementation

Implement the logic to update tool usage counts and handle rate limit violations.

Why This Matters

Usage tracking is critical because

  1. Maintains accurate usage statistics
  2. Enables proactive rate limit enforcement
  3. Provides audit trail for tool usage

Debug Tips

  1. State Update Issues:

    • Verify state immutability
    • Check dictionary updates
    • Monitor message addition
def update_usage(state: State, tool_name: str) -> State:
    """Update tool usage counts and handle rate limit violations.

    Args:
        state: Current application state
        tool_name: Name of the tool being used

    Returns:
        State: Updated state with new usage counts or rate limit message
    """
    if check_rate_limit(state, tool_name):
        return {
            **state,
            "tool_usage": {
                **state["tool_usage"],
                tool_name: state["tool_usage"].get(tool_name, 0) + 1,
            },
        }
    return {
        **state,
        "messages": [AIMessage(content=f"Rate limit exceeded for {tool_name}")],
    }

Step 4: Implementation Example

Demonstrate practical usage of the rate limiting system.

Why This Matters

Example implementation helps

  1. Verify system functionality
  2. Demonstrate expected behavior
  3. Provide usage patterns

Debug Tips

  1. Testing Issues:

    • Test edge cases
    • Verify limit boundaries
    • Check message generation

Initialize state with example values

state = {
    "messages": [],
    "tool_usage": {"calculator": 2},
    "rate_limits": {"calculator": 3},
}

Test rate limit checking

calculator_allowed = check_rate_limit(state, "calculator")

print(f"Calculator usage allowed: {calculator_allowed}")

Test usage update

updated_state = update_usage(state, "calculator")

print(f"Updated tool usage: {updated_state['tool_usage']}")

Test rate limit exceeded

final_state = update_usage(updated_state, "calculator")

print(f"Final state messages: {final_state['messages']}")

Common Pitfalls

  1. Forgetting to initialize tool_usage or rate_limits dictionaries
  2. Not handling new tools without defined limits
  3. Mutating state directly instead of creating new state
  4. Missing error handling for edge cases

Key Takeaways

  1. Rate limiting is essential for system stability
  2. Immutable state updates prevent side effects
  3. Clear error messages improve user experience
  4. Default values handle undefined tools gracefully

Next Steps

  1. Add persistent storage for usage tracking
  2. Implement time-based rate limiting
  3. Add usage analytics and reporting
  4. Implement rate limit recovery mechanisms

Expected Output

Calculator usage allowed: True
Updated tool usage: {'calculator': 3}
Final state messages: [AIMessage(content='Rate limit exceeded for calculator')]

Execute example if run directly
if __name__ == "__main__":
    # Initialize state with example values

    state = {
            "messages": [],
            "tool_usage": {"calculator": 2},
            "rate_limits": {"calculator": 3},
        }

    # Test rate limit checking

    calculator_allowed = check_rate_limit(state, "calculator")

    print(f"Calculator usage allowed: {calculator_allowed}")
    
    # Test usage update

    updated_state = update_usage(state, "calculator")

    print(f"Updated tool usage: {updated_state['tool_usage']}")
    
    # Test rate limit exceeded

    final_state = update_usage(updated_state, "calculator")

    print(f"Final state messages: {final_state['messages']}")

Rod Rivera

๐Ÿ‡ฌ๐Ÿ‡ง Chapter