Agentic Architecture Pattern: Spatial Reasoning

Written by

in


    """
Pattern 208: Spatial Reasoning Pattern

Reasons about spatial relationships, locations, distances, orientations,
and navigation. Handles concepts like left/right, near/far, inside/outside.
"""

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


class SpatialRelation(Enum):
    """Types of spatial relationships."""
    ABOVE = "above"
    BELOW = "below"
    LEFT_OF = "left_of"
    RIGHT_OF = "right_of"
    IN_FRONT_OF = "in_front_of"
    BEHIND = "behind"
    INSIDE = "inside"
    OUTSIDE = "outside"
    NEAR = "near"
    FAR = "far"
    TOUCHING = "touching"
    ADJACENT = "adjacent"


@dataclass
class SpatialObject:
    """Object with spatial properties."""
    name: str
    location: Tuple[float, float, float]  # x, y, z coordinates
    size: Tuple[float, float, float]  # width, height, depth
    orientation: float = 0.0  # degrees


@dataclass
class SpatialQuery:
    """Query about spatial relationships."""
    query_text: str
    objects_involved: List[str]
    query_type: str  # "relation", "path", "distance", "visibility"


class SpatialReasoningAgent:
    """Agent that performs spatial reasoning."""
    
    def __init__(self, name: str, model_name: str = "gpt-4"):
        self.name = name
        self.llm = ChatOpenAI(model=model_name, temperature=0.3)
        self.objects: Dict[str, SpatialObject] = {}
    
    def add_object(self, obj: SpatialObject):
        """Add object to spatial environment."""
        self.objects[obj.name] = obj
    
    def determine_relation(self, obj1_name: str, obj2_name: str) -> str:
        """Determine spatial relation between two objects."""
        if obj1_name not in self.objects or obj2_name not in self.objects:
            return "unknown"
        
        obj1 = self.objects[obj1_name]
        obj2 = self.objects[obj2_name]
        
        dx = obj2.location[0] - obj1.location[0]
        dy = obj2.location[1] - obj1.location[1]
        dz = obj2.location[2] - obj1.location[2]
        
        # Simple spatial reasoning based on coordinates
        relations = []
        if dz > 0.5:
            relations.append("above")
        elif dz  0.5:
            relations.append("right of")
        elif dx  0.5:
            relations.append("in front of")
        elif dy < -0.5:
            relations.append("behind")
        
        distance = (dx**2 + dy**2 + dz**2) ** 0.5
        if distance  5.0:
            relations.append("far from")
        
        return ", ".join(relations) if relations else "at similar location"
    
    def calculate_distance(self, obj1_name: str, obj2_name: str) -> float:
        """Calculate Euclidean distance between objects."""
        if obj1_name not in self.objects or obj2_name not in self.objects:
            return -1.0
        
        obj1 = self.objects[obj1_name]
        obj2 = self.objects[obj2_name]
        
        dx = obj2.location[0] - obj1.location[0]
        dy = obj2.location[1] - obj1.location[1]
        dz = obj2.location[2] - obj1.location[2]
        
        return (dx**2 + dy**2 + dz**2) ** 0.5
    
    def find_path(self, start: str, goal: str, obstacles: List[str] = None) -> List[str]:
        """Find path from start to goal avoiding obstacles."""
        print(f"\n  {self.name}: Finding path from {start} to {goal}...")
        
        if start not in self.objects or goal not in self.objects:
            return []
        
        prompt = ChatPromptTemplate.from_messages([
            ("system", "Plan a path from start to goal in 3D space."),
            ("human", """Start location: {start}
Goal location: {goal}
Obstacles: {obstacles}

Describe a path as a sequence of waypoints.""")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({
            "start": str(self.objects[start].location),
            "goal": str(self.objects[goal].location),
            "obstacles": str([self.objects[o].location for o in (obstacles or [])])
        })
        
        waypoints = [line.strip() for line in response.content.split("\n") if line.strip()]
        print(f"  Path: {len(waypoints)} waypoints")
        return waypoints
    
    def check_visibility(self, observer: str, target: str) -> bool:
        """Check if target is visible from observer."""
        if observer not in self.objects or target not in self.objects:
            return False
        
        obs = self.objects[observer]
        tgt = self.objects[target]
        
        # Simple line-of-sight check (no obstacles for now)
        distance = self.calculate_distance(observer, target)
        return distance  Dict[str, Any]:
        """Reason about spatial scene from natural language."""
        print(f"\n  {self.name}: Reasoning about scene...")
        
        prompt = ChatPromptTemplate.from_messages([
            ("system", "Analyze spatial relationships in scene description."),
            ("human", """Scene: {scene}

Identify:
1. Objects and their locations
2. Spatial relationships between objects
3. Possible navigation paths

Provide structured analysis.""")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({"scene": scene_description})
        
        return {
            "scene": scene_description,
            "analysis": response.content,
            "object_count": len(self.objects)
        }


class SpatialReasoningPattern:
    """
    Spatial Reasoning Pattern - Understand space and location.
    
    Handles spatial relationships, distances, paths, visibility,
    and scene understanding.
    """
    
    def __init__(self):
        self.agents: Dict[str, SpatialReasoningAgent] = {}
    
    def register_agent(self, agent: SpatialReasoningAgent):
        """Register spatial reasoning agent."""
        self.agents[agent.name] = agent
    
    def run(self, queries: List[SpatialQuery]) -> Dict[str, Any]:
        """Execute spatial reasoning queries."""
        print("=== Spatial Reasoning Pattern ===\n")
        
        for agent in self.agents.values():
            for query in queries:
                if query.query_type == "relation" and len(query.objects_involved) >= 2:
                    relation = agent.determine_relation(
                        query.objects_involved[0],
                        query.objects_involved[1]
                    )
                    print(f"\n  Relation: {query.objects_involved[0]} is {relation} {query.objects_involved[1]}")
                
                elif query.query_type == "distance" and len(query.objects_involved) >= 2:
                    distance = agent.calculate_distance(
                        query.objects_involved[0],
                        query.objects_involved[1]
                    )
                    print(f"\n  Distance: {distance:.2f} units")
                
                elif query.query_type == "path" and len(query.objects_involved) >= 2:
                    agent.find_path(
                        query.objects_involved[0],
                        query.objects_involved[1]
                    )
                
                elif query.query_type == "scene":
                    agent.reason_about_scene(query.query_text)
        
        return {
            "pattern": "SpatialReasoning",
            "agents": len(self.agents),
            "queries_processed": len(queries)
        }


# Example usage
if __name__ == "__main__":
    print("=== Spatial Reasoning Pattern Demo ===\n")
    
    # Create pattern and agent
    pattern = SpatialReasoningPattern()
    agent = SpatialReasoningAgent("Navigator")
    pattern.register_agent(agent)
    
    # Add objects to spatial environment
    print("1. Setting up spatial environment...\n")
    agent.add_object(SpatialObject("Table", (0.0, 0.0, 0.0), (2.0, 1.0, 0.8)))
    agent.add_object(SpatialObject("Chair", (-1.0, 0.0, 0.0), (0.5, 0.5, 1.0)))
    agent.add_object(SpatialObject("Lamp", (0.0, 0.0, 1.5), (0.2, 0.2, 0.5)))
    agent.add_object(SpatialObject("Door", (5.0, 0.0, 0.0), (1.0, 0.1, 2.0)))
    agent.add_object(SpatialObject("Window", (0.0, 3.0, 1.0), (1.5, 0.1, 1.5)))
    
    # Spatial reasoning queries
    print("2. Executing spatial reasoning queries...\n")
    queries = [
        SpatialQuery(
            "Where is the lamp relative to the table?",
            ["Lamp", "Table"],
            "relation"
        ),
        SpatialQuery(
            "How far is the door from the chair?",
            ["Door", "Chair"],
            "distance"
        ),
        SpatialQuery(
            "Find path from chair to door",
            ["Chair", "Door"],
            "path"
        ),
        SpatialQuery(
            "The room has a table in the center with a lamp on it. "
            "A chair is to the left, and a door is on the right wall.",
            [],
            "scene"
        )
    ]
    
    # Run spatial reasoning
    summary = pattern.run(queries)
    
    print(f"\n=== Summary ===")
    print(f"Queries processed: {summary['queries_processed']}")
    print(f"Objects in environment: {len(agent.objects)}")
    
    print("\nāœ“ Spatial reasoning: understanding locations and relationships!")

    

Agentic Architecture Pattern: Spatial Reasoning

Pattern 208: 208 Spatial Reasoning Pattern – Line-by-Line Explanation

Overview

Pattern 208: Spatial Reasoning Pattern

Reasons about spatial relationships, locations, distances, orientations, and navigation. Handles concepts like left/right, near/far, inside/outside.


Code Structure Analysis

File Information

  • Pattern Number: 208
  • Pattern Name: 208 Spatial Reasoning Pattern
  • File Path: patterns\advanced_reasoning\208_spatial_reasoning_pattern.py
  • Total Lines: 278

Detailed Code Explanation

Module Docstring

"""
Pattern 208: Spatial Reasoning Pattern

Reasons about spatial relationships, locations, distances, orientations,
and navigation. Handles concepts like left/right, near/far, inside/outside.
"""

Explanation: This module docstring describes the purpose and key features of the 208 Spatial Reasoning Pattern pattern.


Imports Section

from typing import Dict, Any, List, Optional, Tuple
from dataclasses import dataclass
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, Optional, Tuple: Type hints for better code documentation – from dataclasses import dataclass: Data class decorators for clean data structures – 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

SpatialRelation (Line 15)

class SpatialRelation(Enum):
    ABOVE
    BELOW
    LEFT_OF
    RIGHT_OF
    IN_FRONT_OF
    BEHIND
    INSIDE
    OUTSIDE
    NEAR
    FAR
    TOUCHING
    ADJACENT

Purpose: Types of spatial relationships.

Values: – ABOVE: Represents above – BELOW: Represents below – LEFT_OF: Represents left of – RIGHT_OF: Represents right of – IN_FRONT_OF: Represents in front of – BEHIND: Represents behind – INSIDE: Represents inside – OUTSIDE: Represents outside – NEAR: Represents near – FAR: Represents far – TOUCHING: Represents touching – ADJACENT: Represents adjacent


Classes

SpatialRelation (Line 15)

Description: Types of spatial relationships.

SpatialObject (Line 32)

Description: Object with spatial properties.

SpatialQuery (Line 41)

Description: Query about spatial relationships.

SpatialReasoningAgent (Line 48)

Description: Agent that performs spatial reasoning.

Methods:

__init__ (Line 51)
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”)

add_object (Line 56)
def add_object(self, obj):
    """Add object to spatial environment."""

Purpose: Add object to spatial environment.

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

determine_relation (Line 60)
def determine_relation(self, obj1_name, obj2_name):
    """Determine spatial relation between two objects."""

Purpose: Determine spatial relation between two objects.

Parameters: – self: Instance reference – obj1_name: Parameter for obj1_name – obj2_name: Parameter for obj2_name

calculate_distance (Line 97)
def calculate_distance(self, obj1_name, obj2_name):
    """Calculate Euclidean distance between objects."""

Purpose: Calculate Euclidean distance between objects.

Parameters: – self: Instance reference – obj1_name: Parameter for obj1_name – obj2_name: Parameter for obj2_name

find_path (Line 111)
def find_path(self, start, goal, obstacles):
    """Find path from start to goal avoiding obstacles."""

Purpose: Find path from start to goal avoiding obstacles.

Parameters: – self: Instance reference – start: Parameter for start – goal: Parameter for goal – obstacles: Parameter for obstacles

check_visibility (Line 138)
def check_visibility(self, observer, target):
    """Check if target is visible from observer."""

Purpose: Check if target is visible from observer.

Parameters: – self: Instance reference – observer: Parameter for observer – target: Parameter for target

reason_about_scene (Line 150)
def reason_about_scene(self, scene_description):
    """Reason about spatial scene from natural language."""

Purpose: Reason about spatial scene from natural language.

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

SpatialReasoningPattern (Line 176)

Description: Spatial Reasoning Pattern – Understand space and location.

Handles spatial relationships, distances, paths, visibility, and scene understanding.

Methods:

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

Purpose: No method docstring.

Parameters: – self: Instance reference

register_agent (Line 187)
def register_agent(self, agent):
    """Register spatial reasoning agent."""

Purpose: Register spatial reasoning agent.

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

run (Line 191)
def run(self, queries):
    """Execute spatial reasoning queries."""

Purpose: Execute spatial reasoning queries.

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