diff --git a/graph_workflow_basic.py b/examples/multi_agent/graphworkflow_examples/graph_workflow_basic.py
similarity index 100%
rename from graph_workflow_basic.py
rename to examples/multi_agent/graphworkflow_examples/graph_workflow_basic.py
diff --git a/examples/simulations/euroswarm_parliament/__init__.py b/examples/simulations/euroswarm_parliament/__init__.py
index 381233f0..863b6d26 100644
--- a/examples/simulations/euroswarm_parliament/__init__.py
+++ b/examples/simulations/euroswarm_parliament/__init__.py
@@ -30,6 +30,7 @@ try:
WikipediaPersonalityScraper,
MEPPersonalityProfile,
)
+
WIKIPEDIA_PERSONALITY_AVAILABLE = True
except ImportError:
WIKIPEDIA_PERSONALITY_AVAILABLE = False
@@ -52,4 +53,4 @@ __all__ = [
"WikipediaPersonalityScraper",
"MEPPersonalityProfile",
"WIKIPEDIA_PERSONALITY_AVAILABLE",
-]
\ No newline at end of file
+]
diff --git a/examples/simulations/euroswarm_parliament/euroswarm_parliament.py b/examples/simulations/euroswarm_parliament/euroswarm_parliament.py
index 1694277b..0707ec28 100644
--- a/examples/simulations/euroswarm_parliament/euroswarm_parliament.py
+++ b/examples/simulations/euroswarm_parliament/euroswarm_parliament.py
@@ -15,15 +15,11 @@ ENHANCED WITH COST OPTIMIZATION:
import os
import random
import xml.etree.ElementTree as ET
-import time
import hashlib
-import requests
-import re
-from typing import Dict, List, Optional, Union, Any, Set
+from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
from enum import Enum
-from datetime import datetime, timedelta
-from functools import lru_cache
+from datetime import datetime
from swarms import Agent
from swarms.structs.multi_agent_exec import run_agents_concurrently
@@ -31,10 +27,6 @@ from swarms.structs.board_of_directors_swarm import (
BoardOfDirectorsSwarm,
BoardMember,
BoardMemberRole,
- BoardDecisionType,
- BoardSpec,
- BoardOrder,
- BoardDecision,
enable_board_feature,
)
from swarms.utils.loguru_logger import initialize_logger
@@ -47,38 +39,46 @@ enable_board_feature()
# Import Wikipedia personality system
try:
- from wikipedia_personality_scraper import WikipediaPersonalityScraper, MEPPersonalityProfile
+ from wikipedia_personality_scraper import (
+ WikipediaPersonalityScraper,
+ MEPPersonalityProfile,
+ )
+
WIKIPEDIA_PERSONALITY_AVAILABLE = True
except ImportError:
WIKIPEDIA_PERSONALITY_AVAILABLE = False
- logger.warning("Wikipedia personality system not available. Using basic personality generation.")
+ logger.warning(
+ "Wikipedia personality system not available. Using basic personality generation."
+ )
@dataclass
class CostTracker:
"""Track costs and usage for budget management in parliamentary operations."""
-
+
total_tokens_used: int = 0
total_cost_estimate: float = 0.0
budget_limit: float = 200.0 # Default $200 budget for parliament
token_cost_per_1m: float = 0.15 # GPT-4o-mini cost
requests_made: int = 0
cache_hits: int = 0
-
+
def add_tokens(self, tokens: int):
"""Add tokens used and calculate cost."""
self.total_tokens_used += tokens
- self.total_cost_estimate = (self.total_tokens_used / 1_000_000) * self.token_cost_per_1m
+ self.total_cost_estimate = (
+ self.total_tokens_used / 1_000_000
+ ) * self.token_cost_per_1m
self.requests_made += 1
-
+
def add_cache_hit(self):
"""Record a cache hit."""
self.cache_hits += 1
-
+
def check_budget(self) -> bool:
"""Check if within budget."""
return self.total_cost_estimate <= self.budget_limit
-
+
def get_stats(self) -> Dict[str, Any]:
"""Get cost statistics."""
return {
@@ -86,14 +86,17 @@ class CostTracker:
"total_cost": self.total_cost_estimate,
"requests_made": self.requests_made,
"cache_hits": self.cache_hits,
- "cache_hit_rate": self.cache_hits / max(1, self.requests_made + self.cache_hits),
- "budget_remaining": max(0, self.budget_limit - self.total_cost_estimate)
+ "cache_hit_rate": self.cache_hits
+ / max(1, self.requests_made + self.cache_hits),
+ "budget_remaining": max(
+ 0, self.budget_limit - self.total_cost_estimate
+ ),
}
class ParliamentaryRole(str, Enum):
"""Enumeration of parliamentary roles and positions."""
-
+
PRESIDENT = "president"
VICE_PRESIDENT = "vice_president"
QUAESTOR = "quaestor"
@@ -105,7 +108,7 @@ class ParliamentaryRole(str, Enum):
class VoteType(str, Enum):
"""Enumeration of voting types in the European Parliament."""
-
+
ORDINARY_LEGISLATIVE_PROCEDURE = "ordinary_legislative_procedure"
CONSENT_PROCEDURE = "consent_procedure"
CONSULTATION_PROCEDURE = "consultation_procedure"
@@ -116,7 +119,7 @@ class VoteType(str, Enum):
class VoteResult(str, Enum):
"""Enumeration of possible vote results."""
-
+
PASSED = "passed"
FAILED = "failed"
TIED = "tied"
@@ -127,7 +130,7 @@ class VoteResult(str, Enum):
class ParliamentaryMember:
"""
Represents a Member of the European Parliament (MEP).
-
+
Attributes:
full_name: Full name of the MEP
country: Country the MEP represents
@@ -142,7 +145,7 @@ class ParliamentaryMember:
is_loaded: Whether the agent has been instantiated
wikipedia_info: Wikipedia-scraped personality information (optional)
"""
-
+
full_name: str
country: str
political_group: str
@@ -154,14 +157,16 @@ class ParliamentaryMember:
voting_weight: float = 1.0
agent: Optional[Agent] = None
is_loaded: bool = False
- wikipedia_info: Optional[Any] = None # Wikipedia personality information
+ wikipedia_info: Optional[Any] = (
+ None # Wikipedia personality information
+ )
@dataclass
class ParliamentaryBill:
"""
Represents a bill or legislative proposal in the European Parliament.
-
+
Attributes:
title: Title of the bill
description: Detailed description of the bill
@@ -173,7 +178,7 @@ class ParliamentaryBill:
status: Current status of the bill
amendments: List of proposed amendments
"""
-
+
title: str
description: str
bill_type: VoteType
@@ -189,7 +194,7 @@ class ParliamentaryBill:
class ParliamentaryVote:
"""
Represents a parliamentary vote on a bill or resolution.
-
+
Attributes:
bill: The bill being voted on
vote_type: Type of vote being conducted
@@ -202,7 +207,7 @@ class ParliamentaryVote:
individual_votes: Dictionary of individual MEP votes
reasoning: Dictionary of MEP reasoning for votes
"""
-
+
bill: ParliamentaryBill
vote_type: VoteType
date: datetime = field(default_factory=datetime.now)
@@ -219,7 +224,7 @@ class ParliamentaryVote:
class ParliamentaryCommittee:
"""
Represents a parliamentary committee.
-
+
Attributes:
name: Name of the committee
chair: Committee chairperson
@@ -228,20 +233,22 @@ class ParliamentaryCommittee:
responsibilities: Committee responsibilities
current_bills: Bills currently under consideration
"""
-
+
name: str
chair: str
vice_chair: str
members: List[str] = field(default_factory=list)
responsibilities: List[str] = field(default_factory=list)
- current_bills: List[ParliamentaryBill] = field(default_factory=list)
+ current_bills: List[ParliamentaryBill] = field(
+ default_factory=list
+ )
@dataclass
class PoliticalGroupBoard:
"""
Represents a political group as a Board of Directors with specialized expertise.
-
+
Attributes:
group_name: Name of the political group
members: List of MEPs in this group
@@ -252,7 +259,7 @@ class PoliticalGroupBoard:
total_meps: Total number of MEPs in this group
board_member_percentages: Dictionary mapping board members to their internal percentages
"""
-
+
group_name: str
members: List[str] = field(default_factory=list)
board_members: List[BoardMember] = field(default_factory=list)
@@ -260,14 +267,19 @@ class PoliticalGroupBoard:
voting_weight: float = 0.0
group_speaker: Optional[str] = None
total_meps: int = 0
- board_swarm: Optional[Any] = None # BoardOfDirectorsSwarm instance
- board_member_percentages: Dict[str, float] = field(default_factory=dict) # Internal percentages within group
+ board_swarm: Optional[Any] = (
+ None # BoardOfDirectorsSwarm instance
+ )
+ board_member_percentages: Dict[str, float] = field(
+ default_factory=dict
+ ) # Internal percentages within group
+
@dataclass
class ParliamentSpeaker:
"""
Represents the Parliament Speaker who aggregates decisions from all political groups.
-
+
Attributes:
name: Name of the speaker
agent: AI agent representing the speaker
@@ -275,10 +287,12 @@ class ParliamentSpeaker:
total_meps: Total number of MEPs in parliament
majority_threshold: Number of votes needed for majority
"""
-
+
name: str
agent: Optional[Agent] = None
- political_groups: Dict[str, PoliticalGroupBoard] = field(default_factory=dict)
+ political_groups: Dict[str, PoliticalGroupBoard] = field(
+ default_factory=dict
+ )
total_meps: int = 0
majority_threshold: int = 0
@@ -286,7 +300,7 @@ class ParliamentSpeaker:
class EuroSwarmParliament:
"""
A comprehensive simulation of the European Parliament with 700 MEPs.
-
+
This simulation provides democratic functionality including:
- Bill introduction and analysis
- Committee work and hearings
@@ -295,7 +309,7 @@ class EuroSwarmParliament:
- Political group coordination
- Amendment processes
"""
-
+
def __init__(
self,
eu_data_file: str = "EU.xml",
@@ -311,7 +325,7 @@ class EuroSwarmParliament:
):
"""
Initialize the EuroSwarm Parliament with cost optimization.
-
+
Args:
eu_data_file: Path to EU.xml file containing MEP data
parliament_size: Target size of the parliament (default: None = use all MEPs from EU.xml)
@@ -325,18 +339,22 @@ class EuroSwarmParliament:
verbose: Enable verbose logging
"""
self.eu_data_file = eu_data_file
- self.parliament_size = parliament_size # Will be set to actual MEP count if None
- self.enable_democratic_discussion = enable_democratic_discussion
+ self.parliament_size = (
+ parliament_size # Will be set to actual MEP count if None
+ )
+ self.enable_democratic_discussion = (
+ enable_democratic_discussion
+ )
self.enable_committee_work = enable_committee_work
self.enable_amendment_process = enable_amendment_process
self.enable_lazy_loading = enable_lazy_loading
self.enable_caching = enable_caching
self.batch_size = batch_size
self.verbose = verbose
-
+
# Initialize cost tracking
self.cost_tracker = CostTracker(budget_limit=budget_limit)
-
+
# Initialize parliamentary structures
self.meps: Dict[str, ParliamentaryMember] = {}
self.committees: Dict[str, ParliamentaryCommittee] = {}
@@ -344,155 +362,209 @@ class EuroSwarmParliament:
self.bills: List[ParliamentaryBill] = []
self.votes: List[ParliamentaryVote] = []
self.debates: List[Dict[str, Any]] = []
-
+
# Enhanced democratic structures
- self.political_group_boards: Dict[str, PoliticalGroupBoard] = {}
+ self.political_group_boards: Dict[
+ str, PoliticalGroupBoard
+ ] = {}
self.parliament_speaker: Optional[ParliamentSpeaker] = None
self.enable_hierarchical_democracy: bool = True
-
+
# Wikipedia personality system
- self.enable_wikipedia_personalities: bool = WIKIPEDIA_PERSONALITY_AVAILABLE
- self.personality_profiles: Dict[str, MEPPersonalityProfile] = {}
- self.personality_scraper: Optional[WikipediaPersonalityScraper] = None
-
+ self.enable_wikipedia_personalities: bool = (
+ WIKIPEDIA_PERSONALITY_AVAILABLE
+ )
+ self.personality_profiles: Dict[
+ str, MEPPersonalityProfile
+ ] = {}
+ self.personality_scraper: Optional[
+ WikipediaPersonalityScraper
+ ] = None
+
# Initialize caching
self.response_cache: Dict[str, str] = {}
-
+
# Load MEP data and initialize structures
self.meps = self._load_mep_data()
self.parliament_size = len(self.meps)
-
+
if self.verbose:
- logger.info(f"EuroSwarm Parliament initialized with {self.parliament_size} MEPs")
- logger.info(f"Lazy loading: {self.enable_lazy_loading}, Caching: {self.enable_caching}")
- logger.info(f"Budget limit: ${budget_limit}, Batch size: {batch_size}")
-
+ logger.info(
+ f"EuroSwarm Parliament initialized with {self.parliament_size} MEPs"
+ )
+ logger.info(
+ f"Lazy loading: {self.enable_lazy_loading}, Caching: {self.enable_caching}"
+ )
+ logger.info(
+ f"Budget limit: ${budget_limit}, Batch size: {batch_size}"
+ )
+
# Load Wikipedia personalities if enabled
if self.enable_wikipedia_personalities:
self._load_wikipedia_personalities()
-
+
# Initialize parliamentary structures
self.committees = self._create_committees()
self.political_groups = self._organize_political_groups()
-
+
# Initialize enhanced democratic structures
if self.enable_hierarchical_democracy:
self._create_political_group_boards()
self._create_parliament_speaker()
-
+
# Initialize leadership and democratic decision-making
self._create_parliamentary_leadership()
self._assign_committee_leadership()
-
+
if self.enable_democratic_discussion:
self._init_democratic_decision_making()
-
+
def _load_mep_data(self) -> Dict[str, ParliamentaryMember]:
"""
Load MEP data from official EU Parliament website and create parliamentary members with lazy loading.
Fetches real-time data from https://www.europarl.europa.eu/meps/en/full-list/xml
and scrapes Wikipedia information for each MEP.
-
+
Returns:
Dict[str, ParliamentaryMember]: Dictionary of MEPs
"""
meps = {}
-
+
try:
# Fetch XML data from official EU Parliament website
import requests
import re
-
- eu_xml_url = "https://www.europarl.europa.eu/meps/en/full-list/xml"
-
+
+ eu_xml_url = (
+ "https://www.europarl.europa.eu/meps/en/full-list/xml"
+ )
+
logger.info(f"Fetching MEP data from: {eu_xml_url}")
-
+
# Fetch the XML content
response = requests.get(eu_xml_url, timeout=30)
response.raise_for_status()
content = response.text
-
- logger.info(f"Successfully fetched {len(content)} characters of MEP data")
-
+
+ logger.info(
+ f"Successfully fetched {len(content)} characters of MEP data"
+ )
+
# Parse the XML content to extract MEP information
# The XML is properly formatted, so we can use ElementTree
try:
root = ET.fromstring(content)
mep_matches = []
-
- for mep_element in root.findall('mep'):
- full_name = mep_element.find('fullName').text.strip()
- country = mep_element.find('country').text.strip()
- political_group = mep_element.find('politicalGroup').text.strip()
- mep_id = mep_element.find('id').text.strip()
- national_party = mep_element.find('nationalPoliticalGroup').text.strip()
-
- mep_matches.append((full_name, country, political_group, mep_id, national_party))
-
- logger.info(f"Successfully parsed {len(mep_matches)} MEP entries from XML")
-
+
+ for mep_element in root.findall("mep"):
+ full_name = mep_element.find(
+ "fullName"
+ ).text.strip()
+ country = mep_element.find("country").text.strip()
+ political_group = mep_element.find(
+ "politicalGroup"
+ ).text.strip()
+ mep_id = mep_element.find("id").text.strip()
+ national_party = mep_element.find(
+ "nationalPoliticalGroup"
+ ).text.strip()
+
+ mep_matches.append(
+ (
+ full_name,
+ country,
+ political_group,
+ mep_id,
+ national_party,
+ )
+ )
+
+ logger.info(
+ f"Successfully parsed {len(mep_matches)} MEP entries from XML"
+ )
+
except ET.ParseError as xml_error:
logger.warning(f"XML parsing failed: {xml_error}")
# Fallback to regex parsing for malformed XML
- mep_pattern = r'(.*?)\s*(.*?)\s*(.*?)\s*(.*?)\s*(.*?)'
- mep_matches = re.findall(mep_pattern, content, re.DOTALL)
- logger.info(f"Fallback regex parsing found {len(mep_matches)} MEP entries")
-
+ mep_pattern = r"(.*?)\s*(.*?)\s*(.*?)\s*(.*?)\s*(.*?)"
+ mep_matches = re.findall(
+ mep_pattern, content, re.DOTALL
+ )
+ logger.info(
+ f"Fallback regex parsing found {len(mep_matches)} MEP entries"
+ )
+
# Initialize Wikipedia scraper if available
wikipedia_scraper = None
if WIKIPEDIA_PERSONALITY_AVAILABLE:
try:
wikipedia_scraper = WikipediaPersonalityScraper()
- logger.info("Wikipedia personality scraper initialized")
+ logger.info(
+ "Wikipedia personality scraper initialized"
+ )
except Exception as e:
- logger.warning(f"Failed to initialize Wikipedia scraper: {e}")
-
+ logger.warning(
+ f"Failed to initialize Wikipedia scraper: {e}"
+ )
+
# Process each MEP
for i, mep_data in enumerate(mep_matches):
- if len(mep_data) >= 5: # full_name, country, political_group, mep_id, national_party
+ if (
+ len(mep_data) >= 5
+ ): # full_name, country, political_group, mep_id, national_party
full_name = mep_data[0].strip()
country = mep_data[1].strip()
political_group = mep_data[2].strip()
mep_id = mep_data[3].strip()
national_party = mep_data[4].strip()
-
+
# Clean up political group name
- political_group = self._clean_political_group_name(political_group)
-
+ political_group = (
+ self._clean_political_group_name(
+ political_group
+ )
+ )
+
# Scrape Wikipedia information if scraper is available
wikipedia_info = None
if wikipedia_scraper:
try:
# Create MEP data dictionary for the scraper
mep_data = {
- 'full_name': full_name,
- 'country': country,
- 'political_group': political_group,
- 'national_party': national_party,
- 'mep_id': mep_id
+ "full_name": full_name,
+ "country": country,
+ "political_group": political_group,
+ "national_party": national_party,
+ "mep_id": mep_id,
}
-
+
# Create personality profile
- personality_profile = wikipedia_scraper.create_personality_profile(mep_data)
-
+ personality_profile = wikipedia_scraper.create_personality_profile(
+ mep_data
+ )
+
# Convert to dictionary format for storage
wikipedia_info = {
- 'personality_summary': personality_profile.summary,
- 'political_views': personality_profile.political_views,
- 'policy_focus': personality_profile.policy_focus,
- 'achievements': personality_profile.achievements,
- 'professional_background': personality_profile.professional_background,
- 'political_career': personality_profile.political_career,
- 'education': personality_profile.education,
- 'wikipedia_url': personality_profile.wikipedia_url
+ "personality_summary": personality_profile.summary,
+ "political_views": personality_profile.political_views,
+ "policy_focus": personality_profile.policy_focus,
+ "achievements": personality_profile.achievements,
+ "professional_background": personality_profile.professional_background,
+ "political_career": personality_profile.political_career,
+ "education": personality_profile.education,
+ "wikipedia_url": personality_profile.wikipedia_url,
}
-
+
if self.verbose:
- logger.info(f"Scraped Wikipedia info for {full_name}")
+ logger.info(
+ f"Scraped Wikipedia info for {full_name}"
+ )
except Exception as e:
if self.verbose:
- logger.debug(f"Failed to scrape Wikipedia for {full_name}: {e}")
-
+ logger.debug(
+ f"Failed to scrape Wikipedia for {full_name}: {e}"
+ )
+
# Create parliamentary member (without agent for lazy loading)
mep = ParliamentaryMember(
full_name=full_name,
@@ -500,78 +572,107 @@ class EuroSwarmParliament:
political_group=political_group,
national_party=national_party,
mep_id=mep_id,
- expertise_areas=self._generate_expertise_areas(political_group, country),
- committees=self._assign_committees(political_group),
+ expertise_areas=self._generate_expertise_areas(
+ political_group, country
+ ),
+ committees=self._assign_committees(
+ political_group
+ ),
agent=None, # Will be created on demand
is_loaded=False,
- wikipedia_info=wikipedia_info # Add Wikipedia information
+ wikipedia_info=wikipedia_info, # Add Wikipedia information
)
-
+
meps[full_name] = mep
-
+
# Limit processing for performance (can be adjusted)
- if len(meps) >= 705: # Standard EU Parliament size
+ if (
+ len(meps) >= 705
+ ): # Standard EU Parliament size
break
-
+
# Set parliament size to actual number of MEPs loaded
if self.parliament_size is None:
self.parliament_size = len(meps)
-
- logger.info(f"Successfully loaded {len(meps)} MEP profiles from official EU data (lazy loading enabled)")
+
+ logger.info(
+ f"Successfully loaded {len(meps)} MEP profiles from official EU data (lazy loading enabled)"
+ )
if wikipedia_scraper:
- logger.info(f"Wikipedia scraping completed for {len([m for m in meps.values() if m.wikipedia_info])} MEPs")
-
+ logger.info(
+ f"Wikipedia scraping completed for {len([m for m in meps.values() if m.wikipedia_info])} MEPs"
+ )
+
except Exception as e:
- logger.error(f"Error loading MEP data from official website: {e}")
+ logger.error(
+ f"Error loading MEP data from official website: {e}"
+ )
logger.info("Falling back to local EU.xml file...")
-
+
# Fallback to local file
try:
meps = self._load_mep_data_from_local_file()
except Exception as local_error:
- logger.error(f"Error loading local MEP data: {local_error}")
+ logger.error(
+ f"Error loading local MEP data: {local_error}"
+ )
# Create fallback MEPs if both methods fail
meps = self._create_fallback_meps()
-
+
if self.parliament_size is None:
self.parliament_size = len(meps)
-
+
return meps
-
- def _load_mep_data_from_local_file(self) -> Dict[str, ParliamentaryMember]:
+
+ def _load_mep_data_from_local_file(
+ self,
+ ) -> Dict[str, ParliamentaryMember]:
"""
Fallback method to load MEP data from local EU.xml file.
-
+
Returns:
Dict[str, ParliamentaryMember]: Dictionary of MEPs
"""
meps = {}
-
+
try:
# Construct the full path to EU.xml relative to project root
import os
- project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
- eu_data_path = os.path.join(project_root, self.eu_data_file)
-
+
+ project_root = os.path.dirname(
+ os.path.dirname(
+ os.path.dirname(os.path.abspath(__file__))
+ )
+ )
+ eu_data_path = os.path.join(
+ project_root, self.eu_data_file
+ )
+
# Read the XML file content
- with open(eu_data_path, 'r', encoding='utf-8') as f:
+ with open(eu_data_path, "r", encoding="utf-8") as f:
content = f.read()
-
+
# Use regex to extract MEP data since the XML is malformed
import re
-
+
# Find all MEP blocks
- mep_pattern = r'\s*(.*?)\s*(.*?)\s*(.*?)\s*(.*?)\s*(.*?)\s*'
+ mep_pattern = r"\s*(.*?)\s*(.*?)\s*(.*?)\s*(.*?)\s*(.*?)\s*"
mep_matches = re.findall(mep_pattern, content, re.DOTALL)
-
- for full_name, country, political_group, mep_id, national_party in mep_matches:
+
+ for (
+ full_name,
+ country,
+ political_group,
+ mep_id,
+ national_party,
+ ) in mep_matches:
# Clean up the data
full_name = full_name.strip()
country = country.strip()
political_group = political_group.strip()
mep_id = mep_id.strip()
national_party = national_party.strip()
-
+
# Create parliamentary member (without agent for lazy loading)
mep = ParliamentaryMember(
full_name=full_name,
@@ -579,166 +680,183 @@ class EuroSwarmParliament:
political_group=political_group,
national_party=national_party,
mep_id=mep_id,
- expertise_areas=self._generate_expertise_areas(political_group, country),
- committees=self._assign_committees(political_group),
+ expertise_areas=self._generate_expertise_areas(
+ political_group, country
+ ),
+ committees=self._assign_committees(
+ political_group
+ ),
agent=None, # Will be created on demand
- is_loaded=False
+ is_loaded=False,
)
-
+
meps[full_name] = mep
-
- logger.info(f"Loaded {len(meps)} MEP profiles from local EU.xml file (lazy loading enabled)")
-
+
+ logger.info(
+ f"Loaded {len(meps)} MEP profiles from local EU.xml file (lazy loading enabled)"
+ )
+
except Exception as e:
logger.error(f"Error loading local MEP data: {e}")
raise
-
+
return meps
-
- def _clean_political_group_name(self, political_group: str) -> str:
+
+ def _clean_political_group_name(
+ self, political_group: str
+ ) -> str:
"""
Clean and standardize political group names.
-
+
Args:
political_group: Raw political group name
-
+
Returns:
str: Cleaned political group name
"""
# Map common variations to standard names
group_mapping = {
- 'EPP': 'Group of the European People\'s Party (Christian Democrats)',
- 'S&D': 'Group of the Progressive Alliance of Socialists and Democrats in the European Parliament',
- 'Renew': 'Renew Europe Group',
- 'Greens/EFA': 'Group of the Greens/European Free Alliance',
- 'ECR': 'European Conservatives and Reformists Group',
- 'ID': 'Identity and Democracy Group',
- 'GUE/NGL': 'The Left group in the European Parliament - GUE/NGL',
- 'Non-attached': 'Non-attached Members'
+ "EPP": "Group of the European People's Party (Christian Democrats)",
+ "S&D": "Group of the Progressive Alliance of Socialists and Democrats in the European Parliament",
+ "Renew": "Renew Europe Group",
+ "Greens/EFA": "Group of the Greens/European Free Alliance",
+ "ECR": "European Conservatives and Reformists Group",
+ "ID": "Identity and Democracy Group",
+ "GUE/NGL": "The Left group in the European Parliament - GUE/NGL",
+ "Non-attached": "Non-attached Members",
}
-
+
# Check for exact matches first
for key, value in group_mapping.items():
if political_group.strip() == key:
return value
-
+
# Check for partial matches
political_group_lower = political_group.lower()
for key, value in group_mapping.items():
if key.lower() in political_group_lower:
return value
-
+
# Return original if no match found
return political_group.strip()
-
- def _generate_national_party(self, country: str, political_group: str) -> str:
+
+ def _generate_national_party(
+ self, country: str, political_group: str
+ ) -> str:
"""
Generate a realistic national party name based on country and political group.
-
+
Args:
country: Country of the MEP
political_group: Political group affiliation
-
+
Returns:
str: Generated national party name
"""
# Map of countries to common parties for each political group
party_mapping = {
- 'Germany': {
- 'Group of the European People\'s Party (Christian Democrats)': 'Christlich Demokratische Union Deutschlands',
- 'Group of the Progressive Alliance of Socialists and Democrats in the European Parliament': 'Sozialdemokratische Partei Deutschlands',
- 'Renew Europe Group': 'Freie Demokratische Partei',
- 'Group of the Greens/European Free Alliance': 'Bündnis 90/Die Grünen',
- 'European Conservatives and Reformists Group': 'Alternative für Deutschland',
- 'Identity and Democracy Group': 'Alternative für Deutschland',
- 'The Left group in the European Parliament - GUE/NGL': 'Die Linke'
+ "Germany": {
+ "Group of the European People's Party (Christian Democrats)": "Christlich Demokratische Union Deutschlands",
+ "Group of the Progressive Alliance of Socialists and Democrats in the European Parliament": "Sozialdemokratische Partei Deutschlands",
+ "Renew Europe Group": "Freie Demokratische Partei",
+ "Group of the Greens/European Free Alliance": "Bündnis 90/Die Grünen",
+ "European Conservatives and Reformists Group": "Alternative für Deutschland",
+ "Identity and Democracy Group": "Alternative für Deutschland",
+ "The Left group in the European Parliament - GUE/NGL": "Die Linke",
},
- 'France': {
- 'Group of the European People\'s Party (Christian Democrats)': 'Les Républicains',
- 'Group of the Progressive Alliance of Socialists and Democrats in the European Parliament': 'Parti Socialiste',
- 'Renew Europe Group': 'Renaissance',
- 'Group of the Greens/European Free Alliance': 'Europe Écologie Les Verts',
- 'European Conservatives and Reformists Group': 'Rassemblement National',
- 'Identity and Democracy Group': 'Rassemblement National',
- 'The Left group in the European Parliament - GUE/NGL': 'La France Insoumise'
+ "France": {
+ "Group of the European People's Party (Christian Democrats)": "Les Républicains",
+ "Group of the Progressive Alliance of Socialists and Democrats in the European Parliament": "Parti Socialiste",
+ "Renew Europe Group": "Renaissance",
+ "Group of the Greens/European Free Alliance": "Europe Écologie Les Verts",
+ "European Conservatives and Reformists Group": "Rassemblement National",
+ "Identity and Democracy Group": "Rassemblement National",
+ "The Left group in the European Parliament - GUE/NGL": "La France Insoumise",
+ },
+ "Italy": {
+ "Group of the European People's Party (Christian Democrats)": "Forza Italia",
+ "Group of the Progressive Alliance of Socialists and Democrats in the European Parliament": "Partito Democratico",
+ "Renew Europe Group": "Italia Viva",
+ "Group of the Greens/European Free Alliance": "Federazione dei Verdi",
+ "European Conservatives and Reformists Group": "Fratelli d'Italia",
+ "Identity and Democracy Group": "Lega",
+ "The Left group in the European Parliament - GUE/NGL": "Movimento 5 Stelle",
},
- 'Italy': {
- 'Group of the European People\'s Party (Christian Democrats)': 'Forza Italia',
- 'Group of the Progressive Alliance of Socialists and Democrats in the European Parliament': 'Partito Democratico',
- 'Renew Europe Group': 'Italia Viva',
- 'Group of the Greens/European Free Alliance': 'Federazione dei Verdi',
- 'European Conservatives and Reformists Group': 'Fratelli d\'Italia',
- 'Identity and Democracy Group': 'Lega',
- 'The Left group in the European Parliament - GUE/NGL': 'Movimento 5 Stelle'
- }
}
-
+
# Return mapped party or generate a generic one
- if country in party_mapping and political_group in party_mapping[country]:
+ if (
+ country in party_mapping
+ and political_group in party_mapping[country]
+ ):
return party_mapping[country][political_group]
else:
return f"{country} National Party"
-
+
def _load_mep_agent(self, mep_name: str) -> Optional[Agent]:
"""
Lazy load a single MEP agent on demand.
-
+
Args:
mep_name: Name of the MEP to load
-
+
Returns:
Optional[Agent]: Loaded agent or None if not found
"""
if mep_name not in self.meps:
return None
-
+
mep = self.meps[mep_name]
-
+
# Check if already loaded
if mep.is_loaded and mep.agent:
return mep.agent
-
+
# Check budget before creating agent
if not self.cost_tracker.check_budget():
- logger.warning(f"Budget exceeded. Cannot load MEP agent {mep_name}")
+ logger.warning(
+ f"Budget exceeded. Cannot load MEP agent {mep_name}"
+ )
return None
-
+
# Create agent
mep.agent = self._create_mep_agent(mep)
mep.is_loaded = True
-
+
if self.verbose:
logger.info(f"Loaded MEP agent: {mep_name}")
-
+
return mep.agent
-
- def _load_mep_agents_batch(self, mep_names: List[str]) -> List[Agent]:
+
+ def _load_mep_agents_batch(
+ self, mep_names: List[str]
+ ) -> List[Agent]:
"""
Load multiple MEP agents in a batch.
-
+
Args:
mep_names: List of MEP names to load
-
+
Returns:
List[Agent]: List of loaded agents
"""
loaded_agents = []
-
+
for mep_name in mep_names:
agent = self._load_mep_agent(mep_name)
if agent:
loaded_agents.append(agent)
-
+
return loaded_agents
-
+
def _get_cache_key(self, task: str, mep_names: List[str]) -> str:
"""
Generate a cache key for a task and MEP combination.
-
+
Args:
task: Task to execute
mep_names: List of MEP names
-
+
Returns:
str: Cache key
"""
@@ -746,32 +864,32 @@ class EuroSwarmParliament:
sorted_meps = sorted(mep_names)
content = f"{task}:{':'.join(sorted_meps)}"
return hashlib.md5(content.encode()).hexdigest()
-
+
def _check_cache(self, cache_key: str) -> Optional[str]:
"""
Check if a response is cached.
-
+
Args:
cache_key: Cache key to check
-
+
Returns:
Optional[str]: Cached response or None
"""
if not self.enable_caching:
return None
-
+
cached_response = self.response_cache.get(cache_key)
if cached_response:
self.cost_tracker.add_cache_hit()
if self.verbose:
logger.info(f"Cache hit for key: {cache_key[:20]}...")
-
+
return cached_response
-
+
def _cache_response(self, cache_key: str, response: str):
"""
Cache a response.
-
+
Args:
cache_key: Cache key
response: Response to cache
@@ -779,51 +897,83 @@ class EuroSwarmParliament:
if self.enable_caching:
self.response_cache[cache_key] = response
if self.verbose:
- logger.info(f"Cached response for key: {cache_key[:20]}...")
-
- def _generate_expertise_areas(self, political_group: str, country: str) -> List[str]:
+ logger.info(
+ f"Cached response for key: {cache_key[:20]}..."
+ )
+
+ def _generate_expertise_areas(
+ self, political_group: str, country: str
+ ) -> List[str]:
"""
Generate expertise areas based on political group and country.
-
+
Args:
political_group: MEP's political group
country: MEP's country
-
+
Returns:
List[str]: List of expertise areas
"""
expertise_mapping = {
"Group of the European People's Party (Christian Democrats)": [
- "Economic Policy", "Agriculture", "Regional Development", "Christian Values"
+ "Economic Policy",
+ "Agriculture",
+ "Regional Development",
+ "Christian Values",
],
"Group of the Progressive Alliance of Socialists and Democrats in the European Parliament": [
- "Social Policy", "Labor Rights", "Healthcare", "Education"
+ "Social Policy",
+ "Labor Rights",
+ "Healthcare",
+ "Education",
],
"Renew Europe Group": [
- "Digital Policy", "Innovation", "Trade", "Liberal Values"
+ "Digital Policy",
+ "Innovation",
+ "Trade",
+ "Liberal Values",
],
"Group of the Greens/European Free Alliance": [
- "Environmental Policy", "Climate Change", "Renewable Energy", "Human Rights"
+ "Environmental Policy",
+ "Climate Change",
+ "Renewable Energy",
+ "Human Rights",
],
"European Conservatives and Reformists Group": [
- "Sovereignty", "Defense", "Traditional Values", "Economic Freedom"
+ "Sovereignty",
+ "Defense",
+ "Traditional Values",
+ "Economic Freedom",
],
"The Left group in the European Parliament - GUE/NGL": [
- "Workers' Rights", "Social Justice", "Anti-Austerity", "Public Services"
+ "Workers' Rights",
+ "Social Justice",
+ "Anti-Austerity",
+ "Public Services",
],
"Patriots for Europe Group": [
- "National Sovereignty", "Border Security", "Cultural Identity", "Law and Order"
+ "National Sovereignty",
+ "Border Security",
+ "Cultural Identity",
+ "Law and Order",
],
"Europe of Sovereign Nations Group": [
- "National Independence", "Sovereignty", "Traditional Values", "Security"
+ "National Independence",
+ "Sovereignty",
+ "Traditional Values",
+ "Security",
],
"Non-attached Members": [
- "Independent Policy", "Cross-cutting Issues", "Specialized Topics"
- ]
+ "Independent Policy",
+ "Cross-cutting Issues",
+ "Specialized Topics",
+ ],
}
-
- base_expertise = expertise_mapping.get(political_group, ["General Policy"])
-
+
+ base_expertise = expertise_mapping.get(
+ political_group, ["General Policy"]
+ )
+
# Add country-specific expertise
country_expertise = {
"Germany": ["Industrial Policy", "Manufacturing"],
@@ -837,66 +987,84 @@ class EuroSwarmParliament:
"Sweden": ["Environmental Policy", "Social Welfare"],
"Denmark": ["Green Technology", "Welfare State"],
}
-
+
if country in country_expertise:
base_expertise.extend(country_expertise[country])
-
+
return base_expertise[:5] # Limit to 5 expertise areas
-
+
def _assign_committees(self, political_group: str) -> List[str]:
"""
Assign committees based on political group preferences.
-
+
Args:
political_group: MEP's political group
-
+
Returns:
List[str]: List of committee assignments
"""
committee_mapping = {
"Group of the European People's Party (Christian Democrats)": [
- "Agriculture and Rural Development", "Economic and Monetary Affairs", "Regional Development"
+ "Agriculture and Rural Development",
+ "Economic and Monetary Affairs",
+ "Regional Development",
],
"Group of the Progressive Alliance of Socialists and Democrats in the European Parliament": [
- "Employment and Social Affairs", "Environment, Public Health and Food Safety", "Civil Liberties"
+ "Employment and Social Affairs",
+ "Environment, Public Health and Food Safety",
+ "Civil Liberties",
],
"Renew Europe Group": [
- "Industry, Research and Energy", "Internal Market and Consumer Protection", "Legal Affairs"
+ "Industry, Research and Energy",
+ "Internal Market and Consumer Protection",
+ "Legal Affairs",
],
"Group of the Greens/European Free Alliance": [
- "Environment, Public Health and Food Safety", "Transport and Tourism", "Development"
+ "Environment, Public Health and Food Safety",
+ "Transport and Tourism",
+ "Development",
],
"European Conservatives and Reformists Group": [
- "Foreign Affairs", "Security and Defence", "Budgetary Control"
+ "Foreign Affairs",
+ "Security and Defence",
+ "Budgetary Control",
],
"The Left group in the European Parliament - GUE/NGL": [
- "International Trade", "Development", "Civil Liberties"
+ "International Trade",
+ "Development",
+ "Civil Liberties",
],
"Patriots for Europe Group": [
- "Civil Liberties", "Security and Defence", "Budgetary Control"
+ "Civil Liberties",
+ "Security and Defence",
+ "Budgetary Control",
],
"Europe of Sovereign Nations Group": [
- "Foreign Affairs", "Security and Defence", "Civil Liberties"
+ "Foreign Affairs",
+ "Security and Defence",
+ "Civil Liberties",
],
"Non-attached Members": [
- "Petitions", "Budgetary Control", "Legal Affairs"
- ]
+ "Petitions",
+ "Budgetary Control",
+ "Legal Affairs",
+ ],
}
-
+
return committee_mapping.get(political_group, ["Petitions"])
-
+
def _create_mep_agent(self, mep: ParliamentaryMember) -> Agent:
"""
Create an AI agent representing an MEP.
-
+
Args:
mep: Parliamentary member data
-
+
Returns:
Agent: AI agent representing the MEP
"""
system_prompt = self._generate_mep_system_prompt(mep)
-
+
return Agent(
agent_name=f"MEP_{mep.full_name.replace(' ', '_')}",
system_prompt=system_prompt,
@@ -904,18 +1072,20 @@ class EuroSwarmParliament:
max_loops=3,
verbose=self.verbose,
)
-
- def _generate_mep_system_prompt(self, mep: ParliamentaryMember) -> str:
+
+ def _generate_mep_system_prompt(
+ self, mep: ParliamentaryMember
+ ) -> str:
"""
Generate a comprehensive system prompt for an MEP agent with Wikipedia personality data.
-
+
Args:
mep: Parliamentary member data
-
+
Returns:
str: System prompt for the MEP agent
"""
-
+
# Base prompt structure
prompt = f"""You are {mep.full_name}, a Member of the European Parliament (MEP) representing {mep.country}.
@@ -927,7 +1097,7 @@ POLITICAL BACKGROUND:
- Areas of Expertise: {', '.join(mep.expertise_areas)}
"""
-
+
# Add Wikipedia personality data if available
if mep.wikipedia_info and self.enable_wikipedia_personalities:
prompt += f"""
@@ -948,7 +1118,7 @@ POLITICAL VIEWS AND POSITIONS:
- Policy Focus Areas: {', '.join(mep.expertise_areas)}
- Professional Background: Parliamentary service
"""
-
+
# Add core principles
prompt += f"""
CORE PRINCIPLES:
@@ -988,50 +1158,94 @@ When responding to parliamentary matters, consider:
Remember: You are a real MEP with specific political views, expertise, and responsibilities. Act accordingly in all parliamentary interactions.
"""
-
+
return prompt
-
+
def _create_fallback_meps(self) -> Dict[str, ParliamentaryMember]:
"""
Create fallback MEPs if EU.xml file cannot be loaded.
-
+
Returns:
Dict[str, ParliamentaryMember]: Dictionary of fallback MEPs
"""
fallback_meps = {}
-
+
# Create a representative sample of MEPs
sample_data = [
- ("Jean-Claude Juncker", "Luxembourg", "Group of the European People's Party (Christian Democrats)", "Parti chrétien social luxembourgeois"),
- ("Ursula von der Leyen", "Germany", "Group of the European People's Party (Christian Democrats)", "Christlich Demokratische Union Deutschlands"),
- ("Roberta Metsola", "Malta", "Group of the European People's Party (Christian Democrats)", "Partit Nazzjonalista"),
- ("Iratxe García Pérez", "Spain", "Group of the Progressive Alliance of Socialists and Democrats in the European Parliament", "Partido Socialista Obrero Español"),
- ("Valérie Hayer", "France", "Renew Europe Group", "Renaissance"),
- ("Philippe Lamberts", "Belgium", "Group of the Greens/European Free Alliance", "Ecolo"),
- ("Raffaele Fitto", "Italy", "European Conservatives and Reformists Group", "Fratelli d'Italia"),
- ("Manon Aubry", "France", "The Left group in the European Parliament - GUE/NGL", "La France Insoumise"),
+ (
+ "Jean-Claude Juncker",
+ "Luxembourg",
+ "Group of the European People's Party (Christian Democrats)",
+ "Parti chrétien social luxembourgeois",
+ ),
+ (
+ "Ursula von der Leyen",
+ "Germany",
+ "Group of the European People's Party (Christian Democrats)",
+ "Christlich Demokratische Union Deutschlands",
+ ),
+ (
+ "Roberta Metsola",
+ "Malta",
+ "Group of the European People's Party (Christian Democrats)",
+ "Partit Nazzjonalista",
+ ),
+ (
+ "Iratxe García Pérez",
+ "Spain",
+ "Group of the Progressive Alliance of Socialists and Democrats in the European Parliament",
+ "Partido Socialista Obrero Español",
+ ),
+ (
+ "Valérie Hayer",
+ "France",
+ "Renew Europe Group",
+ "Renaissance",
+ ),
+ (
+ "Philippe Lamberts",
+ "Belgium",
+ "Group of the Greens/European Free Alliance",
+ "Ecolo",
+ ),
+ (
+ "Raffaele Fitto",
+ "Italy",
+ "European Conservatives and Reformists Group",
+ "Fratelli d'Italia",
+ ),
+ (
+ "Manon Aubry",
+ "France",
+ "The Left group in the European Parliament - GUE/NGL",
+ "La France Insoumise",
+ ),
]
-
- for i, (name, country, group, party) in enumerate(sample_data):
+
+ for i, (name, country, group, party) in enumerate(
+ sample_data
+ ):
mep = ParliamentaryMember(
full_name=name,
country=country,
political_group=group,
national_party=party,
mep_id=f"fallback_{i}",
- expertise_areas=self._generate_expertise_areas(group, country),
+ expertise_areas=self._generate_expertise_areas(
+ group, country
+ ),
committees=self._assign_committees(group),
agent=None, # Will be created on demand
- is_loaded=False
+ is_loaded=False,
)
fallback_meps[name] = mep
-
+
return fallback_meps
-
+
def _create_committees(self) -> Dict[str, ParliamentaryCommittee]:
"""
Create parliamentary committees.
-
+
Returns:
Dict[str, ParliamentaryCommittee]: Dictionary of committees
"""
@@ -1040,106 +1254,171 @@ Remember: You are a real MEP with specific political views, expertise, and respo
name="Agriculture and Rural Development",
chair="",
vice_chair="",
- responsibilities=["Agricultural policy", "Rural development", "Food safety"]
+ responsibilities=[
+ "Agricultural policy",
+ "Rural development",
+ "Food safety",
+ ],
),
"Budgetary Control": ParliamentaryCommittee(
name="Budgetary Control",
chair="",
vice_chair="",
- responsibilities=["Budget oversight", "Financial control", "Audit reports"]
+ responsibilities=[
+ "Budget oversight",
+ "Financial control",
+ "Audit reports",
+ ],
),
"Civil Liberties, Justice and Home Affairs": ParliamentaryCommittee(
name="Civil Liberties, Justice and Home Affairs",
chair="",
vice_chair="",
- responsibilities=["Civil rights", "Justice", "Home affairs", "Immigration"]
+ responsibilities=[
+ "Civil rights",
+ "Justice",
+ "Home affairs",
+ "Immigration",
+ ],
),
"Development": ParliamentaryCommittee(
name="Development",
chair="",
vice_chair="",
- responsibilities=["Development cooperation", "Humanitarian aid", "International relations"]
+ responsibilities=[
+ "Development cooperation",
+ "Humanitarian aid",
+ "International relations",
+ ],
),
"Economic and Monetary Affairs": ParliamentaryCommittee(
name="Economic and Monetary Affairs",
chair="",
vice_chair="",
- responsibilities=["Economic policy", "Monetary policy", "Financial services"]
+ responsibilities=[
+ "Economic policy",
+ "Monetary policy",
+ "Financial services",
+ ],
),
"Employment and Social Affairs": ParliamentaryCommittee(
name="Employment and Social Affairs",
chair="",
vice_chair="",
- responsibilities=["Employment policy", "Social policy", "Working conditions"]
+ responsibilities=[
+ "Employment policy",
+ "Social policy",
+ "Working conditions",
+ ],
),
"Environment, Public Health and Food Safety": ParliamentaryCommittee(
name="Environment, Public Health and Food Safety",
chair="",
vice_chair="",
- responsibilities=["Environmental policy", "Public health", "Food safety"]
+ responsibilities=[
+ "Environmental policy",
+ "Public health",
+ "Food safety",
+ ],
),
"Foreign Affairs": ParliamentaryCommittee(
name="Foreign Affairs",
chair="",
vice_chair="",
- responsibilities=["Foreign policy", "International relations", "Security policy"]
+ responsibilities=[
+ "Foreign policy",
+ "International relations",
+ "Security policy",
+ ],
),
"Industry, Research and Energy": ParliamentaryCommittee(
name="Industry, Research and Energy",
chair="",
vice_chair="",
- responsibilities=["Industrial policy", "Research", "Energy policy"]
+ responsibilities=[
+ "Industrial policy",
+ "Research",
+ "Energy policy",
+ ],
),
"Internal Market and Consumer Protection": ParliamentaryCommittee(
name="Internal Market and Consumer Protection",
chair="",
vice_chair="",
- responsibilities=["Internal market", "Consumer protection", "Digital policy"]
+ responsibilities=[
+ "Internal market",
+ "Consumer protection",
+ "Digital policy",
+ ],
),
"International Trade": ParliamentaryCommittee(
name="International Trade",
chair="",
vice_chair="",
- responsibilities=["Trade policy", "International agreements", "Market access"]
+ responsibilities=[
+ "Trade policy",
+ "International agreements",
+ "Market access",
+ ],
),
"Legal Affairs": ParliamentaryCommittee(
name="Legal Affairs",
chair="",
vice_chair="",
- responsibilities=["Legal matters", "Institutional affairs", "Constitutional issues"]
+ responsibilities=[
+ "Legal matters",
+ "Institutional affairs",
+ "Constitutional issues",
+ ],
),
"Petitions": ParliamentaryCommittee(
name="Petitions",
chair="",
vice_chair="",
- responsibilities=["Citizen petitions", "Ombudsman", "Citizen rights"]
+ responsibilities=[
+ "Citizen petitions",
+ "Ombudsman",
+ "Citizen rights",
+ ],
),
"Regional Development": ParliamentaryCommittee(
name="Regional Development",
chair="",
vice_chair="",
- responsibilities=["Regional policy", "Cohesion policy", "Urban development"]
+ responsibilities=[
+ "Regional policy",
+ "Cohesion policy",
+ "Urban development",
+ ],
),
"Security and Defence": ParliamentaryCommittee(
name="Security and Defence",
chair="",
vice_chair="",
- responsibilities=["Security policy", "Defence", "Military cooperation"]
+ responsibilities=[
+ "Security policy",
+ "Defence",
+ "Military cooperation",
+ ],
),
"Transport and Tourism": ParliamentaryCommittee(
name="Transport and Tourism",
chair="",
vice_chair="",
- responsibilities=["Transport policy", "Tourism", "Infrastructure"]
+ responsibilities=[
+ "Transport policy",
+ "Tourism",
+ "Infrastructure",
+ ],
),
}
-
+
return committees
-
+
def _organize_political_groups(self) -> Dict[str, List[str]]:
"""
Organize MEPs by political groups.
-
+
Returns:
Dict[str, List[str]]: Dictionary mapping political groups to MEP names
"""
@@ -1150,75 +1429,91 @@ Remember: You are a real MEP with specific political views, expertise, and respo
groups[group] = []
groups[group].append(mep_name)
return groups
-
+
def _create_parliamentary_leadership(self):
"""Create parliamentary leadership positions."""
# Assign President (from largest political group)
- largest_group = max(self.political_groups.items(), key=lambda x: len(x[1]))
+ largest_group = max(
+ self.political_groups.items(), key=lambda x: len(x[1])
+ )
president_candidate = largest_group[1][0]
- self.meps[president_candidate].role = ParliamentaryRole.PRESIDENT
-
+ self.meps[president_candidate].role = (
+ ParliamentaryRole.PRESIDENT
+ )
+
# Assign Vice Presidents
vice_presidents = []
for group_name, meps in self.political_groups.items():
if group_name != largest_group[0] and len(meps) > 0:
vice_presidents.append(meps[0])
- if len(vice_presidents) >= 14: # EP has 14 Vice Presidents
+ if (
+ len(vice_presidents) >= 14
+ ): # EP has 14 Vice Presidents
break
-
+
for vp in vice_presidents:
self.meps[vp].role = ParliamentaryRole.VICE_PRESIDENT
-
+
# Assign Committee Chairs
self._assign_committee_leadership()
-
+
def _assign_committee_leadership(self):
"""Assign committee chairs and vice-chairs based on political group representation."""
committee_names = list(self.committees.keys())
-
+
# Distribute committee leadership among political groups
group_assignments = {}
for group_name, meps in self.political_groups.items():
if len(meps) > 0:
group_assignments[group_name] = meps
-
+
committee_index = 0
for group_name, meps in group_assignments.items():
if committee_index >= len(committee_names):
break
-
+
committee_name = committee_names[committee_index]
chair = meps[0]
vice_chair = meps[1] if len(meps) > 1 else ""
-
+
self.committees[committee_name].chair = chair
self.committees[committee_name].vice_chair = vice_chair
-
+
# Update MEP roles
self.meps[chair].role = ParliamentaryRole.COMMITTEE_CHAIR
if vice_chair:
- self.meps[vice_chair].role = ParliamentaryRole.COMMITTEE_VICE_CHAIR
-
+ self.meps[vice_chair].role = (
+ ParliamentaryRole.COMMITTEE_VICE_CHAIR
+ )
+
committee_index += 1
-
+
def _init_democratic_decision_making(self):
"""Initialize democratic decision-making using Board of Directors pattern."""
# Create parliamentary board members for democratic decision-making
board_members = []
-
+
# Add political group leaders
for group_name, meps in self.political_groups.items():
if len(meps) > 0:
leader = meps[0]
- if leader in self.meps and self.meps[leader].agent is not None:
+ if (
+ leader in self.meps
+ and self.meps[leader].agent is not None
+ ):
board_member = BoardMember(
agent=self.meps[leader].agent,
role=BoardMemberRole.EXECUTIVE_DIRECTOR,
- voting_weight=len(meps) / len(self.meps), # Weight based on group size
- expertise_areas=self.meps[leader].expertise_areas
+ voting_weight=len(meps)
+ / len(
+ self.meps
+ ), # Weight based on group size
+ expertise_areas=self.meps[
+ leader
+ ].expertise_areas,
)
board_members.append(board_member)
-
+
# Ensure we have at least one board member
if not board_members and len(self.meps) > 0:
# Use the first available MEP as a fallback
@@ -1229,15 +1524,19 @@ Remember: You are a real MEP with specific political views, expertise, and respo
agent=first_mep.agent,
role=BoardMemberRole.EXECUTIVE_DIRECTOR,
voting_weight=1.0,
- expertise_areas=first_mep.expertise_areas
+ expertise_areas=first_mep.expertise_areas,
)
board_members.append(board_member)
-
+
# Create the democratic decision-making swarm
if board_members:
# Extract agents from board members for the parent class
- agents = [member.agent for member in board_members if member.agent is not None]
-
+ agents = [
+ member.agent
+ for member in board_members
+ if member.agent is not None
+ ]
+
self.democratic_swarm = BoardOfDirectorsSwarm(
name="EuroSwarm Parliament Democratic Council",
description="Democratic decision-making body for the European Parliament",
@@ -1250,87 +1549,147 @@ Remember: You are a real MEP with specific political views, expertise, and respo
enable_consensus=True,
)
else:
- logger.warning("No valid board members found for democratic decision-making")
+ logger.warning(
+ "No valid board members found for democratic decision-making"
+ )
self.democratic_swarm = None
-
+
def _create_political_group_boards(self):
"""Create Board of Directors for each political group with specialized expertise and individual percentages."""
-
+
# Define specialized expertise areas for governance
expertise_areas = {
- "economics": ["Economic Policy", "Trade", "Budget", "Taxation", "Financial Services"],
- "law": ["Legal Affairs", "Justice", "Civil Liberties", "Constitutional Affairs"],
- "environment": ["Environment", "Climate Action", "Energy", "Transport"],
- "social": ["Employment", "Social Affairs", "Health", "Education", "Culture"],
- "foreign": ["Foreign Affairs", "Security", "Defense", "International Trade"],
- "agriculture": ["Agriculture", "Rural Development", "Food Safety"],
- "technology": ["Digital Affairs", "Industry", "Research", "Innovation"],
- "regional": ["Regional Development", "Cohesion Policy", "Urban Planning"]
+ "economics": [
+ "Economic Policy",
+ "Trade",
+ "Budget",
+ "Taxation",
+ "Financial Services",
+ ],
+ "law": [
+ "Legal Affairs",
+ "Justice",
+ "Civil Liberties",
+ "Constitutional Affairs",
+ ],
+ "environment": [
+ "Environment",
+ "Climate Action",
+ "Energy",
+ "Transport",
+ ],
+ "social": [
+ "Employment",
+ "Social Affairs",
+ "Health",
+ "Education",
+ "Culture",
+ ],
+ "foreign": [
+ "Foreign Affairs",
+ "Security",
+ "Defense",
+ "International Trade",
+ ],
+ "agriculture": [
+ "Agriculture",
+ "Rural Development",
+ "Food Safety",
+ ],
+ "technology": [
+ "Digital Affairs",
+ "Industry",
+ "Research",
+ "Innovation",
+ ],
+ "regional": [
+ "Regional Development",
+ "Cohesion Policy",
+ "Urban Planning",
+ ],
}
-
+
total_meps = len(self.meps)
-
+
for group_name, mep_list in self.political_groups.items():
if not mep_list:
continue
-
+
# Calculate voting weight (percentage of parliament)
voting_weight = len(mep_list) / total_meps
-
+
# Assign specialized expertise areas based on political group
- group_expertise = self._assign_group_expertise(group_name, expertise_areas)
-
+ group_expertise = self._assign_group_expertise(
+ group_name, expertise_areas
+ )
+
# Create board members with specialized roles and individual percentages
board_members = []
group_speaker = None
board_member_percentages = {}
-
+
# Select group speaker (CEO) - usually the first MEP in the group
if mep_list and mep_list[0] in self.meps:
group_speaker = mep_list[0]
speaker_mep = self.meps[group_speaker]
-
+
# Create group speaker board member with highest percentage
if speaker_mep.agent:
speaker_board_member = BoardMember(
agent=speaker_mep.agent,
role=BoardMemberRole.CHAIRMAN,
voting_weight=1.0,
- expertise_areas=group_expertise
+ expertise_areas=group_expertise,
)
board_members.append(speaker_board_member)
# Group speaker gets 35% of the group's internal voting power
board_member_percentages[group_speaker] = 0.35
-
+
# Create specialized board members for each expertise area with weighted percentages
- expertise_percentages = self._calculate_expertise_percentages(group_name, len(group_expertise))
-
- for i, expertise_area in enumerate(group_expertise[:5]): # Limit to 5 main areas
+ expertise_percentages = (
+ self._calculate_expertise_percentages(
+ group_name, len(group_expertise)
+ )
+ )
+
+ for i, expertise_area in enumerate(
+ group_expertise[:5]
+ ): # Limit to 5 main areas
# Find MEPs with relevant expertise
specialized_meps = [
- mep_name for mep_name in mep_list
- if mep_name in self.meps and
- any(exp.lower() in expertise_area.lower() for exp in self.meps[mep_name].expertise_areas)
+ mep_name
+ for mep_name in mep_list
+ if mep_name in self.meps
+ and any(
+ exp.lower() in expertise_area.lower()
+ for exp in self.meps[mep_name].expertise_areas
+ )
]
-
- if specialized_meps and i < len(expertise_percentages):
+
+ if specialized_meps and i < len(
+ expertise_percentages
+ ):
# Select the first specialized MEP
specialized_mep_name = specialized_meps[0]
specialized_mep = self.meps[specialized_mep_name]
-
+
if specialized_mep.agent:
# Assign percentage based on expertise importance
- expertise_percentage = expertise_percentages[i]
-
+ expertise_percentage = expertise_percentages[
+ i
+ ]
+
board_member = BoardMember(
agent=specialized_mep.agent,
role=BoardMemberRole.EXECUTIVE_DIRECTOR,
voting_weight=expertise_percentage,
- expertise_areas=[expertise_area]
+ expertise_areas=[expertise_area],
)
board_members.append(board_member)
- board_member_percentages[specialized_mep_name] = expertise_percentage
-
+ board_member_percentages[
+ specialized_mep_name
+ ] = expertise_percentage
+
# Create the political group board with individual percentages
political_group_board = PoliticalGroupBoard(
group_name=group_name,
@@ -1340,13 +1699,17 @@ Remember: You are a real MEP with specific political views, expertise, and respo
voting_weight=voting_weight,
group_speaker=group_speaker,
total_meps=len(mep_list),
- board_member_percentages=board_member_percentages
+ board_member_percentages=board_member_percentages,
)
-
+
# Create BoardOfDirectorsSwarm for this political group
if board_members:
- agents = [member.agent for member in board_members if member.agent is not None]
-
+ agents = [
+ member.agent
+ for member in board_members
+ if member.agent is not None
+ ]
+
political_group_board.board_swarm = BoardOfDirectorsSwarm(
name=f"{group_name} Board",
description=f"Specialized board for {group_name} with expertise in {', '.join(group_expertise)}",
@@ -1356,111 +1719,194 @@ Remember: You are a real MEP with specific political views, expertise, and respo
verbose=self.verbose,
decision_threshold=0.6,
enable_voting=True,
- enable_consensus=True
+ enable_consensus=True,
)
-
- self.political_group_boards[group_name] = political_group_board
-
+
+ self.political_group_boards[group_name] = (
+ political_group_board
+ )
+
if self.verbose:
- logger.info(f"Created {group_name} board with {len(board_members)} members, "
- f"voting weight: {voting_weight:.1%}, expertise: {', '.join(group_expertise[:3])}")
- logger.info(f"Board member percentages: {board_member_percentages}")
+ logger.info(
+ f"Created {group_name} board with {len(board_members)} members, "
+ f"voting weight: {voting_weight:.1%}, expertise: {', '.join(group_expertise[:3])}"
+ )
+ logger.info(
+ f"Board member percentages: {board_member_percentages}"
+ )
- def _assign_group_expertise(self, group_name: str, expertise_areas: Dict[str, List[str]]) -> List[str]:
+ def _assign_group_expertise(
+ self, group_name: str, expertise_areas: Dict[str, List[str]]
+ ) -> List[str]:
"""Assign specialized expertise areas based on political group ideology."""
-
+
# Map political groups to their primary expertise areas
group_expertise_mapping = {
"Group of the European People's Party (Christian Democrats)": [
- "economics", "law", "foreign", "social"
+ "economics",
+ "law",
+ "foreign",
+ "social",
],
"Group of the Progressive Alliance of Socialists and Democrats in the European Parliament": [
- "social", "economics", "environment", "law"
+ "social",
+ "economics",
+ "environment",
+ "law",
],
"Renew Europe Group": [
- "economics", "technology", "environment", "foreign"
+ "economics",
+ "technology",
+ "environment",
+ "foreign",
],
"European Conservatives and Reformists Group": [
- "law", "foreign", "economics", "regional"
+ "law",
+ "foreign",
+ "economics",
+ "regional",
],
"Group of the Greens/European Free Alliance": [
- "environment", "social", "technology", "agriculture"
+ "environment",
+ "social",
+ "technology",
+ "agriculture",
],
"The Left group in the European Parliament - GUE/NGL": [
- "social", "economics", "environment", "law"
+ "social",
+ "economics",
+ "environment",
+ "law",
],
"Patriots for Europe Group": [
- "foreign", "law", "regional", "social"
+ "foreign",
+ "law",
+ "regional",
+ "social",
],
"Europe of Sovereign Nations Group": [
- "foreign", "law", "regional", "economics"
+ "foreign",
+ "law",
+ "regional",
+ "economics",
],
"Non-attached Members": [
- "law", "foreign", "economics", "social"
- ]
+ "law",
+ "foreign",
+ "economics",
+ "social",
+ ],
}
-
+
# Get primary expertise areas for this group
- primary_areas = group_expertise_mapping.get(group_name, ["economics", "law", "social"])
-
+ primary_areas = group_expertise_mapping.get(
+ group_name, ["economics", "law", "social"]
+ )
+
# Expand to specific expertise topics
specific_expertise = []
for area in primary_areas:
if area in expertise_areas:
specific_expertise.extend(expertise_areas[area])
-
+
return specific_expertise[:8] # Limit to 8 areas
- def _calculate_expertise_percentages(self, group_name: str, num_expertise_areas: int) -> List[float]:
+ def _calculate_expertise_percentages(
+ self, group_name: str, num_expertise_areas: int
+ ) -> List[float]:
"""Calculate individual percentages for board members based on political group and expertise areas."""
-
+
# Define percentage distributions based on political group characteristics
percentage_distributions = {
- "Group of the European People's Party (Christian Democrats)": [0.25, 0.20, 0.15, 0.05], # CEO gets 35%
- "Group of the Progressive Alliance of Socialists and Democrats in the European Parliament": [0.25, 0.20, 0.15, 0.05],
- "Renew Europe Group": [0.30, 0.20, 0.10, 0.05], # More emphasis on first expertise
- "Group of the Greens/European Free Alliance": [0.30, 0.20, 0.10, 0.05],
- "European Conservatives and Reformists Group": [0.25, 0.20, 0.15, 0.05],
- "The Left group in the European Parliament - GUE/NGL": [0.25, 0.20, 0.15, 0.05],
+ "Group of the European People's Party (Christian Democrats)": [
+ 0.25,
+ 0.20,
+ 0.15,
+ 0.05,
+ ], # CEO gets 35%
+ "Group of the Progressive Alliance of Socialists and Democrats in the European Parliament": [
+ 0.25,
+ 0.20,
+ 0.15,
+ 0.05,
+ ],
+ "Renew Europe Group": [
+ 0.30,
+ 0.20,
+ 0.10,
+ 0.05,
+ ], # More emphasis on first expertise
+ "Group of the Greens/European Free Alliance": [
+ 0.30,
+ 0.20,
+ 0.10,
+ 0.05,
+ ],
+ "European Conservatives and Reformists Group": [
+ 0.25,
+ 0.20,
+ 0.15,
+ 0.05,
+ ],
+ "The Left group in the European Parliament - GUE/NGL": [
+ 0.25,
+ 0.20,
+ 0.15,
+ 0.05,
+ ],
"Patriots for Europe Group": [0.30, 0.20, 0.10, 0.05],
- "Europe of Sovereign Nations Group": [0.30, 0.20, 0.10, 0.05],
- "Non-attached Members": [0.40, 0.20, 0.05, 0.00] # More concentrated power
+ "Europe of Sovereign Nations Group": [
+ 0.30,
+ 0.20,
+ 0.10,
+ 0.05,
+ ],
+ "Non-attached Members": [
+ 0.40,
+ 0.20,
+ 0.05,
+ 0.00,
+ ], # More concentrated power
}
-
+
# Get the distribution for this group
- distribution = percentage_distributions.get(group_name, [0.25, 0.20, 0.15, 0.05])
-
+ distribution = percentage_distributions.get(
+ group_name, [0.25, 0.20, 0.15, 0.05]
+ )
+
# Return the appropriate number of percentages
return distribution[:num_expertise_areas]
def _create_parliament_speaker(self):
"""Create the Parliament Speaker who aggregates decisions from all political groups."""
-
+
# Create parliament speaker agent
speaker_agent = Agent(
name="Parliament Speaker",
system_prompt=self._generate_speaker_system_prompt(),
llm="gpt-4",
- verbose=self.verbose
+ verbose=self.verbose,
)
-
+
# Calculate majority threshold
majority_threshold = (len(self.meps) // 2) + 1
-
+
self.parliament_speaker = ParliamentSpeaker(
name="Parliament Speaker",
agent=speaker_agent,
political_groups=self.political_group_boards,
total_meps=len(self.meps),
- majority_threshold=majority_threshold
+ majority_threshold=majority_threshold,
)
-
+
if self.verbose:
- logger.info(f"Created Parliament Speaker with majority threshold: {majority_threshold}")
+ logger.info(
+ f"Created Parliament Speaker with majority threshold: {majority_threshold}"
+ )
def _generate_speaker_system_prompt(self) -> str:
"""Generate system prompt for the Parliament Speaker."""
-
+
return f"""You are the Parliament Speaker of the European Parliament, responsible for:
1. **Aggregating Political Group Decisions**: Collect and analyze decisions from all political groups
@@ -1483,17 +1929,19 @@ Remember: You are a real MEP with specific political views, expertise, and respo
def _format_political_group_distribution(self) -> str:
"""Format political group distribution for the speaker prompt."""
-
+
if not self.political_group_boards:
return "No political groups available"
-
+
lines = []
for group_name, board in self.political_group_boards.items():
percentage = board.voting_weight * 100
- lines.append(f"- {group_name}: {board.total_meps} MEPs ({percentage:.1f}%)")
-
+ lines.append(
+ f"- {group_name}: {board.total_meps} MEPs ({percentage:.1f}%)"
+ )
+
return "\n".join(lines)
-
+
def introduce_bill(
self,
title: str,
@@ -1501,11 +1949,11 @@ Remember: You are a real MEP with specific political views, expertise, and respo
bill_type: VoteType,
committee: str,
sponsor: str,
- co_sponsors: List[str] = None
+ co_sponsors: List[str] = None,
) -> ParliamentaryBill:
"""
Introduce a new bill to the parliament.
-
+
Args:
title: Bill title
description: Bill description
@@ -1513,61 +1961,71 @@ Remember: You are a real MEP with specific political views, expertise, and respo
committee: Primary committee
sponsor: Sponsoring MEP
co_sponsors: List of co-sponsoring MEPs
-
+
Returns:
ParliamentaryBill: The introduced bill
"""
if sponsor not in self.meps:
raise ValueError(f"Sponsor {sponsor} is not a valid MEP")
-
+
if committee not in self.committees:
raise ValueError(f"Committee {committee} does not exist")
-
+
bill = ParliamentaryBill(
title=title,
description=description,
bill_type=bill_type,
committee=committee,
sponsor=sponsor,
- co_sponsors=co_sponsors or []
+ co_sponsors=co_sponsors or [],
)
-
+
self.bills.append(bill)
self.committees[committee].current_bills.append(bill)
-
- logger.info(f"Bill '{title}' introduced by {sponsor} in {committee} committee")
+
+ logger.info(
+ f"Bill '{title}' introduced by {sponsor} in {committee} committee"
+ )
return bill
-
+
def conduct_committee_hearing(
self,
committee: str,
bill: ParliamentaryBill,
- participants: List[str] = None
+ participants: List[str] = None,
) -> Dict[str, Any]:
"""
Conduct a committee hearing on a bill with cost optimization.
-
+
Args:
committee: Committee name
bill: Bill under consideration
participants: List of MEPs to participate
-
+
Returns:
Dict[str, Any]: Hearing results and transcript
"""
if committee not in self.committees:
raise ValueError(f"Committee {committee} does not exist")
-
+
# Check budget before starting
if not self.cost_tracker.check_budget():
- return {"error": "Budget exceeded", "cost_stats": self.cost_tracker.get_stats()}
-
+ return {
+ "error": "Budget exceeded",
+ "cost_stats": self.cost_tracker.get_stats(),
+ }
+
committee_meps = self.committees[committee].members
if not participants:
- participants = committee_meps[:10] # Limit to 10 participants
-
+ participants = committee_meps[
+ :10
+ ] # Limit to 10 participants
+
# Check cache first
- cache_key = self._get_cache_key(f"committee_hearing_{committee}_{bill.title}", participants)
+ cache_key = self._get_cache_key(
+ f"committee_hearing_{committee}_{bill.title}",
+ participants,
+ )
cached_result = self._check_cache(cache_key)
if cached_result:
return {
@@ -1577,9 +2035,9 @@ Remember: You are a real MEP with specific political views, expertise, and respo
"responses": cached_result,
"date": datetime.now(),
"cached": True,
- "cost_stats": self.cost_tracker.get_stats()
+ "cost_stats": self.cost_tracker.get_stats(),
}
-
+
hearing_prompt = f"""
Committee Hearing: {committee}
Bill: {bill.title}
@@ -1595,51 +2053,65 @@ Remember: You are a real MEP with specific political views, expertise, and respo
Provide a detailed analysis with specific recommendations.
"""
-
+
# Load MEP agents in batches
all_responses = {}
total_processed = 0
-
+
for i in range(0, len(participants), self.batch_size):
- batch_participants = participants[i:i + self.batch_size]
-
+ batch_participants = participants[i : i + self.batch_size]
+
# Check budget for this batch
if not self.cost_tracker.check_budget():
- logger.warning(f"Budget exceeded after processing {total_processed} participants")
+ logger.warning(
+ f"Budget exceeded after processing {total_processed} participants"
+ )
break
-
+
# Load agents for this batch
- batch_agents = self._load_mep_agents_batch(batch_participants)
-
+ batch_agents = self._load_mep_agents_batch(
+ batch_participants
+ )
+
if not batch_agents:
continue
-
+
# Run batch
try:
- batch_results = run_agents_concurrently(batch_agents, hearing_prompt)
-
+ batch_results = run_agents_concurrently(
+ batch_agents, hearing_prompt
+ )
+
# Map results back to participant names
for j, agent in enumerate(batch_agents):
if j < len(batch_results):
participant_name = batch_participants[j]
- all_responses[participant_name] = batch_results[j]
+ all_responses[participant_name] = (
+ batch_results[j]
+ )
total_processed += 1
-
+
# Estimate tokens used
- estimated_tokens = len(batch_agents) * 500 # ~500 tokens per response
+ estimated_tokens = (
+ len(batch_agents) * 500
+ ) # ~500 tokens per response
self.cost_tracker.add_tokens(estimated_tokens)
-
+
if self.verbose:
- logger.info(f"Processed committee hearing batch {i//self.batch_size + 1}: {len(batch_agents)} participants")
-
+ logger.info(
+ f"Processed committee hearing batch {i//self.batch_size + 1}: {len(batch_agents)} participants"
+ )
+
except Exception as e:
- logger.error(f"Error processing committee hearing batch: {e}")
+ logger.error(
+ f"Error processing committee hearing batch: {e}"
+ )
continue
-
+
# Cache the results
if all_responses:
self._cache_response(cache_key, str(all_responses))
-
+
hearing_result = {
"committee": committee,
"bill": bill.title,
@@ -1648,19 +2120,25 @@ Remember: You are a real MEP with specific political views, expertise, and respo
"date": datetime.now(),
"cached": False,
"cost_stats": self.cost_tracker.get_stats(),
- "recommendations": self._synthesize_committee_recommendations(all_responses)
+ "recommendations": self._synthesize_committee_recommendations(
+ all_responses
+ ),
}
-
- logger.info(f"Committee hearing completed for {bill.title} in {committee}")
+
+ logger.info(
+ f"Committee hearing completed for {bill.title} in {committee}"
+ )
return hearing_result
-
- def _synthesize_committee_recommendations(self, responses: Dict[str, str]) -> Dict[str, Any]:
+
+ def _synthesize_committee_recommendations(
+ self, responses: Dict[str, str]
+ ) -> Dict[str, Any]:
"""
Synthesize committee recommendations from individual responses.
-
+
Args:
responses: Dictionary of MEP responses
-
+
Returns:
Dict[str, Any]: Synthesized recommendations
"""
@@ -1668,46 +2146,82 @@ Remember: You are a real MEP with specific political views, expertise, and respo
support_count = 0
oppose_count = 0
amend_count = 0
-
+
for response in responses.values():
response_lower = response.lower()
- if any(word in response_lower for word in ["support", "approve", "recommend", "favorable"]):
+ if any(
+ word in response_lower
+ for word in [
+ "support",
+ "approve",
+ "recommend",
+ "favorable",
+ ]
+ ):
support_count += 1
- elif any(word in response_lower for word in ["oppose", "reject", "against", "unfavorable"]):
+ elif any(
+ word in response_lower
+ for word in [
+ "oppose",
+ "reject",
+ "against",
+ "unfavorable",
+ ]
+ ):
oppose_count += 1
- elif any(word in response_lower for word in ["amend", "modify", "improve", "revise"]):
+ elif any(
+ word in response_lower
+ for word in ["amend", "modify", "improve", "revise"]
+ ):
amend_count += 1
-
+
total = len(responses)
-
+
return {
- "support_percentage": (support_count / total) * 100 if total > 0 else 0,
- "oppose_percentage": (oppose_count / total) * 100 if total > 0 else 0,
- "amend_percentage": (amend_count / total) * 100 if total > 0 else 0,
- "recommendation": "support" if support_count > oppose_count else "oppose" if oppose_count > support_count else "amend"
+ "support_percentage": (
+ (support_count / total) * 100 if total > 0 else 0
+ ),
+ "oppose_percentage": (
+ (oppose_count / total) * 100 if total > 0 else 0
+ ),
+ "amend_percentage": (
+ (amend_count / total) * 100 if total > 0 else 0
+ ),
+ "recommendation": (
+ "support"
+ if support_count > oppose_count
+ else (
+ "oppose"
+ if oppose_count > support_count
+ else "amend"
+ )
+ ),
}
-
+
def conduct_parliamentary_debate(
self,
bill: ParliamentaryBill,
participants: List[str] = None,
- max_speakers: int = 20
+ max_speakers: int = 20,
) -> Dict[str, Any]:
"""
Conduct a parliamentary debate on a bill with cost optimization.
-
+
Args:
bill: Bill under debate
participants: List of MEPs to participate
max_speakers: Maximum number of speakers
-
+
Returns:
Dict[str, Any]: Debate transcript and analysis
"""
# Check budget before starting
if not self.cost_tracker.check_budget():
- return {"error": "Budget exceeded", "cost_stats": self.cost_tracker.get_stats()}
-
+ return {
+ "error": "Budget exceeded",
+ "cost_stats": self.cost_tracker.get_stats(),
+ }
+
if not participants:
# Select diverse participants from different political groups
participants = []
@@ -1716,11 +2230,13 @@ Remember: You are a real MEP with specific political views, expertise, and respo
participants.extend(meps[:3]) # 3 MEPs per group
if len(participants) >= max_speakers:
break
-
+
participants = participants[:max_speakers]
-
+
# Check cache first
- cache_key = self._get_cache_key(f"parliamentary_debate_{bill.title}", participants)
+ cache_key = self._get_cache_key(
+ f"parliamentary_debate_{bill.title}", participants
+ )
cached_result = self._check_cache(cache_key)
if cached_result:
return {
@@ -1729,9 +2245,9 @@ Remember: You are a real MEP with specific political views, expertise, and respo
"transcript": cached_result,
"date": datetime.now(),
"cached": True,
- "cost_stats": self.cost_tracker.get_stats()
+ "cost_stats": self.cost_tracker.get_stats(),
}
-
+
debate_prompt = f"""
Parliamentary Debate: {bill.title}
@@ -1749,60 +2265,70 @@ Remember: You are a real MEP with specific political views, expertise, and respo
Provide a clear, reasoned argument for your position.
"""
-
+
# Conduct debate with batching
debate_transcript = []
total_processed = 0
-
+
for i in range(0, len(participants), self.batch_size):
- batch_participants = participants[i:i + self.batch_size]
-
+ batch_participants = participants[i : i + self.batch_size]
+
# Check budget for this batch
if not self.cost_tracker.check_budget():
- logger.warning(f"Budget exceeded after processing {total_processed} speakers")
+ logger.warning(
+ f"Budget exceeded after processing {total_processed} speakers"
+ )
break
-
+
# Load agents for this batch
- batch_agents = self._load_mep_agents_batch(batch_participants)
-
+ batch_agents = self._load_mep_agents_batch(
+ batch_participants
+ )
+
if not batch_agents:
continue
-
+
# Run batch
try:
- batch_results = run_agents_concurrently(batch_agents, debate_prompt)
-
+ batch_results = run_agents_concurrently(
+ batch_agents, debate_prompt
+ )
+
# Create debate entries
for j, agent in enumerate(batch_agents):
if j < len(batch_results):
participant_name = batch_participants[j]
mep = self.meps[participant_name]
-
+
debate_entry = {
"speaker": participant_name,
"political_group": mep.political_group,
"country": mep.country,
"position": batch_results[j],
- "timestamp": datetime.now()
+ "timestamp": datetime.now(),
}
debate_transcript.append(debate_entry)
total_processed += 1
-
+
# Estimate tokens used
- estimated_tokens = len(batch_agents) * 500 # ~500 tokens per response
+ estimated_tokens = (
+ len(batch_agents) * 500
+ ) # ~500 tokens per response
self.cost_tracker.add_tokens(estimated_tokens)
-
+
if self.verbose:
- logger.info(f"Processed debate batch {i//self.batch_size + 1}: {len(batch_agents)} speakers")
-
+ logger.info(
+ f"Processed debate batch {i//self.batch_size + 1}: {len(batch_agents)} speakers"
+ )
+
except Exception as e:
logger.error(f"Error processing debate batch: {e}")
continue
-
+
# Cache the results
if debate_transcript:
self._cache_response(cache_key, str(debate_transcript))
-
+
debate_result = {
"bill": bill.title,
"participants": participants[:total_processed],
@@ -1810,20 +2336,24 @@ Remember: You are a real MEP with specific political views, expertise, and respo
"date": datetime.now(),
"cached": False,
"cost_stats": self.cost_tracker.get_stats(),
- "analysis": self._analyze_debate(debate_transcript)
+ "analysis": self._analyze_debate(debate_transcript),
}
-
+
self.debates.append(debate_result)
- logger.info(f"Parliamentary debate completed for {bill.title} with {total_processed} speakers")
+ logger.info(
+ f"Parliamentary debate completed for {bill.title} with {total_processed} speakers"
+ )
return debate_result
-
- def _analyze_debate(self, transcript: List[Dict[str, Any]]) -> Dict[str, Any]:
+
+ def _analyze_debate(
+ self, transcript: List[Dict[str, Any]]
+ ) -> Dict[str, Any]:
"""
Analyze debate transcript for key themes and positions.
-
+
Args:
transcript: Debate transcript
-
+
Returns:
Dict[str, Any]: Debate analysis
"""
@@ -1831,39 +2361,49 @@ Remember: You are a real MEP with specific political views, expertise, and respo
support_count = 0
oppose_count = 0
neutral_count = 0
-
+
for entry in transcript:
position = entry["position"].lower()
- if any(word in position for word in ["support", "approve", "favorable", "yes"]):
+ if any(
+ word in position
+ for word in ["support", "approve", "favorable", "yes"]
+ ):
support_count += 1
- elif any(word in position for word in ["oppose", "reject", "against", "no"]):
+ elif any(
+ word in position
+ for word in ["oppose", "reject", "against", "no"]
+ ):
oppose_count += 1
else:
neutral_count += 1
-
+
total = len(transcript)
-
+
return {
"support_count": support_count,
"oppose_count": oppose_count,
"neutral_count": neutral_count,
- "support_percentage": (support_count / total) * 100 if total > 0 else 0,
- "oppose_percentage": (oppose_count / total) * 100 if total > 0 else 0,
- "neutral_percentage": (neutral_count / total) * 100 if total > 0 else 0
+ "support_percentage": (
+ (support_count / total) * 100 if total > 0 else 0
+ ),
+ "oppose_percentage": (
+ (oppose_count / total) * 100 if total > 0 else 0
+ ),
+ "neutral_percentage": (
+ (neutral_count / total) * 100 if total > 0 else 0
+ ),
}
-
+
def conduct_democratic_vote(
- self,
- bill: ParliamentaryBill,
- participants: List[str] = None
+ self, bill: ParliamentaryBill, participants: List[str] = None
) -> ParliamentaryVote:
"""
Conduct a democratic vote on a bill using the Board of Directors pattern with lazy loading.
-
+
Args:
bill: Bill to vote on
participants: List of MEPs to participate
-
+
Returns:
ParliamentaryVote: Vote results
"""
@@ -1872,12 +2412,12 @@ Remember: You are a real MEP with specific political views, expertise, and respo
return ParliamentaryVote(
bill=bill,
vote_type=bill.bill_type,
- result=VoteResult.FAILED
+ result=VoteResult.FAILED,
)
-
+
if not participants:
participants = list(self.meps.keys())
-
+
# Use democratic swarm for decision-making if available
democratic_result = None
if self.democratic_swarm is not None:
@@ -1896,30 +2436,36 @@ Remember: You are a real MEP with specific political views, expertise, and respo
This is a critical legislative decision that will affect all EU citizens.
"""
-
+
# Get democratic decision
- democratic_result = self.democratic_swarm.run_board_meeting(decision_task)
-
+ democratic_result = (
+ self.democratic_swarm.run_board_meeting(decision_task)
+ )
+
# Conduct individual MEP votes with lazy loading
individual_votes = {}
reasoning = {}
total_processed = 0
-
+
# Process participants in batches
for i in range(0, len(participants), self.batch_size):
- batch_participants = participants[i:i + self.batch_size]
-
+ batch_participants = participants[i : i + self.batch_size]
+
# Check budget for this batch
if not self.cost_tracker.check_budget():
- logger.warning(f"Budget exceeded after processing {total_processed} voters")
+ logger.warning(
+ f"Budget exceeded after processing {total_processed} voters"
+ )
break
-
+
# Load agents for this batch
- batch_agents = self._load_mep_agents_batch(batch_participants)
-
+ batch_agents = self._load_mep_agents_batch(
+ batch_participants
+ )
+
if not batch_agents:
continue
-
+
# Create voting prompt
vote_prompt = f"""
Vote on Bill: {bill.title}
@@ -1936,47 +2482,79 @@ Remember: You are a real MEP with specific political views, expertise, and respo
Respond with 'FOR', 'AGAINST', or 'ABSTAIN' and explain your reasoning.
"""
-
+
# Run batch voting
try:
- batch_results = run_agents_concurrently(batch_agents, vote_prompt)
-
+ batch_results = run_agents_concurrently(
+ batch_agents, vote_prompt
+ )
+
# Process results
for j, agent in enumerate(batch_agents):
if j < len(batch_results):
participant_name = batch_participants[j]
response = batch_results[j]
-
+
# Parse vote
response_lower = response.lower()
- if any(word in response_lower for word in ["for", "support", "yes", "approve"]):
+ if any(
+ word in response_lower
+ for word in [
+ "for",
+ "support",
+ "yes",
+ "approve",
+ ]
+ ):
vote = "FOR"
- elif any(word in response_lower for word in ["against", "oppose", "no", "reject"]):
+ elif any(
+ word in response_lower
+ for word in [
+ "against",
+ "oppose",
+ "no",
+ "reject",
+ ]
+ ):
vote = "AGAINST"
else:
vote = "ABSTAIN"
-
+
individual_votes[participant_name] = vote
reasoning[participant_name] = response
total_processed += 1
-
+
# Estimate tokens used
- estimated_tokens = len(batch_agents) * 500 # ~500 tokens per response
+ estimated_tokens = (
+ len(batch_agents) * 500
+ ) # ~500 tokens per response
self.cost_tracker.add_tokens(estimated_tokens)
-
+
if self.verbose:
- logger.info(f"Processed voting batch {i//self.batch_size + 1}: {len(batch_agents)} voters")
-
+ logger.info(
+ f"Processed voting batch {i//self.batch_size + 1}: {len(batch_agents)} voters"
+ )
+
except Exception as e:
logger.error(f"Error processing voting batch: {e}")
continue
-
+
# Calculate results
- votes_for = sum(1 for vote in individual_votes.values() if vote == "FOR")
- votes_against = sum(1 for vote in individual_votes.values() if vote == "AGAINST")
- abstentions = sum(1 for vote in individual_votes.values() if vote == "ABSTAIN")
+ votes_for = sum(
+ 1 for vote in individual_votes.values() if vote == "FOR"
+ )
+ votes_against = sum(
+ 1
+ for vote in individual_votes.values()
+ if vote == "AGAINST"
+ )
+ abstentions = sum(
+ 1
+ for vote in individual_votes.values()
+ if vote == "ABSTAIN"
+ )
absent = len(participants) - len(individual_votes)
-
+
# Determine result
if votes_for > votes_against:
result = VoteResult.PASSED
@@ -1984,7 +2562,7 @@ Remember: You are a real MEP with specific political views, expertise, and respo
result = VoteResult.FAILED
else:
result = VoteResult.TIED
-
+
vote_result = ParliamentaryVote(
bill=bill,
vote_type=bill.bill_type,
@@ -1994,60 +2572,65 @@ Remember: You are a real MEP with specific political views, expertise, and respo
absent=absent,
result=result,
individual_votes=individual_votes,
- reasoning=reasoning
+ reasoning=reasoning,
)
-
+
self.votes.append(vote_result)
bill.status = "voted"
-
- logger.info(f"Democratic vote completed for {bill.title}: {result.value} ({total_processed} voters processed)")
+
+ logger.info(
+ f"Democratic vote completed for {bill.title}: {result.value} ({total_processed} voters processed)"
+ )
return vote_result
-
+
def conduct_hierarchical_democratic_vote(
- self,
- bill: ParliamentaryBill,
- participants: List[str] = None
+ self, bill: ParliamentaryBill, participants: List[str] = None
) -> ParliamentaryVote:
"""
Conduct a hierarchical democratic vote using political group boards and parliament speaker.
-
+
This enhanced voting system:
1. Each political group votes internally as a specialized board
2. Group speakers (CEOs) synthesize their group's position
3. Parliament Speaker aggregates all group decisions based on percentage representation
4. Final result calculated using weighted voting
-
+
Args:
bill: Bill to vote on
participants: List of MEPs to participate (optional, uses all by default)
-
+
Returns:
ParliamentaryVote: Enhanced vote results with group-level analysis
"""
-
+
if not self.enable_hierarchical_democracy:
- logger.warning("Hierarchical democracy not enabled, falling back to standard voting")
+ logger.warning(
+ "Hierarchical democracy not enabled, falling back to standard voting"
+ )
return self.conduct_democratic_vote(bill, participants)
-
- logger.info(f"Conducting hierarchical democratic vote on: {bill.title}")
-
+
+ logger.info(
+ f"Conducting hierarchical democratic vote on: {bill.title}"
+ )
+
# Initialize vote tracking
vote = ParliamentaryVote(
- bill=bill,
- vote_type=bill.bill_type,
- date=datetime.now()
+ bill=bill, vote_type=bill.bill_type, date=datetime.now()
)
-
+
# Step 1: Each political group votes internally
group_decisions = {}
group_reasoning = {}
-
- for group_name, group_board in self.political_group_boards.items():
+
+ for (
+ group_name,
+ group_board,
+ ) in self.political_group_boards.items():
if not group_board.board_swarm:
continue
-
+
logger.info(f"Conducting internal vote for {group_name}")
-
+
# Create voting task for this group
voting_task = f"""
Parliamentary Vote: {bill.title}
@@ -2069,27 +2652,37 @@ Remember: You are a real MEP with specific political views, expertise, and respo
Provide your group's decision: POSITIVE, NEGATIVE, or ABSTAIN
Include detailed reasoning for your position.
"""
-
+
try:
# Get group decision using their specialized board
- group_result = group_board.board_swarm.run(voting_task)
-
+ group_result = group_board.board_swarm.run(
+ voting_task
+ )
+
# Parse the group decision
- group_decision = self._parse_group_decision(group_result)
+ group_decision = self._parse_group_decision(
+ group_result
+ )
group_decisions[group_name] = group_decision
group_reasoning[group_name] = group_result
-
- logger.info(f"{group_name} decision: {group_decision}")
-
+
+ logger.info(
+ f"{group_name} decision: {group_decision}"
+ )
+
except Exception as e:
logger.error(f"Error in {group_name} vote: {e}")
group_decisions[group_name] = "ABSTAIN"
- group_reasoning[group_name] = f"Error during voting: {str(e)}"
-
+ group_reasoning[group_name] = (
+ f"Error during voting: {str(e)}"
+ )
+
# Step 2: Parliament Speaker aggregates group decisions
if self.parliament_speaker and self.parliament_speaker.agent:
- logger.info("Parliament Speaker aggregating group decisions")
-
+ logger.info(
+ "Parliament Speaker aggregating group decisions"
+ )
+
aggregation_task = f"""
Parliamentary Vote Aggregation: {bill.title}
@@ -2110,75 +2703,115 @@ Remember: You are a real MEP with specific political views, expertise, and respo
3. Weighted analysis of each group's contribution
4. Summary of the democratic process
"""
-
+
try:
- speaker_result = self.parliament_speaker.agent.run(aggregation_task)
-
+ speaker_result = self.parliament_speaker.agent.run(
+ aggregation_task
+ )
+
# Parse speaker's analysis
- final_result = self._parse_speaker_analysis(speaker_result, group_decisions)
-
+ final_result = self._parse_speaker_analysis(
+ speaker_result, group_decisions
+ )
+
# Update vote with results
- vote.result = final_result['result']
- vote.votes_for = final_result['votes_for']
- vote.votes_against = final_result['votes_against']
- vote.abstentions = final_result['abstentions']
+ vote.result = final_result["result"]
+ vote.votes_for = final_result["votes_for"]
+ vote.votes_against = final_result["votes_against"]
+ vote.abstentions = final_result["abstentions"]
vote.individual_votes = group_decisions
vote.reasoning = group_reasoning
-
+
logger.info(f"Final result: {vote.result.value}")
- logger.info(f"Votes - For: {vote.votes_for}, Against: {vote.votes_against}, Abstain: {vote.abstentions}")
-
+ logger.info(
+ f"Votes - For: {vote.votes_for}, Against: {vote.votes_against}, Abstain: {vote.abstentions}"
+ )
+
except Exception as e:
logger.error(f"Error in speaker aggregation: {e}")
# Fallback to simple counting
- vote = self._fallback_vote_calculation(vote, group_decisions)
-
+ vote = self._fallback_vote_calculation(
+ vote, group_decisions
+ )
+
# Store the vote
self.votes.append(vote)
-
+
return vote
def _parse_group_decision(self, group_result: str) -> str:
"""Parse the decision from a political group's voting result."""
-
+
result_lower = group_result.lower()
-
- if any(word in result_lower for word in ['positive', 'for', 'support', 'approve', 'pass']):
+
+ if any(
+ word in result_lower
+ for word in [
+ "positive",
+ "for",
+ "support",
+ "approve",
+ "pass",
+ ]
+ ):
return "POSITIVE"
- elif any(word in result_lower for word in ['negative', 'against', 'oppose', 'reject', 'fail']):
+ elif any(
+ word in result_lower
+ for word in [
+ "negative",
+ "against",
+ "oppose",
+ "reject",
+ "fail",
+ ]
+ ):
return "NEGATIVE"
else:
return "ABSTAIN"
- def _format_group_decisions(self, group_decisions: Dict[str, str], group_reasoning: Dict[str, str]) -> str:
+ def _format_group_decisions(
+ self,
+ group_decisions: Dict[str, str],
+ group_reasoning: Dict[str, str],
+ ) -> str:
"""Format group decisions for the speaker's analysis."""
-
+
lines = []
for group_name, decision in group_decisions.items():
board = self.political_group_boards.get(group_name)
if board:
percentage = board.voting_weight * 100
- reasoning = group_reasoning.get(group_name, "No reasoning provided")
- lines.append(f"- {group_name} ({board.total_meps} MEPs, {percentage:.1f}%): {decision}")
+ reasoning = group_reasoning.get(
+ group_name, "No reasoning provided"
+ )
+ lines.append(
+ f"- {group_name} ({board.total_meps} MEPs, {percentage:.1f}%): {decision}"
+ )
lines.append(f" Reasoning: {reasoning[:200]}...")
-
+
return "\n".join(lines)
- def _parse_speaker_analysis(self, speaker_result: str, group_decisions: Dict[str, str]) -> Dict[str, Any]:
+ def _parse_speaker_analysis(
+ self, speaker_result: str, group_decisions: Dict[str, str]
+ ) -> Dict[str, Any]:
"""Parse the Parliament Speaker's analysis to extract final vote results using dual-layer percentage system."""
-
+
# Initialize counters
votes_for = 0
votes_against = 0
abstentions = 0
-
+
# Calculate weighted votes using dual-layer percentage system
for group_name, decision in group_decisions.items():
board = self.political_group_boards.get(group_name)
if board and board.board_member_percentages:
# Calculate weighted votes using individual board member percentages
- group_weighted_votes = self._calculate_group_weighted_votes(board, decision)
-
+ group_weighted_votes = (
+ self._calculate_group_weighted_votes(
+ board, decision
+ )
+ )
+
if decision == "POSITIVE":
votes_for += group_weighted_votes
elif decision == "NEGATIVE":
@@ -2188,15 +2821,17 @@ Remember: You are a real MEP with specific political views, expertise, and respo
else:
# Fallback to simple calculation if no individual percentages available
if board:
- weighted_votes = int(board.total_meps * board.voting_weight)
-
+ weighted_votes = int(
+ board.total_meps * board.voting_weight
+ )
+
if decision == "POSITIVE":
votes_for += weighted_votes
elif decision == "NEGATIVE":
votes_against += weighted_votes
else: # ABSTAIN
abstentions += weighted_votes
-
+
# Determine result
if votes_for > votes_against:
result = VoteResult.PASSED
@@ -2204,44 +2839,57 @@ Remember: You are a real MEP with specific political views, expertise, and respo
result = VoteResult.FAILED
else:
result = VoteResult.TIED
-
+
return {
- 'result': result,
- 'votes_for': votes_for,
- 'votes_against': votes_against,
- 'abstentions': abstentions
+ "result": result,
+ "votes_for": votes_for,
+ "votes_against": votes_against,
+ "abstentions": abstentions,
}
- def _calculate_group_weighted_votes(self, board: PoliticalGroupBoard, decision: str) -> int:
+ def _calculate_group_weighted_votes(
+ self, board: PoliticalGroupBoard, decision: str
+ ) -> int:
"""Calculate weighted votes for a political group using individual board member percentages."""
-
+
total_weighted_votes = 0
-
+
# Calculate votes based on individual board member percentages
- for member_name, internal_percentage in board.board_member_percentages.items():
+ for (
+ member_name,
+ internal_percentage,
+ ) in board.board_member_percentages.items():
# Convert internal percentage to parliament percentage
# internal_percentage is percentage within the group
# board.voting_weight is group's percentage of parliament
- parliament_percentage = internal_percentage * board.voting_weight
-
+ parliament_percentage = (
+ internal_percentage * board.voting_weight
+ )
+
# Calculate weighted votes for this member
- member_weighted_votes = int(board.total_meps * parliament_percentage)
+ member_weighted_votes = int(
+ board.total_meps * parliament_percentage
+ )
total_weighted_votes += member_weighted_votes
-
+
if self.verbose:
- logger.debug(f"{member_name}: {internal_percentage:.1%} of {board.group_name} "
- f"({board.voting_weight:.1%} of parliament) = {parliament_percentage:.3%} "
- f"= {member_weighted_votes} weighted votes")
-
+ logger.debug(
+ f"{member_name}: {internal_percentage:.1%} of {board.group_name} "
+ f"({board.voting_weight:.1%} of parliament) = {parliament_percentage:.3%} "
+ f"= {member_weighted_votes} weighted votes"
+ )
+
return total_weighted_votes
- def _fallback_vote_calculation(self, vote: ParliamentaryVote, group_decisions: Dict[str, str]) -> ParliamentaryVote:
+ def _fallback_vote_calculation(
+ self, vote: ParliamentaryVote, group_decisions: Dict[str, str]
+ ) -> ParliamentaryVote:
"""Fallback vote calculation if speaker analysis fails."""
-
+
votes_for = 0
votes_against = 0
abstentions = 0
-
+
for group_name, decision in group_decisions.items():
board = self.political_group_boards.get(group_name)
if board:
@@ -2251,30 +2899,32 @@ Remember: You are a real MEP with specific political views, expertise, and respo
votes_against += board.total_meps
else:
abstentions += board.total_meps
-
+
vote.votes_for = votes_for
vote.votes_against = votes_against
vote.abstentions = abstentions
-
+
if votes_for > votes_against:
vote.result = VoteResult.PASSED
elif votes_against > votes_for:
vote.result = VoteResult.FAILED
else:
vote.result = VoteResult.TIED
-
+
return vote
-
+
def get_parliament_composition(self) -> Dict[str, Any]:
"""
Get the current composition of the parliament including cost statistics.
-
+
Returns:
Dict[str, Any]: Parliament composition statistics
"""
composition = {
"total_meps": len(self.meps),
- "loaded_meps": len([mep for mep in self.meps.values() if mep.is_loaded]),
+ "loaded_meps": len(
+ [mep for mep in self.meps.values() if mep.is_loaded]
+ ),
"political_groups": {},
"countries": {},
"leadership": {},
@@ -2284,25 +2934,27 @@ Remember: You are a real MEP with specific political views, expertise, and respo
"lazy_loading": self.enable_lazy_loading,
"caching": self.enable_caching,
"batch_size": self.batch_size,
- "budget_limit": self.cost_tracker.budget_limit
- }
+ "budget_limit": self.cost_tracker.budget_limit,
+ },
}
-
+
# Political group breakdown
for group_name, meps in self.political_groups.items():
composition["political_groups"][group_name] = {
"count": len(meps),
- "percentage": (len(meps) / len(self.meps)) * 100
+ "percentage": (len(meps) / len(self.meps)) * 100,
}
-
+
# Country breakdown
country_counts = {}
for mep in self.meps.values():
country = mep.country
- country_counts[country] = country_counts.get(country, 0) + 1
-
+ country_counts[country] = (
+ country_counts.get(country, 0) + 1
+ )
+
composition["countries"] = country_counts
-
+
# Leadership positions
leadership = {}
for mep in self.meps.values():
@@ -2311,44 +2963,63 @@ Remember: You are a real MEP with specific political views, expertise, and respo
if role not in leadership:
leadership[role] = []
leadership[role].append(mep.full_name)
-
+
composition["leadership"] = leadership
-
+
# Committee composition
for committee_name, committee in self.committees.items():
composition["committees"][committee_name] = {
"chair": committee.chair,
"vice_chair": committee.vice_chair,
"member_count": len(committee.members),
- "current_bills": len(committee.current_bills)
+ "current_bills": len(committee.current_bills),
}
-
+
return composition
-
+
def get_cost_statistics(self) -> Dict[str, Any]:
"""
Get detailed cost statistics for the parliamentary operations.
-
+
Returns:
Dict[str, Any]: Cost statistics and optimization metrics
"""
stats = self.cost_tracker.get_stats()
-
+
# Add additional metrics
- stats.update({
- "total_meps": len(self.meps),
- "loaded_meps": len([mep for mep in self.meps.values() if mep.is_loaded]),
- "loading_efficiency": len([mep for mep in self.meps.values() if mep.is_loaded]) / len(self.meps) if self.meps else 0,
- "cache_size": len(self.response_cache),
- "optimization_enabled": {
- "lazy_loading": self.enable_lazy_loading,
- "caching": self.enable_caching,
- "batching": self.batch_size > 1
+ stats.update(
+ {
+ "total_meps": len(self.meps),
+ "loaded_meps": len(
+ [
+ mep
+ for mep in self.meps.values()
+ if mep.is_loaded
+ ]
+ ),
+ "loading_efficiency": (
+ len(
+ [
+ mep
+ for mep in self.meps.values()
+ if mep.is_loaded
+ ]
+ )
+ / len(self.meps)
+ if self.meps
+ else 0
+ ),
+ "cache_size": len(self.response_cache),
+ "optimization_enabled": {
+ "lazy_loading": self.enable_lazy_loading,
+ "caching": self.enable_caching,
+ "batching": self.batch_size > 1,
+ },
}
- })
-
+ )
+
return stats
-
+
def run_optimized_parliamentary_session(
self,
bill_title: str,
@@ -2356,11 +3027,11 @@ Remember: You are a real MEP with specific political views, expertise, and respo
bill_type: VoteType = VoteType.ORDINARY_LEGISLATIVE_PROCEDURE,
committee: str = "Legal Affairs",
sponsor: str = None,
- max_cost: float = 50.0
+ max_cost: float = 50.0,
) -> Dict[str, Any]:
"""
Run a complete parliamentary session with cost optimization.
-
+
Args:
bill_title: Title of the bill
bill_description: Description of the bill
@@ -2368,37 +3039,39 @@ Remember: You are a real MEP with specific political views, expertise, and respo
committee: Primary committee
sponsor: Sponsoring MEP (random if not specified)
max_cost: Maximum cost for this session
-
+
Returns:
Dict[str, Any]: Complete session results with cost tracking
"""
# Set temporary budget for this session
original_budget = self.cost_tracker.budget_limit
- self.cost_tracker.budget_limit = min(original_budget, max_cost)
-
+ self.cost_tracker.budget_limit = min(
+ original_budget, max_cost
+ )
+
try:
# Select sponsor if not provided
if not sponsor:
sponsor = random.choice(list(self.meps.keys()))
-
+
# Introduce bill
bill = self.introduce_bill(
title=bill_title,
description=bill_description,
bill_type=bill_type,
committee=committee,
- sponsor=sponsor
+ sponsor=sponsor,
)
-
+
# Conduct committee hearing
hearing = self.conduct_committee_hearing(committee, bill)
-
+
# Conduct parliamentary debate
debate = self.conduct_parliamentary_debate(bill)
-
+
# Conduct democratic vote
vote = self.conduct_democratic_vote(bill)
-
+
session_result = {
"bill": bill,
"hearing": hearing,
@@ -2409,66 +3082,78 @@ Remember: You are a real MEP with specific political views, expertise, and respo
"bill_title": bill_title,
"sponsor": sponsor,
"committee": committee,
- "hearing_recommendation": hearing.get("recommendations", {}).get("recommendation", "unknown"),
- "debate_support_percentage": debate.get("analysis", {}).get("support_percentage", 0),
+ "hearing_recommendation": hearing.get(
+ "recommendations", {}
+ ).get("recommendation", "unknown"),
+ "debate_support_percentage": debate.get(
+ "analysis", {}
+ ).get("support_percentage", 0),
"vote_result": vote.result.value,
- "final_outcome": "PASSED" if vote.result == VoteResult.PASSED else "FAILED",
- "total_cost": self.cost_tracker.total_cost_estimate
- }
+ "final_outcome": (
+ "PASSED"
+ if vote.result == VoteResult.PASSED
+ else "FAILED"
+ ),
+ "total_cost": self.cost_tracker.total_cost_estimate,
+ },
}
-
- logger.info(f"Optimized parliamentary session completed for {bill_title}: {session_result['session_summary']['final_outcome']}")
- logger.info(f"Session cost: ${self.cost_tracker.total_cost_estimate:.2f}")
-
+
+ logger.info(
+ f"Optimized parliamentary session completed for {bill_title}: {session_result['session_summary']['final_outcome']}"
+ )
+ logger.info(
+ f"Session cost: ${self.cost_tracker.total_cost_estimate:.2f}"
+ )
+
return session_result
-
+
finally:
# Restore original budget
self.cost_tracker.budget_limit = original_budget
-
+
def run_democratic_session(
self,
bill_title: str,
bill_description: str,
bill_type: VoteType = VoteType.ORDINARY_LEGISLATIVE_PROCEDURE,
committee: str = "Legal Affairs",
- sponsor: str = None
+ sponsor: str = None,
) -> Dict[str, Any]:
"""
Run a complete democratic parliamentary session on a bill.
-
+
Args:
bill_title: Title of the bill
bill_description: Description of the bill
bill_type: Type of legislative procedure
committee: Primary committee
sponsor: Sponsoring MEP (random if not specified)
-
+
Returns:
Dict[str, Any]: Complete session results
"""
# Select sponsor if not provided
if not sponsor:
sponsor = random.choice(list(self.meps.keys()))
-
+
# Introduce bill
bill = self.introduce_bill(
title=bill_title,
description=bill_description,
bill_type=bill_type,
committee=committee,
- sponsor=sponsor
+ sponsor=sponsor,
)
-
+
# Conduct committee hearing
hearing = self.conduct_committee_hearing(committee, bill)
-
+
# Conduct parliamentary debate
debate = self.conduct_parliamentary_debate(bill)
-
+
# Conduct democratic vote
vote = self.conduct_democratic_vote(bill)
-
+
session_result = {
"bill": bill,
"hearing": hearing,
@@ -2478,78 +3163,104 @@ Remember: You are a real MEP with specific political views, expertise, and respo
"bill_title": bill_title,
"sponsor": sponsor,
"committee": committee,
- "hearing_recommendation": hearing["recommendations"]["recommendation"],
- "debate_support_percentage": debate["analysis"]["support_percentage"],
+ "hearing_recommendation": hearing["recommendations"][
+ "recommendation"
+ ],
+ "debate_support_percentage": debate["analysis"][
+ "support_percentage"
+ ],
"vote_result": vote.result.value,
- "final_outcome": "PASSED" if vote.result == VoteResult.PASSED else "FAILED"
- }
+ "final_outcome": (
+ "PASSED"
+ if vote.result == VoteResult.PASSED
+ else "FAILED"
+ ),
+ },
}
-
- logger.info(f"Democratic session completed for {bill_title}: {session_result['session_summary']['final_outcome']}")
+
+ logger.info(
+ f"Democratic session completed for {bill_title}: {session_result['session_summary']['final_outcome']}"
+ )
return session_result
-
+
def run_hierarchical_democratic_session(
self,
bill_title: str,
bill_description: str,
bill_type: VoteType = VoteType.ORDINARY_LEGISLATIVE_PROCEDURE,
committee: str = "Legal Affairs",
- sponsor: str = None
+ sponsor: str = None,
) -> Dict[str, Any]:
"""
Run a complete hierarchical democratic session from bill introduction to final vote.
-
+
This enhanced session uses:
1. Political group boards with specialized expertise
2. Group-level internal voting and discussion
3. Parliament Speaker aggregation of group decisions
4. Weighted voting based on political group percentages
-
+
Args:
bill_title: Title of the bill
bill_description: Description of the bill
bill_type: Type of legislative procedure
committee: Committee responsible for the bill
sponsor: MEP sponsoring the bill
-
+
Returns:
Dict[str, Any]: Complete session results including group decisions and final vote
"""
-
+
if not self.enable_hierarchical_democracy:
- logger.warning("Hierarchical democracy not enabled, falling back to standard session")
- return self.run_democratic_session(bill_title, bill_description, bill_type, committee, sponsor)
-
- logger.info(f"Starting hierarchical democratic session: {bill_title}")
-
+ logger.warning(
+ "Hierarchical democracy not enabled, falling back to standard session"
+ )
+ return self.run_democratic_session(
+ bill_title,
+ bill_description,
+ bill_type,
+ committee,
+ sponsor,
+ )
+
+ logger.info(
+ f"Starting hierarchical democratic session: {bill_title}"
+ )
+
# Step 1: Introduce the bill
if not sponsor:
- sponsor = list(self.meps.keys())[0] # Use first MEP as sponsor
-
+ sponsor = list(self.meps.keys())[
+ 0
+ ] # Use first MEP as sponsor
+
bill = self.introduce_bill(
title=bill_title,
description=bill_description,
bill_type=bill_type,
committee=committee,
- sponsor=sponsor
+ sponsor=sponsor,
)
-
+
# Step 2: Conduct committee hearing (if enabled)
committee_result = None
if self.enable_committee_work:
- logger.info(f"Conducting committee hearing in {committee}")
- committee_result = self.conduct_committee_hearing(committee, bill)
-
+ logger.info(
+ f"Conducting committee hearing in {committee}"
+ )
+ committee_result = self.conduct_committee_hearing(
+ committee, bill
+ )
+
# Step 3: Conduct parliamentary debate (if enabled)
debate_result = None
if self.enable_democratic_discussion:
logger.info("Conducting parliamentary debate")
debate_result = self.conduct_parliamentary_debate(bill)
-
+
# Step 4: Conduct hierarchical democratic vote
logger.info("Conducting hierarchical democratic vote")
vote_result = self.conduct_hierarchical_democratic_vote(bill)
-
+
# Step 5: Compile comprehensive session report
session_report = {
"session_type": "hierarchical_democratic",
@@ -2559,7 +3270,7 @@ Remember: You are a real MEP with specific political views, expertise, and respo
"type": bill.bill_type.value,
"committee": bill.committee,
"sponsor": bill.sponsor,
- "status": bill.status
+ "status": bill.status,
},
"committee_work": committee_result,
"parliamentary_debate": debate_result,
@@ -2568,24 +3279,36 @@ Remember: You are a real MEP with specific political views, expertise, and respo
"votes_for": vote_result.votes_for,
"votes_against": vote_result.votes_against,
"abstentions": vote_result.abstentions,
- "total_votes": vote_result.votes_for + vote_result.votes_against + vote_result.abstentions
+ "total_votes": vote_result.votes_for
+ + vote_result.votes_against
+ + vote_result.abstentions,
},
"political_group_decisions": vote_result.individual_votes,
"group_reasoning": vote_result.reasoning,
"parliament_composition": self.get_parliament_composition(),
- "session_summary": self._generate_hierarchical_session_summary(bill, vote_result)
+ "session_summary": self._generate_hierarchical_session_summary(
+ bill, vote_result
+ ),
}
-
- logger.info(f"Hierarchical democratic session completed. Final result: {vote_result.result.value}")
-
+
+ logger.info(
+ f"Hierarchical democratic session completed. Final result: {vote_result.result.value}"
+ )
+
return session_report
- def _generate_hierarchical_session_summary(self, bill: ParliamentaryBill, vote: ParliamentaryVote) -> str:
+ def _generate_hierarchical_session_summary(
+ self, bill: ParliamentaryBill, vote: ParliamentaryVote
+ ) -> str:
"""Generate a summary of the hierarchical democratic session with dual-layer percentage breakdown."""
-
- total_votes = vote.votes_for + vote.votes_against + vote.abstentions
- participation_rate = (total_votes / len(self.meps)) * 100 if self.meps else 0
-
+
+ total_votes = (
+ vote.votes_for + vote.votes_against + vote.abstentions
+ )
+ participation_rate = (
+ (total_votes / len(self.meps)) * 100 if self.meps else 0
+ )
+
summary = f"""
🏛️ HIERARCHICAL DEMOCRATIC SESSION SUMMARY
@@ -2600,168 +3323,212 @@ Remember: You are a real MEP with specific political views, expertise, and respo
🏛️ POLITICAL GROUP DECISIONS (Dual-Layer Percentage System):
"""
-
+
for group_name, decision in vote.individual_votes.items():
board = self.political_group_boards.get(group_name)
if board:
group_percentage = board.voting_weight * 100
summary += f"\n• {group_name}: {decision} ({board.total_meps} MEPs, {group_percentage:.1f}% of parliament)"
-
+
# Show individual board member percentages
if board.board_member_percentages:
- summary += f"\n 📊 Board Member Breakdown:"
- for member_name, internal_percentage in board.board_member_percentages.items():
- parliament_percentage = internal_percentage * board.voting_weight * 100
+ summary += "\n 📊 Board Member Breakdown:"
+ for (
+ member_name,
+ internal_percentage,
+ ) in board.board_member_percentages.items():
+ parliament_percentage = (
+ internal_percentage
+ * board.voting_weight
+ * 100
+ )
summary += f"\n - {member_name}: {internal_percentage:.1%} of group = {parliament_percentage:.3f}% of parliament"
-
- summary += f"\n\n🎯 DUAL-LAYER DEMOCRATIC PROCESS:"
- summary += f"\n• Each political group operates as a specialized board"
- summary += f"\n• Board members have individual percentages within their group"
- summary += f"\n• Individual percentages × Group percentage = Parliament percentage"
- summary += f"\n• Parliament Speaker aggregates all weighted decisions"
+
+ summary += "\n\n🎯 DUAL-LAYER DEMOCRATIC PROCESS:"
+ summary += (
+ "\n• Each political group operates as a specialized board"
+ )
+ summary += "\n• Board members have individual percentages within their group"
+ summary += "\n• Individual percentages × Group percentage = Parliament percentage"
+ summary += (
+ "\n• Parliament Speaker aggregates all weighted decisions"
+ )
summary += f"\n• Final result based on {len(self.political_group_boards)} political groups with {sum(len(board.board_member_percentages) for board in self.political_group_boards.values())} board members"
-
+
return summary
-
+
def get_mep(self, mep_name: str) -> Optional[ParliamentaryMember]:
"""
Get a specific MEP by name.
-
+
Args:
mep_name: Name of the MEP
-
+
Returns:
Optional[ParliamentaryMember]: MEP if found, None otherwise
"""
return self.meps.get(mep_name)
-
- def get_committee(self, committee_name: str) -> Optional[ParliamentaryCommittee]:
+
+ def get_committee(
+ self, committee_name: str
+ ) -> Optional[ParliamentaryCommittee]:
"""
Get a specific committee by name.
-
+
Args:
committee_name: Name of the committee
-
+
Returns:
Optional[ParliamentaryCommittee]: Committee if found, None otherwise
"""
return self.committees.get(committee_name)
-
- def get_political_group_members(self, group_name: str) -> List[str]:
+
+ def get_political_group_members(
+ self, group_name: str
+ ) -> List[str]:
"""
Get all MEPs in a specific political group.
-
+
Args:
group_name: Name of the political group
-
+
Returns:
List[str]: List of MEP names in the group
"""
return self.political_groups.get(group_name, [])
-
+
def get_country_members(self, country: str) -> List[str]:
"""
Get all MEPs from a specific country.
-
+
Args:
country: Name of the country
-
+
Returns:
List[str]: List of MEP names from the country
"""
- return [mep_name for mep_name, mep in self.meps.items() if mep.country == country]
+ return [
+ mep_name
+ for mep_name, mep in self.meps.items()
+ if mep.country == country
+ ]
def _load_wikipedia_personalities(self):
"""Load Wikipedia personality profiles for MEPs."""
-
+
if not self.enable_wikipedia_personalities:
return
-
+
try:
# Initialize personality scraper
self.personality_scraper = WikipediaPersonalityScraper(
- output_dir="mep_personalities",
- verbose=self.verbose
+ output_dir="mep_personalities", verbose=self.verbose
)
-
+
# Load existing personality profiles
personality_dir = "mep_personalities"
if os.path.exists(personality_dir):
- profile_files = [f for f in os.listdir(personality_dir) if f.endswith('.json')]
-
+ profile_files = [
+ f
+ for f in os.listdir(personality_dir)
+ if f.endswith(".json")
+ ]
+
for filename in profile_files:
filepath = os.path.join(personality_dir, filename)
try:
- profile = self.personality_scraper.load_personality_profile(filepath)
- self.personality_profiles[profile.full_name] = profile
-
+ profile = self.personality_scraper.load_personality_profile(
+ filepath
+ )
+ self.personality_profiles[
+ profile.full_name
+ ] = profile
+
if self.verbose:
- logger.debug(f"Loaded personality profile: {profile.full_name}")
-
+ logger.debug(
+ f"Loaded personality profile: {profile.full_name}"
+ )
+
except Exception as e:
- logger.warning(f"Error loading personality profile {filename}: {e}")
-
+ logger.warning(
+ f"Error loading personality profile {filename}: {e}"
+ )
+
if self.verbose:
- logger.info(f"Loaded {len(self.personality_profiles)} Wikipedia personality profiles")
+ logger.info(
+ f"Loaded {len(self.personality_profiles)} Wikipedia personality profiles"
+ )
else:
if self.verbose:
- logger.info("No existing personality profiles found. Run Wikipedia scraper to create profiles.")
-
+ logger.info(
+ "No existing personality profiles found. Run Wikipedia scraper to create profiles."
+ )
+
except Exception as e:
- logger.error(f"Error loading Wikipedia personalities: {e}")
+ logger.error(
+ f"Error loading Wikipedia personalities: {e}"
+ )
self.enable_wikipedia_personalities = False
- def scrape_wikipedia_personalities(self, delay: float = 1.0) -> Dict[str, str]:
+ def scrape_wikipedia_personalities(
+ self, delay: float = 1.0
+ ) -> Dict[str, str]:
"""
Scrape Wikipedia personality data for all MEPs.
-
+
Args:
delay: Delay between requests to be respectful to Wikipedia
-
+
Returns:
Dictionary mapping MEP names to their personality profile file paths
"""
-
+
if not self.enable_wikipedia_personalities:
logger.error("Wikipedia personality system not available")
return {}
-
+
if not self.personality_scraper:
self.personality_scraper = WikipediaPersonalityScraper(
- output_dir="mep_personalities",
- verbose=self.verbose
+ output_dir="mep_personalities", verbose=self.verbose
)
-
- logger.info("Starting Wikipedia personality scraping for all MEPs...")
- profile_files = self.personality_scraper.scrape_all_mep_personalities(
- xml_file=self.eu_data_file,
- delay=delay
+
+ logger.info(
+ "Starting Wikipedia personality scraping for all MEPs..."
)
-
+ profile_files = (
+ self.personality_scraper.scrape_all_mep_personalities(
+ xml_file=self.eu_data_file, delay=delay
+ )
+ )
+
# Reload personality profiles
self._load_wikipedia_personalities()
-
+
return profile_files
- def get_mep_personality_profile(self, mep_name: str) -> Optional[MEPPersonalityProfile]:
+ def get_mep_personality_profile(
+ self, mep_name: str
+ ) -> Optional[MEPPersonalityProfile]:
"""
Get personality profile for a specific MEP.
-
+
Args:
mep_name: Name of the MEP
-
+
Returns:
MEPPersonalityProfile if found, None otherwise
"""
return self.personality_profiles.get(mep_name)
- def analyze_political_landscape(self, bill: ParliamentaryBill) -> Dict[str, Any]:
+ def analyze_political_landscape(
+ self, bill: ParliamentaryBill
+ ) -> Dict[str, Any]:
"""
Analyze the political landscape for a bill to predict voting outcomes.
-
+
Args:
bill: Bill to analyze
-
+
Returns:
Dict[str, Any]: Political analysis results
"""
@@ -2769,59 +3536,79 @@ Remember: You are a real MEP with specific political views, expertise, and respo
"overall_support": 0.0,
"opposition": 0.0,
"uncertainty": 0.0,
- "group_analysis": {}
+ "group_analysis": {},
}
-
+
# Analyze by political group
for group_name, meps in self.political_groups.items():
if not meps:
continue
-
+
# Simple analysis based on political group alignment
group_support = 0.0
group_opposition = 0.0
-
+
# Assign support based on political group characteristics
- if "Green" in group_name or "Environment" in bill.description:
+ if (
+ "Green" in group_name
+ or "Environment" in bill.description
+ ):
group_support = 75.0
group_opposition = 15.0
- elif "Socialist" in group_name or "Social" in bill.description:
+ elif (
+ "Socialist" in group_name
+ or "Social" in bill.description
+ ):
group_support = 70.0
group_opposition = 20.0
- elif "Conservative" in group_name or "Economic" in bill.description:
+ elif (
+ "Conservative" in group_name
+ or "Economic" in bill.description
+ ):
group_support = 60.0
group_opposition = 30.0
- elif "Liberal" in group_name or "Digital" in bill.description:
+ elif (
+ "Liberal" in group_name
+ or "Digital" in bill.description
+ ):
group_support = 65.0
group_opposition = 25.0
else:
group_support = 50.0
group_opposition = 30.0
-
- group_uncertainty = 100.0 - group_support - group_opposition
-
+
+ group_uncertainty = (
+ 100.0 - group_support - group_opposition
+ )
+
analysis["group_analysis"][group_name] = {
"support": group_support,
"opposition": group_opposition,
"uncertainty": group_uncertainty,
- "mep_count": len(meps)
+ "mep_count": len(meps),
}
-
+
# Calculate overall support weighted by group size
total_meps = len(self.meps)
if total_meps > 0:
weighted_support = 0.0
weighted_opposition = 0.0
weighted_uncertainty = 0.0
-
- for group_name, group_data in analysis["group_analysis"].items():
+
+ for group_name, group_data in analysis[
+ "group_analysis"
+ ].items():
weight = group_data["mep_count"] / total_meps
weighted_support += group_data["support"] * weight
- weighted_opposition += group_data["opposition"] * weight
- weighted_uncertainty += group_data["uncertainty"] * weight
-
+ weighted_opposition += (
+ group_data["opposition"] * weight
+ )
+ weighted_uncertainty += (
+ group_data["uncertainty"] * weight
+ )
+
analysis["overall_support"] = weighted_support
analysis["opposition"] = weighted_opposition
analysis["uncertainty"] = weighted_uncertainty
-
+
return analysis
diff --git a/examples/simulations/euroswarm_parliament/euroswarm_parliament_example.py b/examples/simulations/euroswarm_parliament/euroswarm_parliament_example.py
index 7dc60d02..b2ccf858 100644
--- a/examples/simulations/euroswarm_parliament/euroswarm_parliament_example.py
+++ b/examples/simulations/euroswarm_parliament/euroswarm_parliament_example.py
@@ -5,25 +5,21 @@ This script demonstrates the comprehensive democratic functionality of the EuroS
including bill introduction, committee work, parliamentary debates, and democratic voting.
"""
-import json
-import time
-from datetime import datetime
-
# Import directly from the file
from euroswarm_parliament import (
EuroSwarmParliament,
VoteType,
- ParliamentaryRole,
- ParliamentaryMember
)
def demonstrate_parliament_initialization():
"""Demonstrate parliament initialization and basic functionality with cost optimization."""
-
- print("\nEUROSWARM PARLIAMENT INITIALIZATION DEMONSTRATION (COST OPTIMIZED)")
+
+ print(
+ "\nEUROSWARM PARLIAMENT INITIALIZATION DEMONSTRATION (COST OPTIMIZED)"
+ )
print("=" * 60)
-
+
# Initialize the parliament with cost optimization
parliament = EuroSwarmParliament(
eu_data_file="EU.xml",
@@ -35,487 +31,632 @@ def demonstrate_parliament_initialization():
enable_caching=True, # NEW: Enable response caching
batch_size=25, # NEW: Batch size for concurrent execution
budget_limit=100.0, # NEW: Budget limit in dollars
- verbose=True
+ verbose=True,
)
-
+
print(f"Parliament initialized with {len(parliament.meps)} MEPs")
-
+
# Show parliament composition with cost stats
composition = parliament.get_parliament_composition()
-
- print(f"\nPARLIAMENT COMPOSITION:")
+
+ print("\nPARLIAMENT COMPOSITION:")
print(f"Total MEPs: {composition['total_meps']}")
- print(f"Loaded MEPs: {composition['loaded_meps']} (lazy loading active)")
-
- print(f"\nCOST OPTIMIZATION:")
- cost_stats = composition['cost_stats']
- print(f"Budget Limit: ${cost_stats['budget_remaining'] + cost_stats['total_cost']:.2f}")
+ print(
+ f"Loaded MEPs: {composition['loaded_meps']} (lazy loading active)"
+ )
+
+ print("\nCOST OPTIMIZATION:")
+ cost_stats = composition["cost_stats"]
+ print(
+ f"Budget Limit: ${cost_stats['budget_remaining'] + cost_stats['total_cost']:.2f}"
+ )
print(f"Budget Used: ${cost_stats['total_cost']:.2f}")
print(f"Budget Remaining: ${cost_stats['budget_remaining']:.2f}")
print(f"Cache Hit Rate: {cost_stats['cache_hit_rate']:.1%}")
-
- print(f"\nPOLITICAL GROUP DISTRIBUTION:")
- for group, data in composition['political_groups'].items():
- count = data['count']
- percentage = data['percentage']
+
+ print("\nPOLITICAL GROUP DISTRIBUTION:")
+ for group, data in composition["political_groups"].items():
+ count = data["count"]
+ percentage = data["percentage"]
print(f" {group}: {count} MEPs ({percentage:.1f}%)")
-
- print(f"\nCOMMITTEE LEADERSHIP:")
- for committee_name, committee_data in composition['committees'].items():
- chair = committee_data['chair']
+
+ print("\nCOMMITTEE LEADERSHIP:")
+ for committee_name, committee_data in composition[
+ "committees"
+ ].items():
+ chair = committee_data["chair"]
if chair:
print(f" {committee_name}: {chair}")
-
+
return parliament
def demonstrate_individual_mep_interaction(parliament):
"""Demonstrate individual MEP interaction and personality."""
-
+
print("\nINDIVIDUAL MEP INTERACTION DEMONSTRATION")
print("=" * 60)
-
+
# Get a sample MEP
sample_mep_name = list(parliament.meps.keys())[0]
sample_mep = parliament.meps[sample_mep_name]
-
+
print(f"Sample MEP: {sample_mep.full_name}")
print(f"Country: {sample_mep.country}")
print(f"Political Group: {sample_mep.political_group}")
print(f"National Party: {sample_mep.national_party}")
print(f"Committees: {', '.join(sample_mep.committees)}")
print(f"Expertise Areas: {', '.join(sample_mep.expertise_areas)}")
-
+
# Test MEP agent interaction
if sample_mep.agent:
test_prompt = "What are your views on European integration and how do you approach cross-border cooperation?"
-
+
print(f"\nMEP Response to: '{test_prompt}'")
print("-" * 50)
-
+
try:
response = sample_mep.agent.run(test_prompt)
- print(response[:500] + "..." if len(response) > 500 else response)
+ print(
+ response[:500] + "..."
+ if len(response) > 500
+ else response
+ )
except Exception as e:
print(f"Error getting MEP response: {e}")
def demonstrate_committee_work(parliament):
"""Demonstrate committee work and hearings."""
-
+
print("\nCOMMITTEE WORK DEMONSTRATION")
print("=" * 60)
-
+
# Get a real MEP as sponsor
sponsor = list(parliament.meps.keys())[0]
-
+
# Create a test bill
bill = parliament.introduce_bill(
title="European Digital Rights and Privacy Protection Act",
description="Comprehensive legislation to strengthen digital rights, enhance privacy protection, and establish clear guidelines for data handling across the European Union.",
bill_type=VoteType.ORDINARY_LEGISLATIVE_PROCEDURE,
committee="Legal Affairs",
- sponsor=sponsor
+ sponsor=sponsor,
)
-
+
print(f"Bill: {bill.title}")
print(f"Committee: {bill.committee}")
print(f"Sponsor: {bill.sponsor}")
-
+
# Conduct committee hearing
- print(f"\nCONDUCTING COMMITTEE HEARING...")
- hearing_result = parliament.conduct_committee_hearing(bill.committee, bill)
-
+ print("\nCONDUCTING COMMITTEE HEARING...")
+ hearing_result = parliament.conduct_committee_hearing(
+ bill.committee, bill
+ )
+
print(f"Committee: {hearing_result['committee']}")
print(f"Participants: {len(hearing_result['participants'])} MEPs")
- print(f"Recommendation: {hearing_result['recommendations']['recommendation']}")
- print(f"Support: {hearing_result['recommendations']['support_percentage']:.1f}%")
- print(f"Oppose: {hearing_result['recommendations']['oppose_percentage']:.1f}%")
- print(f"Amend: {hearing_result['recommendations']['amend_percentage']:.1f}%")
+ print(
+ f"Recommendation: {hearing_result['recommendations']['recommendation']}"
+ )
+ print(
+ f"Support: {hearing_result['recommendations']['support_percentage']:.1f}%"
+ )
+ print(
+ f"Oppose: {hearing_result['recommendations']['oppose_percentage']:.1f}%"
+ )
+ print(
+ f"Amend: {hearing_result['recommendations']['amend_percentage']:.1f}%"
+ )
def demonstrate_parliamentary_debate(parliament):
"""Demonstrate parliamentary debate functionality."""
-
+
print("\nPARLIAMENTARY DEBATE DEMONSTRATION")
print("=" * 60)
-
+
# Get a real MEP as sponsor
sponsor = list(parliament.meps.keys())[1]
-
+
# Create a test bill
bill = parliament.introduce_bill(
title="European Green Deal Implementation Act",
description="Legislation to implement the European Green Deal, including carbon neutrality targets, renewable energy investments, and sustainable development measures.",
bill_type=VoteType.ORDINARY_LEGISLATIVE_PROCEDURE,
committee="Environment, Public Health and Food Safety",
- sponsor=sponsor
+ sponsor=sponsor,
)
-
+
print(f"Bill: {bill.title}")
print(f"Description: {bill.description}")
-
+
# Conduct parliamentary debate
- print(f"\nCONDUCTING PARLIAMENTARY DEBATE...")
- debate_result = parliament.conduct_parliamentary_debate(bill, max_speakers=10)
-
- print(f"Debate Participants: {len(debate_result['participants'])} MEPs")
- print(f"Debate Analysis:")
- print(f" Support: {debate_result['analysis']['support_count']} speakers ({debate_result['analysis']['support_percentage']:.1f}%)")
- print(f" Oppose: {debate_result['analysis']['oppose_count']} speakers ({debate_result['analysis']['oppose_percentage']:.1f}%)")
- print(f" Neutral: {debate_result['analysis']['neutral_count']} speakers ({debate_result['analysis']['neutral_percentage']:.1f}%)")
+ print("\nCONDUCTING PARLIAMENTARY DEBATE...")
+ debate_result = parliament.conduct_parliamentary_debate(
+ bill, max_speakers=10
+ )
+
+ print(
+ f"Debate Participants: {len(debate_result['participants'])} MEPs"
+ )
+ print("Debate Analysis:")
+ print(
+ f" Support: {debate_result['analysis']['support_count']} speakers ({debate_result['analysis']['support_percentage']:.1f}%)"
+ )
+ print(
+ f" Oppose: {debate_result['analysis']['oppose_count']} speakers ({debate_result['analysis']['oppose_percentage']:.1f}%)"
+ )
+ print(
+ f" Neutral: {debate_result['analysis']['neutral_count']} speakers ({debate_result['analysis']['neutral_percentage']:.1f}%)"
+ )
def demonstrate_democratic_voting(parliament):
"""Demonstrate democratic voting functionality."""
-
+
print("\nDEMOCRATIC VOTING DEMONSTRATION")
print("=" * 60)
-
+
# Get a real MEP as sponsor
sponsor = list(parliament.meps.keys())[2]
-
+
# Create a test bill
bill = parliament.introduce_bill(
title="European Social Rights and Labor Protection Act",
description="Legislation to strengthen social rights, improve labor conditions, and ensure fair treatment of workers across the European Union.",
bill_type=VoteType.ORDINARY_LEGISLATIVE_PROCEDURE,
committee="Employment and Social Affairs",
- sponsor=sponsor
+ sponsor=sponsor,
)
-
+
print(f"Bill: {bill.title}")
print(f"Sponsor: {bill.sponsor}")
-
+
# Conduct democratic vote
- print(f"\nCONDUCTING DEMOCRATIC VOTE...")
+ print("\nCONDUCTING DEMOCRATIC VOTE...")
vote_result = parliament.conduct_democratic_vote(bill)
-
+
# Calculate percentages
- total_votes = vote_result.votes_for + vote_result.votes_against + vote_result.abstentions
- in_favor_percentage = (vote_result.votes_for / total_votes * 100) if total_votes > 0 else 0
- against_percentage = (vote_result.votes_against / total_votes * 100) if total_votes > 0 else 0
- abstentions_percentage = (vote_result.abstentions / total_votes * 100) if total_votes > 0 else 0
-
- print(f"Vote Results:")
+ total_votes = (
+ vote_result.votes_for
+ + vote_result.votes_against
+ + vote_result.abstentions
+ )
+ in_favor_percentage = (
+ (vote_result.votes_for / total_votes * 100)
+ if total_votes > 0
+ else 0
+ )
+ against_percentage = (
+ (vote_result.votes_against / total_votes * 100)
+ if total_votes > 0
+ else 0
+ )
+ abstentions_percentage = (
+ (vote_result.abstentions / total_votes * 100)
+ if total_votes > 0
+ else 0
+ )
+
+ print("Vote Results:")
print(f" Total Votes: {total_votes}")
- print(f" In Favor: {vote_result.votes_for} ({in_favor_percentage:.1f}%)")
- print(f" Against: {vote_result.votes_against} ({against_percentage:.1f}%)")
- print(f" Abstentions: {vote_result.abstentions} ({abstentions_percentage:.1f}%)")
+ print(
+ f" In Favor: {vote_result.votes_for} ({in_favor_percentage:.1f}%)"
+ )
+ print(
+ f" Against: {vote_result.votes_against} ({against_percentage:.1f}%)"
+ )
+ print(
+ f" Abstentions: {vote_result.abstentions} ({abstentions_percentage:.1f}%)"
+ )
print(f" Result: {vote_result.result.value}")
-
+
# Show political group breakdown if available
- if hasattr(vote_result, 'group_votes') and vote_result.group_votes:
- print(f"\nPOLITICAL GROUP BREAKDOWN:")
+ if (
+ hasattr(vote_result, "group_votes")
+ and vote_result.group_votes
+ ):
+ print("\nPOLITICAL GROUP BREAKDOWN:")
for group, votes in vote_result.group_votes.items():
- print(f" {group}: {votes['in_favor']}/{votes['total']} in favor ({votes['percentage']:.1f}%)")
+ print(
+ f" {group}: {votes['in_favor']}/{votes['total']} in favor ({votes['percentage']:.1f}%)"
+ )
else:
- print(f"\nIndividual votes recorded: {len(vote_result.individual_votes)} MEPs")
+ print(
+ f"\nIndividual votes recorded: {len(vote_result.individual_votes)} MEPs"
+ )
def demonstrate_complete_democratic_session(parliament):
"""Demonstrate a complete democratic parliamentary session."""
-
+
print("\nCOMPLETE DEMOCRATIC SESSION DEMONSTRATION")
print("=" * 60)
-
+
# Get a real MEP as sponsor
sponsor = list(parliament.meps.keys())[3]
-
+
# Run complete session
session_result = parliament.run_democratic_session(
bill_title="European Innovation and Technology Advancement Act",
bill_description="Comprehensive legislation to promote innovation, support technology startups, and establish Europe as a global leader in digital transformation and technological advancement.",
bill_type=VoteType.ORDINARY_LEGISLATIVE_PROCEDURE,
committee="Industry, Research and Energy",
- sponsor=sponsor
+ sponsor=sponsor,
)
-
- print(f"Session Results:")
+
+ print("Session Results:")
print(f" Bill: {session_result['bill'].title}")
- print(f" Committee Hearing: {session_result['hearing']['recommendations']['recommendation']}")
- print(f" Debate Participants: {len(session_result['debate']['participants'])} MEPs")
+ print(
+ f" Committee Hearing: {session_result['hearing']['recommendations']['recommendation']}"
+ )
+ print(
+ f" Debate Participants: {len(session_result['debate']['participants'])} MEPs"
+ )
print(f" Final Vote: {session_result['vote']['result']}")
- print(f" Vote Margin: {session_result['vote']['in_favor_percentage']:.1f}% in favor")
+ print(
+ f" Vote Margin: {session_result['vote']['in_favor_percentage']:.1f}% in favor"
+ )
def demonstrate_political_analysis(parliament):
"""Demonstrate political analysis and voting prediction."""
-
+
print("\nPOLITICAL ANALYSIS DEMONSTRATION")
print("=" * 60)
-
+
# Get a real MEP as sponsor
sponsor = list(parliament.meps.keys())[4]
-
+
# Create a test bill
bill = parliament.introduce_bill(
title="European Climate Action and Sustainability Act",
description="Comprehensive climate action legislation including carbon pricing, renewable energy targets, and sustainable development measures.",
bill_type=VoteType.ORDINARY_LEGISLATIVE_PROCEDURE,
committee="Environment, Public Health and Food Safety",
- sponsor=sponsor
+ sponsor=sponsor,
)
-
+
print(f"Bill: {bill.title}")
print(f"Sponsor: {bill.sponsor}")
-
+
# Analyze political landscape
analysis = parliament.analyze_political_landscape(bill)
-
- print(f"\nPOLITICAL LANDSCAPE ANALYSIS:")
+
+ print("\nPOLITICAL LANDSCAPE ANALYSIS:")
print(f" Overall Support: {analysis['overall_support']:.1f}%")
print(f" Opposition: {analysis['opposition']:.1f}%")
print(f" Uncertainty: {analysis['uncertainty']:.1f}%")
-
- print(f"\nPOLITICAL GROUP ANALYSIS:")
- for group, data in analysis['group_analysis'].items():
- print(f" {group}: {data['support']:.1f}% support, {data['opposition']:.1f}% opposition")
+
+ print("\nPOLITICAL GROUP ANALYSIS:")
+ for group, data in analysis["group_analysis"].items():
+ print(
+ f" {group}: {data['support']:.1f}% support, {data['opposition']:.1f}% opposition"
+ )
def demonstrate_hierarchical_democratic_voting(parliament):
"""Demonstrate hierarchical democratic voting with political group boards."""
-
+
print("\nHIERARCHICAL DEMOCRATIC VOTING DEMONSTRATION")
print("=" * 60)
-
+
# Get a real MEP as sponsor
sponsor = list(parliament.meps.keys())[5]
-
+
# Create a test bill
bill = parliament.introduce_bill(
title="European Climate Action and Sustainability Act",
description="Comprehensive climate action legislation including carbon pricing, renewable energy targets, and sustainable development measures.",
bill_type=VoteType.ORDINARY_LEGISLATIVE_PROCEDURE,
committee="Environment, Public Health and Food Safety",
- sponsor=sponsor
+ sponsor=sponsor,
)
-
+
print(f"Bill: {bill.title}")
print(f"Sponsor: {bill.sponsor}")
-
+
# Conduct hierarchical vote
- print(f"\nCONDUCTING HIERARCHICAL DEMOCRATIC VOTE...")
- hierarchical_result = parliament.conduct_hierarchical_democratic_vote(bill)
-
- print(f"Hierarchical Vote Results:")
+ print("\nCONDUCTING HIERARCHICAL DEMOCRATIC VOTE...")
+ hierarchical_result = (
+ parliament.conduct_hierarchical_democratic_vote(bill)
+ )
+
+ print("Hierarchical Vote Results:")
print(f" Total Votes: {hierarchical_result['total_votes']}")
- print(f" In Favor: {hierarchical_result['in_favor']} ({hierarchical_result['in_favor_percentage']:.1f}%)")
- print(f" Against: {hierarchical_result['against']} ({hierarchical_result['against_percentage']:.1f}%)")
+ print(
+ f" In Favor: {hierarchical_result['in_favor']} ({hierarchical_result['in_favor_percentage']:.1f}%)"
+ )
+ print(
+ f" Against: {hierarchical_result['against']} ({hierarchical_result['against_percentage']:.1f}%)"
+ )
print(f" Result: {hierarchical_result['result']}")
-
- print(f"\nPOLITICAL GROUP BOARD DECISIONS:")
- for group, decision in hierarchical_result['group_decisions'].items():
- print(f" {group}: {decision['decision']} ({decision['confidence']:.1f}% confidence)")
+
+ print("\nPOLITICAL GROUP BOARD DECISIONS:")
+ for group, decision in hierarchical_result[
+ "group_decisions"
+ ].items():
+ print(
+ f" {group}: {decision['decision']} ({decision['confidence']:.1f}% confidence)"
+ )
def demonstrate_complete_hierarchical_session(parliament):
"""Demonstrate a complete hierarchical democratic session."""
-
+
print("\nCOMPLETE HIERARCHICAL DEMOCRATIC SESSION DEMONSTRATION")
print("=" * 60)
-
+
# Get a real MEP as sponsor
sponsor = list(parliament.meps.keys())[6]
-
+
# Run complete hierarchical session
session_result = parliament.run_hierarchical_democratic_session(
bill_title="European Climate Action and Sustainability Act",
bill_description="Comprehensive climate action legislation including carbon pricing, renewable energy targets, and sustainable development measures.",
bill_type=VoteType.ORDINARY_LEGISLATIVE_PROCEDURE,
committee="Environment, Public Health and Food Safety",
- sponsor=sponsor
+ sponsor=sponsor,
)
-
- print(f"Hierarchical Session Results:")
+
+ print("Hierarchical Session Results:")
print(f" Bill: {session_result['bill'].title}")
- print(f" Committee Hearing: {session_result['hearing']['recommendations']['recommendation']}")
- print(f" Debate Participants: {len(session_result['debate']['participants'])} MEPs")
+ print(
+ f" Committee Hearing: {session_result['hearing']['recommendations']['recommendation']}"
+ )
+ print(
+ f" Debate Participants: {len(session_result['debate']['participants'])} MEPs"
+ )
print(f" Final Vote: {session_result['vote']['result']}")
- print(f" Vote Margin: {session_result['vote']['in_favor_percentage']:.1f}% in favor")
+ print(
+ f" Vote Margin: {session_result['vote']['in_favor_percentage']:.1f}% in favor"
+ )
def demonstrate_wikipedia_personalities(parliament):
"""Demonstrate the Wikipedia personality system for realistic MEP behavior."""
-
+
print("\nWIKIPEDIA PERSONALITY SYSTEM DEMONSTRATION")
print("=" * 60)
-
+
# Check if Wikipedia personalities are available
if not parliament.enable_wikipedia_personalities:
print("Wikipedia personality system not available")
- print("To enable: Install required dependencies and run Wikipedia scraper")
+ print(
+ "To enable: Install required dependencies and run Wikipedia scraper"
+ )
return
-
- print(f"Wikipedia personality system enabled")
- print(f"Loaded {len(parliament.personality_profiles)} personality profiles")
-
+
+ print("Wikipedia personality system enabled")
+ print(
+ f"Loaded {len(parliament.personality_profiles)} personality profiles"
+ )
+
# Show sample personality profiles
- print(f"\nSAMPLE PERSONALITY PROFILES:")
+ print("\nSAMPLE PERSONALITY PROFILES:")
print("-" * 40)
-
+
sample_count = 0
for mep_name, profile in parliament.personality_profiles.items():
if sample_count >= 3: # Show only 3 samples
break
-
+
print(f"\n{mep_name}")
- print(f" Wikipedia URL: {profile.wikipedia_url if profile.wikipedia_url else 'Not available'}")
- print(f" Summary: {profile.summary[:200]}..." if profile.summary else "No summary available")
- print(f" Political Views: {profile.political_views[:150]}..." if profile.political_views else "Based on party alignment")
- print(f" Policy Focus: {profile.policy_focus[:150]}..." if profile.policy_focus else "General parliamentary work")
- print(f" Achievements: {profile.achievements[:150]}..." if profile.achievements else "Parliamentary service")
+ print(
+ f" Wikipedia URL: {profile.wikipedia_url if profile.wikipedia_url else 'Not available'}"
+ )
+ print(
+ f" Summary: {profile.summary[:200]}..."
+ if profile.summary
+ else "No summary available"
+ )
+ print(
+ f" Political Views: {profile.political_views[:150]}..."
+ if profile.political_views
+ else "Based on party alignment"
+ )
+ print(
+ f" Policy Focus: {profile.policy_focus[:150]}..."
+ if profile.policy_focus
+ else "General parliamentary work"
+ )
+ print(
+ f" Achievements: {profile.achievements[:150]}..."
+ if profile.achievements
+ else "Parliamentary service"
+ )
print(f" Last Updated: {profile.last_updated}")
-
+
sample_count += 1
-
+
# Demonstrate personality-driven voting
- print(f"\nPERSONALITY-DRIVEN VOTING DEMONSTRATION:")
+ print("\nPERSONALITY-DRIVEN VOTING DEMONSTRATION:")
print("-" * 50)
-
+
# Create a test bill that would trigger different personality responses
bill = parliament.introduce_bill(
title="European Climate Action and Green Technology Investment Act",
description="Comprehensive legislation to accelerate Europe's transition to renewable energy, including massive investments in green technology, carbon pricing mechanisms, and support for affected industries and workers.",
bill_type=VoteType.ORDINARY_LEGISLATIVE_PROCEDURE,
committee="Environment",
- sponsor="Climate Action Leader"
+ sponsor="Climate Action Leader",
)
-
+
print(f"Bill: {bill.title}")
print(f"Description: {bill.description}")
-
+
# Show how different MEPs with Wikipedia personalities would respond
- print(f"\nPERSONALITY-BASED RESPONSES:")
+ print("\nPERSONALITY-BASED RESPONSES:")
print("-" * 40)
-
+
sample_meps = list(parliament.personality_profiles.keys())[:3]
-
+
for mep_name in sample_meps:
mep = parliament.meps.get(mep_name)
profile = parliament.personality_profiles.get(mep_name)
-
+
if mep and profile:
print(f"\n{mep_name} ({mep.political_group})")
-
+
# Show personality influence
if profile.political_views:
- print(f" Political Views: {profile.political_views[:100]}...")
-
+ print(
+ f" Political Views: {profile.political_views[:100]}..."
+ )
+
if profile.policy_focus:
- print(f" Policy Focus: {profile.policy_focus[:100]}...")
-
+ print(
+ f" Policy Focus: {profile.policy_focus[:100]}..."
+ )
+
# Predict voting behavior based on personality
- if "environment" in profile.policy_focus.lower() or "climate" in profile.political_views.lower():
+ if (
+ "environment" in profile.policy_focus.lower()
+ or "climate" in profile.political_views.lower()
+ ):
predicted_vote = "LIKELY SUPPORT"
- reasoning = "Environmental policy focus and climate advocacy"
- elif "economic" in profile.policy_focus.lower() or "business" in profile.political_views.lower():
+ reasoning = (
+ "Environmental policy focus and climate advocacy"
+ )
+ elif (
+ "economic" in profile.policy_focus.lower()
+ or "business" in profile.political_views.lower()
+ ):
predicted_vote = "LIKELY OPPOSE"
reasoning = "Economic concerns about investment costs"
else:
predicted_vote = "UNCERTAIN"
- reasoning = "Mixed considerations based on party alignment"
-
+ reasoning = (
+ "Mixed considerations based on party alignment"
+ )
+
print(f" Predicted Vote: {predicted_vote}")
print(f" Reasoning: {reasoning}")
-
+
# Demonstrate scraping functionality
- print(f"\nWIKIPEDIA SCRAPING CAPABILITIES:")
+ print("\nWIKIPEDIA SCRAPING CAPABILITIES:")
print("-" * 50)
print("Can scrape Wikipedia data for all 717 MEPs")
- print("Extracts political views, career history, and achievements")
+ print(
+ "Extracts political views, career history, and achievements"
+ )
print("Creates detailed personality profiles in JSON format")
- print("Integrates real personality data into AI agent system prompts")
+ print(
+ "Integrates real personality data into AI agent system prompts"
+ )
print("Enables realistic, personality-driven voting behavior")
print("Respectful API usage with configurable delays")
-
- print(f"\nTo scrape all MEP personalities:")
+
+ print("\nTo scrape all MEP personalities:")
print(" parliament.scrape_wikipedia_personalities(delay=1.0)")
- print(" # This will create personality profiles for all 717 MEPs")
+ print(
+ " # This will create personality profiles for all 717 MEPs"
+ )
print(" # Profiles are saved in 'mep_personalities/' directory")
def demonstrate_optimized_parliamentary_session(parliament):
"""Demonstrate cost-optimized parliamentary session."""
-
+
print("\nCOST-OPTIMIZED PARLIAMENTARY SESSION DEMONSTRATION")
print("=" * 60)
-
+
# Run optimized session with cost limit
session_result = parliament.run_optimized_parliamentary_session(
bill_title="European Digital Rights and Privacy Protection Act",
bill_description="Comprehensive legislation to strengthen digital rights, enhance privacy protection, and establish clear guidelines for data handling across the European Union.",
bill_type=VoteType.ORDINARY_LEGISLATIVE_PROCEDURE,
committee="Legal Affairs",
- max_cost=25.0 # Max $25 for this session
- )
-
- print(f"Session Results:")
- print(f" Bill: {session_result['session_summary']['bill_title']}")
- print(f" Final Outcome: {session_result['session_summary']['final_outcome']}")
- print(f" Total Cost: ${session_result['session_summary']['total_cost']:.2f}")
- print(f" Budget Remaining: ${session_result['cost_stats']['budget_remaining']:.2f}")
-
+ max_cost=25.0, # Max $25 for this session
+ )
+
+ print("Session Results:")
+ print(
+ f" Bill: {session_result['session_summary']['bill_title']}"
+ )
+ print(
+ f" Final Outcome: {session_result['session_summary']['final_outcome']}"
+ )
+ print(
+ f" Total Cost: ${session_result['session_summary']['total_cost']:.2f}"
+ )
+ print(
+ f" Budget Remaining: ${session_result['cost_stats']['budget_remaining']:.2f}"
+ )
+
# Show detailed cost statistics
cost_stats = parliament.get_cost_statistics()
- print(f"\nDETAILED COST STATISTICS:")
+ print("\nDETAILED COST STATISTICS:")
print(f" Total Tokens Used: {cost_stats['total_tokens']:,}")
print(f" Requests Made: {cost_stats['requests_made']}")
print(f" Cache Hits: {cost_stats['cache_hits']}")
print(f" Cache Hit Rate: {cost_stats['cache_hit_rate']:.1%}")
- print(f" Loading Efficiency: {cost_stats['loading_efficiency']:.1%}")
+ print(
+ f" Loading Efficiency: {cost_stats['loading_efficiency']:.1%}"
+ )
print(f" Cache Size: {cost_stats['cache_size']} entries")
-
+
return session_result
def main():
"""Main demonstration function."""
-
+
print("EUROSWARM PARLIAMENT - COST OPTIMIZED DEMONSTRATION")
print("=" * 60)
- print("This demonstration shows the EuroSwarm Parliament with cost optimization features:")
+ print(
+ "This demonstration shows the EuroSwarm Parliament with cost optimization features:"
+ )
print("• Lazy loading of MEP agents (only create when needed)")
print("• Response caching (avoid repeated API calls)")
print("• Batch processing (control memory and cost)")
print("• Budget controls (hard limits on spending)")
print("• Cost tracking (real-time monitoring)")
-
+
# Initialize parliament with cost optimization
parliament = demonstrate_parliament_initialization()
-
+
# Demonstrate individual MEP interaction (will trigger lazy loading)
demonstrate_individual_mep_interaction(parliament)
-
+
# Demonstrate committee work with cost optimization
demonstrate_committee_work(parliament)
-
+
# Demonstrate parliamentary debate with cost optimization
demonstrate_parliamentary_debate(parliament)
-
+
# Demonstrate democratic voting with cost optimization
demonstrate_democratic_voting(parliament)
-
+
# Demonstrate political analysis with cost optimization
demonstrate_political_analysis(parliament)
-
+
# Demonstrate optimized parliamentary session
demonstrate_optimized_parliamentary_session(parliament)
-
+
# Show final cost statistics
final_stats = parliament.get_cost_statistics()
- print(f"\nFINAL COST STATISTICS:")
+ print("\nFINAL COST STATISTICS:")
print(f"Total Cost: ${final_stats['total_cost']:.2f}")
print(f"Budget Remaining: ${final_stats['budget_remaining']:.2f}")
print(f"Cache Hit Rate: {final_stats['cache_hit_rate']:.1%}")
- print(f"Loading Efficiency: {final_stats['loading_efficiency']:.1%}")
-
- print(f"\n✅ COST OPTIMIZATION DEMONSTRATION COMPLETED!")
- print(f"✅ EuroSwarm Parliament now supports cost-effective large-scale simulations")
- print(f"✅ Lazy loading: {final_stats['loaded_meps']}/{final_stats['total_meps']} MEPs loaded")
+ print(
+ f"Loading Efficiency: {final_stats['loading_efficiency']:.1%}"
+ )
+
+ print("\n✅ COST OPTIMIZATION DEMONSTRATION COMPLETED!")
+ print(
+ "✅ EuroSwarm Parliament now supports cost-effective large-scale simulations"
+ )
+ print(
+ f"✅ Lazy loading: {final_stats['loaded_meps']}/{final_stats['total_meps']} MEPs loaded"
+ )
print(f"✅ Caching: {final_stats['cache_hit_rate']:.1%} hit rate")
- print(f"✅ Budget control: ${final_stats['total_cost']:.2f} spent of ${final_stats['budget_remaining'] + final_stats['total_cost']:.2f} budget")
+ print(
+ f"✅ Budget control: ${final_stats['total_cost']:.2f} spent of ${final_stats['budget_remaining'] + final_stats['total_cost']:.2f} budget"
+ )
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/examples/simulations/euroswarm_parliament/mass_agent_template.py b/examples/simulations/euroswarm_parliament/mass_agent_template.py
index a6e29d8c..fc42e3f9 100644
--- a/examples/simulations/euroswarm_parliament/mass_agent_template.py
+++ b/examples/simulations/euroswarm_parliament/mass_agent_template.py
@@ -17,13 +17,10 @@ Key Features:
import os
import random
import json
-import time
import hashlib
-from typing import Dict, List, Optional, Union, Any, Set
+from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
from enum import Enum
-from datetime import datetime
-from functools import lru_cache
from swarms import Agent
from swarms.structs.multi_agent_exec import run_agents_concurrently
@@ -31,10 +28,6 @@ from swarms.structs.board_of_directors_swarm import (
BoardOfDirectorsSwarm,
BoardMember,
BoardMemberRole,
- BoardDecisionType,
- BoardSpec,
- BoardOrder,
- BoardDecision,
enable_board_feature,
)
from swarms.utils.loguru_logger import initialize_logger
@@ -48,7 +41,7 @@ enable_board_feature()
class AgentRole(str, Enum):
"""Enumeration of agent roles and specializations."""
-
+
WORKER = "worker"
MANAGER = "manager"
SPECIALIST = "specialist"
@@ -61,7 +54,7 @@ class AgentRole(str, Enum):
class AgentCategory(str, Enum):
"""Enumeration of agent categories for organization."""
-
+
TECHNICAL = "technical"
CREATIVE = "creative"
ANALYTICAL = "analytical"
@@ -74,7 +67,7 @@ class AgentCategory(str, Enum):
class AgentProfile:
"""
Represents a single agent in the mass agent system.
-
+
Attributes:
name: Unique name of the agent
role: Primary role of the agent
@@ -86,7 +79,7 @@ class AgentProfile:
agent: The AI agent instance (lazy loaded)
is_loaded: Whether the agent has been instantiated
"""
-
+
name: str
role: AgentRole
category: AgentCategory
@@ -102,7 +95,7 @@ class AgentProfile:
class AgentGroup:
"""
Represents a group of agents with similar roles or categories.
-
+
Attributes:
name: Name of the group
category: Category of the group
@@ -112,7 +105,7 @@ class AgentGroup:
group_swarm: Board of Directors swarm for this group
is_swarm_loaded: Whether the swarm has been instantiated
"""
-
+
name: str
category: AgentCategory
agents: List[str] = field(default_factory=list)
@@ -125,28 +118,30 @@ class AgentGroup:
@dataclass
class CostTracker:
"""Track costs and usage for budget management."""
-
+
total_tokens_used: int = 0
total_cost_estimate: float = 0.0
budget_limit: float = 100.0 # Default $100 budget
token_cost_per_1m: float = 0.15 # GPT-4o-mini cost
requests_made: int = 0
cache_hits: int = 0
-
+
def add_tokens(self, tokens: int):
"""Add tokens used and calculate cost."""
self.total_tokens_used += tokens
- self.total_cost_estimate = (self.total_tokens_used / 1_000_000) * self.token_cost_per_1m
+ self.total_cost_estimate = (
+ self.total_tokens_used / 1_000_000
+ ) * self.token_cost_per_1m
self.requests_made += 1
-
+
def add_cache_hit(self):
"""Record a cache hit."""
self.cache_hits += 1
-
+
def check_budget(self) -> bool:
"""Check if within budget."""
return self.total_cost_estimate <= self.budget_limit
-
+
def get_stats(self) -> Dict[str, Any]:
"""Get cost statistics."""
return {
@@ -154,19 +149,22 @@ class CostTracker:
"total_cost": self.total_cost_estimate,
"requests_made": self.requests_made,
"cache_hits": self.cache_hits,
- "cache_hit_rate": self.cache_hits / max(1, self.requests_made + self.cache_hits),
- "budget_remaining": max(0, self.budget_limit - self.total_cost_estimate)
+ "cache_hit_rate": self.cache_hits
+ / max(1, self.requests_made + self.cache_hits),
+ "budget_remaining": max(
+ 0, self.budget_limit - self.total_cost_estimate
+ ),
}
class MassAgentTemplate:
"""
Template for creating large-scale multi-agent systems with cost optimization.
-
+
This class provides a framework for generating hundreds of agents on the fly,
organizing them into groups, and managing their interactions with cost controls.
"""
-
+
def __init__(
self,
data_source: str = None, # Path to data file (CSV, JSON, XML, etc.)
@@ -181,7 +179,7 @@ class MassAgentTemplate:
):
"""
Initialize the Mass Agent Template with cost optimization.
-
+
Args:
data_source: Path to data file containing agent information
agent_count: Target number of agents to generate
@@ -195,65 +193,78 @@ class MassAgentTemplate:
"""
self.data_source = data_source
self.agent_count = agent_count
- self.enable_hierarchical_organization = enable_hierarchical_organization
+ self.enable_hierarchical_organization = (
+ enable_hierarchical_organization
+ )
self.enable_group_swarms = enable_group_swarms
self.enable_lazy_loading = enable_lazy_loading
self.enable_caching = enable_caching
self.batch_size = batch_size
self.verbose = verbose
-
+
# Initialize cost tracking
self.cost_tracker = CostTracker(budget_limit=budget_limit)
-
+
# Initialize agent storage
self.agents: Dict[str, AgentProfile] = {}
self.groups: Dict[str, AgentGroup] = {}
self.categories: Dict[AgentCategory, List[str]] = {}
-
+
# Initialize caching
self.response_cache: Dict[str, str] = {}
-
+
# Load agent profiles (without creating agents)
self._load_agent_profiles()
-
+
if self.enable_hierarchical_organization:
self._organize_agents()
-
+
if self.verbose:
- logger.info(f"Mass Agent Template initialized with {len(self.agents)} agent profiles")
- logger.info(f"Lazy loading: {self.enable_lazy_loading}, Caching: {self.enable_caching}")
- logger.info(f"Budget limit: ${budget_limit}, Batch size: {batch_size}")
-
+ logger.info(
+ f"Mass Agent Template initialized with {len(self.agents)} agent profiles"
+ )
+ logger.info(
+ f"Lazy loading: {self.enable_lazy_loading}, Caching: {self.enable_caching}"
+ )
+ logger.info(
+ f"Budget limit: ${budget_limit}, Batch size: {batch_size}"
+ )
+
def _load_agent_profiles(self) -> List[Dict[str, Any]]:
"""
Load agent profiles from the specified data source.
-
+
This method loads agent data but doesn't create AI agents yet (lazy loading).
-
+
Returns:
List[Dict[str, Any]]: List of agent data dictionaries
"""
agent_data = []
-
+
if self.data_source and os.path.exists(self.data_source):
# Load from file - customize based on your data format
try:
- if self.data_source.endswith('.json'):
- with open(self.data_source, 'r', encoding='utf-8') as f:
+ if self.data_source.endswith(".json"):
+ with open(
+ self.data_source, "r", encoding="utf-8"
+ ) as f:
agent_data = json.load(f)
- elif self.data_source.endswith('.csv'):
+ elif self.data_source.endswith(".csv"):
import pandas as pd
+
df = pd.read_csv(self.data_source)
- agent_data = df.to_dict('records')
+ agent_data = df.to_dict("records")
else:
- logger.warning(f"Unsupported data format: {self.data_source}")
+ logger.warning(
+ f"Unsupported data format: {self.data_source}"
+ )
except Exception as e:
logger.error(f"Error loading agent data: {e}")
-
+
# If no data loaded, generate synthetic data
if not agent_data:
agent_data = self._generate_synthetic_data()
-
+
# Create agent profiles (without instantiating agents)
for data in agent_data:
agent_profile = AgentProfile(
@@ -265,68 +276,72 @@ class MassAgentTemplate:
skills=data["skills"],
experience_level=data["experience_level"],
agent=None, # Will be created on demand
- is_loaded=False
+ is_loaded=False,
)
-
+
self.agents[data["name"]] = agent_profile
-
+
return agent_data
-
+
def _load_agent(self, agent_name: str) -> Optional[Agent]:
"""
Lazy load a single agent on demand.
-
+
Args:
agent_name: Name of the agent to load
-
+
Returns:
Optional[Agent]: Loaded agent or None if not found
"""
if agent_name not in self.agents:
return None
-
+
profile = self.agents[agent_name]
-
+
# Check if already loaded
if profile.is_loaded and profile.agent:
return profile.agent
-
+
# Create agent (no cost for creation, only for running)
profile.agent = self._create_agent(profile)
profile.is_loaded = True
-
+
if self.verbose:
logger.info(f"Loaded agent: {agent_name}")
-
+
return profile.agent
-
- def _load_agents_batch(self, agent_names: List[str]) -> List[Agent]:
+
+ def _load_agents_batch(
+ self, agent_names: List[str]
+ ) -> List[Agent]:
"""
Load multiple agents in a batch.
-
+
Args:
agent_names: List of agent names to load
-
+
Returns:
List[Agent]: List of loaded agents
"""
loaded_agents = []
-
+
for agent_name in agent_names:
agent = self._load_agent(agent_name)
if agent:
loaded_agents.append(agent)
-
+
return loaded_agents
-
- def _get_cache_key(self, task: str, agent_names: List[str]) -> str:
+
+ def _get_cache_key(
+ self, task: str, agent_names: List[str]
+ ) -> str:
"""
Generate a cache key for a task and agent combination.
-
+
Args:
task: Task to execute
agent_names: List of agent names
-
+
Returns:
str: Cache key
"""
@@ -334,32 +349,32 @@ class MassAgentTemplate:
sorted_agents = sorted(agent_names)
content = f"{task}:{':'.join(sorted_agents)}"
return hashlib.md5(content.encode()).hexdigest()
-
+
def _check_cache(self, cache_key: str) -> Optional[str]:
"""
Check if a response is cached.
-
+
Args:
cache_key: Cache key to check
-
+
Returns:
Optional[str]: Cached response or None
"""
if not self.enable_caching:
return None
-
+
cached_response = self.response_cache.get(cache_key)
if cached_response:
self.cost_tracker.add_cache_hit()
if self.verbose:
logger.info(f"Cache hit for key: {cache_key[:20]}...")
-
+
return cached_response
-
+
def _cache_response(self, cache_key: str, response: str):
"""
Cache a response.
-
+
Args:
cache_key: Cache key
response: Response to cache
@@ -367,101 +382,167 @@ class MassAgentTemplate:
if self.enable_caching:
self.response_cache[cache_key] = response
if self.verbose:
- logger.info(f"Cached response for key: {cache_key[:20]}...")
-
+ logger.info(
+ f"Cached response for key: {cache_key[:20]}..."
+ )
+
def _generate_synthetic_data(self) -> List[Dict[str, Any]]:
"""
Generate synthetic agent data for demonstration purposes.
-
+
Returns:
List[Dict[str, Any]]: List of synthetic agent data
"""
synthetic_data = []
-
+
# Define sample data for different agent types
sample_agents = [
{
"name": "Alex_Developer",
"role": AgentRole.SPECIALIST,
"category": AgentCategory.TECHNICAL,
- "specialization": ["Python", "Machine Learning", "API Development"],
- "personality_traits": ["analytical", "detail-oriented", "problem-solver"],
- "skills": ["Python", "TensorFlow", "FastAPI", "Docker"],
- "experience_level": "senior"
+ "specialization": [
+ "Python",
+ "Machine Learning",
+ "API Development",
+ ],
+ "personality_traits": [
+ "analytical",
+ "detail-oriented",
+ "problem-solver",
+ ],
+ "skills": [
+ "Python",
+ "TensorFlow",
+ "FastAPI",
+ "Docker",
+ ],
+ "experience_level": "senior",
},
{
"name": "Sarah_Designer",
"role": AgentRole.CREATOR,
"category": AgentCategory.CREATIVE,
- "specialization": ["UI/UX Design", "Visual Design", "Brand Identity"],
- "personality_traits": ["creative", "user-focused", "aesthetic"],
- "skills": ["Figma", "Adobe Creative Suite", "User Research", "Prototyping"],
- "experience_level": "senior"
+ "specialization": [
+ "UI/UX Design",
+ "Visual Design",
+ "Brand Identity",
+ ],
+ "personality_traits": [
+ "creative",
+ "user-focused",
+ "aesthetic",
+ ],
+ "skills": [
+ "Figma",
+ "Adobe Creative Suite",
+ "User Research",
+ "Prototyping",
+ ],
+ "experience_level": "senior",
},
{
"name": "Mike_Analyst",
"role": AgentRole.ANALYST,
"category": AgentCategory.ANALYTICAL,
- "specialization": ["Data Analysis", "Business Intelligence", "Market Research"],
- "personality_traits": ["data-driven", "curious", "insightful"],
+ "specialization": [
+ "Data Analysis",
+ "Business Intelligence",
+ "Market Research",
+ ],
+ "personality_traits": [
+ "data-driven",
+ "curious",
+ "insightful",
+ ],
"skills": ["SQL", "Python", "Tableau", "Statistics"],
- "experience_level": "expert"
+ "experience_level": "expert",
},
{
"name": "Lisa_Manager",
"role": AgentRole.MANAGER,
"category": AgentCategory.STRATEGIC,
- "specialization": ["Project Management", "Team Leadership", "Strategic Planning"],
- "personality_traits": ["organized", "leadership", "strategic"],
- "skills": ["Agile", "Scrum", "Risk Management", "Stakeholder Communication"],
- "experience_level": "senior"
+ "specialization": [
+ "Project Management",
+ "Team Leadership",
+ "Strategic Planning",
+ ],
+ "personality_traits": [
+ "organized",
+ "leadership",
+ "strategic",
+ ],
+ "skills": [
+ "Agile",
+ "Scrum",
+ "Risk Management",
+ "Stakeholder Communication",
+ ],
+ "experience_level": "senior",
},
{
"name": "Tom_Coordinator",
"role": AgentRole.COORDINATOR,
"category": AgentCategory.OPERATIONAL,
- "specialization": ["Process Optimization", "Workflow Management", "Resource Allocation"],
- "personality_traits": ["efficient", "coordinated", "systematic"],
- "skills": ["Process Mapping", "Automation", "Resource Planning", "Quality Assurance"],
- "experience_level": "senior"
- }
+ "specialization": [
+ "Process Optimization",
+ "Workflow Management",
+ "Resource Allocation",
+ ],
+ "personality_traits": [
+ "efficient",
+ "coordinated",
+ "systematic",
+ ],
+ "skills": [
+ "Process Mapping",
+ "Automation",
+ "Resource Planning",
+ "Quality Assurance",
+ ],
+ "experience_level": "senior",
+ },
]
-
+
# Generate the specified number of agents
for i in range(self.agent_count):
# Use sample data as template and create variations
template = random.choice(sample_agents)
-
+
agent_data = {
"name": f"{template['name']}_{i:04d}",
"role": template["role"],
"category": template["category"],
"specialization": template["specialization"].copy(),
- "personality_traits": template["personality_traits"].copy(),
+ "personality_traits": template[
+ "personality_traits"
+ ].copy(),
"skills": template["skills"].copy(),
- "experience_level": template["experience_level"]
+ "experience_level": template["experience_level"],
}
-
+
# Add some randomization for variety
if random.random() < 0.3:
- agent_data["experience_level"] = random.choice(["junior", "senior", "expert"])
-
+ agent_data["experience_level"] = random.choice(
+ ["junior", "senior", "expert"]
+ )
+
synthetic_data.append(agent_data)
-
+
return synthetic_data
-
+
def _create_agent(self, profile: AgentProfile) -> Agent:
"""
Create an AI agent for the given profile.
-
+
Args:
profile: Agent profile data
-
+
Returns:
Agent: AI agent instance
"""
system_prompt = self._generate_agent_system_prompt(profile)
-
+
return Agent(
agent_name=profile.name,
system_prompt=system_prompt,
@@ -469,14 +550,16 @@ class MassAgentTemplate:
max_loops=3,
verbose=self.verbose,
)
-
- def _generate_agent_system_prompt(self, profile: AgentProfile) -> str:
+
+ def _generate_agent_system_prompt(
+ self, profile: AgentProfile
+ ) -> str:
"""
Generate a comprehensive system prompt for an agent.
-
+
Args:
profile: Agent profile data
-
+
Returns:
str: System prompt for the agent
"""
@@ -513,12 +596,12 @@ When working on tasks:
Remember: You are part of a large multi-agent system. Your unique combination of role, skills, and personality makes you valuable to the team.
"""
-
+
return prompt
-
+
def _get_role_responsibilities(self, role: AgentRole) -> str:
"""Get responsibilities for a specific role."""
-
+
responsibilities = {
AgentRole.WORKER: """
- Execute assigned tasks efficiently and accurately
@@ -526,126 +609,137 @@ Remember: You are part of a large multi-agent system. Your unique combination of
- Report progress and any issues encountered
- Maintain quality standards in all work
- Collaborate with team members as needed""",
-
AgentRole.MANAGER: """
- Oversee team activities and coordinate efforts
- Set priorities and allocate resources
- Monitor progress and ensure deadlines are met
- Provide guidance and support to team members
- Make strategic decisions for the team""",
-
AgentRole.SPECIALIST: """
- Provide expert knowledge in specific domains
- Solve complex technical problems
- Mentor other agents in your area of expertise
- Stay updated on latest developments in your field
- Contribute specialized insights to projects""",
-
AgentRole.COORDINATOR: """
- Facilitate communication between different groups
- Ensure smooth workflow and process optimization
- Manage dependencies and resource allocation
- Track project timelines and milestones
- Resolve conflicts and bottlenecks""",
-
AgentRole.ANALYST: """
- Analyze data and extract meaningful insights
- Identify patterns and trends
- Provide evidence-based recommendations
- Create reports and visualizations
- Support decision-making with data""",
-
AgentRole.CREATOR: """
- Generate innovative ideas and solutions
- Design and develop new content or products
- Think creatively and outside the box
- Prototype and iterate on concepts
- Inspire and motivate other team members""",
-
AgentRole.VALIDATOR: """
- Review and validate work quality
- Ensure compliance with standards and requirements
- Provide constructive feedback
- Identify potential issues and risks
- Maintain quality assurance processes""",
-
AgentRole.EXECUTOR: """
- Implement plans and strategies
- Execute tasks with precision and efficiency
- Adapt to changing circumstances
- Ensure successful completion of objectives
-- Maintain focus on results and outcomes"""
+- Maintain focus on results and outcomes""",
}
-
- return responsibilities.get(role, "Execute tasks according to your role and expertise.")
-
+
+ return responsibilities.get(
+ role,
+ "Execute tasks according to your role and expertise.",
+ )
+
def _organize_agents(self):
"""Organize agents into groups and categories."""
-
+
# Organize by category
for agent_name, profile in self.agents.items():
category = profile.category
if category not in self.categories:
self.categories[category] = []
self.categories[category].append(agent_name)
-
+
# Create groups for each category
for category, agent_names in self.categories.items():
group_name = f"{category.value.capitalize()}_Group"
-
+
# Select a leader (first agent in the category)
leader = agent_names[0] if agent_names else None
-
+
group = AgentGroup(
name=group_name,
category=category,
agents=agent_names,
leader=leader,
- total_agents=len(agent_names)
+ total_agents=len(agent_names),
)
-
+
self.groups[group_name] = group
-
+
if self.verbose:
- logger.info(f"Organized agents into {len(self.groups)} groups")
-
+ logger.info(
+ f"Organized agents into {len(self.groups)} groups"
+ )
+
def _create_group_swarms(self):
"""Create Board of Directors swarms for each group."""
-
+
for group_name, group in self.groups.items():
if not group.agents:
continue
-
+
# Create board members from group agents
board_members = []
-
+
# Add group leader as chairman
if group.leader and group.leader in self.agents:
leader_profile = self.agents[group.leader]
if leader_profile.agent:
- board_members.append(BoardMember(
- agent=leader_profile.agent,
- role=BoardMemberRole.CHAIRMAN,
- voting_weight=1.0,
- expertise_areas=leader_profile.specialization
- ))
-
+ board_members.append(
+ BoardMember(
+ agent=leader_profile.agent,
+ role=BoardMemberRole.CHAIRMAN,
+ voting_weight=1.0,
+ expertise_areas=leader_profile.specialization,
+ )
+ )
+
# Add other agents as board members
- for agent_name in group.agents[:5]: # Limit to 5 board members
- if agent_name != group.leader and agent_name in self.agents:
+ for agent_name in group.agents[
+ :5
+ ]: # Limit to 5 board members
+ if (
+ agent_name != group.leader
+ and agent_name in self.agents
+ ):
profile = self.agents[agent_name]
if profile.agent:
- board_members.append(BoardMember(
- agent=profile.agent,
- role=BoardMemberRole.EXECUTIVE_DIRECTOR,
- voting_weight=0.8,
- expertise_areas=profile.specialization
- ))
-
+ board_members.append(
+ BoardMember(
+ agent=profile.agent,
+ role=BoardMemberRole.EXECUTIVE_DIRECTOR,
+ voting_weight=0.8,
+ expertise_areas=profile.specialization,
+ )
+ )
+
# Create Board of Directors swarm
if board_members:
- agents = [member.agent for member in board_members if member.agent is not None]
-
+ agents = [
+ member.agent
+ for member in board_members
+ if member.agent is not None
+ ]
+
group.group_swarm = BoardOfDirectorsSwarm(
name=group_name,
description=f"Specialized swarm for {group_name} with expertise in {group.category.value}",
@@ -655,78 +749,94 @@ Remember: You are part of a large multi-agent system. Your unique combination of
verbose=self.verbose,
decision_threshold=0.6,
enable_voting=True,
- enable_consensus=True
+ enable_consensus=True,
)
-
+
if self.verbose:
- logger.info(f"Created {len([g for g in self.groups.values() if g.group_swarm])} group swarms")
-
+ logger.info(
+ f"Created {len([g for g in self.groups.values() if g.group_swarm])} group swarms"
+ )
+
def get_agent(self, agent_name: str) -> Optional[AgentProfile]:
"""
Get a specific agent by name.
-
+
Args:
agent_name: Name of the agent
-
+
Returns:
Optional[AgentProfile]: Agent profile if found, None otherwise
"""
return self.agents.get(agent_name)
-
+
def get_group(self, group_name: str) -> Optional[AgentGroup]:
"""
Get a specific group by name.
-
+
Args:
group_name: Name of the group
-
+
Returns:
Optional[AgentGroup]: Group if found, None otherwise
"""
return self.groups.get(group_name)
-
- def get_agents_by_category(self, category: AgentCategory) -> List[str]:
+
+ def get_agents_by_category(
+ self, category: AgentCategory
+ ) -> List[str]:
"""
Get all agents in a specific category.
-
+
Args:
category: Agent category
-
+
Returns:
List[str]: List of agent names in the category
"""
return self.categories.get(category, [])
-
+
def get_agents_by_role(self, role: AgentRole) -> List[str]:
"""
Get all agents with a specific role.
-
+
Args:
role: Agent role
-
+
Returns:
List[str]: List of agent names with the role
"""
- return [name for name, profile in self.agents.items() if profile.role == role]
-
- def run_mass_task(self, task: str, agent_count: int = 10) -> Dict[str, Any]:
+ return [
+ name
+ for name, profile in self.agents.items()
+ if profile.role == role
+ ]
+
+ def run_mass_task(
+ self, task: str, agent_count: int = 10
+ ) -> Dict[str, Any]:
"""
Run a task with multiple agents working in parallel with cost optimization.
-
+
Args:
task: Task to execute
agent_count: Number of agents to use
-
+
Returns:
Dict[str, Any]: Results from the mass task execution
"""
# Check budget before starting
if not self.cost_tracker.check_budget():
- return {"error": "Budget exceeded", "cost_stats": self.cost_tracker.get_stats()}
-
+ return {
+ "error": "Budget exceeded",
+ "cost_stats": self.cost_tracker.get_stats(),
+ }
+
# Select random agents
- selected_agent_names = random.sample(list(self.agents.keys()), min(agent_count, len(self.agents)))
-
+ selected_agent_names = random.sample(
+ list(self.agents.keys()),
+ min(agent_count, len(self.agents)),
+ )
+
# Check cache first
cache_key = self._get_cache_key(task, selected_agent_names)
cached_result = self._check_cache(cache_key)
@@ -737,132 +847,160 @@ Remember: You are part of a large multi-agent system. Your unique combination of
"results": cached_result,
"total_agents": len(selected_agent_names),
"cached": True,
- "cost_stats": self.cost_tracker.get_stats()
+ "cost_stats": self.cost_tracker.get_stats(),
}
-
+
# Process in batches to control memory and cost
all_results = []
total_processed = 0
-
+
for i in range(0, len(selected_agent_names), self.batch_size):
- batch_names = selected_agent_names[i:i + self.batch_size]
-
+ batch_names = selected_agent_names[
+ i : i + self.batch_size
+ ]
+
# Check budget for this batch
if not self.cost_tracker.check_budget():
- logger.warning(f"Budget exceeded after processing {total_processed} agents")
- logger.warning(f"Current cost: ${self.cost_tracker.total_cost_estimate:.4f}, Budget: ${self.cost_tracker.budget_limit:.2f}")
+ logger.warning(
+ f"Budget exceeded after processing {total_processed} agents"
+ )
+ logger.warning(
+ f"Current cost: ${self.cost_tracker.total_cost_estimate:.4f}, Budget: ${self.cost_tracker.budget_limit:.2f}"
+ )
break
-
+
# Load agents for this batch
batch_agents = self._load_agents_batch(batch_names)
-
+
if not batch_agents:
continue
-
+
# Run batch
try:
- batch_results = run_agents_concurrently(batch_agents, task)
+ batch_results = run_agents_concurrently(
+ batch_agents, task
+ )
all_results.extend(batch_results)
total_processed += len(batch_agents)
-
+
# Estimate tokens used (more realistic approximation)
# Include both input tokens (task) and output tokens (response)
- task_tokens = len(task.split()) * 1.3 # ~1.3 tokens per word
- response_tokens = len(batch_agents) * 200 # ~200 tokens per response
+ task_tokens = (
+ len(task.split()) * 1.3
+ ) # ~1.3 tokens per word
+ response_tokens = (
+ len(batch_agents) * 200
+ ) # ~200 tokens per response
total_tokens = int(task_tokens + response_tokens)
self.cost_tracker.add_tokens(total_tokens)
-
+
if self.verbose:
- logger.info(f"Processed batch {i//self.batch_size + 1}: {len(batch_agents)} agents")
- logger.info(f"Current cost: ${self.cost_tracker.total_cost_estimate:.4f}, Budget remaining: ${self.cost_tracker.budget_limit - self.cost_tracker.total_cost_estimate:.2f}")
-
+ logger.info(
+ f"Processed batch {i//self.batch_size + 1}: {len(batch_agents)} agents"
+ )
+ logger.info(
+ f"Current cost: ${self.cost_tracker.total_cost_estimate:.4f}, Budget remaining: ${self.cost_tracker.budget_limit - self.cost_tracker.total_cost_estimate:.2f}"
+ )
+
except Exception as e:
logger.error(f"Error processing batch: {e}")
continue
-
+
# Cache the results
if all_results:
self._cache_response(cache_key, str(all_results))
-
+
return {
"task": task,
"agents_used": selected_agent_names[:total_processed],
"results": all_results,
"total_agents": total_processed,
"cached": False,
- "cost_stats": self.cost_tracker.get_stats()
+ "cost_stats": self.cost_tracker.get_stats(),
}
-
- def run_mass_task_optimized(self, task: str, agent_count: int = 1000,
- max_cost: float = 10.0) -> Dict[str, Any]:
+
+ def run_mass_task_optimized(
+ self,
+ task: str,
+ agent_count: int = 1000,
+ max_cost: float = 10.0,
+ ) -> Dict[str, Any]:
"""
Run a task with cost-optimized mass execution for large-scale operations.
-
+
Args:
task: Task to execute
agent_count: Target number of agents to use
max_cost: Maximum cost for this task in dollars
-
+
Returns:
Dict[str, Any]: Results from the optimized mass task execution
"""
# Store original settings
original_budget = self.cost_tracker.budget_limit
original_batch_size = self.batch_size
-
+
try:
# Set temporary budget for this task (don't reduce if max_cost is higher)
if max_cost < original_budget:
self.cost_tracker.budget_limit = max_cost
-
+
# Use smaller batches for better cost control
- self.batch_size = min(25, self.batch_size) # Smaller batches for cost control
-
+ self.batch_size = min(
+ 25, self.batch_size
+ ) # Smaller batches for cost control
+
result = self.run_mass_task(task, agent_count)
-
+
return result
-
+
finally:
# Restore original settings
self.cost_tracker.budget_limit = original_budget
self.batch_size = original_batch_size
-
- def run_group_task(self, group_name: str, task: str) -> Dict[str, Any]:
+
+ def run_group_task(
+ self, group_name: str, task: str
+ ) -> Dict[str, Any]:
"""
Run a task with a specific group using their Board of Directors swarm.
-
+
Args:
group_name: Name of the group
task: Task to execute
-
+
Returns:
Dict[str, Any]: Results from the group task execution
"""
group = self.groups.get(group_name)
if not group or not group.group_swarm:
- return {"error": f"Group {group_name} not found or no swarm available"}
-
+ return {
+ "error": f"Group {group_name} not found or no swarm available"
+ }
+
# Run task with group swarm
result = group.group_swarm.run(task)
-
+
return {
"group": group_name,
"task": task,
"result": result,
- "agents_involved": group.agents
+ "agents_involved": group.agents,
}
-
+
def get_system_stats(self) -> Dict[str, Any]:
"""
Get statistics about the mass agent system including cost tracking.
-
+
Returns:
Dict[str, Any]: System statistics
"""
stats = {
"total_agents": len(self.agents),
"total_groups": len(self.groups),
- "loaded_agents": len([a for a in self.agents.values() if a.is_loaded]),
+ "loaded_agents": len(
+ [a for a in self.agents.values() if a.is_loaded]
+ ),
"categories": {},
"roles": {},
"experience_levels": {},
@@ -871,35 +1009,41 @@ Remember: You are part of a large multi-agent system. Your unique combination of
"lazy_loading": self.enable_lazy_loading,
"caching": self.enable_caching,
"batch_size": self.batch_size,
- "budget_limit": self.cost_tracker.budget_limit
- }
+ "budget_limit": self.cost_tracker.budget_limit,
+ },
}
-
+
# Category breakdown
for category in AgentCategory:
- stats["categories"][category.value] = len(self.get_agents_by_category(category))
-
+ stats["categories"][category.value] = len(
+ self.get_agents_by_category(category)
+ )
+
# Role breakdown
for role in AgentRole:
- stats["roles"][role.value] = len(self.get_agents_by_role(role))
-
+ stats["roles"][role.value] = len(
+ self.get_agents_by_role(role)
+ )
+
# Experience level breakdown
experience_counts = {}
for profile in self.agents.values():
level = profile.experience_level
- experience_counts[level] = experience_counts.get(level, 0) + 1
+ experience_counts[level] = (
+ experience_counts.get(level, 0) + 1
+ )
stats["experience_levels"] = experience_counts
-
+
return stats
# Example usage and demonstration
def demonstrate_mass_agent_template():
"""Demonstrate the Mass Agent Template functionality with cost optimization."""
-
+
print("MASS AGENT TEMPLATE DEMONSTRATION (COST OPTIMIZED)")
print("=" * 60)
-
+
# Initialize the template with 1000 agents and cost optimization
template = MassAgentTemplate(
agent_count=1000,
@@ -909,90 +1053,114 @@ def demonstrate_mass_agent_template():
enable_caching=True,
batch_size=25,
budget_limit=50.0, # $50 budget limit
- verbose=True
+ verbose=True,
)
-
+
# Show system statistics
stats = template.get_system_stats()
-
- print(f"\nSYSTEM STATISTICS:")
+
+ print("\nSYSTEM STATISTICS:")
print(f"Total Agents: {stats['total_agents']}")
- print(f"Loaded Agents: {stats['loaded_agents']} (lazy loading active)")
+ print(
+ f"Loaded Agents: {stats['loaded_agents']} (lazy loading active)"
+ )
print(f"Total Groups: {stats['total_groups']}")
-
- print(f"\nCOST OPTIMIZATION:")
- cost_stats = stats['cost_stats']
- print(f"Budget Limit: ${cost_stats['budget_remaining'] + cost_stats['total_cost']:.2f}")
+
+ print("\nCOST OPTIMIZATION:")
+ cost_stats = stats["cost_stats"]
+ print(
+ f"Budget Limit: ${cost_stats['budget_remaining'] + cost_stats['total_cost']:.2f}"
+ )
print(f"Budget Used: ${cost_stats['total_cost']:.2f}")
print(f"Budget Remaining: ${cost_stats['budget_remaining']:.2f}")
print(f"Cache Hit Rate: {cost_stats['cache_hit_rate']:.1%}")
-
- print(f"\nCATEGORY BREAKDOWN:")
- for category, count in stats['categories'].items():
+
+ print("\nCATEGORY BREAKDOWN:")
+ for category, count in stats["categories"].items():
print(f" {category}: {count} agents")
-
- print(f"\nROLE BREAKDOWN:")
- for role, count in stats['roles'].items():
+
+ print("\nROLE BREAKDOWN:")
+ for role, count in stats["roles"].items():
print(f" {role}: {count} agents")
-
- print(f"\nEXPERIENCE LEVEL BREAKDOWN:")
- for level, count in stats['experience_levels'].items():
+
+ print("\nEXPERIENCE LEVEL BREAKDOWN:")
+ for level, count in stats["experience_levels"].items():
print(f" {level}: {count} agents")
-
+
# Demonstrate cost-optimized mass task execution
- print(f"\nCOST-OPTIMIZED MASS TASK DEMONSTRATION:")
+ print("\nCOST-OPTIMIZED MASS TASK DEMONSTRATION:")
print("-" * 40)
-
+
# Small task first (low cost)
small_result = template.run_mass_task(
"What is the most important skill for a software developer?",
- agent_count=5
+ agent_count=5,
)
-
- print(f"Small Task Results:")
+
+ print("Small Task Results:")
print(f" Agents Used: {len(small_result['agents_used'])}")
print(f" Cached: {small_result.get('cached', False)}")
print(f" Cost: ${small_result['cost_stats']['total_cost']:.2f}")
-
+
# Large task to demonstrate full capability
- print(f"\nLarge Task Demonstration (Full Capability):")
+ print("\nLarge Task Demonstration (Full Capability):")
large_result = template.run_mass_task(
"Analyze the benefits of cloud computing for small businesses",
- agent_count=200 # Use more agents to show capability
+ agent_count=200, # Use more agents to show capability
)
-
+
print(f" Agents Used: {len(large_result['agents_used'])}")
print(f" Cached: {large_result.get('cached', False)}")
print(f" Cost: ${large_result['cost_stats']['total_cost']:.2f}")
- print(f" Budget Remaining: ${large_result['cost_stats']['budget_remaining']:.2f}")
-
+ print(
+ f" Budget Remaining: ${large_result['cost_stats']['budget_remaining']:.2f}"
+ )
+
# Show what happens with cost limits
- print(f"\nCost-Limited Task Demonstration:")
+ print("\nCost-Limited Task Demonstration:")
cost_limited_result = template.run_mass_task_optimized(
"What are the key principles of agile development?",
agent_count=100,
- max_cost=2.0 # Show cost limiting in action
+ max_cost=2.0, # Show cost limiting in action
)
-
+
print(f" Agents Used: {len(cost_limited_result['agents_used'])}")
print(f" Cached: {cost_limited_result.get('cached', False)}")
- print(f" Cost: ${cost_limited_result['cost_stats']['total_cost']:.2f}")
- print(f" Budget Remaining: ${cost_limited_result['cost_stats']['budget_remaining']:.2f}")
-
+ print(
+ f" Cost: ${cost_limited_result['cost_stats']['total_cost']:.2f}"
+ )
+ print(
+ f" Budget Remaining: ${cost_limited_result['cost_stats']['budget_remaining']:.2f}"
+ )
+
# Show final cost statistics
final_stats = template.get_system_stats()
- print(f"\nFINAL COST STATISTICS:")
- print(f"Total Cost: ${final_stats['cost_stats']['total_cost']:.2f}")
- print(f"Budget Remaining: ${final_stats['cost_stats']['budget_remaining']:.2f}")
- print(f"Cache Hit Rate: {final_stats['cost_stats']['cache_hit_rate']:.1%}")
- print(f"Total Requests: {final_stats['cost_stats']['requests_made']}")
+ print("\nFINAL COST STATISTICS:")
+ print(
+ f"Total Cost: ${final_stats['cost_stats']['total_cost']:.2f}"
+ )
+ print(
+ f"Budget Remaining: ${final_stats['cost_stats']['budget_remaining']:.2f}"
+ )
+ print(
+ f"Cache Hit Rate: {final_stats['cost_stats']['cache_hit_rate']:.1%}"
+ )
+ print(
+ f"Total Requests: {final_stats['cost_stats']['requests_made']}"
+ )
print(f"Cache Hits: {final_stats['cost_stats']['cache_hits']}")
-
- print(f"\nDEMONSTRATION COMPLETED SUCCESSFULLY!")
- print(f"✅ Cost optimization working: ${final_stats['cost_stats']['total_cost']:.2f} spent")
- print(f"✅ Lazy loading working: {final_stats['loaded_agents']}/{final_stats['total_agents']} agents loaded")
- print(f"✅ Caching working: {final_stats['cost_stats']['cache_hit_rate']:.1%} hit rate")
+
+ print("\nDEMONSTRATION COMPLETED SUCCESSFULLY!")
+ print(
+ f"✅ Cost optimization working: ${final_stats['cost_stats']['total_cost']:.2f} spent"
+ )
+ print(
+ f"✅ Lazy loading working: {final_stats['loaded_agents']}/{final_stats['total_agents']} agents loaded"
+ )
+ print(
+ f"✅ Caching working: {final_stats['cost_stats']['cache_hit_rate']:.1%} hit rate"
+ )
if __name__ == "__main__":
- demonstrate_mass_agent_template()
\ No newline at end of file
+ demonstrate_mass_agent_template()
diff --git a/examples/simulations/euroswarm_parliament/test_mass_agents.py b/examples/simulations/euroswarm_parliament/test_mass_agents.py
index 54ec2223..c747b0a0 100644
--- a/examples/simulations/euroswarm_parliament/test_mass_agents.py
+++ b/examples/simulations/euroswarm_parliament/test_mass_agents.py
@@ -5,63 +5,76 @@ Test script to verify mass agent template can process more than 500 agents.
from mass_agent_template import MassAgentTemplate
+
def test_mass_agents():
- print("Testing Mass Agent Template - Processing More Than 50 Agents")
+ print(
+ "Testing Mass Agent Template - Processing More Than 50 Agents"
+ )
print("=" * 60)
-
+
# Initialize template with 200 agents
template = MassAgentTemplate(
agent_count=200,
budget_limit=50.0,
batch_size=25,
- verbose=True
+ verbose=True,
)
-
+
print(f"Initialized with {len(template.agents)} agents")
print(f"Budget limit: ${template.cost_tracker.budget_limit}")
-
+
# Test processing 100 agents
- print(f"\nTesting with 100 agents...")
+ print("\nTesting with 100 agents...")
result = template.run_mass_task(
"What is the most important skill for your role?",
- agent_count=100
+ agent_count=100,
)
-
- print(f"Results:")
+
+ print("Results:")
print(f" Agents processed: {len(result['agents_used'])}")
print(f" Cost: ${result['cost_stats']['total_cost']:.4f}")
- print(f" Budget remaining: ${result['cost_stats']['budget_remaining']:.2f}")
+ print(
+ f" Budget remaining: ${result['cost_stats']['budget_remaining']:.2f}"
+ )
print(f" Cached: {result.get('cached', False)}")
-
+
# Test processing 150 agents
- print(f"\nTesting with 150 agents...")
+ print("\nTesting with 150 agents...")
result2 = template.run_mass_task(
- "Describe your approach to problem-solving",
- agent_count=150
+ "Describe your approach to problem-solving", agent_count=150
)
-
- print(f"Results:")
+
+ print("Results:")
print(f" Agents processed: {len(result2['agents_used'])}")
print(f" Cost: ${result2['cost_stats']['total_cost']:.4f}")
- print(f" Budget remaining: ${result2['cost_stats']['budget_remaining']:.2f}")
+ print(
+ f" Budget remaining: ${result2['cost_stats']['budget_remaining']:.2f}"
+ )
print(f" Cached: {result2.get('cached', False)}")
-
+
# Show final stats
final_stats = template.get_system_stats()
- print(f"\nFinal Statistics:")
+ print("\nFinal Statistics:")
print(f" Total agents: {final_stats['total_agents']}")
print(f" Loaded agents: {final_stats['loaded_agents']}")
- print(f" Total cost: ${final_stats['cost_stats']['total_cost']:.4f}")
- print(f" Budget remaining: ${final_stats['cost_stats']['budget_remaining']:.2f}")
-
+ print(
+ f" Total cost: ${final_stats['cost_stats']['total_cost']:.4f}"
+ )
+ print(
+ f" Budget remaining: ${final_stats['cost_stats']['budget_remaining']:.2f}"
+ )
+
# Success criteria
- total_processed = len(result['agents_used']) + len(result2['agents_used'])
+ total_processed = len(result["agents_used"]) + len(
+ result2["agents_used"]
+ )
print(f"\nTotal agents processed: {total_processed}")
-
+
if total_processed > 50:
print("✅ SUCCESS: Template processed more than 50 agents!")
else:
print("❌ FAILURE: Template still limited to 50 agents")
+
if __name__ == "__main__":
- test_mass_agents()
\ No newline at end of file
+ test_mass_agents()
diff --git a/examples/simulations/euroswarm_parliament/wikipedia_personality_scraper.py b/examples/simulations/euroswarm_parliament/wikipedia_personality_scraper.py
index 0ef7cda7..e7c555cf 100644
--- a/examples/simulations/euroswarm_parliament/wikipedia_personality_scraper.py
+++ b/examples/simulations/euroswarm_parliament/wikipedia_personality_scraper.py
@@ -14,14 +14,13 @@ from typing import Dict, List, Optional, Any
from dataclasses import dataclass, asdict
import requests
from loguru import logger
-import xml.etree.ElementTree as ET
@dataclass
class MEPPersonalityProfile:
"""
Comprehensive personality profile for an MEP based on Wikipedia data.
-
+
Attributes:
full_name: Full name of the MEP
mep_id: Unique MEP identifier
@@ -47,7 +46,7 @@ class MEPPersonalityProfile:
social_media: Social media presence
last_updated: When the profile was last updated
"""
-
+
full_name: str
mep_id: str
wikipedia_url: Optional[str] = None
@@ -77,11 +76,15 @@ class WikipediaPersonalityScraper:
"""
Scraper for gathering Wikipedia personality data for MEPs.
"""
-
- def __init__(self, output_dir: str = "mep_personalities", verbose: bool = True):
+
+ def __init__(
+ self,
+ output_dir: str = "mep_personalities",
+ verbose: bool = True,
+ ):
"""
Initialize the Wikipedia personality scraper.
-
+
Args:
output_dir: Directory to store personality profiles
verbose: Enable verbose logging
@@ -89,61 +92,81 @@ class WikipediaPersonalityScraper:
self.output_dir = output_dir
self.verbose = verbose
self.session = requests.Session()
- self.session.headers.update({
- 'User-Agent': 'EuroSwarm Parliament Personality Scraper/1.0 (https://github.com/swarms-democracy)'
- })
-
+ self.session.headers.update(
+ {
+ "User-Agent": "EuroSwarm Parliament Personality Scraper/1.0 (https://github.com/swarms-democracy)"
+ }
+ )
+
# Create output directory
os.makedirs(output_dir, exist_ok=True)
-
+
if verbose:
- logger.info(f"Wikipedia Personality Scraper initialized. Output directory: {output_dir}")
+ logger.info(
+ f"Wikipedia Personality Scraper initialized. Output directory: {output_dir}"
+ )
- def extract_mep_data_from_xml(self, xml_file: str = "EU.xml") -> List[Dict[str, str]]:
+ def extract_mep_data_from_xml(
+ self, xml_file: str = "EU.xml"
+ ) -> List[Dict[str, str]]:
"""
Extract MEP data from EU.xml file.
-
+
Args:
xml_file: Path to EU.xml file
-
+
Returns:
List of MEP data dictionaries
"""
meps = []
-
+
try:
- with open(xml_file, 'r', encoding='utf-8') as f:
+ with open(xml_file, "r", encoding="utf-8") as f:
content = f.read()
-
+
# Use regex to extract MEP data
- mep_pattern = r'\s*(.*?)\s*(.*?)\s*(.*?)\s*(.*?)\s*(.*?)\s*'
+ mep_pattern = r"\s*(.*?)\s*(.*?)\s*(.*?)\s*(.*?)\s*(.*?)\s*"
mep_matches = re.findall(mep_pattern, content, re.DOTALL)
-
- for full_name, country, political_group, mep_id, national_party in mep_matches:
- meps.append({
- 'full_name': full_name.strip(),
- 'country': country.strip(),
- 'political_group': political_group.strip(),
- 'mep_id': mep_id.strip(),
- 'national_party': national_party.strip()
- })
-
+
+ for (
+ full_name,
+ country,
+ political_group,
+ mep_id,
+ national_party,
+ ) in mep_matches:
+ meps.append(
+ {
+ "full_name": full_name.strip(),
+ "country": country.strip(),
+ "political_group": political_group.strip(),
+ "mep_id": mep_id.strip(),
+ "national_party": national_party.strip(),
+ }
+ )
+
if self.verbose:
- logger.info(f"Extracted {len(meps)} MEPs from {xml_file}")
-
+ logger.info(
+ f"Extracted {len(meps)} MEPs from {xml_file}"
+ )
+
except Exception as e:
- logger.error(f"Error extracting MEP data from {xml_file}: {e}")
-
+ logger.error(
+ f"Error extracting MEP data from {xml_file}: {e}"
+ )
+
return meps
- def search_wikipedia_page(self, mep_name: str, country: str) -> Optional[str]:
+ def search_wikipedia_page(
+ self, mep_name: str, country: str
+ ) -> Optional[str]:
"""
Search for a Wikipedia page for an MEP.
-
+
Args:
mep_name: Full name of the MEP
country: Country of the MEP
-
+
Returns:
Wikipedia page title if found, None otherwise
"""
@@ -151,48 +174,56 @@ class WikipediaPersonalityScraper:
# Search for the MEP on Wikipedia
search_url = "https://en.wikipedia.org/w/api.php"
search_params = {
- 'action': 'query',
- 'format': 'json',
- 'list': 'search',
- 'srsearch': f'"{mep_name}" {country}',
- 'srlimit': 5,
- 'srnamespace': 0
+ "action": "query",
+ "format": "json",
+ "list": "search",
+ "srsearch": f'"{mep_name}" {country}',
+ "srlimit": 5,
+ "srnamespace": 0,
}
-
- response = self.session.get(search_url, params=search_params)
+
+ response = self.session.get(
+ search_url, params=search_params
+ )
response.raise_for_status()
-
+
data = response.json()
- search_results = data.get('query', {}).get('search', [])
-
+ search_results = data.get("query", {}).get("search", [])
+
if search_results:
# Return the first result
- return search_results[0]['title']
-
+ return search_results[0]["title"]
+
# Try alternative search without quotes
- search_params['srsearch'] = f'{mep_name} {country}'
- response = self.session.get(search_url, params=search_params)
+ search_params["srsearch"] = f"{mep_name} {country}"
+ response = self.session.get(
+ search_url, params=search_params
+ )
response.raise_for_status()
-
+
data = response.json()
- search_results = data.get('query', {}).get('search', [])
-
+ search_results = data.get("query", {}).get("search", [])
+
if search_results:
- return search_results[0]['title']
-
+ return search_results[0]["title"]
+
except Exception as e:
if self.verbose:
- logger.warning(f"Error searching Wikipedia for {mep_name}: {e}")
-
+ logger.warning(
+ f"Error searching Wikipedia for {mep_name}: {e}"
+ )
+
return None
- def get_wikipedia_content(self, page_title: str) -> Optional[Dict[str, Any]]:
+ def get_wikipedia_content(
+ self, page_title: str
+ ) -> Optional[Dict[str, Any]]:
"""
Get Wikipedia content for a specific page.
-
+
Args:
page_title: Wikipedia page title
-
+
Returns:
Dictionary containing page content and metadata
"""
@@ -200,376 +231,451 @@ class WikipediaPersonalityScraper:
# Get page content
content_url = "https://en.wikipedia.org/w/api.php"
content_params = {
- 'action': 'query',
- 'format': 'json',
- 'titles': page_title,
- 'prop': 'extracts|info|categories',
- 'exintro': True,
- 'explaintext': True,
- 'inprop': 'url',
- 'cllimit': 50
+ "action": "query",
+ "format": "json",
+ "titles": page_title,
+ "prop": "extracts|info|categories",
+ "exintro": True,
+ "explaintext": True,
+ "inprop": "url",
+ "cllimit": 50,
}
-
- response = self.session.get(content_url, params=content_params)
+
+ response = self.session.get(
+ content_url, params=content_params
+ )
response.raise_for_status()
-
+
data = response.json()
- pages = data.get('query', {}).get('pages', {})
-
+ pages = data.get("query", {}).get("pages", {})
+
if pages:
page_id = list(pages.keys())[0]
page_data = pages[page_id]
-
+
return {
- 'title': page_data.get('title', ''),
- 'extract': page_data.get('extract', ''),
- 'url': page_data.get('fullurl', ''),
- 'categories': [cat['title'] for cat in page_data.get('categories', [])],
- 'pageid': page_data.get('pageid', ''),
- 'length': page_data.get('length', 0)
+ "title": page_data.get("title", ""),
+ "extract": page_data.get("extract", ""),
+ "url": page_data.get("fullurl", ""),
+ "categories": [
+ cat["title"]
+ for cat in page_data.get("categories", [])
+ ],
+ "pageid": page_data.get("pageid", ""),
+ "length": page_data.get("length", 0),
}
-
+
except Exception as e:
if self.verbose:
- logger.warning(f"Error getting Wikipedia content for {page_title}: {e}")
-
+ logger.warning(
+ f"Error getting Wikipedia content for {page_title}: {e}"
+ )
+
return None
- def parse_wikipedia_content(self, content: str, mep_name: str) -> Dict[str, str]:
+ def parse_wikipedia_content(
+ self, content: str, mep_name: str
+ ) -> Dict[str, str]:
"""
Parse Wikipedia content to extract structured personality information.
-
+
Args:
content: Raw Wikipedia content
mep_name: Name of the MEP
-
+
Returns:
Dictionary of parsed personality information
"""
personality_data = {
- 'summary': '',
- 'early_life': '',
- 'political_career': '',
- 'political_views': '',
- 'policy_focus': '',
- 'achievements': '',
- 'controversies': '',
- 'personal_life': '',
- 'education': '',
- 'professional_background': '',
- 'party_affiliations': '',
- 'committee_experience': '',
- 'voting_record': '',
- 'public_statements': '',
- 'interests': '',
- 'languages': '',
- 'awards': '',
- 'publications': '',
- 'social_media': ''
+ "summary": "",
+ "early_life": "",
+ "political_career": "",
+ "political_views": "",
+ "policy_focus": "",
+ "achievements": "",
+ "controversies": "",
+ "personal_life": "",
+ "education": "",
+ "professional_background": "",
+ "party_affiliations": "",
+ "committee_experience": "",
+ "voting_record": "",
+ "public_statements": "",
+ "interests": "",
+ "languages": "",
+ "awards": "",
+ "publications": "",
+ "social_media": "",
}
-
+
# Extract summary (first paragraph)
- paragraphs = content.split('\n\n')
+ paragraphs = content.split("\n\n")
if paragraphs:
- personality_data['summary'] = paragraphs[0][:1000] # Limit summary length
-
+ personality_data["summary"] = paragraphs[0][
+ :1000
+ ] # Limit summary length
+
# Look for specific sections
content_lower = content.lower()
-
+
# Early life and education
early_life_patterns = [
- r'early life[^.]*\.',
- r'born[^.]*\.',
- r'childhood[^.]*\.',
- r'grew up[^.]*\.',
- r'education[^.]*\.'
+ r"early life[^.]*\.",
+ r"born[^.]*\.",
+ r"childhood[^.]*\.",
+ r"grew up[^.]*\.",
+ r"education[^.]*\.",
]
-
+
for pattern in early_life_patterns:
- matches = re.findall(pattern, content_lower, re.IGNORECASE)
+ matches = re.findall(
+ pattern, content_lower, re.IGNORECASE
+ )
if matches:
- personality_data['early_life'] = ' '.join(matches[:3]) # Take first 3 matches
+ personality_data["early_life"] = " ".join(
+ matches[:3]
+ ) # Take first 3 matches
break
-
+
# Political career
political_patterns = [
- r'political career[^.]*\.',
- r'elected[^.]*\.',
- r'parliament[^.]*\.',
- r'minister[^.]*\.',
- r'party[^.]*\.'
+ r"political career[^.]*\.",
+ r"elected[^.]*\.",
+ r"parliament[^.]*\.",
+ r"minister[^.]*\.",
+ r"party[^.]*\.",
]
-
+
for pattern in political_patterns:
- matches = re.findall(pattern, content_lower, re.IGNORECASE)
+ matches = re.findall(
+ pattern, content_lower, re.IGNORECASE
+ )
if matches:
- personality_data['political_career'] = ' '.join(matches[:5]) # Take first 5 matches
+ personality_data["political_career"] = " ".join(
+ matches[:5]
+ ) # Take first 5 matches
break
-
+
# Political views
views_patterns = [
- r'political views[^.]*\.',
- r'positions[^.]*\.',
- r'advocates[^.]*\.',
- r'supports[^.]*\.',
- r'opposes[^.]*\.'
+ r"political views[^.]*\.",
+ r"positions[^.]*\.",
+ r"advocates[^.]*\.",
+ r"supports[^.]*\.",
+ r"opposes[^.]*\.",
]
-
+
for pattern in views_patterns:
- matches = re.findall(pattern, content_lower, re.IGNORECASE)
+ matches = re.findall(
+ pattern, content_lower, re.IGNORECASE
+ )
if matches:
- personality_data['political_views'] = ' '.join(matches[:3])
+ personality_data["political_views"] = " ".join(
+ matches[:3]
+ )
break
-
+
# Policy focus
policy_patterns = [
- r'policy[^.]*\.',
- r'focus[^.]*\.',
- r'issues[^.]*\.',
- r'legislation[^.]*\.'
+ r"policy[^.]*\.",
+ r"focus[^.]*\.",
+ r"issues[^.]*\.",
+ r"legislation[^.]*\.",
]
-
+
for pattern in policy_patterns:
- matches = re.findall(pattern, content_lower, re.IGNORECASE)
+ matches = re.findall(
+ pattern, content_lower, re.IGNORECASE
+ )
if matches:
- personality_data['policy_focus'] = ' '.join(matches[:3])
+ personality_data["policy_focus"] = " ".join(
+ matches[:3]
+ )
break
-
+
# Achievements
achievement_patterns = [
- r'achievements[^.]*\.',
- r'accomplishments[^.]*\.',
- r'success[^.]*\.',
- r'won[^.]*\.',
- r'received[^.]*\.'
+ r"achievements[^.]*\.",
+ r"accomplishments[^.]*\.",
+ r"success[^.]*\.",
+ r"won[^.]*\.",
+ r"received[^.]*\.",
]
-
+
for pattern in achievement_patterns:
- matches = re.findall(pattern, content_lower, re.IGNORECASE)
+ matches = re.findall(
+ pattern, content_lower, re.IGNORECASE
+ )
if matches:
- personality_data['achievements'] = ' '.join(matches[:3])
+ personality_data["achievements"] = " ".join(
+ matches[:3]
+ )
break
-
+
return personality_data
- def create_personality_profile(self, mep_data: Dict[str, str]) -> MEPPersonalityProfile:
+ def create_personality_profile(
+ self, mep_data: Dict[str, str]
+ ) -> MEPPersonalityProfile:
"""
Create a personality profile for an MEP.
-
+
Args:
mep_data: MEP data from XML file
-
+
Returns:
MEPPersonalityProfile object
"""
- mep_name = mep_data['full_name']
- country = mep_data['country']
-
+ mep_name = mep_data["full_name"]
+ country = mep_data["country"]
+
# Search for Wikipedia page
page_title = self.search_wikipedia_page(mep_name, country)
-
+
if page_title:
# Get Wikipedia content
wiki_content = self.get_wikipedia_content(page_title)
-
+
if wiki_content:
# Parse content
- personality_data = self.parse_wikipedia_content(wiki_content['extract'], mep_name)
-
+ personality_data = self.parse_wikipedia_content(
+ wiki_content["extract"], mep_name
+ )
+
# Create profile
profile = MEPPersonalityProfile(
full_name=mep_name,
- mep_id=mep_data['mep_id'],
- wikipedia_url=wiki_content['url'],
- summary=personality_data['summary'],
- early_life=personality_data['early_life'],
- political_career=personality_data['political_career'],
- political_views=personality_data['political_views'],
- policy_focus=personality_data['policy_focus'],
- achievements=personality_data['achievements'],
- controversies=personality_data['controversies'],
- personal_life=personality_data['personal_life'],
- education=personality_data['education'],
- professional_background=personality_data['professional_background'],
- party_affiliations=personality_data['party_affiliations'],
- committee_experience=personality_data['committee_experience'],
- voting_record=personality_data['voting_record'],
- public_statements=personality_data['public_statements'],
- interests=personality_data['interests'],
- languages=personality_data['languages'],
- awards=personality_data['awards'],
- publications=personality_data['publications'],
- social_media=personality_data['social_media'],
- last_updated=time.strftime("%Y-%m-%d %H:%M:%S")
+ mep_id=mep_data["mep_id"],
+ wikipedia_url=wiki_content["url"],
+ summary=personality_data["summary"],
+ early_life=personality_data["early_life"],
+ political_career=personality_data[
+ "political_career"
+ ],
+ political_views=personality_data[
+ "political_views"
+ ],
+ policy_focus=personality_data["policy_focus"],
+ achievements=personality_data["achievements"],
+ controversies=personality_data["controversies"],
+ personal_life=personality_data["personal_life"],
+ education=personality_data["education"],
+ professional_background=personality_data[
+ "professional_background"
+ ],
+ party_affiliations=personality_data[
+ "party_affiliations"
+ ],
+ committee_experience=personality_data[
+ "committee_experience"
+ ],
+ voting_record=personality_data["voting_record"],
+ public_statements=personality_data[
+ "public_statements"
+ ],
+ interests=personality_data["interests"],
+ languages=personality_data["languages"],
+ awards=personality_data["awards"],
+ publications=personality_data["publications"],
+ social_media=personality_data["social_media"],
+ last_updated=time.strftime("%Y-%m-%d %H:%M:%S"),
)
-
+
if self.verbose:
- logger.info(f"Created personality profile for {mep_name} from Wikipedia")
-
+ logger.info(
+ f"Created personality profile for {mep_name} from Wikipedia"
+ )
+
return profile
-
+
# Create minimal profile if no Wikipedia data found
profile = MEPPersonalityProfile(
full_name=mep_name,
- mep_id=mep_data['mep_id'],
+ mep_id=mep_data["mep_id"],
summary=f"{mep_name} is a Member of the European Parliament representing {country}.",
political_career=f"Currently serving as MEP for {country}.",
political_views=f"Member of {mep_data['political_group']} and {mep_data['national_party']}.",
- last_updated=time.strftime("%Y-%m-%d %H:%M:%S")
+ last_updated=time.strftime("%Y-%m-%d %H:%M:%S"),
)
-
+
if self.verbose:
- logger.warning(f"No Wikipedia data found for {mep_name}, created minimal profile")
-
+ logger.warning(
+ f"No Wikipedia data found for {mep_name}, created minimal profile"
+ )
+
return profile
- def save_personality_profile(self, profile: MEPPersonalityProfile) -> str:
+ def save_personality_profile(
+ self, profile: MEPPersonalityProfile
+ ) -> str:
"""
Save personality profile to JSON file.
-
+
Args:
profile: MEPPersonalityProfile object
-
+
Returns:
Path to saved file
"""
# Create safe filename
- safe_name = re.sub(r'[^\w\s-]', '', profile.full_name).strip()
- safe_name = re.sub(r'[-\s]+', '_', safe_name)
+ safe_name = re.sub(r"[^\w\s-]", "", profile.full_name).strip()
+ safe_name = re.sub(r"[-\s]+", "_", safe_name)
filename = f"{safe_name}_{profile.mep_id}.json"
filepath = os.path.join(self.output_dir, filename)
-
+
# Convert to dictionary and save
profile_dict = asdict(profile)
-
- with open(filepath, 'w', encoding='utf-8') as f:
+
+ with open(filepath, "w", encoding="utf-8") as f:
json.dump(profile_dict, f, indent=2, ensure_ascii=False)
-
+
if self.verbose:
logger.info(f"Saved personality profile: {filepath}")
-
+
return filepath
- def scrape_all_mep_personalities(self, xml_file: str = "EU.xml", delay: float = 1.0) -> Dict[str, str]:
+ def scrape_all_mep_personalities(
+ self, xml_file: str = "EU.xml", delay: float = 1.0
+ ) -> Dict[str, str]:
"""
Scrape personality data for all MEPs.
-
+
Args:
xml_file: Path to EU.xml file
delay: Delay between requests to be respectful to Wikipedia
-
+
Returns:
Dictionary mapping MEP names to their personality profile file paths
"""
meps = self.extract_mep_data_from_xml(xml_file)
profile_files = {}
-
+
if self.verbose:
- logger.info(f"Starting personality scraping for {len(meps)} MEPs")
-
+ logger.info(
+ f"Starting personality scraping for {len(meps)} MEPs"
+ )
+
for i, mep_data in enumerate(meps, 1):
- mep_name = mep_data['full_name']
-
+ mep_name = mep_data["full_name"]
+
if self.verbose:
logger.info(f"Processing {i}/{len(meps)}: {mep_name}")
-
+
try:
# Create personality profile
profile = self.create_personality_profile(mep_data)
-
+
# Save profile
filepath = self.save_personality_profile(profile)
profile_files[mep_name] = filepath
-
+
# Respectful delay
time.sleep(delay)
-
+
except Exception as e:
logger.error(f"Error processing {mep_name}: {e}")
continue
-
+
if self.verbose:
- logger.info(f"Completed personality scraping. {len(profile_files)} profiles created.")
-
+ logger.info(
+ f"Completed personality scraping. {len(profile_files)} profiles created."
+ )
+
return profile_files
- def load_personality_profile(self, filepath: str) -> MEPPersonalityProfile:
+ def load_personality_profile(
+ self, filepath: str
+ ) -> MEPPersonalityProfile:
"""
Load personality profile from JSON file.
-
+
Args:
filepath: Path to personality profile JSON file
-
+
Returns:
MEPPersonalityProfile object
"""
- with open(filepath, 'r', encoding='utf-8') as f:
+ with open(filepath, "r", encoding="utf-8") as f:
data = json.load(f)
-
+
return MEPPersonalityProfile(**data)
- def get_personality_summary(self, profile: MEPPersonalityProfile) -> str:
+ def get_personality_summary(
+ self, profile: MEPPersonalityProfile
+ ) -> str:
"""
Generate a personality summary for use in AI agent system prompts.
-
+
Args:
profile: MEPPersonalityProfile object
-
+
Returns:
Formatted personality summary
"""
summary_parts = []
-
+
if profile.summary:
summary_parts.append(f"Background: {profile.summary}")
-
+
if profile.political_career:
- summary_parts.append(f"Political Career: {profile.political_career}")
-
+ summary_parts.append(
+ f"Political Career: {profile.political_career}"
+ )
+
if profile.political_views:
- summary_parts.append(f"Political Views: {profile.political_views}")
-
+ summary_parts.append(
+ f"Political Views: {profile.political_views}"
+ )
+
if profile.policy_focus:
- summary_parts.append(f"Policy Focus: {profile.policy_focus}")
-
+ summary_parts.append(
+ f"Policy Focus: {profile.policy_focus}"
+ )
+
if profile.achievements:
- summary_parts.append(f"Notable Achievements: {profile.achievements}")
-
+ summary_parts.append(
+ f"Notable Achievements: {profile.achievements}"
+ )
+
if profile.education:
summary_parts.append(f"Education: {profile.education}")
-
+
if profile.professional_background:
- summary_parts.append(f"Professional Background: {profile.professional_background}")
-
+ summary_parts.append(
+ f"Professional Background: {profile.professional_background}"
+ )
+
return "\n".join(summary_parts)
def main():
"""Main function to run the Wikipedia personality scraper."""
-
+
print("🏛️ WIKIPEDIA PERSONALITY SCRAPER FOR EUROSWARM PARLIAMENT")
print("=" * 70)
-
+
# Initialize scraper
- scraper = WikipediaPersonalityScraper(output_dir="mep_personalities", verbose=True)
-
+ scraper = WikipediaPersonalityScraper(
+ output_dir="mep_personalities", verbose=True
+ )
+
# Scrape all MEP personalities
profile_files = scraper.scrape_all_mep_personalities(delay=1.0)
-
- print(f"\n✅ Scraping completed!")
+
+ print("\n✅ Scraping completed!")
print(f"📁 Profiles saved to: {scraper.output_dir}")
print(f"📊 Total profiles created: {len(profile_files)}")
-
+
# Show sample profile
if profile_files:
sample_name = list(profile_files.keys())[0]
sample_file = profile_files[sample_name]
sample_profile = scraper.load_personality_profile(sample_file)
-
+
print(f"\n📋 Sample Profile: {sample_name}")
print("-" * 50)
print(scraper.get_personality_summary(sample_profile))
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/swarms/structs/__init__.py b/swarms/structs/__init__.py
index 8da55bf3..288c0273 100644
--- a/swarms/structs/__init__.py
+++ b/swarms/structs/__init__.py
@@ -1,5 +1,6 @@
from swarms.structs.agent import Agent
from swarms.structs.agent_builder import AgentsBuilder
+from swarms.structs.agent_rearrange import AgentRearrange, rearrange
from swarms.structs.auto_swarm_builder import AutoSwarmBuilder
from swarms.structs.base_structure import BaseStructure
from swarms.structs.base_swarm import BaseSwarm
@@ -66,7 +67,6 @@ from swarms.structs.multi_agent_exec import (
run_single_agent,
)
from swarms.structs.multi_agent_router import MultiAgentRouter
-from swarms.structs.agent_rearrange import AgentRearrange, rearrange
from swarms.structs.round_robin import RoundRobinSwarm
from swarms.structs.sequential_workflow import SequentialWorkflow
from swarms.structs.spreadsheet_swarm import SpreadSheetSwarm
diff --git a/swarms/structs/agent.py b/swarms/structs/agent.py
index 914c0adb..f9e04013 100644
--- a/swarms/structs/agent.py
+++ b/swarms/structs/agent.py
@@ -21,6 +21,13 @@ from typing import (
import toml
import yaml
+from litellm import model_list
+from litellm.utils import (
+ get_max_tokens,
+ supports_function_calling,
+ supports_parallel_function_calling,
+ supports_vision,
+)
from loguru import logger
from pydantic import BaseModel
@@ -45,7 +52,6 @@ from swarms.schemas.base_schemas import (
ChatMessageResponse,
)
from swarms.schemas.conversation_schema import ConversationSchema
-from swarms.schemas.llm_agent_schema import ModelConfigOrigin
from swarms.schemas.mcp_schemas import (
MCPConnection,
)
@@ -422,7 +428,6 @@ class Agent:
mcp_config: Optional[MCPConnection] = None,
top_p: Optional[float] = 0.90,
conversation_schema: Optional[ConversationSchema] = None,
- aditional_llm_config: Optional[ModelConfigOrigin] = None,
llm_base_url: Optional[str] = None,
llm_api_key: Optional[str] = None,
rag_config: Optional[RAGConfig] = None,
@@ -430,8 +435,8 @@ class Agent:
output_raw_json_from_tool_call: bool = False,
summarize_multiple_images: bool = False,
tool_retry_attempts: int = 3,
- speed_mode: str = None,
reasoning_prompt_on: bool = True,
+ dynamic_context_window: bool = True,
*args,
**kwargs,
):
@@ -562,7 +567,6 @@ class Agent:
self.mcp_config = mcp_config
self.top_p = top_p
self.conversation_schema = conversation_schema
- self.aditional_llm_config = aditional_llm_config
self.llm_base_url = llm_base_url
self.llm_api_key = llm_api_key
self.rag_config = rag_config
@@ -572,8 +576,8 @@ class Agent:
)
self.summarize_multiple_images = summarize_multiple_images
self.tool_retry_attempts = tool_retry_attempts
- self.speed_mode = speed_mode
self.reasoning_prompt_on = reasoning_prompt_on
+ self.dynamic_context_window = dynamic_context_window
# Initialize the feedback
self.feedback = []
@@ -676,17 +680,15 @@ class Agent:
# Initialize the short term memory
memory = Conversation(
name=f"{self.agent_name}_conversation",
+ system_prompt=prompt,
user=self.user_name,
rules=self.rules,
token_count=False,
message_id_on=False,
time_enabled=True,
- )
-
- # Add the system prompt to the conversation
- memory.add(
- role="system",
- content=prompt,
+ dynamic_context_window=self.dynamic_context_window,
+ tokenizer_model_name=self.model_name,
+ context_length=self.context_length,
)
return memory
@@ -888,11 +890,7 @@ class Agent:
Returns:
bool: True if model supports vision and image is provided, False otherwise.
"""
- from litellm.utils import (
- supports_function_calling,
- supports_parallel_function_calling,
- supports_vision,
- )
+
# Only check vision support if an image is provided
if img is not None:
@@ -1294,8 +1292,6 @@ class Agent:
self._handle_run_error(error)
def __handle_run_error(self, error: any):
- import traceback
-
if self.autosave is True:
self.save()
log_agent_data(self.to_dict())
@@ -1539,11 +1535,6 @@ class Agent:
raise
def reliability_check(self):
- from litellm import model_list
- from litellm.utils import (
- get_max_tokens,
- supports_function_calling,
- )
if self.system_prompt is None:
logger.warning(
diff --git a/swarms/structs/conversation.py b/swarms/structs/conversation.py
index 97316aa3..de7c1de2 100644
--- a/swarms/structs/conversation.py
+++ b/swarms/structs/conversation.py
@@ -1,21 +1,21 @@
-import traceback
import concurrent.futures
import datetime
+import inspect
import json
import os
+import traceback
import uuid
from typing import (
TYPE_CHECKING,
+ Any,
Dict,
List,
+ Literal,
Optional,
Union,
- Literal,
- Any,
)
import yaml
-import inspect
from swarms.utils.any_to_str import any_to_str
from swarms.utils.litellm_tokenizer import count_tokens
@@ -26,6 +26,18 @@ if TYPE_CHECKING:
from loguru import logger
+# Define available providers
+providers = Literal[
+ "mem0",
+ "in-memory",
+ "supabase",
+ "redis",
+ "sqlite",
+ "duckdb",
+ "pulsar",
+]
+
+
def generate_conversation_id():
"""Generate a unique conversation ID."""
return str(uuid.uuid4())
@@ -50,18 +62,6 @@ def get_conversation_dir():
return conversation_dir
-# Define available providers
-providers = Literal[
- "mem0",
- "in-memory",
- "supabase",
- "redis",
- "sqlite",
- "duckdb",
- "pulsar",
-]
-
-
def _create_backend_conversation(backend: str, **kwargs):
"""
Create a backend conversation instance based on the specified backend type.
@@ -183,9 +183,9 @@ class Conversation:
name: str = "conversation-test",
system_prompt: Optional[str] = None,
time_enabled: bool = False,
- autosave: bool = False, # Changed default to False
+ autosave: bool = False,
save_filepath: str = None,
- load_filepath: str = None, # New parameter to specify which file to load from
+ load_filepath: str = None,
context_length: int = 8192,
rules: str = None,
custom_rules_prompt: str = None,
@@ -211,6 +211,8 @@ class Conversation:
redis_data_dir: Optional[str] = None,
conversations_dir: Optional[str] = None,
export_method: str = "json",
+ dynamic_context_window: bool = True,
+ caching: bool = True,
*args,
**kwargs,
):
@@ -249,6 +251,8 @@ class Conversation:
self.auto_persist = auto_persist
self.redis_data_dir = redis_data_dir
self.export_method = export_method
+ self.dynamic_context_window = dynamic_context_window
+ self.caching = caching
if self.name is None:
self.name = id
@@ -933,7 +937,15 @@ class Conversation:
# Fallback to in-memory implementation
pass
+ elif self.dynamic_context_window is True:
+ return self.dynamic_auto_chunking()
+
+ else:
+ return self._return_history_as_string_worker()
+
+ def _return_history_as_string_worker(self):
formatted_messages = []
+
for message in self.conversation_history:
formatted_messages.append(
f"{message['role']}: {message['content']}"
@@ -1778,20 +1790,38 @@ class Conversation:
pass
self.conversation_history = []
+ def dynamic_auto_chunking(self):
+ all_tokens = self._return_history_as_string_worker()
+
+ total_tokens = count_tokens(
+ all_tokens, self.tokenizer_model_name
+ )
+
+ if total_tokens > self.context_length:
+ # Get the difference between the count_tokens and the context_length
+ difference = total_tokens - self.context_length
+
+ # Slice the first difference number of messages and contents from the beginning of the conversation history
+ new_history = all_tokens[difference:]
+
+ return new_history
-# # Example usage
-# # conversation = Conversation()
-# conversation = Conversation(token_count=True)
+
+# Example usage
+# conversation = Conversation()
+# conversation = Conversation(token_count=True, context_length=14)
# conversation.add("user", "Hello, how are you?")
# conversation.add("assistant", "I am doing well, thanks.")
+# conversation.add("user", "What is the weather in Tokyo?")
+# print(conversation.dynamic_auto_chunking())
# # conversation.add(
# # "assistant", {"name": "tool_1", "output": "Hello, how are you?"}
-# # )
-# # print(conversation.return_json())
+# )
+# print(conversation.return_json())
-# # # print(conversation.get_last_message_as_string())
+# # print(conversation.get_last_message_as_string())
# print(conversation.return_json())
-# # # conversation.add("assistant", "I am doing well, thanks.")
-# # # # print(conversation.to_json())
-# # print(type(conversation.to_dict()))
-# # print(conversation.to_yaml())
+# # conversation.add("assistant", "I am doing well, thanks.")
+# # # print(conversation.to_json())
+# print(type(conversation.to_dict()))
+# print(conversation.to_yaml())