Agentic Architecture Pattern: Ontology Based

Written by

in


    """
Pattern 67: Ontology-Based Pattern

Formal knowledge representation using ontologies with classes, properties, relationships,
inheritance, and logical reasoning capabilities.
"""

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


class PropertyType(Enum):
    """Types of ontology properties."""
    DATA_PROPERTY = "data_property"  # Property with literal value
    OBJECT_PROPERTY = "object_property"  # Property relating to another entity
    FUNCTIONAL = "functional"  # Has exactly one value
    TRANSITIVE = "transitive"  # If A->B and B->C then A->C
    SYMMETRIC = "symmetric"  # If A->B then B->A


@dataclass
class OntologyProperty:
    """A property definition in ontology."""
    name: str
    property_type: PropertyType
    domain: Optional[str] = None  # Which class it belongs to
    range: Optional[str] = None  # What type of value it has
    restrictions: Dict[str, Any] = field(default_factory=dict)


@dataclass
class OntologyClass:
    """A class in the ontology."""
    name: str
    parent: Optional[str] = None
    properties: Dict[str, OntologyProperty] = field(default_factory=dict)
    instances: Set[str] = field(default_factory=set)
    constraints: List[str] = field(default_factory=list)
    
    def get_all_properties(self, ontology: 'OntologyBasedPattern') -> Dict[str, OntologyProperty]:
        """Get all properties including inherited ones."""
        props = dict(self.properties)
        
        # Add parent properties
        if self.parent and self.parent in ontology.classes:
            parent_class = ontology.classes[self.parent]
            parent_props = parent_class.get_all_properties(ontology)
            # Parent properties come first (can be overridden)
            for name, prop in parent_props.items():
                if name not in props:
                    props[name] = prop
        
        return props


@dataclass
class OntologyRelation:
    """A relationship between entities."""
    subject: str
    predicate: str
    object: str
    properties: Dict[str, Any] = field(default_factory=dict)
    timestamp: datetime = field(default_factory=datetime.now)


@dataclass
class OntologyInstance:
    """An instance of an ontology class."""
    name: str
    class_name: str
    properties: Dict[str, Any] = field(default_factory=dict)
    relations: List[OntologyRelation] = field(default_factory=list)


class OntologyReasoner:
    """Reasoning engine for ontology."""
    
    def __init__(self, ontology: 'OntologyBasedPattern'):
        self.ontology = ontology
        self.llm = ontology.llm
    
    def is_subclass_of(self, child: str, parent: str) -> bool:
        """Check if child is a subclass of parent."""
        if child == parent:
            return True
        
        if child not in self.ontology.classes:
            return False
        
        current = self.ontology.classes[child]
        while current.parent:
            if current.parent == parent:
                return True
            if current.parent not in self.ontology.classes:
                break
            current = self.ontology.classes[current.parent]
        
        return False
    
    def is_instance_of(self, instance_name: str, class_name: str) -> bool:
        """Check if instance is of given class (including subclasses)."""
        if instance_name not in self.ontology.instances:
            return False
        
        instance = self.ontology.instances[instance_name]
        return self.is_subclass_of(instance.class_name, class_name)
    
    def infer_transitive_relations(self, predicate: str) -> List[OntologyRelation]:
        """Infer transitive relations (if A->B and B->C then A->C)."""
        # Find property definition
        prop = None
        for onto_class in self.ontology.classes.values():
            if predicate in onto_class.properties:
                prop = onto_class.properties[predicate]
                break
        
        if not prop or prop.property_type != PropertyType.TRANSITIVE:
            return []
        
        # Build relation map
        relations_map: Dict[str, List[str]] = {}
        for rel in self.ontology.relations:
            if rel.predicate == predicate:
                if rel.subject not in relations_map:
                    relations_map[rel.subject] = []
                relations_map[rel.subject].append(rel.object)
        
        # Infer transitive closures
        inferred = []
        for start, targets in relations_map.items():
            visited = set()
            queue = list(targets)
            
            while queue:
                current = queue.pop(0)
                if current in visited:
                    continue
                visited.add(current)
                
                # Add inferred relation
                if current not in targets:  # Not direct relation
                    inferred.append(OntologyRelation(
                        subject=start,
                        predicate=predicate,
                        object=current,
                        properties={"inferred": True, "type": "transitive"}
                    ))
                
                # Add next level
                if current in relations_map:
                    queue.extend(relations_map[current])
        
        return inferred
    
    def query_with_reasoning(self, query: str) -> str:
        """Answer query using ontology reasoning."""
        # Get ontology context
        context_parts = [
            "Ontology Classes:",
            *[f"- {name}: {cls.parent or 'root'}" for name, cls in self.ontology.classes.items()],
            "\nInstances:",
            *[f"- {inst.name} (type: {inst.class_name})" for inst in self.ontology.instances.values()],
            "\nRelations:",
            *[f"- {rel.subject} {rel.predicate} {rel.object}" for rel in self.ontology.relations[:20]]
        ]
        
        context = "\n".join(context_parts)
        
        prompt = ChatPromptTemplate.from_messages([
            ("system", "You are an ontology reasoning expert. Use the ontology structure to answer questions."),
            ("human", f"Ontology:\n{context}\n\nQuery: {query}\n\nProvide a reasoned answer based on the ontology.")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({})
        
        return str(response.content)


class OntologyBasedPattern:
    """
    Ontology-Based Pattern - Formal knowledge representation with reasoning.
    
    Provides structured knowledge representation using classes, properties,
    instances, and relationships with inheritance and logical inference.
    """
    
    def __init__(self, model_name: str = "gpt-4"):
        self.llm = ChatOpenAI(model=model_name, temperature=0.2)
        self.classes: Dict[str, OntologyClass] = {}
        self.relations: List[OntologyRelation] = []
        self.instances: Dict[str, OntologyInstance] = {}
        self.reasoner = OntologyReasoner(self)
    
    def add_class(self, name: str, parent: Optional[str] = None, 
                  properties: Optional[Dict[str, OntologyProperty]] = None,
                  constraints: Optional[List[str]] = None):
        """Add a class to ontology."""
        onto_class = OntologyClass(
            name=name,
            parent=parent,
            properties=properties or {},
            constraints=constraints or []
        )
        self.classes[name] = onto_class
        print(f"📚 Class added: {name}" + (f" (extends {parent})" if parent else ""))
    
    def add_instance(self, class_name: str, instance_name: str, properties: Optional[Dict[str, Any]] = None):
        """Add an instance of a class."""
        if class_name not in self.classes:
            raise ValueError(f"Class {class_name} not found in ontology")
        
        instance = OntologyInstance(
            name=instance_name,
            class_name=class_name,
            properties=properties or {}
        )
        
        self.classes[class_name].instances.add(instance_name)
        self.instances[instance_name] = instance
        print(f"➕ Instance added: {instance_name} of class {class_name}")
    
    def add_relation(self, subject: str, predicate: str, obj: str):
        """Add a relation between entities."""
        relation = OntologyRelation(subject, predicate, obj)
        self.relations.append(relation)
        print(f"🔗 Relation: {subject} {predicate} {obj}")
    
    def query(self, question: str) -> str:
        """Query the ontology using natural language."""
        ontology_desc = self._describe_ontology()
        
        prompt = ChatPromptTemplate.from_messages([
            ("system", """You are an ontology reasoner. Use the ontology to answer questions.

Ontology:
{ontology}"""),
            ("human", "{question}")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({
            "ontology": ontology_desc,
            "question": question
        })
        
        return str(response.content)
    
    def _describe_ontology(self) -> str:
        """Describe the ontology structure."""
        desc = []
        desc.append("Classes:")
        for name, cls in self.classes.items():
            parent_info = f" extends {cls.parent}" if cls.parent else ""
            desc.append(f"  - {name}{parent_info}")
            if cls.properties:
                desc.append(f"    Properties: {list(cls.properties.keys())}")
        
        desc.append("\nInstances:")
        for inst_name, inst_data in self.instances.items():
            desc.append(f"  - {inst_name}: {inst_data.class_name}")
        
        desc.append("\nRelations:")
        for rel in self.relations:
            desc.append(f"  - {rel.subject} {rel.predicate} {rel.object}")
        
        return "\n".join(desc)
    
    def run(self, queries: List[str]) -> Dict[str, Any]:
        """Run queries against the ontology."""
        results = []
        for query in queries:
            answer = self.query(query)
            results.append({"query": query, "answer": answer})
        
        return {
            "ontology_stats": {
                "classes": len(self.classes),
                "instances": len(self.instances),
                "relations": len(self.relations)
            },
            "queries": results
        }


# Example usage
if __name__ == "__main__":
    print("\n=== Ontology-Based Pattern Example ===")
    
    onto = OntologyBasedPattern()
    
    # Build ontology
    onto.add_class("Animal")
    onto.add_class("Mammal", parent="Animal")
    onto.add_class("Bird", parent="Animal")
    onto.add_class("Dog", parent="Mammal")
    onto.add_class("Cat", parent="Mammal")
    
    # Add instances
    onto.add_instance("Dog", "Buddy", {"age": 5, "breed": "Golden Retriever"})
    onto.add_instance("Cat", "Whiskers", {"age": 3, "breed": "Siamese"})
    onto.add_instance("Bird", "Tweety", {"species": "Canary"})
    
    # Add relations
    onto.add_relation("Buddy", "likes", "playing fetch")
    onto.add_relation("Whiskers", "likes", "sleeping")
    onto.add_relation("Dog", "is_a", "Mammal")
    
    # Query
    queries = [
        "What animals are mammals?",
        "Tell me about Buddy",
        "What properties do mammals have?"
    ]
    
    result = onto.run(queries)
    
    print("\n=== Results ===")
    print(f"Ontology: {result['ontology_stats']}")
    print("\nQuery Results:")
    for q in result['queries']:
        print(f"\nQ: {q['query']}")
        print(f"A: {q['answer']}")

    

Agentic Architecture Pattern: Ontology Based

Pattern 67: 67 Ontology Based – Line-by-Line Explanation

Overview

Pattern 67: Ontology-Based Pattern

Formal knowledge representation using ontologies with classes, properties, relationships, inheritance, and logical reasoning capabilities.


Code Structure Analysis

File Information

  • Pattern Number: 67
  • Pattern Name: 67 Ontology Based
  • File Path: patterns\memory_knowledge\67_ontology_based.py
  • Total Lines: 328

Detailed Code Explanation

Module Docstring

"""
Pattern 67: Ontology-Based Pattern

Formal knowledge representation using ontologies with classes, properties, relationships,
inheritance, and logical reasoning capabilities.
"""

Explanation: This module docstring describes the purpose and key features of the 67 Ontology Based pattern.


Imports Section

from typing import Dict, Any, List, Set, Optional, Tuple
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
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, Set, Optional, Tuple: 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 enum import Enum: Enumeration support for fixed sets of values – from langchain_core.prompts import ChatPromptTemplate: LangChain framework integration – from langchain_openai import ChatOpenAI: LangChain framework integration


Enumerations

PropertyType (Line 16)

class PropertyType(Enum):
    DATA_PROPERTY
    OBJECT_PROPERTY
    FUNCTIONAL
    TRANSITIVE
    SYMMETRIC

Purpose: Types of ontology properties.

Values: – DATA_PROPERTY: Represents data property – OBJECT_PROPERTY: Represents object property – FUNCTIONAL: Represents functional – TRANSITIVE: Represents transitive – SYMMETRIC: Represents symmetric


Classes

PropertyType (Line 16)

Description: Types of ontology properties.

OntologyProperty (Line 26)

Description: A property definition in ontology.

OntologyClass (Line 36)

Description: A class in the ontology.

Methods:

get_all_properties (Line 44)
def get_all_properties(self, ontology):
    """Get all properties including inherited ones."""

Purpose: Get all properties including inherited ones.

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

OntologyRelation (Line 61)

Description: A relationship between entities.

OntologyInstance (Line 71)

Description: An instance of an ontology class.

OntologyReasoner (Line 79)

Description: Reasoning engine for ontology.

Methods:

__init__ (Line 82)
def __init__(self, ontology):
    """No method docstring."""

Purpose: No method docstring.

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

is_subclass_of (Line 86)
def is_subclass_of(self, child, parent):
    """Check if child is a subclass of parent."""

Purpose: Check if child is a subclass of parent.

Parameters: – self: Instance reference – child: Parameter for child – parent: Parameter for parent

is_instance_of (Line 104)
def is_instance_of(self, instance_name, class_name):
    """Check if instance is of given class (including subclasses)."""

Purpose: Check if instance is of given class (including subclasses).

Parameters: – self: Instance reference – instance_name: Parameter for instance_name – class_name: Parameter for class_name

infer_transitive_relations (Line 112)
def infer_transitive_relations(self, predicate):
    """Infer transitive relations (if A->B and B->C then A->C)."""

Purpose: Infer transitive relations (if A->B and B->C then A->C).

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

query_with_reasoning (Line 159)
def query_with_reasoning(self, query):
    """Answer query using ontology reasoning."""

Purpose: Answer query using ontology reasoning.

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

OntologyBasedPattern (Line 184)

Description: Ontology-Based Pattern – Formal knowledge representation with reasoning.

Provides structured knowledge representation using classes, properties, instances, and relationships with inheritance and logical inference.

Methods:

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

Purpose: No method docstring.

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

add_class (Line 199)
def add_class(self, name, parent, properties, constraints):
    """Add a class to ontology."""

Purpose: Add a class to ontology.

Parameters: – self: Instance reference – name: Identifier or label – parent: Parameter for parent – properties: Parameter for properties – constraints: Parameter for constraints

add_instance (Line 212)
def add_instance(self, class_name, instance_name, properties):
    """Add an instance of a class."""

Purpose: Add an instance of a class.

Parameters: – self: Instance reference – class_name: Parameter for class_name – instance_name: Parameter for instance_name – properties: Parameter for properties

add_relation (Line 227)
def add_relation(self, subject, predicate, obj):
    """Add a relation between entities."""

Purpose: Add a relation between entities.

Parameters: – self: Instance reference – subject: Parameter for subject – predicate: Parameter for predicate – obj: Parameter for obj

query (Line 233)
def query(self, question):
    """Query the ontology using natural language."""

Purpose: Query the ontology using natural language.

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

_describe_ontology (Line 253)
def _describe_ontology(self):
    """Describe the ontology structure."""

Purpose: Describe the ontology structure.

Parameters: – self: Instance reference

run (Line 273)
def run(self, queries):
    """Run queries against the ontology."""

Purpose: Run queries against the ontology.

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


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 = PropertyType(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 *