Overview Modern AI development often begins with simple prompts but quickly devolves into unmanageable "spaghetti code" as developers add tools, data pipelines, and multiple model calls. This tutorial demonstrates how to apply classic software design patterns—Chain of Responsibility, Observer, and Strategy—to Python AI agents. By decoupling logic from prompts, you create modular systems that are easier to test, debug, and scale. These patterns transform one-off hacks into professional, maintainable software architectures. Prerequisites To follow this guide, you should have a solid grasp of Python basics, specifically functions and lists. Familiarity with Pydantic for data validation is helpful, along with a baseline understanding of how Large Language Models (LLMs) interact with system prompts and context. Key Libraries & Tools * **Pydantic AI**: A framework for building production-grade agents with built-in validation. * **OpenAI API**: The underlying model provider for generating agent responses. * **Python `typing` module**: Used for defining protocols and callable types to ensure structural typing. Code Walkthrough The Chain of Responsibility This pattern allows a series of specialized agents to process a request sequentially. Each step handles a specific concern—like finding a hotel or booking a flight—and passes the updated context to the next handler. ```python def plan_trip(user_input, deps): context = TripContext() # The Chain: A list of callables executed in sequence chain = [handle_destination, handle_flight, handle_hotel, handle_activities] for handler in chain: handler(user_input, deps, context) return context ``` The Observer Pattern Monitoring agent behavior is critical for debugging. The Observer pattern allows you to attach logging or monitoring tools without polluting your core business logic. ```python class AgentObserver(Protocol): def notify(self, agent_name: str, prompt: str, duration: float): ... def run_with_observers(agent, prompt, observers): start = time.time() output = agent.run(prompt) duration = time.time() - start for obs in observers: obs.notify(agent.name, prompt, duration) return output ``` The Strategy Pattern Use the Strategy pattern to swap agent behaviors or "personalities" dynamically. Instead of hardcoding prompts, you pass a strategy function that returns a preconfigured agent. ```python def run_travel_strategy(strategy_func, prompt): # The strategy_func acts as a pluggable behavior factory agent = strategy_func() return agent.run(prompt) Usage run_travel_strategy(get_budget_agent, "I need a trip to Paris") ``` Syntax Notes We utilize **Protocols** from Python's `typing` module to implement structural subtyping. This allows us to define what an "Observer" looks like without forcing rigid inheritance hierarchies. Additionally, using **Callables** as strategies keeps the implementation functional and lightweight compared to traditional class-heavy patterns. Practical Examples These patterns excel in multi-step workflows such as automated customer support triaging (Chain), real-time performance dashboards (Observer), or persona-driven marketing copy generation (Strategy). Tips & Gotchas Always ensure your context object is passed by reference through the chain to maintain state. A common mistake is failing to handle errors midway through a chain; if the destination agent fails, the flight agent should likely never run. Implement early exits or error-handling strategies within your loop to prevent cascading failures.
Pydantic AI
Products
- Sep 12, 2025
- Aug 29, 2025