Search Docs…

Search Docs…

Tutorials

Build your first AI Agent evaluation and test using Python

This hands-on tutorial will guide you through building a complete AI agent application using CoAgent's Python client. You'll create a recipe generator agent with context switching, logging integration, and error handling.

What You'll Build

By the end of this tutorial, you'll have:

  • A recipe generator agent that adapts to different cooking styles

  • Context-aware responses based on ingredients and dietary preferences

  • Integrated logging and monitoring

  • Error handling and recovery mechanisms

  • A complete Python application ready for production use

Prerequisites

  • CoAgent running locally (docker-compose up)

  • Python 3.8+ installed

  • Basic knowledge of Python programming

  • Familiarity with virtual environments (recommended)

Tutorial Overview

Phase 1: Environment Setup
Phase 2: Basic Agent Creation
Phase 3: Context-Aware Responses
Phase 4: Logging Integration
Phase 5: Error Handling & Recovery
Phase 6: Advanced Features
Phase 7: Testing & Validation

Phase 1: Environment Setup

1.1 Create Project Directory

mkdir recipe-agent-tutorial
cd

1.2 Set Up Virtual Environment

# Create virtual environment
python -m venv venv

# Activate virtual environment
# On macOS/Linux:
source venv/bin/activate
# On Windows:
# venv\Scripts\activate

1.3 Install Dependencies

Navigate to your CoAgent installation and install the Python client:

# Assuming CoAgent is cloned at /path/to/coagent
cd /path/to/coagent/langchain
pip install -r requirements.txt
pip install -e .  # Install coagent package in development mode

# Return to your project directory
cd

Create a requirements.txt for your project:

cat > requirements.txt << EOF
# Core dependencies
langchain-ollama>=0.1.0
langchain>=0.1.0
pydantic>=2.0.0
requests>=2.28.0

# Development dependencies
pytest>=7.0.0
python-dotenv>=1.0.0
rich>=13.0.0  # For pretty console output
EOF

pip install -r

1.4 Verify CoAgent Connection

Create a quick connection test:

# test_connection.py
import requests

def test_coagent_connection():
    try:
        response = requests.get("http://localhost:3000/health")
        if response.status_code == 200:
            print("āœ… CoAgent is running and accessible")
            return True
        else:
            print(f"āŒ CoAgent health check failed: {response.status_code}")
            return False
    except requests.ConnectionError:
        print("āŒ Cannot connect to CoAgent. Is it running on localhost:3000?")
        return False

if __name__ == "__main__":
    test_coagent_connection()

Run the test:

Phase 2: Basic Agent Creation

2.1 Create the Base Recipe Agent

Create recipe_agent.py:

#!/usr/bin/env python3
"""
Recipe Generator Agent - CoAgent Python Client Tutorial

A context-aware recipe generator that provides personalized cooking suggestions.
"""

from coagent import Coagent
from coagent_types import CoagentConfig, CoagentContext, LoggerConfig
from typing import List, Optional
import json
import os
from rich.console import Console
from rich.table import Table

console = Console()

class RecipeAgent:
    """A recipe generator agent with context-switching capabilities."""
    
    def __init__(self, model_name: str = "llama3.1:8b", enable_logging: bool = True):
        """Initialize the recipe agent with CoAgent configuration."""
        
        # Configure logging
        logger_config = None
        if enable_logging:
            logger_config = LoggerConfig(
                base_url="http://localhost:3000",
                enabled=True,
                timeout=30
            )
        
        # Create agent configuration
        config = CoagentConfig(
            model_name=model_name,
            logger_config=logger_config,
            contexts=self._create_contexts()
        )
        
        # Initialize the CoAgent
        self.agent = Coagent(config)
        console.print(f"āœ… Recipe Agent initialized with model: {model_name}")
    
    def _create_contexts(self) -> List[CoagentContext]:
        """Create specialized cooking contexts for different scenarios."""
        
        return [
            CoagentContext(
                name="quick_meals",
                description="Fast and easy recipes for busy schedules",
                prompt="""You are a quick meal specialist. Focus on recipes that can be prepared in 30 minutes or less using common ingredients. Prioritize simple techniques and minimal cleanup. Always include prep and cook times."""
            ),
            CoagentContext(
                name="healthy_cooking",
                description="Nutritious and balanced meal suggestions",
                prompt="""You are a nutrition-focused chef. Create healthy, balanced recipes that are both nutritious and delicious. Include nutritional benefits, suggest ingredient substitutions for dietary restrictions, and focus on whole foods."""
            ),
            CoagentContext(
                name="comfort_food",
                description="Hearty, satisfying comfort food recipes",
                prompt="""You are a comfort food specialist. Create hearty, satisfying recipes that bring warmth and happiness. Focus on traditional techniques, rich flavors, and recipes that feed the soul. Include stories or tips that make cooking enjoyable."""
            ),
            CoagentContext(
                name="international_cuisine",
                description="Authentic recipes from around the world",
                prompt="""You are a world cuisine expert. Provide authentic international recipes with cultural context. Explain unique ingredients, traditional cooking methods, and the cultural significance of dishes. Help users explore global flavors."""
            ),
            CoagentContext(
                name="baking_pastry",
                description="Baking and pastry recipes with precise techniques",
                prompt="""You are a professional baker and pastry chef. Provide precise, detailed baking recipes with exact measurements and techniques. Explain the science behind baking, troubleshoot common issues, and emphasize precision and timing."""
            )
        ]
    
    def generate_recipe(self, ingredients: List[str], context: Optional[str] = None, 
                       dietary_restrictions: Optional[List[str]] = None,
                       servings: int = 4) -> dict:
        """Generate a recipe based on ingredients and preferences."""
        
        # Build the prompt
        prompt_parts = [
            f"Create a recipe using these ingredients: {', '.join(ingredients)}",
            f"The recipe should serve {servings} people."
        ]
        
        if dietary_restrictions:
            prompt_parts.append(f"Dietary restrictions: {', '.join(dietary_restrictions)}")
        
        prompt_parts.append("Please provide the recipe in a structured format with ingredients, instructions, and cooking tips.")
        
        prompt = " ".join(prompt_parts)
        
        try:
            # Process the prompt with optional context
            response = self.agent.process_prompt(prompt, context or "")
            
            # Parse the response
            return {
                "success": True,
                "recipe": response.response,
                "context_used": response.meta.get("context_name", "auto-selected"),
                "llm_calls": len(response.llm_calls),
                "run_id": response.meta.get("run_id")
            }
            
        except Exception as e:
            console.print(f"āŒ Error generating recipe: {str(e)}")
            return {
                "success": False,
                "error": str(e),
                "recipe": None
            }
    
    def suggest_context(self, ingredients: List[str], preferences: dict) -> str:
        """Suggest the best context based on ingredients and user preferences."""
        
        # Simple heuristics for context selection
        if preferences.get("time_limit", 60) <= 30:
            return "quick_meals"
        elif any(word in " ".join(ingredients).lower() for word in ["flour", "sugar", "butter", "eggs"]):
            return "baking_pastry"
        elif preferences.get("healthy", False):
            return "healthy_cooking"
        elif preferences.get("comfort", False):
            return "comfort_food"
        elif preferences.get("international", False):
            return "international_cuisine"
        else:
            return ""  # Auto-select
    
    def display_recipe(self, result: dict):
        """Display the recipe result in a formatted way."""
        
        if not result["success"]:
            console.print(f"[red]Failed to generate recipe: {result.get('error', 'Unknown error')}[/red]")
            return
        
        # Create a table for recipe metadata
        table = Table(title="Recipe Generation Results")
        table.add_column("Attribute", style="cyan")
        table.add_column("Value", style="green")
        
        table.add_row("Context Used", result["context_used"])
        table.add_row("LLM Calls Made", str(result["llm_calls"]))
        if result.get("run_id"):
            table.add_row("Run ID", result["run_id"])
        
        console.print(table)
        console.print("\n[bold yellow]Generated Recipe:[/bold yellow]")
        console.print(result["recipe"])

# Test the basic functionality
if __name__ == "__main__":
    # Create the agent
    agent = RecipeAgent()
    
    # Test with sample ingredients
    ingredients = ["chicken", "broccoli", "rice"]
    preferences = {"healthy": True}
    
    # Suggest context
    suggested_context = agent.suggest_context(ingredients, preferences)
    console.print(f"Suggested context: {suggested_context}")
    
    # Generate recipe
    result = agent.generate_recipe(
        ingredients=ingredients,
        context=suggested_context,
        dietary_restrictions=["gluten-free"]
    )
    
    # Display result
    agent.display_recipe(result)

2.2 Test the Basic Agent

Run your basic agent:

You should see output similar to:

āœ… Recipe Agent initialized with model: llama3.1:8b
Suggested context: healthy_cooking
ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”³ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”“
ā”ƒ Attribute      ā”ƒ Value                                            ā”ƒ
└━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ Context Used   │ healthy_cooking                                  │
│ LLM Calls Made │ 1                                               │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Generated Recipe:
[Recipe content here

Phase 3: Context-Aware Responses

3.1 Enhance Context Selection

Update recipe_agent.py to include more sophisticated context selection:

def analyze_ingredients_and_preferences(self, ingredients: List[str], 
                                      preferences: dict) -> dict:
    """Analyze ingredients and preferences to suggest optimal context."""
    
    analysis = {
        "ingredient_categories": [],
        "cooking_complexity": "medium",
        "estimated_time": 45,
        "suggested_contexts": []
    }
    
    # Categorize ingredients
    protein_keywords = ["chicken", "beef", "pork", "fish", "tofu", "beans"]
    carb_keywords = ["rice", "pasta", "bread", "potatoes", "quinoa"]
    vegetable_keywords = ["broccoli", "spinach", "carrots", "onions"]
    baking_keywords = ["flour", "sugar", "butter", "eggs", "milk"]
    
    ingredients_lower = [ing.lower() for ing in ingredients]
    
    if any(kw in " ".join(ingredients_lower) for kw in protein_keywords):
        analysis["ingredient_categories"].append("protein")
    if any(kw in " ".join(ingredients_lower) for kw in carb_keywords):
        analysis["ingredient_categories"].append("carbohydrates")
    if any(kw in " ".join(ingredients_lower) for kw in vegetable_keywords):
        analysis["ingredient_categories"].append("vegetables")
    if any(kw in " ".join(ingredients_lower) for kw in baking_keywords):
        analysis["ingredient_categories"].append("baking")
        analysis["suggested_contexts"].append("baking_pastry")
    
    # Time-based context selection
    if preferences.get("time_limit", 60) <= 20:
        analysis["suggested_contexts"].append("quick_meals")
        analysis["estimated_time"] = 20
    elif preferences.get("time_limit", 60) <= 40:
        analysis["estimated_time"] = 30
    
    # Health-based selection
    if preferences.get("healthy", False) or len(analysis["ingredient_categories"]) >= 2:
        analysis["suggested_contexts"].append("healthy_cooking")
    
    # Comfort food indicators
    if preferences.get("comfort", False) or any(word in " ".join(ingredients_lower) 
                                              for word in ["cheese", "cream", "butter"]):
        analysis["suggested_contexts"].append("comfort_food")
    
    # International cuisine
    if preferences.get("cuisine_style") in ["italian", "asian", "mexican", "indian"]:
        analysis["suggested_contexts"].append("international_cuisine")
    
    return analysis

def get_context_with_analysis(self, ingredients: List[str], 
                             preferences: dict) -> tuple[str, dict]:
    """Get the best context along with analysis information."""
    
    analysis = self.analyze_ingredients_and_preferences(ingredients, preferences)
    
    if analysis["suggested_contexts"]:
        # Use the first suggested context
        selected_context = analysis["suggested_contexts"][0]
    else:
        # Fallback to general context selection
        selected_context = self.suggest_context(ingredients, preferences)
    
    return selected_context, analysis

3.2 Add Recipe Refinement

Add a method to refine recipes based on feedback:

def refine_recipe(self, original_recipe: str, refinement_request: str, 
                 context: str) -> dict:
    """Refine an existing recipe based on user feedback."""
    
    prompt = f"""
    Here's a recipe I generated earlier:
    {original_recipe}
    
    The user wants to refine it with this request: {refinement_request}
    
    Please provide an improved version of the recipe that addresses their request while maintaining the overall structure and quality.
    """
    
    try:
        response = self.agent.process_prompt(prompt, context)
        return {
            "success": True,
            "refined_recipe": response.response,
            "context_used": response.meta.get("context_name", context),
            "refinement_applied": refinement_request
        }
    except Exception as e:
        return {
            "success": False,
            "error": str(e)
        }

Phase 4: Logging Integration

4.1 Create Interactive Recipe Session

Create interactive_recipe_session.py:

#!/usr/bin/env python3
"""
Interactive Recipe Session - Demonstrates logging and monitoring integration
"""

from recipe_agent import RecipeAgent
from rich.console import Console
from rich.prompt import Prompt, Confirm
from rich.panel import Panel
import time
import uuid

console = Console()

class RecipeSession:
    """Interactive recipe generation session with full logging."""
    
    def __init__(self):
        self.agent = RecipeAgent(enable_logging=True)
        self.session_id = str(uuid.uuid4())
        self.recipes_generated = []
        
    def start_session(self):
        """Start an interactive recipe generation session."""
        
        console.print(Panel.fit(
            "[bold blue]Welcome to CoAgent Recipe Generator![/bold blue]\n"
            f"Session ID: {self.session_id}\n"
            "Let's create some amazing recipes together!",
            border_style="blue"
        ))
        
        while True:
            try:
                # Get user input
                ingredients = self._get_ingredients()
                if not ingredients:
                    break
                
                preferences = self._get_preferences()
                
                # Generate recipe with timing
                start_time = time.time()
                context, analysis = self.agent.get_context_with_analysis(ingredients, preferences)
                
                console.print(f"\nšŸ¤– Using context: [cyan]{context}[/cyan]")
                console.print(f"šŸ“Š Analysis: {analysis}")
                
                result = self.agent.generate_recipe(
                    ingredients=ingredients,
                    context=context,
                    dietary_restrictions=preferences.get("dietary_restrictions", []),
                    servings=preferences.get("servings", 4)
                )
                
                generation_time = time.time() - start_time
                
                # Store recipe
                recipe_data = {
                    **result,
                    "ingredients": ingredients,
                    "preferences": preferences,
                    "analysis": analysis,
                    "generation_time": generation_time,
                    "session_id": self.session_id
                }
                self.recipes_generated.append(recipe_data)
                
                # Display results
                self.agent.display_recipe(result)
                console.print(f"ā±ļø  Generation time: {generation_time:.2f}s")
                
                # Offer refinement
                if result["success"]:
                    if Confirm.ask("\nWould you like to refine this recipe?"):
                        self._refine_recipe_interactive(result["recipe"], context)
                
                # Continue?
                if not Confirm.ask("\nGenerate another recipe?"):
                    break
                    
            except KeyboardInterrupt:
                console.print("\nšŸ‘‹ Session interrupted by user")
                break
            except Exception as e:
                console.print(f"[red]āŒ Unexpected error: {str(e)}[/red]")
                if not Confirm.ask("Continue despite error?"):
                    break
        
        self._session_summary()
    
    def _get_ingredients(self) -> list:
        """Get ingredients from user input."""
        ingredients_input = Prompt.ask(
            "\nšŸ„— Enter ingredients (comma-separated), or 'quit' to exit"
        )
        
        if ingredients_input.lower() in ['quit', 'exit', 'q']:
            return []
        
        return [ing.strip() for ing in ingredients_input.split(',') if ing.strip()]
    
    def _get_preferences(self) -> dict:
        """Get cooking preferences from user."""
        preferences = {}
        
        # Time preference
        time_input = Prompt.ask(
            "ā° Time limit in minutes (press Enter for no limit)", 
            default=""
        )
        if time_input:
            try:
                preferences["time_limit"] = int(time_input)
            except ValueError:
                console.print("[yellow]Invalid time input, using no limit[/yellow]")
        
        # Dietary restrictions
        dietary = Prompt.ask(
            "šŸ„— Dietary restrictions (comma-separated, or Enter for none)", 
            default=""
        )
        if dietary:
            preferences["dietary_restrictions"] = [d.strip() for d in dietary.split(',')]
        
        # Servings
        servings = Prompt.ask("šŸ‘„ Number of servings", default="4")
        try:
            preferences["servings"] = int(servings)
        except ValueError:
            preferences["servings"] = 4
        
        # Style preferences
        preferences["healthy"] = Confirm.ask("🄦 Focus on healthy options?", default=False)
        preferences["comfort"] = Confirm.ask("šŸ² Want comfort food?", default=False)
        
        cuisine = Prompt.ask(
            "šŸŒ Preferred cuisine style (italian, asian, mexican, indian, or Enter for any)", 
            default=""
        )
        if cuisine:
            preferences["cuisine_style"] = cuisine.lower()
        
        return preferences
    
    def _refine_recipe_interactive(self, original_recipe: str, context: str):
        """Interactive recipe refinement."""
        refinement = Prompt.ask("\n✨ How would you like to refine the recipe?")
        
        console.print("šŸ”„ Refining recipe...")
        result = self.agent.refine_recipe(original_recipe, refinement, context)
        
        if result["success"]:
            console.print("\n[bold green]Refined Recipe:[/bold green]")
            console.print(result["refined_recipe"])
        else:
            console.print(f"[red]āŒ Refinement failed: {result.get('error')}[/red]")
    
    def _session_summary(self):
        """Display session summary with monitoring insights."""
        console.print(Panel.fit(
            f"[bold green]Session Complete![/bold green]\n"
            f"Session ID: {self.session_id}\n"
            f"Recipes Generated: {len(self.recipes_generated)}\n"
            f"Total Generation Time: {sum(r['generation_time'] for r in self.recipes_generated):.2f}s\n"
            f"Average Time per Recipe: {sum(r['generation_time'] for r in self.recipes_generated) / len(self.recipes_generated):.2f}s" if self.recipes_generated else "No recipes generated",
            border_style="green"
        ))
        
        # Log session summary
        if self.recipes_generated:
            successful_recipes = [r for r in self.recipes_generated if r['success']]
            console.print(f"\nāœ… Success Rate: {len(successful_recipes)}/{len(self.recipes_generated)} ({len(successful_recipes)/len(self.recipes_generated)*100:.1f}%)")
            
            # Show contexts used
            contexts_used = [r.get('context_used', 'unknown') for r in successful_recipes]
            context_counts = {}
            for ctx in contexts_used:
                context_counts[ctx] = context_counts.get(ctx, 0) + 1
            
            console.print("\nšŸ“Š Contexts Used:")
            for ctx, count in context_counts.items():
                console.print(f"  • {ctx}: {count}")

if __name__ == "__main__":
    session = RecipeSession()
    session.start_session()

4.2 Monitor the Session

Run the interactive session:

While it's running, you can monitor the activity in the CoAgent monitoring dashboard:

  1. Open http://localhost:3000/monitoring in your browser

  2. Watch real-time logs appear as you interact with the agent

  3. View performance metrics and response times

Phase 5: Error Handling & Recovery

5.1 Add Robust Error Handling

Update recipe_agent.py with comprehensive error handling:

import logging
from typing import Union
import time

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class RecipeAgentError(Exception):
    """Custom exception for recipe agent errors."""
    pass

class RecipeAgent:
    # ... (existing code) ...
    
    def __init__(self, model_name: str = "llama3.1:8b", enable_logging: bool = True,
                 max_retries: int = 3, timeout: int = 30):
        """Initialize with error handling configuration."""
        self.max_retries = max_retries
        self.timeout = timeout
        # ... (rest of existing init code) ...
    
    def generate_recipe_with_retry(self, ingredients: List[str], 
                                  context: Optional[str] = None,
                                  dietary_restrictions: Optional[List[str]] = None,
                                  servings: int = 4) -> dict:
        """Generate recipe with retry logic and error recovery."""
        
        last_error = None
        
        for attempt in range(self.max_retries):
            try:
                logger.info(f"Recipe generation attempt {attempt + 1}/{self.max_retries}")
                
                result = self.generate_recipe(
                    ingredients=ingredients,
                    context=context,
                    dietary_restrictions=dietary_restrictions,
                    servings=servings
                )
                
                if result["success"]:
                    logger.info("Recipe generated successfully")
                    return result
                else:
                    last_error = result.get("error", "Unknown error")
                    logger.warning(f"Recipe generation failed: {last_error}")
                
            except Exception as e:
                last_error = str(e)
                logger.error(f"Exception in attempt {attempt + 1}: {last_error}")
                
                # Wait before retry (exponential backoff)
                if attempt < self.max_retries - 1:
                    wait_time = 2 ** attempt
                    logger.info(f"Waiting {wait_time}s before retry...")
                    time.sleep(wait_time)
        
        # All attempts failed
        error_msg = f"Failed to generate recipe after {self.max_retries} attempts. Last error: {last_error}"
        logger.error(error_msg)
        
        return {
            "success": False,
            "error": error_msg,
            "attempts_made": self.max_retries,
            "recipe": None
        }
    
    def validate_inputs(self, ingredients: List[str], servings: int) -> Union[bool, str]:
        """Validate user inputs before processing."""
        
        if not ingredients:
            return "No ingredients provided"
        
        if len(ingredients) > 20:
            return "Too many ingredients (max 20)"
        
        if servings <= 0 or servings > 50:
            return "Invalid serving size (must be 1-50)"
        
        # Check for obviously invalid ingredients
        invalid_chars = set('0123456789!@#$%^&*()+={}[]|\\:";\'<>?/~`')
        for ingredient in ingredients:
            if len(ingredient) > 100:
                return f"Ingredient name too long: {ingredient[:50]}..."
            if any(char in ingredient for char in invalid_chars):
                return f"Invalid ingredient format: {ingredient}"
        
        return True
    
    def health_check(self) -> dict:
        """Check if the agent is functioning properly."""
        
        try:
            # Simple test prompt
            start_time = time.time()
            result = self.agent.process_prompt("Say hello", "")
            response_time = time.time() - start_time
            
            return {
                "healthy": True,
                "response_time": response_time,
                "model_accessible": True,
                "logging_enabled": self.agent.logger_enabled
            }
        
        except Exception as e:
            return {
                "healthy": False,
                "error": str(e),
                "response_time": None,
                "model_accessible": False
            }

5.2 Create Error Recovery Demo

Create error_recovery_demo.py:

#!/usr/bin/env python3
"""
Error Recovery Demo - Shows robust error handling in action
"""

from recipe_agent import RecipeAgent
from rich.console import Console
import time

console = Console()

def test_error_scenarios():
    """Test various error scenarios and recovery mechanisms."""
    
    console.print("[bold blue]Testing Error Recovery Scenarios[/bold blue]\n")
    
    agent = RecipeAgent(max_retries=3, timeout=10)
    
    # Test 1: Invalid inputs
    console.print("[yellow]Test 1: Invalid Inputs[/yellow]")
    
    invalid_tests = [
        ([], 4, "Empty ingredients list"),
        (["chicken"], 0, "Invalid serving size"),
        (["ingredient_with_numbers123", "normal_ingredient"], 4, "Invalid ingredient format"),
        (["x" * 150], 4, "Ingredient name too long")
    ]
    
    for ingredients, servings, description in invalid_tests:
        validation_result = agent.validate_inputs(ingredients, servings)
        if validation_result is True:
            console.print(f"  āœ… {description}: Validation passed")
        else:
            console.print(f"  āŒ {description}: {validation_result}")
    
    # Test 2: Health check
    console.print(f"\n[yellow]Test 2: Agent Health Check[/yellow]")
    health = agent.health_check()
    
    if health["healthy"]:
        console.print(f"  āœ… Agent healthy (response time: {health['response_time']:.2f}s)")
    else:
        console.print(f"  āŒ Agent unhealthy: {health['error']}")
    
    # Test 3: Normal operation with monitoring
    console.print(f"\n[yellow]Test 3: Normal Operation with Error Monitoring[/yellow]")
    
    test_cases = [
        (["chicken", "rice"], {"healthy": True}, "Simple healthy recipe"),
        (["chocolate", "flour", "butter"], {"comfort": True}, "Comfort food baking"),
        (["tofu", "vegetables"], {"time_limit": 15}, "Quick vegetarian meal")
    ]
    
    for ingredients, preferences, description in test_cases:
        console.print(f"  🧪 Testing: {description}")
        
        try:
            start_time = time.time()
            result = agent.generate_recipe_with_retry(
                ingredients=ingredients,
                dietary_restrictions=preferences.get("dietary_restrictions")
            )
            
            elapsed = time.time() - start_time
            
            if result["success"]:
                console.print(f"    āœ… Success in {elapsed:.2f}s")
                console.print(f"    šŸ“Š Context: {result.get('context_used', 'unknown')}")
            else:
                console.print(f"    āŒ Failed: {result.get('error', 'Unknown error')}")
                console.print(f"    šŸ”„ Attempts made: {result.get('attempts_made', 'unknown')}")
        
        except Exception as e:
            console.print(f"    šŸ’„ Unexpected error: {str(e)}")
        
        # Brief pause between tests
        time.sleep(1)
    
    console.print("\n[green]Error recovery testing complete![/green]")

if __name__ == "__main__":
    test_error_scenarios()

Phase 6: Advanced Features

6.1 Add Recipe Persistence

Create recipe_storage.py:

#!/usr/bin/env python3
"""
Recipe Storage - Persistent storage for generated recipes
"""

import json
import sqlite3
from datetime import datetime
from typing import List, Dict, Optional
import uuid
import os

class RecipeStorage:
    """SQLite-based storage for recipe data and session history."""
    
    def __init__(self, db_path: str = "recipes.db"):
        self.db_path = db_path
        self.init_database()
    
    def init_database(self):
        """Initialize the SQLite database with required tables."""
        
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS recipes (
                    id TEXT PRIMARY KEY,
                    session_id TEXT NOT NULL,
                    ingredients TEXT NOT NULL,
                    recipe_text TEXT NOT NULL,
                    context_used TEXT,
                    preferences TEXT,
                    generation_time REAL,
                    success INTEGER NOT NULL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    rating INTEGER,
                    notes TEXT
                )
            """)
            
            conn.execute("""
                CREATE TABLE IF NOT EXISTS sessions (
                    session_id TEXT PRIMARY KEY,
                    total_recipes INTEGER DEFAULT 0,
                    successful_recipes INTEGER DEFAULT 0,
                    total_time REAL DEFAULT 0,
                    started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    ended_at TIMESTAMP,
                    user_notes TEXT
                )
            """)
            
            conn.commit()
    
    def save_recipe(self, recipe_data: Dict) -> str:
        """Save a recipe to the database."""
        
        recipe_id = str(uuid.uuid4())
        
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                INSERT INTO recipes 
                (id, session_id, ingredients, recipe_text, context_used, preferences, 
                 generation_time, success)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            """, (
                recipe_id,
                recipe_data.get("session_id"),
                json.dumps(recipe_data.get("ingredients", [])),
                recipe_data.get("recipe", ""),
                recipe_data.get("context_used"),
                json.dumps(recipe_data.get("preferences", {})),
                recipe_data.get("generation_time", 0),
                1 if recipe_data.get("success", False) else 0
            ))
            
            conn.commit()
        
        return recipe_id
    
    def get_recipe(self, recipe_id: str) -> Optional[Dict]:
        """Retrieve a recipe by ID."""
        
        with sqlite3.connect(self.db_path) as conn:
            conn.row_factory = sqlite3.Row
            cursor = conn.cursor()
            
            cursor.execute("SELECT * FROM recipes WHERE id = ?", (recipe_id,))
            row = cursor.fetchone()
            
            if row:
                return dict(row)
            return None
    
    def search_recipes(self, ingredient: str = None, context: str = None, 
                      limit: int = 10) -> List[Dict]:
        """Search recipes by ingredient or context."""
        
        query = "SELECT * FROM recipes WHERE success = 1"
        params = []
        
        if ingredient:
            query += " AND ingredients LIKE ?"
            params.append(f"%{ingredient}%")
        
        if context:
            query += " AND context_used = ?"
            params.append(context)
        
        query += " ORDER BY created_at DESC LIMIT ?"
        params.append(limit)
        
        with sqlite3.connect(self.db_path) as conn:
            conn.row_factory = sqlite3.Row
            cursor = conn.cursor()
            
            cursor.execute(query, params)
            return [dict(row) for row in cursor.fetchall()]
    
    def rate_recipe(self, recipe_id: str, rating: int, notes: str = ""):
        """Rate a recipe and add notes."""
        
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                UPDATE recipes 
                SET rating = ?, notes = ?
                WHERE id = ?
            """, (rating, notes, recipe_id))
            
            conn.commit()
    
    def get_session_stats(self, session_id: str) -> Dict:
        """Get statistics for a session."""
        
        with sqlite3.connect(self.db_path) as conn:
            conn.row_factory = sqlite3.Row
            cursor = conn.cursor()
            
            cursor.execute("""
                SELECT 
                    COUNT(*) as total_recipes,
                    SUM(success) as successful_recipes,
                    SUM(generation_time) as total_time,
                    AVG(generation_time) as avg_time,
                    AVG(rating) as avg_rating
                FROM recipes 
                WHERE session_id = ?
            """, (session_id,))
            
            stats = dict(cursor.fetchone())
            
            # Get most used context
            cursor.execute("""
                SELECT context_used, COUNT(*) as count
                FROM recipes 
                WHERE session_id = ? AND success = 1
                GROUP BY context_used
                ORDER BY count DESC
                LIMIT 1
            """, (session_id,))
            
            context_row = cursor.fetchone()
            if context_row:
                stats["most_used_context"] = dict(context_row)
            
            return stats

6.2 Enhanced Recipe Agent with Persistence

Update recipe_agent.py to include storage:

from recipe_storage import RecipeStorage

class RecipeAgent:
    # ... (existing code) ...
    
    def __init__(self, model_name: str = "llama3.1:8b", enable_logging: bool = True,
                 max_retries: int = 3, timeout: int = 30, enable_storage: bool = True):
        # ... (existing initialization) ...
        
        # Initialize storage if enabled
        self.storage = RecipeStorage() if enable_storage else None
        if self.storage:
            console.print("šŸ’¾ Recipe storage enabled")
    
    def generate_recipe_with_storage(self, ingredients: List[str], 
                                   session_id: str,
                                   context: Optional[str] = None,
                                   dietary_restrictions: Optional[List[str]] = None,
                                   servings: int = 4) -> dict:
        """Generate recipe and automatically save to storage."""
        
        result = self.generate_recipe_with_retry(
            ingredients=ingredients,
            context=context,
            dietary_restrictions=dietary_restrictions,
            servings=servings
        )
        
        # Add session and storage info
        result.update({
            "session_id": session_id,
            "ingredients": ingredients,
            "preferences": {
                "dietary_restrictions": dietary_restrictions or [],
                "servings": servings
            }
        })
        
        # Save to storage if enabled and successful
        if self.storage and result.get("success", False):
            recipe_id = self.storage.save_recipe(result)
            result["recipe_id"] = recipe_id
            console.print(f"šŸ’¾ Recipe saved with ID: {recipe_id}")
        
        return result
    
    def find_similar_recipes(self, ingredients: List[str], limit: int = 5) -> List[Dict]:
        """Find similar recipes based on ingredients."""
        
        if not self.storage:
            return []
        
        # Search for recipes containing any of the ingredients
        similar_recipes = []
        for ingredient in ingredients:
            recipes = self.storage.search_recipes(ingredient=ingredient, limit=limit)
            similar_recipes.extend(recipes)
        
        # Remove duplicates and sort by creation date
        seen_ids = set()
        unique_recipes = []
        for recipe in similar_recipes:
            if recipe["id"] not in seen_ids:
                unique_recipes.append(recipe)
                seen_ids.add(recipe["id"])
        
        return unique_recipes[:limit]

Phase 7: Testing & Validation

7.1 Create Comprehensive Test Suite

Create test_recipe_agent.py:

#!/usr/bin/env python3
"""
Test Suite for Recipe Agent - Comprehensive testing of all features
"""

import pytest
import tempfile
import os
from recipe_agent import RecipeAgent
from recipe_storage import RecipeStorage
import time

class TestRecipeAgent:
    """Test suite for the RecipeAgent class."""
    
    def setup_method(self):
        """Set up test environment before each test."""
        self.agent = RecipeAgent(enable_logging=False)  # Disable logging for tests
        self.test_ingredients = ["chicken", "broccoli", "rice"]
        self.test_preferences = {"healthy": True, "servings": 4}
    
    def test_agent_initialization(self):
        """Test that the agent initializes correctly."""
        assert self.agent is not None
        assert hasattr(self.agent, 'agent')
        assert len(self.agent._create_contexts()) > 0
    
    def test_input_validation(self):
        """Test input validation functionality."""
        # Valid inputs
        assert self.agent.validate_inputs(self.test_ingredients, 4) is True
        
        # Invalid inputs
        assert self.agent.validate_inputs([], 4) != True  # Empty ingredients
        assert self.agent.validate_inputs(self.test_ingredients, 0) != True  # Invalid servings
        assert self.agent.validate_inputs(["ingredient123"], 4) != True  # Invalid format
    
    def test_context_suggestion(self):
        """Test context suggestion logic."""
        # Health-focused
        health_context = self.agent.suggest_context(
            ["vegetables", "quinoa"], 
            {"healthy": True}
        )
        assert health_context == "healthy_cooking"
        
        # Quick meals
        quick_context = self.agent.suggest_context(
            ["pasta"], 
            {"time_limit": 15}
        )
        assert quick_context == "quick_meals"
        
        # Baking
        baking_context = self.agent.suggest_context(
            ["flour", "sugar", "butter"], 
            {}
        )
        assert baking_context == "baking_pastry"
    
    def test_health_check(self):
        """Test agent health check functionality."""
        health = self.agent.health_check()
        
        # Should have required keys
        required_keys = ["healthy", "response_time", "model_accessible"]
        for key in required_keys:
            assert key in health
    
    @pytest.mark.integration
    def test_recipe_generation(self):
        """Integration test for recipe generation."""
        result = self.agent.generate_recipe(
            ingredients=self.test_ingredients,
            context="healthy_cooking"
        )
        
        # Should have required keys
        assert "success" in result
        assert "recipe" in result or "error" in result
        
        if result["success"]:
            assert result["recipe"] is not None
            assert len(result["recipe"]) > 0
    
    @pytest.mark.integration  
    def test_recipe_generation_with_retry(self):
        """Test recipe generation with retry logic."""
        result = self.agent.generate_recipe_with_retry(
            ingredients=self.test_ingredients
        )
        
        assert "success" in result
        if not result["success"]:
            assert "attempts_made" in result

class TestRecipeStorage:
    """Test suite for recipe storage functionality."""
    
    def setup_method(self):
        """Set up test database before each test."""
        self.temp_db = tempfile.NamedTemporaryFile(delete=False, suffix='.db')
        self.temp_db.close()
        self.storage = RecipeStorage(db_path=self.temp_db.name)
        
        self.test_recipe_data = {
            "session_id": "test-session-123",
            "ingredients": ["chicken", "rice"],
            "recipe": "Test recipe content...",
            "context_used": "healthy_cooking",
            "preferences": {"healthy": True},
            "generation_time": 2.5,
            "success": True
        }
    
    def teardown_method(self):
        """Clean up test database after each test."""
        os.unlink(self.temp_db.name)
    
    def test_save_and_retrieve_recipe(self):
        """Test saving and retrieving recipes."""
        # Save recipe
        recipe_id = self.storage.save_recipe(self.test_recipe_data)
        assert recipe_id is not None
        
        # Retrieve recipe
        retrieved = self.storage.get_recipe(recipe_id)
        assert retrieved is not None
        assert retrieved["ingredients"] == '["chicken", "rice"]'  # JSON serialized
        assert retrieved["success"] == 1
    
    def test_search_recipes(self):
        """Test recipe search functionality."""
        # Save test recipe
        recipe_id = self.storage.save_recipe(self.test_recipe_data)
        
        # Search by ingredient
        results = self.storage.search_recipes(ingredient="chicken")
        assert len(results) >= 1
        assert any(r["id"] == recipe_id for r in results)
        
        # Search by context
        results = self.storage.search_recipes(context="healthy_cooking")
        assert len(results) >= 1
        assert any(r["id"] == recipe_id for r in results)
    
    def test_recipe_rating(self):
        """Test recipe rating functionality."""
        recipe_id = self.storage.save_recipe(self.test_recipe_data)
        
        # Rate the recipe
        self.storage.rate_recipe(recipe_id, 5, "Excellent!")
        
        # Verify rating
        recipe = self.storage.get_recipe(recipe_id)
        assert recipe["rating"] == 5
        assert recipe["notes"] == "Excellent!"

def run_performance_test():
    """Run a performance test to measure response times."""
    print("\nšŸš€ Running Performance Tests...")
    
    agent = RecipeAgent(enable_logging=True)
    test_cases = [
        (["pasta", "tomatoes"], "quick_meals"),
        (["chicken", "vegetables"], "healthy_cooking"),
        (["flour", "chocolate"], "baking_pastry"),
        (["beef", "potatoes"], "comfort_food")
    ]
    
    total_time = 0
    successful_generations = 0
    
    for ingredients, context in test_cases:
        start_time = time.time()
        result = agent.generate_recipe(ingredients=ingredients, context=context)
        elapsed = time.time() - start_time
        
        total_time += elapsed
        if result["success"]:
            successful_generations += 1
        
        print(f"  • {context}: {elapsed:.2f}s ({'āœ…' if result['success'] else 'āŒ'})")
    
    print(f"\nšŸ“Š Performance Summary:")
    print(f"  • Total time: {total_time:.2f}s")
    print(f"  • Average time: {total_time/len(test_cases):.2f}s")
    print(f"  • Success rate: {successful_generations}/{len(test_cases)} ({successful_generations/len(test_cases)*100:.1f}%)")

if __name__ == "__main__":
    # Run basic tests
    print("🧪 Running Recipe Agent Tests...")
    
    # You can run pytest if installed, or run basic tests manually
    try:
        import pytest
        pytest.main([__file__, "-v"])
    except ImportError:
        print("pytest not available, running manual tests...")
        
        # Manual test execution
        test_agent = TestRecipeAgent()
        test_agent.setup_method()
        
        try:
            test_agent.test_agent_initialization()
            print("āœ… Agent initialization test passed")
        except Exception as e:
            print(f"āŒ Agent initialization test failed: {e}")
        
        try:
            test_agent.test_input_validation()
            print("āœ… Input validation test passed")
        except Exception as e:
            print(f"āŒ Input validation test failed: {e}")
        
        try:
            test_agent.test_context_suggestion()
            print("āœ… Context suggestion test passed")
        except Exception as e:
            print(f"āŒ Context suggestion test failed: {e}")
    
    # Run performance test
    run_performance_test()

7.2 Create Final Demo Application

Create final_demo.py:

#!/usr/bin/env python3
"""
Final Demo - Complete Recipe Agent with all features
"""

from recipe_agent import RecipeAgent
from recipe_storage import RecipeStorage
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.prompt import Prompt, Confirm
import uuid
import time

console = Console()

class RecipeAgentDemo:
    """Complete demo showcasing all Recipe Agent features."""
    
    def __init__(self):
        self.agent = RecipeAgent(enable_storage=True, enable_logging=True)
        self.session_id = str(uuid.uuid4())
        console.print(Panel.fit(
            f"[bold blue]CoAgent Recipe Generator - Complete Demo[/bold blue]\n"
            f"Session ID: {self.session_id[:8]}...\n"
            "Features: Context Switching • Logging • Storage • Error Recovery",
            border_style="blue"
        ))
    
    def run_demo(self):
        """Run the complete feature demonstration."""
        
        while True:
            console.print("\n[bold cyan]Choose an option:[/bold cyan]")
            console.print("1. Generate new recipe")
            console.print("2. Find similar recipes")
            console.print("3. Rate a previous recipe") 
            console.print("4. View session statistics")
            console.print("5. Run performance test")
            console.print("6. Exit")
            
            choice = Prompt.ask("Enter your choice", choices=["1", "2", "3", "4", "5", "6"])
            
            if choice == "1":
                self._generate_recipe_workflow()
            elif choice == "2":
                self._find_similar_recipes()
            elif choice == "3":
                self._rate_recipe()
            elif choice == "4":
                self._show_session_stats()
            elif choice == "5":
                self._run_performance_test()
            else:
                break
        
        console.print("\n[green]Thanks for trying the CoAgent Recipe Generator![/green]")
    
    def _generate_recipe_workflow(self):
        """Complete recipe generation workflow."""
        
        # Get ingredients
        ingredients_input = Prompt.ask("šŸ„— Enter ingredients (comma-separated)")
        ingredients = [ing.strip() for ing in ingredients_input.split(',') if ing.strip()]
        
        # Validate inputs
        validation = self.agent.validate_inputs(ingredients, 4)
        if validation is not True:
            console.print(f"[red]āŒ {validation}[/red]")
            return
        
        # Get preferences  
        preferences = {}
        if Confirm.ask("Configure preferences?", default=False):
            time_limit = Prompt.ask("Time limit (minutes, or Enter for none)", default="")
            if time_limit:
                preferences["time_limit"] = int(time_limit)
            
            preferences["healthy"] = Confirm.ask("Focus on healthy?", default=False)
            preferences["comfort"] = Confirm.ask("Want comfort food?", default=False)
        
        # Context analysis
        context, analysis = self.agent.get_context_with_analysis(ingredients, preferences)
        
        console.print(f"\nšŸ¤– Context Analysis:")
        console.print(f"  • Selected context: [cyan]{context}[/cyan]")
        console.print(f"  • Ingredient categories: {analysis.get('ingredient_categories', [])}")
        console.print(f"  • Estimated time: {analysis.get('estimated_time', 'unknown')} minutes")
        
        # Generate recipe
        console.print("\nšŸ³ Generating recipe...")
        start_time = time.time()
        
        result = self.agent.generate_recipe_with_storage(
            ingredients=ingredients,
            session_id=self.session_id,
            context=context,
            dietary_restrictions=preferences.get("dietary_restrictions")
        )
        
        generation_time = time.time() - start_time
        
        # Display results
        if result["success"]:
            console.print(f"\n[bold green]Recipe Generated Successfully![/bold green]")
            console.print(f"ā±ļø  Generation time: {generation_time:.2f}s")
            console.print(f"šŸ†” Recipe ID: {result.get('recipe_id', 'Not saved')}")
            
            console.print("\n[bold yellow]Recipe:[/bold yellow]")
            console.print(Panel(result["recipe"], border_style="yellow"))
            
            # Offer refinement
            if Confirm.ask("Refine this recipe?", default=False):
                refinement = Prompt.ask("How would you like to refine it?")
                refined = self.agent.refine_recipe(result["recipe"], refinement, context)
                
                if refined["success"]:
                    console.print("\n[bold green]Refined Recipe:[/bold green]")
                    console.print(Panel(refined["refined_recipe"], border_style="green"))
        else:
            console.print(f"[red]āŒ Recipe generation failed: {result.get('error')}[/red]")
    
    def _find_similar_recipes(self):
        """Find and display similar recipes."""
        
        ingredient = Prompt.ask("šŸ” Search for recipes containing which ingredient?")
        
        similar_recipes = self.agent.find_similar_recipes([ingredient], limit=5)
        
        if similar_recipes:
            table = Table(title=f"Recipes containing '{ingredient}'")
            table.add_column("ID", style="cyan")
            table.add_column("Ingredients", style="green") 
            table.add_column("Context", style="yellow")
            table.add_column("Rating", style="magenta")
            table.add_column("Created", style="blue")
            
            for recipe in similar_recipes:
                ingredients = eval(recipe["ingredients"]) if recipe["ingredients"] else []
                rating = f"⭐ {recipe['rating']}" if recipe["rating"] else "No rating"
                created = recipe["created_at"][:10] if recipe["created_at"] else "Unknown"
                
                table.add_row(
                    recipe["id"][:8] + "...",
                    ", ".join(ingredients[:3]) + ("..." if len(ingredients) > 3 else ""),
                    recipe["context_used"] or "Unknown",
                    rating,
                    created
                )
            
            console.print(table)
        else:
            console.print(f"[yellow]No recipes found containing '{ingredient}'[/yellow]")
    
    def _rate_recipe(self):
        """Rate a previously generated recipe."""
        
        recipe_id = Prompt.ask("šŸ†” Enter recipe ID to rate")
        
        if self.agent.storage:
            recipe = self.agent.storage.get_recipe(recipe_id)
            
            if recipe:
                # Show recipe summary
                ingredients = eval(recipe["ingredients"]) if recipe["ingredients"] else []
                console.print(f"\nšŸ“„ Recipe: {', '.join(ingredients)}")
                console.print(f"šŸ“… Created: {recipe['created_at']}")
                
                # Get rating
                rating = int(Prompt.ask("⭐ Rate this recipe (1-5)", choices=["1", "2", "3", "4", "5"]))
                notes = Prompt.ask("šŸ“ Notes (optional)", default="")
                
                self.agent.storage.rate_recipe(recipe_id, rating, notes)
                console.print("[green]āœ… Rating saved![/green]")
            else:
                console.print("[red]āŒ Recipe not found[/red]")
        else:
            console.print("[red]āŒ Storage not enabled[/red]")
    
    def _show_session_stats(self):
        """Display session statistics."""
        
        if self.agent.storage:
            stats = self.agent.storage.get_session_stats(self.session_id)
            
            table = Table(title="Session Statistics")
            table.add_column("Metric", style="cyan")
            table.add_column("Value", style="green")
            
            table.add_row("Total Recipes", str(stats.get("total_recipes", 0)))
            table.add_row("Successful", str(stats.get("successful_recipes", 0)))
            table.add_row("Success Rate", f"{stats.get('successful_recipes', 0) / max(1, stats.get('total_recipes', 1)) * 100:.1f}%")
            table.add_row("Total Time", f"{stats.get('total_time', 0):.2f}s")
            table.add_row("Avg Time", f"{stats.get('avg_time', 0):.2f}s")
            
            if stats.get("avg_rating"):
                table.add_row("Avg Rating", f"⭐ {stats['avg_rating']:.1f}")
            
            if stats.get("most_used_context"):
                context_info = stats["most_used_context"]
                table.add_row("Most Used Context", f"{context_info['context_used']} ({context_info['count']}x)")
            
            console.print(table)
        else:
            console.print("[red]āŒ Storage not enabled[/red]")
    
    def _run_performance_test(self):
        """Run a quick performance test."""
        
        console.print("šŸš€ Running performance test...")
        
        test_cases = [
            (["pasta", "tomatoes"], "Italian pasta"),
            (["chicken", "vegetables"], "Healthy meal"), 
            (["flour", "chocolate"], "Dessert")
        ]
        
        total_time = 0
        successful = 0
        
        for ingredients, description in test_cases:
            start = time.time()
            result = self.agent.generate_recipe(ingredients=ingredients)
            elapsed = time.time() - start
            
            total_time += elapsed
            if result["success"]:
                successful += 1
            
            console.print(f"  • {description}: {elapsed:.2f}s ({'āœ…' if result['success'] else 'āŒ'})")
        
        console.print(f"\nšŸ“Š Performance: {successful}/{len(test_cases)} successful, avg {total_time/len(test_cases):.2f}s")

if __name__ == "__main__":
    demo = RecipeAgentDemo()
    demo.run_demo()

7.3 Run the Complete Tutorial

# Test the complete application
python final_demo.py

# Run the test suite
python test_recipe_agent.py

# Try the interactive session

Monitoring Your Application

While using your recipe agent, monitor its performance:

  1. CoAgent Dashboard: Visit http://localhost:3000/monitoring

  2. View Logs: Check the Runs section for detailed execution logs

  3. Performance Metrics: Monitor response times and success rates

  4. Cost Tracking: Watch token usage and estimated costs

Summary

šŸŽ‰ Congratulations! You've built a complete AI agent application with:

  • āœ… Context-Aware Responses - Adaptive behavior based on cooking scenarios

  • āœ… Robust Error Handling - Retry logic and input validation

  • āœ… Integrated Logging - Full observability with CoAgent monitoring

  • āœ… Persistent Storage - SQLite database for recipe history

  • āœ… Performance Testing - Comprehensive test suite

  • āœ… Interactive Interface - Rich command-line experience

Next Steps