Agentic Architecture Pattern: Voting

Written by

in


    """
Pattern 25: Voting Pattern

Agents vote to reach consensus on decisions.
Supports multiple voting methods and weighted votes.
"""

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


class VotingMethod(Enum):
    """Type of voting method."""
    MAJORITY = "majority"  # Simple majority wins
    PLURALITY = "plurality"  # Most votes wins
    UNANIMOUS = "unanimous"  # All must agree
    WEIGHTED = "weighted"  # Votes have different weights
    RANKED_CHOICE = "ranked_choice"  # Rank preferences


@dataclass
class VoteOption:
    """Option that can be voted on."""
    option_id: str
    description: str
    proposed_by: str


@dataclass
class Vote:
    """Individual vote cast by an agent."""
    voter_name: str
    proposal_id: str
    choice: str  # option_id
    weight: float = 1.0
    ranking: Optional[List[str]] = None  # For ranked choice
    reasoning: str = ""
    timestamp: datetime = field(default_factory=datetime.now)


@dataclass
class VotingResult:
    """Result of a vote."""
    proposal_id: str
    method: VotingMethod
    winner: Optional[str]
    all_votes: List[Vote]
    vote_counts: Dict[str, float]
    total_voters: int
    passed: bool


class VotingAgent:
    """Agent that participates in voting."""
    
    def __init__(self, name: str, role: str, expertise: List[str],
                 vote_weight: float = 1.0, model_name: str = "gpt-4"):
        self.name = name
        self.role = role
        self.expertise = expertise
        self.vote_weight = vote_weight
        self.llm = ChatOpenAI(model=model_name, temperature=0.7)
    
    def cast_vote(self, proposal_id: str, options: List[VoteOption],
                  method: VotingMethod = VotingMethod.MAJORITY) -> Vote:
        """Cast vote on proposal."""
        
        options_text = "\n".join([f"- {opt.option_id}: {opt.description}" 
                                  for opt in options])
        
        prompt = ChatPromptTemplate.from_messages([
            ("system", f"You are {self.name}, a {self.role} with expertise in {', '.join(self.expertise)}. "
                      f"Analyze the options and vote based on your expertise."),
            ("human", f"Proposal: {proposal_id}\n\n"
                     f"Options:\n{options_text}\n\n"
                     f"Choose the best option and explain why briefly.")
        ])
        
        chain = prompt | self.llm
        response = chain.invoke({})
        content = str(response.content)
        
        # Parse choice from response
        choice = options[0].option_id  # Default
        for opt in options:
            if opt.option_id.lower() in content.lower():
                choice = opt.option_id
                break
        
        # For ranked choice, create ranking
        ranking = None
        if method == VotingMethod.RANKED_CHOICE:
            ranking = [opt.option_id for opt in options]
        
        vote = Vote(
            voter_name=self.name,
            proposal_id=proposal_id,
            choice=choice,
            weight=self.vote_weight,
            ranking=ranking,
            reasoning=content[:150]
        )
        
        return vote


class VotingPattern:
    """
    Voting Pattern - Democratic decision-making through voting.
    
    Agents cast votes on proposals using various voting methods.
    Supports weighted votes, ranked choice, and different consensus rules.
    """
    
    def __init__(self, voting_method: VotingMethod = VotingMethod.MAJORITY):
        self.voting_method = voting_method
        self.agents: Dict[str, VotingAgent] = {}
        self.voting_history: List[VotingResult] = []
    
    def register_agent(self, agent: VotingAgent):
        """Register agent as voter."""
        self.agents[agent.name] = agent
    
    def conduct_vote(self, proposal_id: str, options: List[VoteOption]) -> VotingResult:
        """Conduct vote among registered agents."""
        print(f"\n   Voting on: {proposal_id}")
        print(f"   Options: {', '.join([opt.option_id for opt in options])}")
        print(f"   Method: {self.voting_method.value}")
        
        # Collect votes
        votes: List[Vote] = []
        for agent in self.agents.values():
            vote = agent.cast_vote(proposal_id, options, self.voting_method)
            votes.append(vote)
            print(f"   - {agent.name} voted for: {vote.choice}")
        
        # Count votes based on method
        vote_counts: Dict[str, float] = {}
        
        if self.voting_method == VotingMethod.WEIGHTED:
            for vote in votes:
                vote_counts[vote.choice] = vote_counts.get(vote.choice, 0) + vote.weight
        else:
            for vote in votes:
                vote_counts[vote.choice] = vote_counts.get(vote.choice, 0) + 1
        
        # Determine winner
        winner = None
        passed = False
        
        if self.voting_method == VotingMethod.MAJORITY:
            total = sum(vote_counts.values())
            if vote_counts:
                winner = max(vote_counts, key=vote_counts.get)
                passed = vote_counts[winner] > total / 2
        
        elif self.voting_method == VotingMethod.PLURALITY:
            if vote_counts:
                winner = max(vote_counts, key=vote_counts.get)
                passed = True
        
        elif self.voting_method == VotingMethod.UNANIMOUS:
            if len(set(v.choice for v in votes)) == 1:
                winner = votes[0].choice
                passed = True
        
        elif self.voting_method == VotingMethod.WEIGHTED:
            if vote_counts:
                winner = max(vote_counts, key=vote_counts.get)
                total_weight = sum(vote_counts.values())
                passed = vote_counts[winner] > total_weight / 2
        
        if passed:
            print(f"   ✓ Winner: {winner} ({vote_counts.get(winner, 0)} votes)")
        else:
            print(f"   ✗ No winner (consensus not reached)")
        
        result = VotingResult(
            proposal_id=proposal_id,
            method=self.voting_method,
            winner=winner,
            all_votes=votes,
            vote_counts=vote_counts,
            total_voters=len(votes),
            passed=passed
        )
        
        self.voting_history.append(result)
        return result
    
    def run(self, scenario: str) -> Dict[str, Any]:
        """Execute voting pattern."""
        passed_votes = sum(1 for v in self.voting_history if v.passed)
        
        return {
            "pattern": "Voting",
            "voting_method": self.voting_method.value,
            "total_votes": len(self.voting_history),
            "passed": passed_votes,
            "failed": len(self.voting_history) - passed_votes,
            "registered_voters": len(self.agents),
            "scenario": scenario
        }


# Example usage
if __name__ == "__main__":
    print("=== Voting Pattern Demo ===\n")
    
    # Create pattern with weighted voting
    pattern = VotingPattern(voting_method=VotingMethod.WEIGHTED)
    
    # Register voters with different weights
    print("1. Registering voters...")
    
    tech_lead = VotingAgent("Alice", "tech lead", ["architecture", "security"], vote_weight=2.0)
    senior_dev = VotingAgent("Bob", "senior developer", ["backend", "databases"], vote_weight=1.5)
    designer = VotingAgent("Carol", "designer", ["UI", "UX"], vote_weight=1.0)
    pm = VotingAgent("Dave", "product manager", ["business", "strategy"], vote_weight=1.5)
    
    for agent in [tech_lead, senior_dev, designer, pm]:
        pattern.register_agent(agent)
    
    # Vote on proposals
    print("\n2. Conducting votes...")
    
    proposal1_options = [
        VoteOption("opt1", "Use microservices architecture", "Alice"),
        VoteOption("opt2", "Use monolithic architecture", "Bob"),
        VoteOption("opt3", "Use serverless architecture", "Dave")
    ]
    
    proposal2_options = [
        VoteOption("opt1", "React for frontend", "Carol"),
        VoteOption("opt2", "Vue for frontend", "Bob"),
        VoteOption("opt3", "Angular for frontend", "Alice")
    ]
    
    result1 = pattern.conduct_vote("architecture_decision", proposal1_options)
    result2 = pattern.conduct_vote("frontend_framework", proposal2_options)
    
    # Summary
    print("\n3. Voting Summary:")
    summary = pattern.run("Team technical decisions")
    print(f"   Method: {summary['voting_method']}")
    print(f"   Total Votes: {summary['total_votes']}")
    print(f"   Passed: {summary['passed']}")
    print(f"   Failed: {summary['failed']}")
    print(f"   Registered Voters: {summary['registered_voters']}")
    
    print("\n✓ Voting pattern enabled democratic decision-making!")

    

Agentic Architecture Pattern: Voting

Pattern 25: 25 Voting – Line-by-Line Explanation

Overview

Pattern 25: Voting Pattern

Agents vote to reach consensus on decisions. Supports multiple voting methods and weighted votes.


Code Structure Analysis

File Information

  • Pattern Number: 25
  • Pattern Name: 25 Voting
  • File Path: patterns\collaboration\25_voting.py
  • Total Lines: 257

Detailed Code Explanation

Module Docstring

"""
Pattern 25: Voting Pattern

Agents vote to reach consensus on decisions.
Supports multiple voting methods and weighted votes.
"""

Explanation: This module docstring describes the purpose and key features of the 25 Voting pattern.


Imports Section

from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from collections import Counter
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 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

VotingMethod (Line 17)

class VotingMethod(Enum):
    MAJORITY
    PLURALITY
    UNANIMOUS
    WEIGHTED
    RANKED_CHOICE

Purpose: Type of voting method.

Values: – MAJORITY: Represents majority – PLURALITY: Represents plurality – UNANIMOUS: Represents unanimous – WEIGHTED: Represents weighted – RANKED_CHOICE: Represents ranked choice


Classes

VotingMethod (Line 17)

Description: Type of voting method.

VoteOption (Line 27)

Description: Option that can be voted on.

Vote (Line 35)

Description: Individual vote cast by an agent.

VotingResult (Line 47)

Description: Result of a vote.

VotingAgent (Line 58)

Description: Agent that participates in voting.

Methods:

__init__ (Line 61)
def __init__(self, name, role, expertise, vote_weight, model_name):
    """No method docstring."""

Purpose: No method docstring.

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

cast_vote (Line 69)
def cast_vote(self, proposal_id, options, method):
    """Cast vote on proposal."""

Purpose: Cast vote on proposal.

Parameters: – self: Instance reference – proposal_id: Parameter for proposal_id – options: Parameter for options – method: Parameter for method

VotingPattern (Line 112)

Description: Voting Pattern – Democratic decision-making through voting.

Agents cast votes on proposals using various voting methods. Supports weighted votes, ranked choice, and different consensus rules.

Methods:

__init__ (Line 120)
def __init__(self, voting_method):
    """No method docstring."""

Purpose: No method docstring.

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

register_agent (Line 125)
def register_agent(self, agent):
    """Register agent as voter."""

Purpose: Register agent as voter.

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

conduct_vote (Line 129)
def conduct_vote(self, proposal_id, options):
    """Conduct vote among registered agents."""

Purpose: Conduct vote among registered agents.

Parameters: – self: Instance reference – proposal_id: Parameter for proposal_id – options: Parameter for options

run (Line 196)
def run(self, scenario):
    """Execute voting pattern."""

Purpose: Execute voting pattern.

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


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