{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load env variables and create client\n",
    "from dotenv import load_dotenv\n",
    "from anthropic import Anthropic\n",
    "\n",
    "load_dotenv()\n",
    "\n",
    "client = Anthropic()\n",
    "model = \"claude-3-7-sonnet-latest\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Helper functions\n",
    "from anthropic.types import Message\n",
    "\n",
    "\n",
    "def add_user_message(messages, message):\n",
    "    user_message = {\n",
    "        \"role\": \"user\",\n",
    "        \"content\": message.content if isinstance(message, Message) else message,\n",
    "    }\n",
    "    messages.append(user_message)\n",
    "\n",
    "\n",
    "def add_assistant_message(messages, message):\n",
    "    assistant_message = {\n",
    "        \"role\": \"assistant\",\n",
    "        \"content\": message.content if isinstance(message, Message) else message,\n",
    "    }\n",
    "    messages.append(assistant_message)\n",
    "\n",
    "\n",
    "def chat(messages, system=None, temperature=1.0, stop_sequences=[], tools=None):\n",
    "    params = {\n",
    "        \"model\": model,\n",
    "        \"max_tokens\": 1000,\n",
    "        \"messages\": messages,\n",
    "        \"temperature\": temperature,\n",
    "        \"stop_sequences\": stop_sequences,\n",
    "    }\n",
    "\n",
    "    if tools:\n",
    "        params[\"tools\"] = tools\n",
    "\n",
    "    if system:\n",
    "        params[\"system\"] = system\n",
    "\n",
    "    message = client.messages.create(**params)\n",
    "    return message\n",
    "\n",
    "\n",
    "def text_from_message(message):\n",
    "    return \"\\n\".join(\n",
    "        [block.text for block in message.content if block.type == \"text\"]\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Tools and Schemas\n",
    "\n",
    "from datetime import datetime, timedelta\n",
    "\n",
    "\n",
    "def add_duration_to_datetime(\n",
    "    datetime_str, duration=0, unit=\"days\", input_format=\"%Y-%m-%d\"\n",
    "):\n",
    "    date = datetime.strptime(datetime_str, input_format)\n",
    "\n",
    "    if unit == \"seconds\":\n",
    "        new_date = date + timedelta(seconds=duration)\n",
    "    elif unit == \"minutes\":\n",
    "        new_date = date + timedelta(minutes=duration)\n",
    "    elif unit == \"hours\":\n",
    "        new_date = date + timedelta(hours=duration)\n",
    "    elif unit == \"days\":\n",
    "        new_date = date + timedelta(days=duration)\n",
    "    elif unit == \"weeks\":\n",
    "        new_date = date + timedelta(weeks=duration)\n",
    "    elif unit == \"months\":\n",
    "        month = date.month + duration\n",
    "        year = date.year + month // 12\n",
    "        month = month % 12\n",
    "        if month == 0:\n",
    "            month = 12\n",
    "            year -= 1\n",
    "        day = min(\n",
    "            date.day,\n",
    "            [\n",
    "                31,\n",
    "                29\n",
    "                if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)\n",
    "                else 28,\n",
    "                31,\n",
    "                30,\n",
    "                31,\n",
    "                30,\n",
    "                31,\n",
    "                31,\n",
    "                30,\n",
    "                31,\n",
    "                30,\n",
    "                31,\n",
    "            ][month - 1],\n",
    "        )\n",
    "        new_date = date.replace(year=year, month=month, day=day)\n",
    "    elif unit == \"years\":\n",
    "        new_date = date.replace(year=date.year + duration)\n",
    "    else:\n",
    "        raise ValueError(f\"Unsupported time unit: {unit}\")\n",
    "\n",
    "    return new_date.strftime(\"%A, %B %d, %Y %I:%M:%S %p\")\n",
    "\n",
    "\n",
    "def set_reminder(content, timestamp):\n",
    "    print(\n",
    "        f\"----\\nSetting the following reminder for {timestamp}:\\n{content}\\n----\"\n",
    "    )\n",
    "\n",
    "\n",
    "add_duration_to_datetime_schema = {\n",
    "    \"name\": \"add_duration_to_datetime\",\n",
    "    \"description\": \"Adds a specified duration to a datetime string and returns the resulting datetime in a detailed format. This tool converts an input datetime string to a Python datetime object, adds the specified duration in the requested unit, and returns a formatted string of the resulting datetime. It handles various time units including seconds, minutes, hours, days, weeks, months, and years, with special handling for month and year calculations to account for varying month lengths and leap years. The output is always returned in a detailed format that includes the day of the week, month name, day, year, and time with AM/PM indicator (e.g., 'Thursday, April 03, 2025 10:30:00 AM').\",\n",
    "    \"input_schema\": {\n",
    "        \"type\": \"object\",\n",
    "        \"properties\": {\n",
    "            \"datetime_str\": {\n",
    "                \"type\": \"string\",\n",
    "                \"description\": \"The input datetime string to which the duration will be added. This should be formatted according to the input_format parameter.\",\n",
    "            },\n",
    "            \"duration\": {\n",
    "                \"type\": \"number\",\n",
    "                \"description\": \"The amount of time to add to the datetime. Can be positive (for future dates) or negative (for past dates). Defaults to 0.\",\n",
    "            },\n",
    "            \"unit\": {\n",
    "                \"type\": \"string\",\n",
    "                \"description\": \"The unit of time for the duration. Must be one of: 'seconds', 'minutes', 'hours', 'days', 'weeks', 'months', or 'years'. Defaults to 'days'.\",\n",
    "            },\n",
    "            \"input_format\": {\n",
    "                \"type\": \"string\",\n",
    "                \"description\": \"The format string for parsing the input datetime_str, using Python's strptime format codes. For example, '%Y-%m-%d' for ISO format dates like '2025-04-03'. Defaults to '%Y-%m-%d'.\",\n",
    "            },\n",
    "        },\n",
    "        \"required\": [\"datetime_str\"],\n",
    "    },\n",
    "}\n",
    "\n",
    "set_reminder_schema = {\n",
    "    \"name\": \"set_reminder\",\n",
    "    \"description\": \"Creates a timed reminder that will notify the user at the specified time with the provided content. This tool schedules a notification to be delivered to the user at the exact timestamp provided. It should be used when a user wants to be reminded about something specific at a future point in time. The reminder system will store the content and timestamp, then trigger a notification through the user's preferred notification channels (mobile alerts, email, etc.) when the specified time arrives. Reminders are persisted even if the application is closed or the device is restarted. Users can rely on this function for important time-sensitive notifications such as meetings, tasks, medication schedules, or any other time-bound activities.\",\n",
    "    \"input_schema\": {\n",
    "        \"type\": \"object\",\n",
    "        \"properties\": {\n",
    "            \"content\": {\n",
    "                \"type\": \"string\",\n",
    "                \"description\": \"The message text that will be displayed in the reminder notification. This should contain the specific information the user wants to be reminded about, such as 'Take medication', 'Join video call with team', or 'Pay utility bills'.\",\n",
    "            },\n",
    "            \"timestamp\": {\n",
    "                \"type\": \"string\",\n",
    "                \"description\": \"The exact date and time when the reminder should be triggered, formatted as an ISO 8601 timestamp (YYYY-MM-DDTHH:MM:SS) or a Unix timestamp. The system handles all timezone processing internally, ensuring reminders are triggered at the correct time regardless of where the user is located. Users can simply specify the desired time without worrying about timezone configurations.\",\n",
    "            },\n",
    "        },\n",
    "        \"required\": [\"content\", \"timestamp\"],\n",
    "    },\n",
    "}\n",
    "\n",
    "batch_tool_schema = {\n",
    "    \"name\": \"batch_tool\",\n",
    "    \"description\": \"Invoke multiple other tool calls simultaneously\",\n",
    "    \"input_schema\": {\n",
    "        \"type\": \"object\",\n",
    "        \"properties\": {\n",
    "            \"invocations\": {\n",
    "                \"type\": \"array\",\n",
    "                \"description\": \"The tool calls to invoke\",\n",
    "                \"items\": {\n",
    "                    \"type\": \"object\",\n",
    "                    \"properties\": {\n",
    "                        \"name\": {\n",
    "                            \"type\": \"string\",\n",
    "                            \"description\": \"The name of the tool to invoke\",\n",
    "                        },\n",
    "                        \"arguments\": {\n",
    "                            \"type\": \"string\",\n",
    "                            \"description\": \"The arguments to the tool, encoded as a JSON string\",\n",
    "                        },\n",
    "                    },\n",
    "                    \"required\": [\"name\", \"arguments\"],\n",
    "                },\n",
    "            }\n",
    "        },\n",
    "        \"required\": [\"invocations\"],\n",
    "    },\n",
    "}\n",
    "\n",
    "pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# get_current_datetime tool function\n",
    "from anthropic.types import ToolParam\n",
    "\n",
    "\n",
    "def get_current_datetime(date_format=\"%Y-%m-%d %H:%M:%S\"):\n",
    "    if not date_format:\n",
    "        raise ValueError(\"date_format cannot be empty\")\n",
    "    return datetime.now().strftime(date_format)\n",
    "\n",
    "\n",
    "get_current_datetime_schema = ToolParam(\n",
    "    {\n",
    "        \"name\": \"get_current_datetime\",\n",
    "        \"description\": \"Returns the current date and time formatted according to the specified format string. This tool provides the current system time formatted as a string. Use this tool when you need to know the current date and time, such as for timestamping records, calculating time differences, or displaying the current time to users. The default format returns the date and time in ISO-like format (YYYY-MM-DD HH:MM:SS).\",\n",
    "        \"input_schema\": {\n",
    "            \"type\": \"object\",\n",
    "            \"properties\": {\n",
    "                \"date_format\": {\n",
    "                    \"type\": \"string\",\n",
    "                    \"description\": \"A string specifying the format of the returned datetime. Uses Python's strftime format codes. For example, '%Y-%m-%d' returns just the date in YYYY-MM-DD format, '%H:%M:%S' returns just the time in HH:MM:SS format, '%B %d, %Y' returns a date like 'May 07, 2025'. The default is '%Y-%m-%d %H:%M:%S' which returns a complete timestamp like '2025-05-07 14:32:15'.\",\n",
    "                    \"default\": \"%Y-%m-%d %H:%M:%S\",\n",
    "                }\n",
    "            },\n",
    "            \"required\": [],\n",
    "        },\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Tool Running\n",
    "import json\n",
    "\n",
    "\n",
    "def run_batch(invocations=[]):\n",
    "    batch_output = []\n",
    "\n",
    "    for invocation in invocations:\n",
    "        name = invocation[\"name\"]\n",
    "        args = json.loads(invocation[\"arguments\"])\n",
    "\n",
    "        tool_output = run_tool(name, args)\n",
    "\n",
    "        batch_output.append({\"tool_name\": name, \"output\": tool_output})\n",
    "\n",
    "    return batch_output\n",
    "\n",
    "\n",
    "def run_tool(tool_name, tool_input):\n",
    "    if tool_name == \"get_current_datetime\":\n",
    "        return get_current_datetime(**tool_input)\n",
    "    elif tool_name == \"add_duration_to_datetime\":\n",
    "        return add_duration_to_datetime(**tool_input)\n",
    "    elif tool_name == \"set_reminder\":\n",
    "        return set_reminder(**tool_input)\n",
    "    elif tool_name == \"batch_tool\":\n",
    "        return run_batch(**tool_input)\n",
    "\n",
    "\n",
    "def run_tools(message):\n",
    "    tool_requests = [\n",
    "        block for block in message.content if block.type == \"tool_use\"\n",
    "    ]\n",
    "    tool_result_blocks = []\n",
    "\n",
    "    for tool_request in tool_requests:\n",
    "        try:\n",
    "            tool_output = run_tool(tool_request.name, tool_request.input)\n",
    "            tool_result_block = {\n",
    "                \"type\": \"tool_result\",\n",
    "                \"tool_use_id\": tool_request.id,\n",
    "                \"content\": json.dumps(tool_output),\n",
    "                \"is_error\": False,\n",
    "            }\n",
    "        except Exception as e:\n",
    "            tool_result_block = {\n",
    "                \"type\": \"tool_result\",\n",
    "                \"tool_use_id\": tool_request.id,\n",
    "                \"content\": f\"Error: {e}\",\n",
    "                \"is_error\": True,\n",
    "            }\n",
    "\n",
    "        tool_result_blocks.append(tool_result_block)\n",
    "\n",
    "    return tool_result_blocks"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Run conversation\n",
    "def run_conversation(messages):\n",
    "    while True:\n",
    "        response = chat(\n",
    "            messages,\n",
    "            tools=[\n",
    "                get_current_datetime_schema,\n",
    "                add_duration_to_datetime_schema,\n",
    "                set_reminder_schema,\n",
    "                batch_tool_schema,\n",
    "            ],\n",
    "        )\n",
    "\n",
    "        add_assistant_message(messages, response)\n",
    "        print(text_from_message(response))\n",
    "\n",
    "        if response.stop_reason != \"tool_use\":\n",
    "            break\n",
    "\n",
    "        tool_results = run_tools(response)\n",
    "        add_user_message(messages, tool_results)\n",
    "\n",
    "    return messages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "messages = []\n",
    "add_user_message(\n",
    "    messages,\n",
    "    \"\"\"\n",
    "    Write a one-paragraph scholarly article about computer science. \n",
    "    Include a title and author name.\n",
    "    \"\"\",\n",
    ")\n",
    "response = chat(messages)\n",
    "text_from_message(response)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
