Agentic Design Pattern: Ontology Based Reasoning

Written by

in


    """
Pattern 124: Ontology-Based Reasoning

Description:
    Uses formal ontologies (OWL, RDF, SKOS) for domain knowledge
    representation and semantic reasoning.

Components:
    - Ontology definition
    - Class hierarchies
    - Property definitions
    - Inference rules

Use Cases:
    - Semantic reasoning
    - Domain expertise
    - Formal logic

LangChain Implementation:
    Implements ontology concepts with LLM-based reasoning over formal structures.
"""

import os
from typing import List, Dict, Any, Set
from dataclasses import dataclass
from dotenv import load_dotenv

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

load_dotenv()


@dataclass
class OntologyClass:
    """Represents a class in the ontology."""
    name: str
    parent: str = None
    properties: List[str] = None
    description: str = ""
    
    def __post_init__(self):
        if self.properties is None:
            self.properties = []


@dataclass
class OntologyProperty:
    """Represents a property in the ontology."""
    name: str
    domain: str  # Class it belongs to
    range: str   # Type of value
    description: str = ""


@dataclass
class OntologyInstance:
    """Represents an instance of a class."""
    id: str
    class_name: str
    properties: Dict[str, Any]


class Ontology:
    """Formal ontology for domain knowledge."""
    
    def __init__(self, name: str):
        self.name = name
        self.classes: Dict[str, OntologyClass] = {}
        self.properties: Dict[str, OntologyProperty] = {}
        self.instances: Dict[str, OntologyInstance] = {}
        
    def add_class(self, ontology_class: OntologyClass):
        """Add a class to the ontology."""
        self.classes[ontology_class.name] = ontology_class
        
    def add_property(self, ontology_property: OntologyProperty):
        """Add a property to the ontology."""
        self.properties[ontology_property.name] = ontology_property
        
    def add_instance(self, instance: OntologyInstance):
        """Add an instance to the ontology."""
        self.instances[instance.id] = instance
        
    def get_subclasses(self, class_name: str) -> List[str]:
        """Get all subclasses of a class."""
        subclasses = []
        for cls_name, cls in self.classes.items():
            if cls.parent == class_name:
                subclasses.append(cls_name)
                # Recursively get subclasses
                subclasses.extend(self.get_subclasses(cls_name))
        return subclasses
    
    def get_superclasses(self, class_name: str) -> List[str]:
        """Get all superclasses of a class."""
        superclasses = []
        current = self.classes.get(class_name)
        while current and current.parent:
            superclasses.append(current.parent)
            current = self.classes.get(current.parent)
        return superclasses
    
    def is_subclass_of(self, class1: str, class2: str) -> bool:
        """Check if class1 is a subclass of class2."""
        return class2 in self.get_superclasses(class1)
    
    def get_instances_of_class(self, class_name: str, include_subclasses: bool = True) -> List[OntologyInstance]:
        """Get all instances of a class."""
        instances = [inst for inst in self.instances.values() if inst.class_name == class_name]
        
        if include_subclasses:
            subclasses = self.get_subclasses(class_name)
            for subclass in subclasses:
                instances.extend([inst for inst in self.instances.values() if inst.class_name == subclass])
        
        return instances


class OntologyReasoningAgent:
    """Agent that performs reasoning using ontologies."""
    
    def __init__(self, ontology: Ontology, model_name: str = "gpt-4"):
        self.ontology = ontology
        self.llm = ChatOpenAI(model=model_name, temperature=0.3)
        
    def classify_instance(self, instance_description: str) -> str:
        """Classify an instance into ontology classes."""
        # Get ontology structure
        ontology_structure = "Ontology Classes:\n"
        for cls_name, cls in self.ontology.classes.items():
            ontology_structure += f"- {cls_name}"
            if cls.parent:
                ontology_structure += f" (subclass of {cls.parent})"
            ontology_structure += f": {cls.description}\n"
        
        classification_prompt = ChatPromptTemplate.from_messages([
            ("system", "Classify this instance into the most appropriate ontology class."),
            ("user", """{ontology_structure}

Instance to classify: {instance}

Most appropriate class:""")
        ])
        
        chain = classification_prompt | self.llm | StrOutputParser()
        return chain.invoke({
            "ontology_structure": ontology_structure,
            "instance": instance_description
        })
    
    def infer_properties(self, instance_id: str) -> Dict[str, Any]:
        """Infer properties of an instance based on ontology."""
        instance = self.ontology.instances.get(instance_id)
        if not instance:
            return {}
        
        # Get class hierarchy
        superclasses = self.ontology.get_superclasses(instance.class_name)
        
        # Collect inherited properties
        inherited_properties = {}
        for superclass in superclasses:
            cls = self.ontology.classes.get(superclass)
            if cls:
                for prop in cls.properties:
                    if prop not in instance.properties:
                        inherited_properties[prop] = f"inherited from {superclass}"
        
        inference_prompt = ChatPromptTemplate.from_messages([
            ("system", "Infer additional properties for this instance based on ontology."),
            ("user", """Instance: {instance_id}
Class: {class_name}
Superclasses: {superclasses}
Current Properties: {current_props}

Infer likely values for these properties:
{inherited_props}""")
        ])
        
        chain = inference_prompt | self.llm | StrOutputParser()
        inferences = chain.invoke({
            "instance_id": instance_id,
            "class_name": instance.class_name,
            "superclasses": ", ".join(superclasses),
            "current_props": str(instance.properties),
            "inherited_props": ", ".join(inherited_properties.keys())
        })
        
        return {"inferences": inferences, "inherited": inherited_properties}
    
    def answer_semantic_query(self, query: str) -> str:
        """Answer semantic queries using ontology."""
        # Serialize ontology info
        ontology_info = f"Ontology: {self.ontology.name}\n\n"
        ontology_info += "Classes:\n"
        for cls_name, cls in self.ontology.classes.items():
            ontology_info += f"- {cls_name}: {cls.description}\n"
        
        ontology_info += "\nInstances:\n"
        for inst_id, inst in list(self.ontology.instances.items())[:10]:  # Limit to 10
            ontology_info += f"- {inst_id} ({inst.class_name}): {inst.properties}\n"
        
        query_prompt = ChatPromptTemplate.from_messages([
            ("system", "Answer this semantic query using the ontology knowledge."),
            ("user", """{ontology_info}

Query: {query}

Answer:""")
        ])
        
        chain = query_prompt | self.llm | StrOutputParser()
        return chain.invoke({
            "ontology_info": ontology_info,
            "query": query
        })
    
    def check_consistency(self) -> str:
        """Check ontology consistency."""
        consistency_prompt = ChatPromptTemplate.from_messages([
            ("system", "Check this ontology for logical consistency and conflicts."),
            ("user", """Ontology Structure:
{structure}

Check for:
1. Circular class hierarchies
2. Property domain/range conflicts
3. Instance classification errors
4. Missing definitions""")
        ])
        
        structure = ""
        for cls_name, cls in self.ontology.classes.items():
            structure += f"{cls_name} -> {cls.parent}\n"
        
        chain = consistency_prompt | self.llm | StrOutputParser()
        return chain.invoke({"structure": structure})


def demonstrate_ontology_based_reasoning():
    """Demonstrate ontology-based reasoning pattern."""
    print("=== Ontology-Based Reasoning Pattern ===\n")
    
    # Create ontology
    print("1. Building Domain Ontology")
    print("-" * 50)
    
    ontology = Ontology("VehicleOntology")
    
    # Define class hierarchy
    ontology.add_class(OntologyClass(
        name="Vehicle",
        description="Any mode of transportation"
    ))
    
    ontology.add_class(OntologyClass(
        name="LandVehicle",
        parent="Vehicle",
        description="Vehicles that travel on land"
    ))
    
    ontology.add_class(OntologyClass(
        name="WaterVehicle",
        parent="Vehicle",
        description="Vehicles that travel on water"
    ))
    
    ontology.add_class(OntologyClass(
        name="AirVehicle",
        parent="Vehicle",
        description="Vehicles that travel in air"
    ))
    
    ontology.add_class(OntologyClass(
        name="Car",
        parent="LandVehicle",
        properties=["num_wheels", "fuel_type", "num_doors"],
        description="Four-wheeled motor vehicle"
    ))
    
    ontology.add_class(OntologyClass(
        name="Bicycle",
        parent="LandVehicle",
        properties=["num_wheels", "has_motor"],
        description="Two-wheeled human-powered vehicle"
    ))
    
    ontology.add_class(OntologyClass(
        name="Boat",
        parent="WaterVehicle",
        properties=["propulsion_type", "max_speed"],
        description="Watercraft of any size"
    ))
    
    # Add instances
    ontology.add_instance(OntologyInstance(
        id="vehicle1",
        class_name="Car",
        properties={"num_wheels": 4, "fuel_type": "electric", "num_doors": 4}
    ))
    
    ontology.add_instance(OntologyInstance(
        id="vehicle2",
        class_name="Bicycle",
        properties={"num_wheels": 2, "has_motor": False}
    ))
    
    print(f"✓ Created ontology with {len(ontology.classes)} classes")
    print(f"✓ Added {len(ontology.instances)} instances")
    print()
    
    # Create reasoning agent
    agent = OntologyReasoningAgent(ontology)
    
    # Classification
    print("2. Instance Classification")
    print("-" * 50)
    
    test_descriptions = [
        "A motorized two-wheeler used for transportation",
        "A watercraft with sails",
        "A four-wheeled vehicle with an engine"
    ]
    
    for desc in test_descriptions:
        print(f"\nDescription: {desc}")
        classification = agent.classify_instance(desc)
        print(f"Classification: {classification}")
    
    # Property inference
    print("\n\n3. Property Inference")
    print("-" * 50)
    
    inferred = agent.infer_properties("vehicle1")
    print(f"\nInferred properties for vehicle1:")
    print(inferred)
    
    # Semantic queries
    print("\n\n4. Semantic Queries")
    print("-" * 50)
    
    queries = [
        "What are all the types of land vehicles?",
        "What properties do cars have?",
        "How are bicycles and cars related?"
    ]
    
    for query in queries:
        print(f"\nQuery: {query}")
        answer = agent.answer_semantic_query(query)
        print(f"Answer: {answer}")
    
    # Consistency check
    print("\n\n5. Ontology Consistency Check")
    print("-" * 50)
    consistency = agent.check_consistency()
    print(consistency)
    
    print("\n=== Summary ===")
    print("Ontology-based reasoning demonstrated with:")
    print("- Formal class hierarchies")
    print("- Property definitions")
    print("- Instance classification")
    print("- Property inference")
    print("- Semantic queries")
    print("- Consistency checking")


if __name__ == "__main__":
    demonstrate_ontology_based_reasoning()

    

Agentic Design Pattern: Ontology Based Reasoning

Pattern 124: Ontology Based Reasoning – Line-by-Line Explanation

Overview

This document provides a detailed line-by-line explanation of the Ontology Based Reasoning pattern implementation.


Pattern Description

Pattern 124: Ontology-Based Reasoning

Description: Uses formal ontologies (OWL, RDF, SKOS) for domain knowledge representation and semantic reasoning.

Components: – Ontology definition – Class hierarchies – Property definitions – Inference rules

Use Cases: – Semantic reasoning – Domain expertise – Formal logic

LangChain Implementation: Implements ontology concepts with LLM-based reasoning over formal structures.


File Statistics

  • Total Lines: 373
  • Classes: 5
  • Functions: 1
  • Constants: 0

Module Documentation

"""
Pattern 124: Ontology-Based Reasoning

Description:
    Uses formal ontologies (OWL, RDF, SKOS) for domain knowledge
    representation and semantic reasoning.

Components:
    - Ontology definition
    - Class hierarchies
    - Property definitions
    - Inference rules

Use Cases:
    - Semantic reasoning
    - Domain expertise
    - Formal logic

LangChain Implementation:
    Implements ontology concepts with LLM-based reasoning over formal structures.
"""

Purpose: This module implements the Ontology Based Reasoning pattern, providing a structured approach to agent design and implementation.


Import Statements

Standard Library Imports

import dataclasses.dataclass
import langchain_core.output_parsers.StrOutputParser
import langchain_core.prompts.ChatPromptTemplate
import os
import typing.Any
import typing.Dict
import typing.List
import typing.Set

Purpose: Core Python functionality: – dataclasses.dataclass: Simplified class definitions with @dataclass – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – os: Environment variable access and system operations – typing.Any: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Dict: Type hints for better code documentation (List, Dict, Any, Optional) – typing.List: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Set: Type hints for better code documentation (List, Dict, Any, Optional)

LangChain Imports

from langchain_openai.ChatOpenAI

Purpose: LangChain framework components: – LLM Interface: Wrapper for OpenAI models – Chains: Composable building blocks for complex workflows

Other Imports

from dotenv.load_dotenv

Purpose: Additional dependencies for specialized functionality.


Classes

Class: OntologyClass

Lines 36-45

Purpose:

Represents a class in the ontology.

Methods:

__post_init__(self)

Lines 43-45

Purpose: Post Init operation.

Parameters:


Class: OntologyProperty

Lines 49-54

Purpose:

Represents a property in the ontology.

Class: OntologyInstance

Lines 58-62

Purpose:

Represents an instance of a class.

Class: Ontology

Lines 65-118

Purpose:

Formal ontology for domain knowledge.

Methods:

__init__(self, name)

Lines 68-72

Purpose: Initialize the class instance with required parameters.

Parameters: – name: Name

add_class(self, ontology_class)

Lines 74-76

Purpose:

Add a class to the ontology.

Parameters: – ontology_class: Ontology class

add_property(self, ontology_property)

Lines 78-80

Purpose:

Add a property to the ontology.

Parameters: – ontology_property: Ontology property

add_instance(self, instance)

Lines 82-84

Purpose:

Add an instance to the ontology.

Parameters: – instance: Instance

get_subclasses(self, class_name)

Lines 86-94

Purpose:

Get all subclasses of a class.

Parameters: – class_name: Class name

Returns: <ast.Subscript object at 0x000001A06D749290>

get_superclasses(self, class_name)

Lines 96-103

Purpose:

Get all superclasses of a class.

Parameters: – class_name: Class name

Returns: <ast.Subscript object at 0x000001A06D64BF50>

is_subclass_of(self, class1, class2)

Lines 105-107

Purpose:

Check if class1 is a subclass of class2.

Parameters: – class1: Class1 – class2: Class2

Returns: bool

get_instances_of_class(self, class_name, include_subclasses)

Lines 109-118

Purpose:

Get all instances of a class.

Parameters: – class_name: Class name – include_subclasses: Include subclasses

Returns: <ast.Subscript object at 0x000001A06D60FF90>


Class: OntologyReasoningAgent

Lines 121-239

Purpose:

Agent that performs reasoning using ontologies.

Methods:

__init__(self, ontology, model_name)

Lines 124-126

Purpose: Initialize the class instance with required parameters.

Parameters: – ontology: Ontology – model_name: Language model instance

classify_instance(self, instance_description)

Lines 128-151

Purpose:

Classify an instance into ontology classes.

Parameters: – instance_description: Instance description

Returns: str

infer_properties(self, instance_id)

Lines 153-191

Purpose:

Infer properties of an instance based on ontology.

Parameters: – instance_id: Instance id

Returns: <ast.Subscript object at 0x000001A06D825C90>

answer_semantic_query(self, query)

Lines 193-218

Purpose:

Answer semantic queries using ontology.

Parameters: – query: Input query or question

Returns: str

check_consistency(self)

Lines 220-239

Purpose:

Check ontology consistency.

Parameters:

Returns: str


Functions

demonstrate_ontology_based_reasoning()

Lines 242-368

Purpose:

Demonstrate ontology-based reasoning pattern.

Key Concepts Summary

Pattern Overview

The Ontology Based Reasoning pattern provides a structured approach to building AI agents with specific capabilities and behaviors. This implementation leverages LangChain’s composable components to create a robust and maintainable solution.

Core Components

  1. Agent Class: Main implementation of the pattern logic
  2. Prompt Engineering: Carefully crafted prompts to guide LLM behavior
  3. Tool Integration: Optional tools for extended functionality
  4. Memory Management: State handling across interactions
  5. Output Parsing: Structured response processing

When to Use

✅ Good for: – Scenarios requiring this specific pattern’s strengths – Applications needing structured agent behavior – Systems where explainability is important – Production environments requiring reliability

❌ Not ideal for: – Simple single-prompt tasks – Scenarios requiring different pattern characteristics – Resource-constrained environments (if computationally intensive)


Best Practices Demonstrated

1. Environment Configuration

  • ✅ Use .env files for API keys
  • ✅ Never hardcode credentials
  • ✅ Validate environment variables on startup

2. Error Handling

  • ✅ Graceful degradation on failures
  • ✅ Informative error messages
  • ✅ Logging for debugging

3. Prompt Engineering

  • ✅ Clear, specific instructions
  • ✅ Structured output formats
  • ✅ Few-shot examples when beneficial

4. Code Organization

  • ✅ Separation of concerns (class-based design)
  • ✅ Type hints for clarity
  • ✅ Docstrings for documentation

5. Performance Optimization

  • ✅ Appropriate temperature settings
  • ✅ Token budget management
  • ✅ Caching where applicable

LangChain Components Used

  • ChatOpenAI: OpenAI model interface
  • Chains: Sequential component composition

Why LangChain? – Composable abstractions for LLM applications – Rich ecosystem of integrations – Production-ready patterns – Active community and development


Execution Flow

1. Initialize agent with configuration
   ↓
2. Load environment and dependencies
   ↓
3. Prepare prompt with input data
   ↓
4. Invoke LLM through LangChain
   ↓
5. Parse and validate response
   ↓
6. Return formatted result

Key Decision Points: – Model selection (temperature, model type) – Prompt structure (few-shot vs zero-shot) – Error handling strategy – Output format validation


Performance Considerations

Computational Cost

  • LLM Calls: Primary cost factor (based on tokens)
  • Latency: Depends on model and input size
  • Memory: Minimal for most patterns

Optimization Strategies

  1. Prompt Optimization: Minimize tokens while maintaining clarity
  2. Caching: Cache repeated queries/responses
  3. Model Selection: Use smaller models when appropriate
  4. Batch Processing: Group similar requests
  5. Streaming: For long responses, stream tokens

Cost Estimates (Approximate)

  • GPT-3.5-turbo: ~$0.002 per request
  • GPT-4: ~$0.03 per request
  • GPT-4-turbo: ~$0.01 per request

Actual costs vary based on input/output token counts


Common Pitfalls to Avoid

1. Prompt Design Issues

❌ Vague or ambiguous instructions
✅ Clear, specific, testable prompts

2. Error Handling

❌ Ignoring API errors or rate limits
✅ Implement retry logic and graceful degradation

3. Token Management

❌ Exceeding model token limits
✅ Truncate inputs, chunk processing

4. Security

❌ Exposing API keys in code
✅ Use environment variables

5. Testing

❌ Only testing happy paths
✅ Test edge cases, failures, malformed inputs

6. Cost Management

❌ Unmonitored API usage
✅ Implement budgets, alerts, and monitoring


Further Improvements

Production Enhancements

  1. Monitoring: Add logging, metrics, tracing
  2. Validation: Input/output schema validation
  3. Rate Limiting: Implement request throttling
  4. Caching: Redis or in-memory cache for responses
  5. Async Support: Non-blocking operations for scalability

Feature Extensions

  1. Multi-Model Support: Switch between providers
  2. Fallback Models: Graceful degradation strategy
  3. Custom Tools: Domain-specific function calling
  4. Advanced Memory: Vector stores for long-term memory
  5. Evaluation: Automated testing and benchmarking

Code Quality

  1. Testing: Unit tests, integration tests
  2. Documentation: API docs, usage examples
  3. Type Safety: Stricter type hints
  4. Configuration: Externalized config files

Related Patterns

  • Pattern 123
  • Pattern 125

Pattern Families: – Reasoning Patterns (001-011) – Multi-Agent Patterns (012-019) – Tool Use Patterns (020-025) – Memory Patterns (026-031) – And many more…


Summary

The Ontology Based Reasoning pattern provides a powerful approach to building AI agents with specific capabilities. This implementation demonstrates:

✅ Key Strengths: – Clean, maintainable code structure – LangChain integration for composability – Type hints and documentation – Production-ready patterns

🎯 Best Use Cases: – Applications requiring this pattern’s characteristics – Systems needing structured agent behavior – Production environments requiring reliability

📚 Learning Resources: – LangChain Documentation: https://python.langchain.com/ – Pattern Documentation: See related patterns – OpenAI API Reference: https://platform.openai.com/docs/


File: 124_ontology_based_reasoning.py
Total Lines: 373
Pattern Category: AI Agent Design Pattern #124


Generated from actual implementation code
For educational and reference purposes

Comments

Leave a Reply

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