105 lines
3.6 KiB
Python
105 lines
3.6 KiB
Python
|
|
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."
|