a digital person for bluesky
1#!/usr/bin/env python3 2""" 3Simple CLI tool to converse with the Organon ecosystem group. 4""" 5 6import os 7import sys 8from dotenv import load_dotenv 9from letta_client import Letta 10from rich.console import Console 11from rich.prompt import Prompt 12from rich.panel import Panel 13from rich.text import Text 14 15# Add parent directory to path for imports 16sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 17from config_loader import get_config 18 19load_dotenv() 20 21class OrganonChat: 22 def __init__(self): 23 """Initialize the Organon chat client.""" 24 self.console = Console() 25 self.config = get_config() 26 27 # Initialize Letta client 28 self.letta_client = Letta( 29 base_url=self.config.get('letta.base_url', os.environ.get('LETTA_BASE_URL')), 30 token=self.config.get('letta.api_key', os.environ.get('LETTA_API_KEY')), 31 timeout=self.config.get('letta.timeout', 30) 32 ) 33 34 # Get project ID 35 self.project_id = self.config.get('letta.project_id', os.environ.get('LETTA_PROJECT_ID')) 36 if not self.project_id: 37 raise ValueError("Project ID must be set in config.yaml under letta.project_id or as LETTA_PROJECT_ID environment variable") 38 39 # Find the Organon ecosystem group 40 self.group_id = self._find_organon_group() 41 42 self.console.print(Panel.fit( 43 "[bold green]Organon Ecosystem Chat[/bold green]\n" 44 f"Connected to group: {self.group_id}\n" 45 "Type 'exit' or 'quit' to leave, 'help' for commands", 46 title="🧠 Organon Chat" 47 )) 48 49 def _find_organon_group(self) -> str: 50 """Find the Organon ecosystem group.""" 51 try: 52 self.console.print("[dim]Searching for Organon ecosystem group...[/dim]") 53 54 # First, get the organon-central agent ID 55 organon_agents = self.letta_client.agents.list(name="organon-central") 56 if not organon_agents: 57 raise ValueError("Organon central agent not found. Run create_organon.py first.") 58 59 organon_central_id = organon_agents[0].id 60 self.console.print(f"[dim]Found organon-central: {organon_central_id[:8]}[/dim]") 61 62 # Get all groups (with and without project_id filter) 63 try: 64 groups = self.letta_client.groups.list() 65 self.console.print(f"[dim]Found {len(groups)} groups with project filter[/dim]") 66 except: 67 try: 68 groups = self.letta_client.groups.list() 69 self.console.print(f"[dim]Found {len(groups)} groups without project filter[/dim]") 70 except: 71 groups = [] 72 self.console.print("[dim]No groups found[/dim]") 73 74 # Look for groups with organon-central as supervisor 75 for group in groups: 76 if hasattr(group, 'manager_config') and group.manager_config: 77 if hasattr(group.manager_config, 'manager_agent_id') and group.manager_config.manager_agent_id == organon_central_id: 78 self.console.print(f"[dim]Found Organon group: {group.id[:12]}[/dim]") 79 return group.id 80 81 # Look for the organon-ecosystem group by description 82 for group in groups: 83 if hasattr(group, 'description') and group.description and 'organon ecosystem' in group.description.lower(): 84 self.console.print(f"[dim]Found Organon group by description: {group.id[:12]}[/dim]") 85 return group.id 86 87 # If still not found, try to find any group with organon shards 88 shard_agents = self.letta_client.agents.list(project_id=self.project_id, tags=["organon-shard"]) 89 if shard_agents: 90 shard_ids = {shard.id for shard in shard_agents} 91 for group in groups: 92 try: 93 members = self.letta_client.groups.agents.list(group_id=group.id) 94 member_ids = {member.id for member in members} 95 if shard_ids & member_ids: # If any shard is in this group 96 self.console.print(f"[dim]Found group with Organon shards: {group.id[:12]}[/dim]") 97 return group.id 98 except: 99 continue 100 101 raise ValueError("Organon ecosystem group not found. Run 'python organon/setup_group.py' to create the group.") 102 103 except Exception as e: 104 raise ValueError(f"Error finding Organon group: {e}") 105 106 def _display_response(self, response): 107 """Display the group response in a formatted way.""" 108 if hasattr(response, 'messages') and response.messages: 109 for i, message in enumerate(response.messages): 110 # Determine the sender 111 sender = "Unknown" 112 if hasattr(message, 'agent_id'): 113 # Try to get agent name 114 try: 115 agent = self.letta_client.agents.retrieve(agent_id=message.agent_id) 116 sender = agent.name if hasattr(agent, 'name') else f"Agent {message.agent_id[:8]}" 117 except: 118 sender = f"Agent {message.agent_id[:8]}" 119 120 # Get message content 121 content = "" 122 if hasattr(message, 'text'): 123 content = message.text 124 elif hasattr(message, 'content'): 125 content = message.content 126 elif hasattr(message, 'message'): 127 content = message.message 128 elif message.message_type == "tool_return_message" and message.name == "send_message_to_all_agents_in_group": 129 content = "Group responses:" 130 try: 131 # Parse the string representation of the list 132 import ast 133 responses = ast.literal_eval(message.tool_return) 134 135 # Add each response to the content 136 for response in responses: 137 content += f"\n - {response}" 138 except (ValueError, SyntaxError): 139 # Fallback if parsing fails 140 content += f"\n - {message.tool_return}" 141 else: 142 content = str(message) 143 144 # Color based on sender type 145 if "central" in sender.lower(): 146 color = "blue" 147 icon = "🧠" 148 elif "shard" in sender.lower(): 149 color = "cyan" 150 icon = "🔹" 151 else: 152 color = "white" 153 icon = "💬" 154 155 self.console.print(Panel( 156 Text(content, style=color), 157 title=f"{icon} {sender}", 158 border_style=color 159 )) 160 else: 161 self.console.print("[yellow]No response messages received[/yellow]") 162 163 def run(self): 164 """Run the interactive chat loop.""" 165 try: 166 while True: 167 # Get user input 168 user_input = Prompt.ask("\n[bold green]You[/bold green]") 169 170 # Handle special commands 171 if user_input.lower() in ['exit', 'quit', 'q']: 172 self.console.print("[yellow]Goodbye![/yellow]") 173 break 174 elif user_input.lower() == 'help': 175 self.console.print(Panel( 176 "[bold]Available commands:[/bold]\n" 177 "• Type any message to send to the Organon ecosystem\n" 178 "'help' - Show this help message\n" 179 "'info' - Show group information\n" 180 "'exit', 'quit', 'q' - Exit the chat", 181 title="Help" 182 )) 183 continue 184 elif user_input.lower() == 'info': 185 self._show_group_info() 186 continue 187 elif not user_input.strip(): 188 continue 189 190 # Send message to group 191 self.console.print("[dim]Sending to Organon ecosystem...[/dim]") 192 193 try: 194 response = self.letta_client.groups.messages.create( 195 group_id=self.group_id, 196 messages=[{ 197 "role": "user", 198 "content": user_input 199 }] 200 ) 201 202 self.console.print("\n[bold]Organon Ecosystem Response:[/bold]") 203 self._display_response(response) 204 205 except Exception as e: 206 self.console.print(f"[red]Error sending message: {e}[/red]") 207 208 except KeyboardInterrupt: 209 self.console.print("\n[yellow]Chat interrupted. Goodbye![/yellow]") 210 except Exception as e: 211 self.console.print(f"[red]Unexpected error: {e}[/red]") 212 213 def _show_group_info(self): 214 """Show information about the Organon group.""" 215 try: 216 group = self.letta_client.groups.retrieve(group_id=self.group_id) 217 agents = self.letta_client.groups.agents.list(group_id=self.group_id) 218 219 info_text = f"[bold]Group ID:[/bold] {self.group_id}\n" 220 if hasattr(group, 'description'): 221 info_text += f"[bold]Description:[/bold] {group.description}\n" 222 223 info_text += f"[bold]Worker Agents:[/bold] {len(agents)}\n" 224 225 for agent in agents: 226 try: 227 agent_detail = self.letta_client.agents.retrieve(agent_id=agent.id) 228 name = agent_detail.name if hasattr(agent_detail, 'name') else agent.id[:8] 229 info_text += f"{name}\n" 230 except: 231 info_text += f"{agent.id[:8]}\n" 232 233 # Show supervisor info 234 if hasattr(group, 'manager_config') and group.manager_config: 235 if hasattr(group.manager_config, 'manager_agent_id'): 236 try: 237 supervisor = self.letta_client.agents.retrieve(agent_id=group.manager_config.manager_agent_id) 238 supervisor_name = supervisor.name if hasattr(supervisor, 'name') else group.manager_config.manager_agent_id[:8] 239 info_text += f"[bold]Supervisor:[/bold] {supervisor_name}\n" 240 except: 241 info_text += f"[bold]Supervisor:[/bold] {group.manager_config.manager_agent_id[:8]}\n" 242 243 self.console.print(Panel(info_text, title="Group Information")) 244 245 except Exception as e: 246 self.console.print(f"[red]Error getting group info: {e}[/red]") 247 248def main(): 249 """Main function.""" 250 try: 251 chat = OrganonChat() 252 chat.run() 253 except Exception as e: 254 console = Console() 255 console.print(f"[red]Failed to initialize chat: {e}[/red]") 256 sys.exit(1) 257 258if __name__ == "__main__": 259 main()