Agentic Architecture Pattern: Graph Of Thought

Written by

in


    """
Pattern 76: Graph-of-Thought Pattern

Reasoning as a graph structure with nodes (thoughts) and edges (relationships).
Allows parallel exploration, backtracking, and aggregation of multiple reasoning paths.
"""

from typing import Dict, Any, List, Optional, Set
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from typing_extensions import TypedDict


class ThoughtType(Enum):
    """Type of thought node."""
    HYPOTHESIS = "hypothesis"
    EVIDENCE = "evidence"
    REASONING = "reasoning"
    CONCLUSION = "conclusion"
    QUESTION = "question"


@dataclass
class ThoughtNode:
    """Node in the thought graph."""
    node_id: str
    content: str
    thought_type: ThoughtType
    confidence: float = 0.5  # 0.0 to 1.0
    parent_ids: List[str] = field(default_factory=list)
    child_ids: List[str] = field(default_factory=list)
    metadata: Dict[str, Any] = field(default_factory=dict)
    timestamp: datetime = field(default_factory=datetime.now)


@dataclass
class ThoughtEdge:
    """Edge connecting thoughts."""
    from_id: str
    to_id: str
    relationship: str  # supports, contradicts, questions, leads_to
    strength: float = 0.5  # 0.0 to 1.0


class GraphOfThoughtState(TypedDict):
    """State for graph-of-thought workflow."""
    problem: str
    nodes: List[ThoughtNode]
    edges: List[ThoughtEdge]
    current_node_id: Optional[str]
    explored_ids: Set[str]
    conclusions: List[str]


class GraphOfThoughtPattern:
    """
    Graph-of-Thought Pattern - Non-linear reasoning with graph structure.
    
    Thoughts are organized as a graph allowing:
    - Parallel exploration of multiple paths
    - Backtracking when needed
    - Aggregation of insights from different branches
    - Complex relationship modeling
    """
    
    def __init__(self, model_name: str = "gpt-4"):
        self.llm = ChatOpenAI(model=model_name, temperature=0.7)
        self.graph = StateGraph(GraphOfThoughtState)
        self._build_graph()
        self.workflow = self.graph.compile()
    
    def _build_graph(self):
        """Build the LangGraph workflow."""
        self.graph.add_node("generate_hypotheses", self._generate_hypotheses)
        self.graph.add_node("explore_path", self._explore_path)
        self.graph.add_node("evaluate_node", self._evaluate_node)
        self.graph.add_node("aggregate_insights", self._aggregate_insights)
        
        self.graph.set_entry_point("generate_hypotheses")
        
        self.graph.add_edge("generate_hypotheses", "explore_path")
        self.graph.add_edge("explore_path", "evaluate_node")
        
        self.graph.add_conditional_edges(
            "evaluate_node",
            self._should_continue_exploration,
            {
                "explore": "explore_path",
                "aggregate": "aggregate_insights",
            }
        )
        
        self.graph.add_edge("aggregate_insights", END)
    
    def _generate_hypotheses(self, state: GraphOfThoughtState) -> GraphOfThoughtState:
        """Generate initial hypothesis nodes."""
        print(f"\n   Generating hypotheses for: {state['problem']}")
        
        prompt = ChatPromptTemplate.from_messages([
            ("system", "Generate 3 diverse hypotheses or approaches to solve this problem. "
                      "Each should explore a different angle. Return one per line."),
            ("human", "{problem}")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({"problem": state['problem']})
        
        hypotheses = [h.strip() for h in response.content.split('\n') if h.strip()][:3]
        
        nodes = []
        for i, hypothesis in enumerate(hypotheses):
            node = ThoughtNode(
                node_id=f"hyp_{i}",
                content=hypothesis,
                thought_type=ThoughtType.HYPOTHESIS,
                confidence=0.5
            )
            nodes.append(node)
            print(f"   - Hypothesis {i+1}: {hypothesis[:60]}...")
        
        state["nodes"] = nodes
        state["edges"] = []
        state["explored_ids"] = set()
        state["conclusions"] = []
        
        return state
    
    def _explore_path(self, state: GraphOfThoughtState) -> GraphOfThoughtState:
        """Explore a thought path by expanding a node."""
        # Find unexplored node
        unexplored = [n for n in state["nodes"] 
                     if n.node_id not in state["explored_ids"]]
        
        if not unexplored:
            return state
        
        current_node = unexplored[0]
        state["current_node_id"] = current_node.node_id
        
        print(f"\n   Exploring: {current_node.content[:60]}...")
        
        # Generate reasoning from this node
        prompt = ChatPromptTemplate.from_messages([
            ("system", "Based on this thought, generate 2 follow-up thoughts: "
                      "either supporting evidence, counterarguments, or logical next steps. "
                      "Return one per line."),
            ("human", "{thought}")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({"thought": current_node.content})
        
        follow_ups = [f.strip() for f in response.content.split('\n') if f.strip()][:2]
        
        for i, follow_up in enumerate(follow_ups):
            child_id = f"{current_node.node_id}_child_{i}"
            child_node = ThoughtNode(
                node_id=child_id,
                content=follow_up,
                thought_type=ThoughtType.REASONING,
                parent_ids=[current_node.node_id],
                confidence=0.6
            )
            state["nodes"].append(child_node)
            current_node.child_ids.append(child_id)
            
            # Create edge
            edge = ThoughtEdge(
                from_id=current_node.node_id,
                to_id=child_id,
                relationship="leads_to",
                strength=0.7
            )
            state["edges"].append(edge)
            
            print(f"   - Follow-up: {follow_up[:50]}...")
        
        state["explored_ids"].add(current_node.node_id)
        
        return state
    
    def _evaluate_node(self, state: GraphOfThoughtState) -> GraphOfThoughtState:
        """Evaluate current node's confidence."""
        current_id = state.get("current_node_id")
        if not current_id:
            return state
        
        current_node = next((n for n in state["nodes"] if n.node_id == current_id), None)
        if not current_node:
            return state
        
        # Simple evaluation based on depth and branching
        depth = len([p for p in current_node.parent_ids])
        children_count = len(current_node.child_ids)
        
        # Update confidence
        current_node.confidence = min(1.0, 0.5 + (children_count * 0.1) + (depth * 0.05))
        
        return state
    
    def _should_continue_exploration(self, state: GraphOfThoughtState) -> str:
        """Decide whether to continue exploring or aggregate."""
        explored = len(state["explored_ids"])
        total = len(state["nodes"])
        
        # Explore at least 5 nodes or until 80% explored
        if explored  0 and explored / total  GraphOfThoughtState:
        """Aggregate insights from the thought graph."""
        print(f"\n   Aggregating insights from {len(state['nodes'])} thoughts...")
        
        # Find leaf nodes (conclusions)
        leaf_nodes = [n for n in state["nodes"] if not n.child_ids]
        
        # Prepare summary of reasoning paths
        paths_summary = []
        for leaf in leaf_nodes[:3]:  # Top 3 paths
            path = self._trace_path(leaf, state["nodes"])
            paths_summary.append(" → ".join([n.content[:40] for n in path]))
        
        prompt = ChatPromptTemplate.from_messages([
            ("system", "Given these reasoning paths, synthesize a final conclusion. "
                      "Be comprehensive and consider all perspectives."),
            ("human", "Problem: {problem}\n\nReasoning paths:\n{paths}\n\nProvide final conclusion:")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({
            "problem": state['problem'],
            "paths": "\n".join([f"{i+1}. {p}" for i, p in enumerate(paths_summary)])
        })
        
        state["conclusions"] = [response.content]
        print(f"   - Synthesized conclusion")
        
        return state
    
    def _trace_path(self, node: ThoughtNode, all_nodes: List[ThoughtNode]) -> List[ThoughtNode]:
        """Trace path from root to this node."""
        path = [node]
        current = node
        
        while current.parent_ids:
            parent_id = current.parent_ids[0]
            parent = next((n for n in all_nodes if n.node_id == parent_id), None)
            if not parent:
                break
            path.insert(0, parent)
            current = parent
        
        return path
    
    def run(self, problem: str) -> Dict[str, Any]:
        """Execute graph-of-thought reasoning."""
        print(f"\n=== Graph-of-Thought Pattern ===")
        
        initial_state: GraphOfThoughtState = {
            "problem": problem,
            "nodes": [],
            "edges": [],
            "current_node_id": None,
            "explored_ids": set(),
            "conclusions": []
        }
        
        final_state = self.workflow.invoke(initial_state)
        
        return {
            "pattern": "GraphOfThought",
            "problem": problem,
            "total_nodes": len(final_state["nodes"]),
            "total_edges": len(final_state["edges"]),
            "explored_nodes": len(final_state["explored_ids"]),
            "conclusions": final_state["conclusions"],
            "graph_structure": {
                "nodes": len(final_state["nodes"]),
                "edges": len(final_state["edges"]),
                "hypotheses": len([n for n in final_state["nodes"] 
                                  if n.thought_type == ThoughtType.HYPOTHESIS])
            }
        }


# Example usage
if __name__ == "__main__":
    print("=== Graph-of-Thought Pattern Demo ===\n")
    
    pattern = GraphOfThoughtPattern()
    
    # Complex problem requiring non-linear reasoning
    problem = """
    A company wants to reduce its carbon footprint by 50% in 2 years.
    They have manufacturing plants, offices, and a delivery fleet.
    What strategy should they pursue?
    """
    
    print("Problem:", problem.strip())
    
    result = pattern.run(problem)
    
    print(f"\n=== Results ===")
    print(f"Total thought nodes: {result['total_nodes']}")
    print(f"Explored nodes: {result['explored_nodes']}")
    print(f"Graph edges: {result['total_edges']}")
    print(f"Hypotheses generated: {result['graph_structure']['hypotheses']}")
    
    print(f"\n=== Final Conclusion ===")
    for conclusion in result['conclusions']:
        print(f"{conclusion}")
    
    print("\nāœ“ Graph-of-thought reasoning completed with non-linear exploration!")

    

Agentic Architecture Pattern: Graph Of Thought

Pattern 76: 76 Graph Of Thought – Line-by-Line Explanation

Overview

Pattern 76: Graph-of-Thought Pattern

Reasoning as a graph structure with nodes (thoughts) and edges (relationships). Allows parallel exploration, backtracking, and aggregation of multiple reasoning paths.


Code Structure Analysis

File Information

  • Pattern Number: 76
  • Pattern Name: 76 Graph Of Thought
  • File Path: patterns\execution_control\76_graph_of_thought.py
  • Total Lines: 319

Detailed Code Explanation

Module Docstring

"""
Pattern 76: Graph-of-Thought Pattern

Reasoning as a graph structure with nodes (thoughts) and edges (relationships).
Allows parallel exploration, backtracking, and aggregation of multiple reasoning paths.
"""

Explanation: This module docstring describes the purpose and key features of the 76 Graph Of Thought pattern.


Imports Section

from typing import Dict, Any, List, Optional, Set
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from typing_extensions import TypedDict  # First 20 imports shown

Explanation: The module imports necessary dependencies: – from typing import Dict, Any, List, Optional, Set: 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 – from langgraph.graph import StateGraph, END: LangGraph for workflow orchestration – from typing_extensions import TypedDict: Type hints for better code documentation


Enumerations

ThoughtType (Line 18)

class ThoughtType(Enum):
    HYPOTHESIS
    EVIDENCE
    REASONING
    CONCLUSION
    QUESTION

Purpose: Type of thought node.

Values: – HYPOTHESIS: Represents hypothesis – EVIDENCE: Represents evidence – REASONING: Represents reasoning – CONCLUSION: Represents conclusion – QUESTION: Represents question


Classes

ThoughtType (Line 18)

Description: Type of thought node.

ThoughtNode (Line 28)

Description: Node in the thought graph.

ThoughtEdge (Line 41)

Description: Edge connecting thoughts.

GraphOfThoughtState (Line 49)

Description: State for graph-of-thought workflow.

GraphOfThoughtPattern (Line 59)

Description: Graph-of-Thought Pattern – Non-linear reasoning with graph structure.

Thoughts are organized as a graph allowing: – Parallel exploration of multiple paths – Backtracking when needed – Aggregation of insights from different branches – Complex relationship modeling

Methods:

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

_build_graph (Line 76)
def _build_graph(self):
    """Build the LangGraph workflow."""

Purpose: Build the LangGraph workflow.

Parameters: – self: Instance reference

_generate_hypotheses (Line 99)
def _generate_hypotheses(self, state):
    """Generate initial hypothesis nodes."""

Purpose: Generate initial hypothesis nodes.

Parameters: – self: Instance reference – state: Current workflow state

_explore_path (Line 132)
def _explore_path(self, state):
    """Explore a thought path by expanding a node."""

Purpose: Explore a thought path by expanding a node.

Parameters: – self: Instance reference – state: Current workflow state

_evaluate_node (Line 186)
def _evaluate_node(self, state):
    """Evaluate current node's confidence."""

Purpose: Evaluate current node’s confidence.

Parameters: – self: Instance reference – state: Current workflow state

_should_continue_exploration (Line 205)
def _should_continue_exploration(self, state):
    """Decide whether to continue exploring or aggregate."""

Purpose: Decide whether to continue exploring or aggregate.

Parameters: – self: Instance reference – state: Current workflow state

_aggregate_insights (Line 215)
def _aggregate_insights(self, state):
    """Aggregate insights from the thought graph."""

Purpose: Aggregate insights from the thought graph.

Parameters: – self: Instance reference – state: Current workflow state

_trace_path (Line 245)
def _trace_path(self, node, all_nodes):
    """Trace path from root to this node."""

Purpose: Trace path from root to this node.

Parameters: – self: Instance reference – node: Parameter for node – all_nodes: Parameter for all_nodes

run (Line 260)
def run(self, problem):
    """Execute graph-of-thought reasoning."""

Purpose: Execute graph-of-thought reasoning.

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


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