Agentic Architecture Pattern: Case Based Reasoning

Written by

in


    """
Pattern 45: Case-Based Reasoning Pattern

Solves new problems by adapting solutions from similar past cases.
Features case retrieval, adaptation, and case library management.
"""

from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from datetime import datetime
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI


@dataclass
class Case:
    """Represents a past problem-solution case."""
    case_id: str
    problem: str
    solution: str
    context: Dict[str, Any]
    success_rating: float = 0.0  # 0.0 to 1.0
    usage_count: int = 0
    timestamp: datetime = field(default_factory=datetime.now)


@dataclass
class CaseMatch:
    """Represents a case match with similarity."""
    case: Case
    similarity: float  # 0.0 to 1.0


class CaseLibrary:
    """Storage for past cases."""
    
    def __init__(self):
        self.cases: List[Case] = []
        self.llm = ChatOpenAI(model="gpt-4", temperature=0.3)
    
    def add_case(self, case: Case):
        """Add new case to library."""
        self.cases.append(case)
    
    def find_similar(self, problem: str, top_k: int = 3) -> List[CaseMatch]:
        """Find similar cases using LLM-based similarity."""
        if not self.cases:
            return []
        
        # Use LLM to score similarity
        matches = []
        for case in self.cases:
            prompt = ChatPromptTemplate.from_messages([
                ("system", "Rate the similarity between two problems on a scale of 0.0 to 1.0. "
                          "Only respond with the number."),
                ("human", "Problem 1: {problem1}\nProblem 2: {problem2}")
            ])
            
            chain = prompt | self.llm
            response = chain.invoke({
                "problem1": problem,
                "problem2": case.problem
            })
            
            try:
                similarity = float(response.content.strip())
                similarity = max(0.0, min(1.0, similarity))
                matches.append(CaseMatch(case=case, similarity=similarity))
            except:
                continue
        
        # Sort by similarity and success rating
        matches.sort(key=lambda m: (m.similarity, m.case.success_rating), reverse=True)
        return matches[:top_k]


class CaseBasedReasoningAgent:
    """Agent that solves problems using case-based reasoning."""
    
    def __init__(self, name: str, model_name: str = "gpt-4"):
        self.name = name
        self.case_library = CaseLibrary()
        self.llm = ChatOpenAI(model=model_name, temperature=0.7)
        self.solutions_generated = 0
    
    def retrieve_and_adapt(self, problem: str, context: Dict[str, Any]) -> str:
        """Retrieve similar cases and adapt solution."""
        print(f"\n   {self.name}: Retrieving similar cases...")
        
        # Find similar cases
        similar_cases = self.case_library.find_similar(problem, top_k=3)
        
        if not similar_cases:
            print(f"   No similar cases found. Generating new solution...")
            return self._generate_new_solution(problem, context)
        
        # Display similar cases
        print(f"   Found {len(similar_cases)} similar cases:")
        for i, match in enumerate(similar_cases, 1):
            print(f"   {i}. Similarity: {match.similarity:.2f} - {match.case.problem[:50]}...")
        
        # Adapt solution from best match
        best_match = similar_cases[0]
        best_match.case.usage_count += 1
        
        if best_match.similarity >= 0.7:
            print(f"   High similarity ({best_match.similarity:.2f}). Adapting solution...")
            return self._adapt_solution(problem, context, best_match.case)
        else:
            print(f"   Low similarity ({best_match.similarity:.2f}). Generating new solution with case guidance...")
            return self._generate_with_guidance(problem, context, similar_cases)
    
    def _adapt_solution(self, problem: str, context: Dict[str, Any], case: Case) -> str:
        """Adapt an existing solution to new problem."""
        prompt = ChatPromptTemplate.from_messages([
            ("system", "You are adapting a past solution to a new but similar problem."),
            ("human", """Past Problem: {past_problem}
Past Solution: {past_solution}

New Problem: {new_problem}
New Context: {new_context}

Adapt the past solution to solve the new problem. Explain what changes you made.""")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({
            "past_problem": case.problem,
            "past_solution": case.solution,
            "new_problem": problem,
            "new_context": str(context)
        })
        
        return response.content
    
    def _generate_with_guidance(self, problem: str, context: Dict[str, Any], 
                                 similar_cases: List[CaseMatch]) -> str:
        """Generate solution with guidance from similar cases."""
        case_examples = "\n".join([
            f"Example {i}: {match.case.problem}\nSolution: {match.case.solution}\n"
            for i, match in enumerate(similar_cases, 1)
        ])
        
        prompt = ChatPromptTemplate.from_messages([
            ("system", "Solve the problem using insights from similar past cases."),
            ("human", """Similar Past Cases:
{case_examples}

New Problem: {problem}
Context: {context}

Generate a solution for the new problem, drawing inspiration from the past cases.""")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({
            "case_examples": case_examples,
            "problem": problem,
            "context": str(context)
        })
        
        return response.content
    
    def _generate_new_solution(self, problem: str, context: Dict[str, Any]) -> str:
        """Generate completely new solution."""
        prompt = ChatPromptTemplate.from_messages([
            ("system", "You are a problem solver. Generate a solution."),
            ("human", "Problem: {problem}\nContext: {context}")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({"problem": problem, "context": str(context)})
        return response.content
    
    def store_case(self, problem: str, solution: str, context: Dict[str, Any], 
                   success_rating: float = 0.8):
        """Store new case in library."""
        case = Case(
            case_id=f"case_{len(self.case_library.cases) + 1}",
            problem=problem,
            solution=solution,
            context=context,
            success_rating=success_rating
        )
        self.case_library.add_case(case)
        print(f"   Stored case: {case.case_id}")


class CaseBasedReasoningPattern:
    """
    Case-Based Reasoning Pattern - Learn from past experiences.
    
    Retrieves similar past cases, adapts their solutions to new problems.
    Builds reusable case library over time.
    """
    
    def __init__(self):
        self.agents: Dict[str, CaseBasedReasoningAgent] = {}
    
    def register_agent(self, agent: CaseBasedReasoningAgent):
        """Register CBR agent."""
        self.agents[agent.name] = agent
    
    def run(self, scenarios: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Execute CBR pattern on scenarios."""
        print("=== Case-Based Reasoning Pattern Demo ===\n")
        
        for scenario in scenarios:
            problem = scenario["problem"]
            context = scenario.get("context", {})
            
            print(f"\nScenario: {problem[:60]}...")
            
            for agent in self.agents.values():
                solution = agent.retrieve_and_adapt(problem, context)
                print(f"   Solution: {solution[:100]}...")
                
                # Store for future reuse
                if scenario.get("store", True):
                    agent.store_case(problem, solution, context)
        
        # Statistics
        total_cases = sum(len(agent.case_library.cases) for agent in self.agents.values())
        
        return {
            "pattern": "CaseBasedReasoning",
            "total_agents": len(self.agents),
            "total_cases": total_cases,
            "scenarios_processed": len(scenarios)
        }


# Example usage
if __name__ == "__main__":
    print("=== Case-Based Reasoning Pattern Demo ===\n")
    
    # Create pattern
    pattern = CaseBasedReasoningPattern()
    
    # Create agent
    agent = CaseBasedReasoningAgent("TechSupportAgent")
    pattern.register_agent(agent)
    
    # Populate with initial cases
    print("1. Populating case library with historical cases...")
    initial_cases = [
        Case("case_1", "Computer won't start", 
             "Check power cable and press power button for 10 seconds to reset", 
             {"device": "desktop"}, success_rating=0.9),
        Case("case_2", "Internet is slow",
             "Restart router, check for bandwidth-heavy apps, contact ISP if persistent",
             {"connection": "wifi"}, success_rating=0.85),
        Case("case_3", "Printer not printing",
             "Check ink levels, restart printer, reinstall drivers",
             {"device": "printer"}, success_rating=0.8),
    ]
    
    for case in initial_cases:
        agent.case_library.add_case(case)
    
    print(f"   Added {len(initial_cases)} historical cases")
    
    # Test scenarios
    print("\n2. Solving new problems using CBR...")
    
    scenarios = [
        {"problem": "Laptop won't turn on", "context": {"device": "laptop"}, "store": True},
        {"problem": "WiFi keeps disconnecting", "context": {"connection": "wifi"}, "store": True},
        {"problem": "Monitor has no display", "context": {"device": "monitor"}, "store": False},
    ]
    
    summary = pattern.run(scenarios)
    
    print(f"\n3. Summary:")
    print(f"   Total Cases in Library: {summary['total_cases']}")
    print(f"   Scenarios Processed: {summary['scenarios_processed']}")
    
    print("\n4. Case Library Statistics:")
    for case in agent.case_library.cases:
        print(f"   {case.case_id}: Used {case.usage_count} times (rating: {case.success_rating})")
    
    print("\nāœ“ Case-based reasoning reused past solutions!")

    

Agentic Architecture Pattern: Case Based Reasoning

Pattern 45: 45 Case Based Reasoning – Line-by-Line Explanation

Overview

Pattern 45: Case-Based Reasoning Pattern

Solves new problems by adapting solutions from similar past cases. Features case retrieval, adaptation, and case library management.


Code Structure Analysis

File Information

  • Pattern Number: 45
  • Pattern Name: 45 Case Based Reasoning
  • File Path: patterns\decision_making\45_case_based_reasoning.py
  • Total Lines: 283

Detailed Code Explanation

Module Docstring

"""
Pattern 45: Case-Based Reasoning Pattern

Solves new problems by adapting solutions from similar past cases.
Features case retrieval, adaptation, and case library management.
"""

Explanation: This module docstring describes the purpose and key features of the 45 Case Based Reasoning pattern.


Imports Section

from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from datetime import datetime
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI  # First 20 imports shown

Explanation: The module imports necessary dependencies: – from typing import Dict, Any, List, Optional: Type hints for better code documentation – from dataclasses import dataclass, field: Data class decorators for clean data structures – from datetime import datetime: Date and time utilities – from langchain_core.prompts import ChatPromptTemplate: LangChain framework integration – from langchain_openai import ChatOpenAI: LangChain framework integration


Classes

Case (Line 16)

Description: Represents a past problem-solution case.

CaseMatch (Line 28)

Description: Represents a case match with similarity.

CaseLibrary (Line 34)

Description: Storage for past cases.

Methods:

__init__ (Line 37)
def __init__(self):
    """No method docstring."""

Purpose: No method docstring.

Parameters: – self: Instance reference

add_case (Line 41)
def add_case(self, case):
    """Add new case to library."""

Purpose: Add new case to library.

Parameters: – self: Instance reference – case: Parameter for case

find_similar (Line 45)
def find_similar(self, problem, top_k):
    """Find similar cases using LLM-based similarity."""

Purpose: Find similar cases using LLM-based similarity.

Parameters: – self: Instance reference – problem: Parameter for problem – top_k: Parameter for top_k

CaseBasedReasoningAgent (Line 77)

Description: Agent that solves problems using case-based reasoning.

Methods:

__init__ (Line 80)
def __init__(self, name, model_name):
    """No method docstring."""

Purpose: No method docstring.

Parameters: – self: Instance reference – name: Identifier or label – model_name: LLM model to use (e.g., “gpt-4”)

retrieve_and_adapt (Line 86)
def retrieve_and_adapt(self, problem, context):
    """Retrieve similar cases and adapt solution."""

Purpose: Retrieve similar cases and adapt solution.

Parameters: – self: Instance reference – problem: Parameter for problem – context: Parameter for context

_adapt_solution (Line 113)
def _adapt_solution(self, problem, context, case):
    """Adapt an existing solution to new problem."""

Purpose: Adapt an existing solution to new problem.

Parameters: – self: Instance reference – problem: Parameter for problem – context: Parameter for context – case: Parameter for case

_generate_with_guidance (Line 136)
def _generate_with_guidance(self, problem, context, similar_cases):
    """Generate solution with guidance from similar cases."""

Purpose: Generate solution with guidance from similar cases.

Parameters: – self: Instance reference – problem: Parameter for problem – context: Parameter for context – similar_cases: Parameter for similar_cases

_generate_new_solution (Line 164)
def _generate_new_solution(self, problem, context):
    """Generate completely new solution."""

Purpose: Generate completely new solution.

Parameters: – self: Instance reference – problem: Parameter for problem – context: Parameter for context

store_case (Line 175)
def store_case(self, problem, solution, context, success_rating):
    """Store new case in library."""

Purpose: Store new case in library.

Parameters: – self: Instance reference – problem: Parameter for problem – solution: Parameter for solution – context: Parameter for context – success_rating: Parameter for success_rating

CaseBasedReasoningPattern (Line 189)

Description: Case-Based Reasoning Pattern – Learn from past experiences.

Retrieves similar past cases, adapts their solutions to new problems. Builds reusable case library over time.

Methods:

__init__ (Line 197)
def __init__(self):
    """No method docstring."""

Purpose: No method docstring.

Parameters: – self: Instance reference

register_agent (Line 200)
def register_agent(self, agent):
    """Register CBR agent."""

Purpose: Register CBR agent.

Parameters: – self: Instance reference – agent: Parameter for agent

run (Line 204)
def run(self, scenarios):
    """Execute CBR pattern on scenarios."""

Purpose: Execute CBR pattern on scenarios.

Parameters: – self: Instance reference – scenarios: Parameter for scenarios


Design Patterns Used

  • Strategy Pattern: Different execution strategies for various scenarios
  • State Pattern: Managing different states in the workflow
  • Chain of Responsibility: Sequential processing through workflow nodes
  • Observer Pattern: Tracking and monitoring pattern execution
  • Factory Pattern: Creating instances with proper configuration

Usage Example

# Initialize the pattern
pattern = Case(name="example", model_name="gpt-4")

# Configure and execute
result = pattern.execute(data={"key": "value"})

# Check results
print(result)

Key Concepts

  • LLM Integration: Uses LangChain/OpenAI for intelligent decision-making
  • Workflow Orchestration: LangGraph manages execution flow through nodes
  • State Management: TypedDict defines workflow state structure
  • Error Handling: Comprehensive error handling and fallback mechanisms
  • Extensibility: Designed for easy customization and extension

Extension Points

This pattern can be extended by: 1. Adding custom workflow nodes 2. Implementing additional validation logic 3. Integrating with external services 4. Customizing LLM prompts and responses 5. Adding monitoring and logging capabilities

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *