import json import logging import boto3 from shared.dynamo import get_conversation, save_conversation from shared.secrets import get_slack_token from shared.slack import update_message from conversation.tools import TOOLS, dispatch_tool logger = logging.getLogger() logger.setLevel(logging.INFO) _bedrock = boto3.client("bedrock-runtime", region_name="us-east-1") MODEL_ID = "us.anthropic.claude-sonnet-4-6" MAX_TOOL_ITERATIONS = 10 SYSTEM_PROMPT = """\ You are Exec Aide, a personal executive assistant for Adam Moussa, President \ of Sea Haven Industries (a facilities maintenance company). You have access to Adam's email inbox. Use the available tools to answer \ questions about emails, find information, and provide inbox summaries. Be concise and direct. Adam is busy - give him the key information without \ unnecessary preamble. Use bullet points for lists of emails. When referencing emails, always include the sender name and subject line. \ For time references, use relative terms (e.g., "2 hours ago", "yesterday"). If Adam asks about something outside your current capabilities (calendar, \ tasks, etc.), acknowledge it and let him know it's not available yet.\ """ def lambda_handler(event, context): user_message = event["user_message"] thread_ts = event["thread_ts"] channel = event["channel"] placeholder_ts = event["placeholder_ts"] try: messages = get_conversation(thread_ts) messages.append({"role": "user", "content": [{"text": user_message}]}) assistant_message = _run_conversation(messages) final_text = _extract_text(assistant_message) save_conversation(thread_ts, messages) update_message(channel, placeholder_ts, final_text, get_slack_token()) logger.info("Conversation complete: thread=%s, turns=%d", thread_ts, len(messages)) except Exception: logger.exception("Conversation failed for thread %s", thread_ts) try: update_message( channel, placeholder_ts, "Sorry, I hit an error processing that. Try again in a moment.", get_slack_token(), ) except Exception: logger.exception("Failed to update error placeholder") def _run_conversation(messages): assistant_message = None for _ in range(MAX_TOOL_ITERATIONS): response = _bedrock.converse( modelId=MODEL_ID, system=[{"text": SYSTEM_PROMPT}], messages=messages, toolConfig={"tools": TOOLS}, inferenceConfig={"maxTokens": 4096}, ) assistant_message = response["output"]["message"] messages.append(assistant_message) if response["stopReason"] != "tool_use": break tool_results = [] for block in assistant_message["content"]: tool_use = block.get("toolUse") if tool_use: logger.info("Tool call: %s(%s)", tool_use["name"], json.dumps(tool_use["input"])) result = dispatch_tool(tool_use["name"], tool_use["input"]) tool_results.append({ "toolResult": { "toolUseId": tool_use["toolUseId"], "content": [{"text": json.dumps(result)}], } }) messages.append({"role": "user", "content": tool_results}) return assistant_message def _extract_text(message): parts = [] for block in message.get("content", []): if "text" in block: parts.append(block["text"]) return "\n".join(parts) if parts else "I processed your request but have nothing to report."