a digital person for bluesky
at main 5.5 kB view raw
1""" 2Bot detection tools for checking known_bots memory block. 3""" 4import os 5import random 6import logging 7from typing import List, Tuple, Optional 8from pydantic import BaseModel, Field 9from letta_client import Letta 10 11logger = logging.getLogger(__name__) 12 13 14class CheckKnownBotsArgs(BaseModel): 15 """Arguments for checking if users are in the known_bots list.""" 16 handles: List[str] = Field(..., description="List of user handles to check against known_bots") 17 18 19def check_known_bots(handles: List[str], agent_state: "AgentState") -> str: 20 """ 21 Check if any of the provided handles are in the known_bots memory block. 22 23 Args: 24 handles: List of user handles to check (e.g., ['horsedisc.bsky.social', 'user.bsky.social']) 25 agent_state: The agent state object containing agent information 26 27 Returns: 28 JSON string with bot detection results 29 """ 30 import json 31 32 try: 33 # Create Letta client inline (for cloud execution) 34 client = Letta(token=os.environ["LETTA_API_KEY"]) 35 36 # Get all blocks attached to the agent to check if known_bots is mounted 37 attached_blocks = client.agents.blocks.list(agent_id=str(agent_state.id)) 38 attached_labels = {block.label for block in attached_blocks} 39 40 if "known_bots" not in attached_labels: 41 return json.dumps({ 42 "error": "known_bots memory block is not mounted to this agent", 43 "bot_detected": False, 44 "detected_bots": [] 45 }) 46 47 # Retrieve known_bots block content using agent-specific retrieval 48 try: 49 known_bots_block = client.agents.blocks.retrieve( 50 agent_id=str(agent_state.id), 51 block_label="known_bots" 52 ) 53 except Exception as e: 54 return json.dumps({ 55 "error": f"Error retrieving known_bots block: {str(e)}", 56 "bot_detected": False, 57 "detected_bots": [] 58 }) 59 known_bots_content = known_bots_block.value 60 61 # Parse known bots from content 62 known_bot_handles = [] 63 for line in known_bots_content.split('\n'): 64 line = line.strip() 65 if line and not line.startswith('#'): 66 # Extract handle from lines like "- @handle.bsky.social" or "- @handle.bsky.social: description" 67 if line.startswith('- @'): 68 handle = line[3:].split(':')[0].strip() 69 known_bot_handles.append(handle) 70 elif line.startswith('-'): 71 handle = line[1:].split(':')[0].strip().lstrip('@') 72 known_bot_handles.append(handle) 73 74 # Normalize handles for comparison 75 normalized_input_handles = [h.lstrip('@').strip() for h in handles] 76 normalized_bot_handles = [h.strip() for h in known_bot_handles] 77 78 # Check for matches 79 detected_bots = [] 80 for handle in normalized_input_handles: 81 if handle in normalized_bot_handles: 82 detected_bots.append(handle) 83 84 bot_detected = len(detected_bots) > 0 85 86 return json.dumps({ 87 "bot_detected": bot_detected, 88 "detected_bots": detected_bots, 89 "total_known_bots": len(normalized_bot_handles), 90 "checked_handles": normalized_input_handles 91 }) 92 93 except Exception as e: 94 return json.dumps({ 95 "error": f"Error checking known_bots: {str(e)}", 96 "bot_detected": False, 97 "detected_bots": [] 98 }) 99 100 101def should_respond_to_bot_thread() -> bool: 102 """ 103 Determine if we should respond to a bot thread (10% chance). 104 105 Returns: 106 True if we should respond, False if we should skip 107 """ 108 return random.random() < 0.1 109 110 111def extract_handles_from_thread(thread_data: dict) -> List[str]: 112 """ 113 Extract all unique handles from a thread structure. 114 115 Args: 116 thread_data: Thread data dictionary from Bluesky API 117 118 Returns: 119 List of unique handles found in the thread 120 """ 121 handles = set() 122 123 def extract_from_post(post): 124 """Recursively extract handles from a post and its replies.""" 125 if isinstance(post, dict): 126 # Get author handle 127 if 'post' in post and 'author' in post['post']: 128 handle = post['post']['author'].get('handle') 129 if handle: 130 handles.add(handle) 131 elif 'author' in post: 132 handle = post['author'].get('handle') 133 if handle: 134 handles.add(handle) 135 136 # Check replies 137 if 'replies' in post: 138 for reply in post['replies']: 139 extract_from_post(reply) 140 141 # Check parent 142 if 'parent' in post: 143 extract_from_post(post['parent']) 144 145 # Start extraction from thread root 146 if 'thread' in thread_data: 147 extract_from_post(thread_data['thread']) 148 else: 149 extract_from_post(thread_data) 150 151 return list(handles) 152 153 154# Tool configuration for registration 155TOOL_CONFIG = { 156 "type": "function", 157 "function": { 158 "name": "check_known_bots", 159 "description": "Check if any of the provided handles are in the known_bots memory block", 160 "parameters": CheckKnownBotsArgs.model_json_schema(), 161 }, 162}