a digital person for bluesky
at f0dfd62bab20cd1fab1ef77e443164c0675cfe72 149 lines 5.8 kB view raw
1"""Block management tools for user-specific memory blocks.""" 2from pydantic import BaseModel, Field 3from typing import List, Dict, Any 4 5 6class AttachUserBlocksArgs(BaseModel): 7 handles: List[str] = Field(..., description="List of user Bluesky handles (e.g., ['user1.bsky.social', 'user2.bsky.social'])") 8 9 10class DetachUserBlocksArgs(BaseModel): 11 handles: List[str] = Field(..., description="List of user Bluesky handles (e.g., ['user1.bsky.social', 'user2.bsky.social'])") 12 13 14 15def attach_user_blocks(handles: list, agent_state: "AgentState") -> str: 16 """ 17 Attach user-specific memory blocks to the agent. Creates blocks if they don't exist. 18 19 Args: 20 handles: List of user Bluesky handles (e.g., ['user1.bsky.social', 'user2.bsky.social']) 21 agent_state: The agent state object containing agent information 22 23 Returns: 24 String with attachment results for each handle 25 """ 26 import os 27 import logging 28 from letta_client import Letta 29 30 logger = logging.getLogger(__name__) 31 32 try: 33 client = Letta(token=os.environ["LETTA_API_KEY"]) 34 results = [] 35 36 # Get current blocks using the API 37 current_blocks = client.agents.blocks.list(agent_id=str(agent_state.id)) 38 current_block_labels = set() 39 40 for block in current_blocks: 41 current_block_labels.add(block.label) 42 43 for handle in handles: 44 # Sanitize handle for block label - completely self-contained 45 clean_handle = handle.lstrip('@').replace('.', '_').replace('-', '_').replace(' ', '_') 46 block_label = f"user_{clean_handle}" 47 48 # Skip if already attached 49 if block_label in current_block_labels: 50 results.append(f"{handle}: Already attached") 51 continue 52 53 # Check if block exists or create new one 54 try: 55 blocks = client.blocks.list(label=block_label) 56 if blocks and len(blocks) > 0: 57 block = blocks[0] 58 logger.info(f"Found existing block: {block_label}") 59 else: 60 block = client.blocks.create( 61 label=block_label, 62 value=f"# User: {handle}\n\nNo information about this user yet.", 63 limit=5000 64 ) 65 logger.info(f"Created new block: {block_label}") 66 67 # Attach block atomically 68 client.agents.blocks.attach( 69 agent_id=str(agent_state.id), 70 block_id=str(block.id) 71 ) 72 73 # STOPGAP: Also update agent_state.memory to sync in-memory state 74 try: 75 agent_state.memory.set_block(block) 76 print(f"[SYNC] Successfully synced block {block_label} to agent_state.memory") 77 except Exception as sync_error: 78 print(f"[SYNC] Warning: Failed to sync block to agent_state.memory: {sync_error}") 79 80 results.append(f"{handle}: Block attached") 81 logger.info(f"Successfully attached block {block_label} to agent") 82 83 except Exception as e: 84 results.append(f"{handle}: Error - {str(e)}") 85 logger.error(f"Error processing block for {handle}: {e}") 86 87 return f"Attachment results:\n" + "\n".join(results) 88 89 except Exception as e: 90 logger.error(f"Error attaching user blocks: {e}") 91 raise Exception(f"Error attaching user blocks: {str(e)}") 92 93 94def detach_user_blocks(handles: list, agent_state: "AgentState") -> str: 95 """ 96 Detach user-specific memory blocks from the agent. Blocks are preserved for later use. 97 98 Args: 99 handles: List of user Bluesky handles (e.g., ['user1.bsky.social', 'user2.bsky.social']) 100 agent_state: The agent state object containing agent information 101 102 Returns: 103 String with detachment results for each handle 104 """ 105 import os 106 import logging 107 from letta_client import Letta 108 109 logger = logging.getLogger(__name__) 110 111 try: 112 client = Letta(token=os.environ["LETTA_API_KEY"]) 113 results = [] 114 115 # Build mapping of block labels to IDs using the API 116 current_blocks = client.agents.blocks.list(agent_id=str(agent_state.id)) 117 block_label_to_id = {} 118 119 for block in current_blocks: 120 block_label_to_id[block.label] = str(block.id) 121 122 # Process each handle and detach atomically 123 for handle in handles: 124 # Sanitize handle for block label - completely self-contained 125 clean_handle = handle.lstrip('@').replace('.', '_').replace('-', '_').replace(' ', '_') 126 block_label = f"user_{clean_handle}" 127 128 if block_label in block_label_to_id: 129 try: 130 # Detach block atomically 131 client.agents.blocks.detach( 132 agent_id=str(agent_state.id), 133 block_id=block_label_to_id[block_label] 134 ) 135 results.append(f"{handle}: Detached") 136 logger.info(f"Successfully detached block {block_label} from agent") 137 except Exception as e: 138 results.append(f"{handle}: Error during detachment - {str(e)}") 139 logger.error(f"Error detaching block {block_label}: {e}") 140 else: 141 results.append(f"{handle}: Not attached") 142 143 return f"Detachment results:\n" + "\n".join(results) 144 145 except Exception as e: 146 logger.error(f"Error detaching user blocks: {e}") 147 raise Exception(f"Error detaching user blocks: {str(e)}") 148 149