a digital person for bluesky
at rich-panel 8.9 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.feed import get_bluesky_feed, FeedArgs 16from tools.blocks import attach_user_blocks, detach_user_blocks, user_note_append, user_note_replace, user_note_set, user_note_view, AttachUserBlocksArgs, DetachUserBlocksArgs, UserNoteAppendArgs, UserNoteReplaceArgs, UserNoteSetArgs, UserNoteViewArgs 17from tools.halt import halt_activity, HaltArgs 18from tools.thread import add_post_to_bluesky_reply_thread, ReplyThreadPostArgs 19from tools.ignore import ignore_notification, IgnoreNotificationArgs 20from tools.whitewind import create_whitewind_blog_post, WhitewindPostArgs 21from tools.ack import annotate_ack, AnnotateAckArgs 22from tools.webpage import fetch_webpage, WebpageArgs 23 24load_dotenv() 25logging.basicConfig(level=logging.INFO) 26logger = logging.getLogger(__name__) 27console = Console() 28 29 30# Tool configurations: function paired with its args_schema and metadata 31TOOL_CONFIGS = [ 32 { 33 "func": search_bluesky_posts, 34 "args_schema": SearchArgs, 35 "description": "Search for posts on Bluesky matching the given criteria", 36 "tags": ["bluesky", "search", "posts"] 37 }, 38 { 39 "func": create_new_bluesky_post, 40 "args_schema": PostArgs, 41 "description": "Create a new Bluesky post or thread", 42 "tags": ["bluesky", "post", "create", "thread"] 43 }, 44 { 45 "func": get_bluesky_feed, 46 "args_schema": FeedArgs, 47 "description": "Retrieve a Bluesky feed (home timeline or custom feed)", 48 "tags": ["bluesky", "feed", "timeline"] 49 }, 50 { 51 "func": attach_user_blocks, 52 "args_schema": AttachUserBlocksArgs, 53 "description": "Attach user-specific memory blocks to the agent. Creates blocks if they don't exist.", 54 "tags": ["memory", "blocks", "user"] 55 }, 56 { 57 "func": detach_user_blocks, 58 "args_schema": DetachUserBlocksArgs, 59 "description": "Detach user-specific memory blocks from the agent. Blocks are preserved for later use.", 60 "tags": ["memory", "blocks", "user"] 61 }, 62 { 63 "func": user_note_append, 64 "args_schema": UserNoteAppendArgs, 65 "description": "Append a note to a user's memory block. Creates the block if it doesn't exist.", 66 "tags": ["memory", "blocks", "user", "append"] 67 }, 68 { 69 "func": user_note_replace, 70 "args_schema": UserNoteReplaceArgs, 71 "description": "Replace text in a user's memory block.", 72 "tags": ["memory", "blocks", "user", "replace"] 73 }, 74 { 75 "func": user_note_set, 76 "args_schema": UserNoteSetArgs, 77 "description": "Set the complete content of a user's memory block.", 78 "tags": ["memory", "blocks", "user", "set"] 79 }, 80 { 81 "func": user_note_view, 82 "args_schema": UserNoteViewArgs, 83 "description": "View the content of a user's memory block.", 84 "tags": ["memory", "blocks", "user", "view"] 85 }, 86 { 87 "func": halt_activity, 88 "args_schema": HaltArgs, 89 "description": "Signal to halt all bot activity and terminate bsky.py", 90 "tags": ["control", "halt", "terminate"] 91 }, 92 { 93 "func": add_post_to_bluesky_reply_thread, 94 "args_schema": ReplyThreadPostArgs, 95 "description": "Add a single post to the current Bluesky reply thread atomically", 96 "tags": ["bluesky", "reply", "thread", "atomic"] 97 }, 98 { 99 "func": ignore_notification, 100 "args_schema": IgnoreNotificationArgs, 101 "description": "Explicitly ignore a notification without replying (useful for ignoring bot interactions)", 102 "tags": ["notification", "ignore", "control", "bot"] 103 }, 104 { 105 "func": create_whitewind_blog_post, 106 "args_schema": WhitewindPostArgs, 107 "description": "Create a blog post on Whitewind with markdown support", 108 "tags": ["whitewind", "blog", "post", "markdown"] 109 }, 110 { 111 "func": annotate_ack, 112 "args_schema": AnnotateAckArgs, 113 "description": "Add a note to the acknowledgment record for the current post interaction", 114 "tags": ["acknowledgment", "note", "annotation", "metadata"] 115 }, 116 { 117 "func": fetch_webpage, 118 "args_schema": WebpageArgs, 119 "description": "Fetch a webpage and convert it to markdown/text format using Jina AI reader", 120 "tags": ["web", "fetch", "webpage", "markdown", "jina"] 121 }, 122] 123 124 125def register_tools(agent_name: str = "void", tools: List[str] = None): 126 """Register tools with a Letta agent. 127 128 Args: 129 agent_name: Name of the agent to attach tools to 130 tools: List of tool names to register. If None, registers all tools. 131 """ 132 try: 133 # Initialize Letta client with API key 134 client = Letta(token=os.environ["LETTA_API_KEY"]) 135 136 # Find the agent 137 agents = client.agents.list() 138 agent = None 139 for a in agents: 140 if a.name == agent_name: 141 agent = a 142 break 143 144 if not agent: 145 console.print(f"[red]Error: Agent '{agent_name}' not found[/red]") 146 console.print("\nAvailable agents:") 147 for a in agents: 148 console.print(f" - {a.name}") 149 return 150 151 # Filter tools if specific ones requested 152 tools_to_register = TOOL_CONFIGS 153 if tools: 154 tools_to_register = [t for t in TOOL_CONFIGS if t["func"].__name__ in tools] 155 if len(tools_to_register) != len(tools): 156 missing = set(tools) - {t["func"].__name__ for t in tools_to_register} 157 console.print(f"[yellow]Warning: Unknown tools: {missing}[/yellow]") 158 159 # Create results table 160 table = Table(title=f"Tool Registration for Agent '{agent_name}'") 161 table.add_column("Tool", style="cyan") 162 table.add_column("Status", style="green") 163 table.add_column("Description") 164 165 # Register each tool 166 for tool_config in tools_to_register: 167 func = tool_config["func"] 168 tool_name = func.__name__ 169 170 try: 171 # Create or update the tool using the standalone function 172 created_tool = client.tools.upsert_from_function( 173 func=func, 174 args_schema=tool_config["args_schema"], 175 tags=tool_config["tags"] 176 ) 177 178 # Get current agent tools 179 current_tools = client.agents.tools.list(agent_id=str(agent.id)) 180 tool_names = [t.name for t in current_tools] 181 182 # Check if already attached 183 if created_tool.name in tool_names: 184 table.add_row(tool_name, "Already Attached", tool_config["description"]) 185 else: 186 # Attach to agent 187 client.agents.tools.attach( 188 agent_id=str(agent.id), 189 tool_id=str(created_tool.id) 190 ) 191 table.add_row(tool_name, "✓ Attached", tool_config["description"]) 192 193 except Exception as e: 194 table.add_row(tool_name, f"✗ Error: {str(e)}", tool_config["description"]) 195 logger.error(f"Error registering tool {tool_name}: {e}") 196 197 console.print(table) 198 199 except Exception as e: 200 console.print(f"[red]Error: {str(e)}[/red]") 201 logger.error(f"Fatal error: {e}") 202 203 204def list_available_tools(): 205 """List all available tools.""" 206 table = Table(title="Available Void Tools") 207 table.add_column("Tool Name", style="cyan") 208 table.add_column("Description") 209 table.add_column("Tags", style="dim") 210 211 for tool_config in TOOL_CONFIGS: 212 table.add_row( 213 tool_config["func"].__name__, 214 tool_config["description"], 215 ", ".join(tool_config["tags"]) 216 ) 217 218 console.print(table) 219 220 221if __name__ == "__main__": 222 import argparse 223 224 parser = argparse.ArgumentParser(description="Register Void tools with a Letta agent") 225 parser.add_argument("agent", nargs="?", default="void", help="Agent name (default: void)") 226 parser.add_argument("--tools", nargs="+", help="Specific tools to register (default: all)") 227 parser.add_argument("--list", action="store_true", help="List available tools") 228 229 args = parser.parse_args() 230 231 if args.list: 232 list_available_tools() 233 else: 234 console.print(f"\n[bold]Registering tools for agent: {args.agent}[/bold]\n") 235 register_tools(args.agent, args.tools)