a digital person for bluesky
at toolchange 6.6 kB view raw
1#!/usr/bin/env python3 2"""Register all Void tools with a Letta agent.""" 3import os 4import sys 5import logging 6from typing import List 7from dotenv import load_dotenv 8from letta_client import Letta 9from rich.console import Console 10from rich.table import Table 11 12# Import standalone functions and their schemas 13from tools.search import search_bluesky_posts, SearchArgs 14from tools.post import create_new_bluesky_post, PostArgs 15from tools.reply import reply_to_bluesky_post, ReplyArgs 16from tools.feed import get_bluesky_feed, FeedArgs 17from tools.blocks import attach_user_blocks, detach_user_blocks, AttachUserBlocksArgs, DetachUserBlocksArgs 18 19load_dotenv() 20logging.basicConfig(level=logging.INFO) 21logger = logging.getLogger(__name__) 22console = Console() 23 24 25# Tool configurations: function paired with its args_schema and metadata 26TOOL_CONFIGS = [ 27 { 28 "func": search_bluesky_posts, 29 "args_schema": SearchArgs, 30 "description": "Search for posts on Bluesky matching the given criteria", 31 "tags": ["bluesky", "search", "posts"] 32 }, 33 { 34 "func": create_new_bluesky_post, 35 "args_schema": PostArgs, 36 "description": "Create a NEW standalone post on Bluesky. DO NOT use for replies.", 37 "tags": ["bluesky", "post", "create", "new"] 38 }, 39 { 40 "func": reply_to_bluesky_post, 41 "args_schema": ReplyArgs, 42 "description": "Reply to an existing Bluesky post. Requires the URI of the post being replied to.", 43 "tags": ["bluesky", "reply", "thread", "conversation"] 44 }, 45 { 46 "func": get_bluesky_feed, 47 "args_schema": FeedArgs, 48 "description": "Retrieve a Bluesky feed (home timeline or custom feed)", 49 "tags": ["bluesky", "feed", "timeline"] 50 }, 51 { 52 "func": attach_user_blocks, 53 "args_schema": AttachUserBlocksArgs, 54 "description": "Attach user-specific memory blocks to the agent. Creates blocks if they don't exist.", 55 "tags": ["memory", "blocks", "user"] 56 }, 57 { 58 "func": detach_user_blocks, 59 "args_schema": DetachUserBlocksArgs, 60 "description": "Detach user-specific memory blocks from the agent. Blocks are preserved for later use.", 61 "tags": ["memory", "blocks", "user"] 62 }, 63 # { 64 # "func": update_user_blocks, 65 # "args_schema": UpdateUserBlockArgs, 66 # "description": "Update the content of user-specific memory blocks", 67 # "tags": ["memory", "blocks", "user"] 68 # }, 69] 70 71 72def register_tools(agent_name: str = "void", tools: List[str] = None): 73 """Register tools with a Letta agent. 74 75 Args: 76 agent_name: Name of the agent to attach tools to 77 tools: List of tool names to register. If None, registers all tools. 78 """ 79 try: 80 # Initialize Letta client with API key 81 client = Letta(token=os.environ["LETTA_API_KEY"]) 82 83 # Find the agent 84 agents = client.agents.list() 85 agent = None 86 for a in agents: 87 if a.name == agent_name: 88 agent = a 89 break 90 91 if not agent: 92 console.print(f"[red]Error: Agent '{agent_name}' not found[/red]") 93 console.print("\nAvailable agents:") 94 for a in agents: 95 console.print(f" - {a.name}") 96 return 97 98 # Filter tools if specific ones requested 99 tools_to_register = TOOL_CONFIGS 100 if tools: 101 tools_to_register = [t for t in TOOL_CONFIGS if t["func"].__name__ in tools] 102 if len(tools_to_register) != len(tools): 103 missing = set(tools) - {t["func"].__name__ for t in tools_to_register} 104 console.print(f"[yellow]Warning: Unknown tools: {missing}[/yellow]") 105 106 # Create results table 107 table = Table(title=f"Tool Registration for Agent '{agent_name}'") 108 table.add_column("Tool", style="cyan") 109 table.add_column("Status", style="green") 110 table.add_column("Description") 111 112 # Register each tool 113 for tool_config in tools_to_register: 114 func = tool_config["func"] 115 tool_name = func.__name__ 116 117 try: 118 # Create or update the tool using the standalone function 119 created_tool = client.tools.upsert_from_function( 120 func=func, 121 args_schema=tool_config["args_schema"], 122 tags=tool_config["tags"] 123 ) 124 125 # Get current agent tools 126 current_tools = client.agents.tools.list(agent_id=str(agent.id)) 127 tool_names = [t.name for t in current_tools] 128 129 # Check if already attached 130 if created_tool.name in tool_names: 131 table.add_row(tool_name, "Already Attached", tool_config["description"]) 132 else: 133 # Attach to agent 134 client.agents.tools.attach( 135 agent_id=str(agent.id), 136 tool_id=str(created_tool.id) 137 ) 138 table.add_row(tool_name, "✓ Attached", tool_config["description"]) 139 140 except Exception as e: 141 table.add_row(tool_name, f"✗ Error: {str(e)}", tool_config["description"]) 142 logger.error(f"Error registering tool {tool_name}: {e}") 143 144 console.print(table) 145 146 except Exception as e: 147 console.print(f"[red]Error: {str(e)}[/red]") 148 logger.error(f"Fatal error: {e}") 149 150 151def list_available_tools(): 152 """List all available tools.""" 153 table = Table(title="Available Void Tools") 154 table.add_column("Tool Name", style="cyan") 155 table.add_column("Description") 156 table.add_column("Tags", style="dim") 157 158 for tool_config in TOOL_CONFIGS: 159 table.add_row( 160 tool_config["func"].__name__, 161 tool_config["description"], 162 ", ".join(tool_config["tags"]) 163 ) 164 165 console.print(table) 166 167 168if __name__ == "__main__": 169 import argparse 170 171 parser = argparse.ArgumentParser(description="Register Void tools with a Letta agent") 172 parser.add_argument("agent", nargs="?", default="void", help="Agent name (default: void)") 173 parser.add_argument("--tools", nargs="+", help="Specific tools to register (default: all)") 174 parser.add_argument("--list", action="store_true", help="List available tools") 175 176 args = parser.parse_args() 177 178 if args.list: 179 list_available_tools() 180 else: 181 console.print(f"\n[bold]Registering tools for agent: {args.agent}[/bold]\n") 182 register_tools(args.agent, args.tools)