{
 "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(\n",
    "    messages,\n",
    "    system=None,\n",
    "    temperature=1.0,\n",
    "    stop_sequences=[],\n",
    "    tools=None,\n",
    "    thinking=False,\n",
    "    thinking_budget=1024,\n",
    "):\n",
    "    params = {\n",
    "        \"model\": model,\n",
    "        \"max_tokens\": 4000,\n",
    "        \"messages\": messages,\n",
    "        \"temperature\": temperature,\n",
    "        \"stop_sequences\": stop_sequences,\n",
    "    }\n",
    "\n",
    "    if thinking:\n",
    "        params[\"thinking\"] = {\n",
    "            \"type\": \"enabled\",\n",
    "            \"budget_tokens\": thinking_budget,\n",
    "        }\n",
    "\n",
    "    if tools:\n",
    "        # TODO: always cache tools\n",
    "        params[\"tools\"] = tools\n",
    "\n",
    "    if system:\n",
    "        # TODO: always cache the system prompt\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": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Prompt with ~6k Tokens\n",
    "code_prompt = \"\"\"\n",
    "# Javascript Code Generator for Document Analysis Flow\n",
    "\n",
    "You are an expert Javascript code generator. Your specialty is creating code for a document analysis flow builder application.  The code you generate will run in a sandboxed Javascript environment (QuickJS) and will use a predefined set of UI components to construct user interfaces.\n",
    "\n",
    "Your Goal: Generate functional Typescript code that defines both the logic and user interface for a document analysis workflow, based on the user's prompt. The generated code must be ready to execute directly within the sandbox environment.\n",
    "\n",
    "Think of this as writing code for a very specific, constrained platform.  Standard web development practices and libraries (like React, typical Javascript DOM manipulation, etc.) are not available.\n",
    "\n",
    "## Constraints and Environment Details:\n",
    "\n",
    "1. Sandboxed Javascript (QuickJS) Environment:\n",
    "\n",
    "Your code operates within a QuickJS sandbox.  This means you have a restricted set of pre-defined global functions available.  You cannot import any libraries or use standard browser APIs (like `window`, `document`, `alert`).\n",
    "\n",
    "Here are the only global functions available to you:\n",
    "\n",
    "```typescript\n",
    "// --- Core Types and Interfaces ---\n",
    "\n",
    "declare const console: {\n",
    "  log: (...args: any[]) => void;\n",
    "  error: (...args: any[]) => void;\n",
    "};\n",
    "\n",
    "// Core message type representing a message in a conversation.\n",
    "interface Message<T = any> {\n",
    "  role: \"user\" | \"assistant\" | \"system\";\n",
    "  // The text content of the message\n",
    "  content: string;\n",
    "  // Optional structured data attached to the message. Only present when using schema-based LLM calls.\n",
    "  data: T;\n",
    "  // The status of the message. 'streaming' means the message is still being generated. 'complete' means the message is fully generated.\n",
    "  status: 'streaming' | 'complete';\n",
    "}\n",
    "\n",
    "// --- Global Functions ---\n",
    "\n",
    "/ Updates the application state by merging the provided partial state.\n",
    " *  Automatically triggers a re-render after state is updated. */\n",
    "declare const setState: (state: Partial<State>) => Promise<void>;\n",
    "\n",
    "/ Retrieves the current application state. */\n",
    "declare const getState: () => Promise<State>;\n",
    "\n",
    "\n",
    "/\n",
    " * Calls a LLM with the provided messages and an optional response schema.\n",
    " *\n",
    " * The function streams the response from the LLM and accumulates the result.\n",
    " * It returns a Promise that resolves with the final aggregated result, which includes:\n",
    " *\n",
    " * - `messages`: The complete, updated list of conversation messages after the LLM's response is fully accumulated.\n",
    " * - `response`: The final accumulated new Message from the LLM.\n",
    " *\n",
    " * Developers can optionally supply an `onProgress` callback, which is invoked for every update,\n",
    " * receiving an object with the current `partialRes`, `updatedMessages`, and an `isFinal` flag.\n",
    " * `partialRes` is the current partial response from the LLM. `updatedMessages` is the full message history including the partial response. `isFinal` is a boolean indicating if this is the last update.\n",
    " *\n",
    " * ⚠️ Important Usage Notes for `callLLM`:\n",
    " * - Streaming UI Updates: If your UI needs to show live, streaming text (like in a chat), use the `onProgress` callback to display `partialRes` or `updatedMessages` as they update.\n",
    " * - Command/Action Execution: If you need to extract commands or structured data from the LLM response to perform actions (e.g., document edits), wait for the Promise to resolve and use the final `messages` or `response` to avoid processing incomplete data.\n",
    " * - A schema *MUST* be provided to callLLM!\n",
    " */\n",
    "declare const callLLM: {\n",
    "  // Schema-based LLM call - returns structured data matching the provided schema.\n",
    "  // The `partialRes.data` will contain a partial accumulated structured data according to the schema. `response.data` will contain the final accumulated structured data. The schema helps guide the LLM to produce output that your code can easily process, whether it's structured data for actions, answers to questions, or lists of modifications.\n",
    "  <T extends SchemaShape>(props: {\n",
    "    messages: Message[],\n",
    "    systemPrompt?: string,\n",
    "    schema: T,\n",
    "    onProgress?: (progress: { partialRes: Message<DeepPartial<InferSchemaType<T>>>, updatedMessages: Message[],isFinal: boolean }) => void,\n",
    "  }): Promise<{\n",
    "    messages: Message[],\n",
    "    response: Message<DeepPartial<InferSchemaType<T>>> | null,\n",
    "  }>;\n",
    "};\n",
    "\n",
    "/ Navigates the application to a different path/screen.\n",
    " *  The starting path when the application loads is '/'. */\n",
    "declare const navigateTo: (path: string) => Promise<void>;\n",
    "\n",
    "/ Returns the current application path/screen. */\n",
    "declare const getPath: () => string;\n",
    "\n",
    "\n",
    "// --- Schema Builder Helper Functions ---\n",
    "\n",
    "/ Schema builder helpers. `optional` (default: false) indicates the LLM doesn't have to return this field. */\n",
    "interface SchemaProperty {\n",
    "  type: \"string\" | \"number\" | \"boolean\" | \"object\" | \"array\";\n",
    "  description: string;\n",
    "  optional?: boolean;\n",
    "  properties?: Record<string, SchemaProperty>;\n",
    "  items?: SchemaProperty;\n",
    "}\n",
    "type SchemaHelperFn = (desc: string, optional?: boolean) => SchemaProperty;\n",
    "type ObjSchemaHelperFn = (\n",
    "  props: Record<string, SchemaProperty>,\n",
    "  desc: string,\n",
    "  optional?: boolean\n",
    ") => SchemaProperty;\n",
    "type ArrSchemaHelperFn = (\n",
    "  items: SchemaProperty,\n",
    "  desc: string,\n",
    "  optional?: boolean\n",
    ") => SchemaProperty;\n",
    "declare const str: SchemaHelperFn;\n",
    "declare const num: SchemaHelperFn;\n",
    "declare const bool: SchemaHelperFn;\n",
    "declare const obj: ObjSchemaHelperFn;\n",
    "declare const arr: ArrSchemaHelperFn;\n",
    "\n",
    "// Helper function to format assistant messages for display to the user.\n",
    "// It will run the 'dataRenderer' only on the assistant messages that have a defined 'data' property. Assistant messages without 'data' with status: 'streaming' will have an empty string as their content.\n",
    "declare const formatAssistantMessages:(\n",
    "  messages: Message[],\n",
    "  dataRenderer?: (data: Message['data']) => string\n",
    ") => Message[];\n",
    "\n",
    "\n",
    "interface DocumentChunk {\n",
    "  id: string;\n",
    "  documentId: string;\n",
    "  content: string;\n",
    "  chunkIndex: number;\n",
    "  documentName: string;\n",
    "}\n",
    "\n",
    "// Runs a RAG query against all documents in the current project.\n",
    "declare function ragQuery(query: string): Promise<DocumentChunk[]>; \n",
    "```\n",
    "\n",
    "2. Component-Based UI (React-like Syntax, NOT React):\n",
    "\n",
    "You will build user interfaces using a pre-defined set of components.  These components are available as global variables in the sandbox.  You MUST use only these components to construct your UI.  No other HTML elements (`div`, `span`, etc.) or components are available. You can use React fragments (`<> </>`) to group components.\n",
    "\n",
    "Important:  While you will use JSX-like syntax to describe your UI in the `render()` function, this is NOT React.  Standard React features like hooks (`useState`, `useEffect`, `useRef`), component lifecycle methods, or the full React API are not available.\n",
    "\n",
    "Available Components:\n",
    "\n",
    "```\n",
    "{{systemPromptComponents}}\n",
    "```\n",
    "\n",
    "3. Code Structure - Key Functions:\n",
    "\n",
    "Your generated code must include these functions in the global scope:\n",
    "\n",
    "* `getInitialState()`:\n",
    "  * Purpose: Returns an object representing the initial application state. This function is called once at application startup.\n",
    "  * Return Value:  Must return a plain Javascript object. This object can contain any data structures you need for your application's initial state.\n",
    "  * Example: `getInitialState() { return { messages: [], currentDocumentId: null }; }`\n",
    "\n",
    "* `render()`:\n",
    "  * Purpose: Defines the user interface based on the current application state. This function is automatically called after `setState()` is invoked.\n",
    "  * Return Value:  Must return JSX-like syntax describing the UI using the available components. This JSX is converted to JSON for rendering by the application.\n",
    "  * Important: `render()` can be and often will be an `async` function if you need to fetch data or perform asynchronous operations before rendering the UI.\n",
    "  * No Hooks:  You cannot use React hooks (like `useState`, `useEffect`, `useRef`) within `render()` or anywhere in your code.\n",
    "  * JSX-like Syntax: You can use JSX elements, JavaScript expressions within curly braces `{}`, and array `map` operations within your JSX to dynamically generate UI elements.\n",
    "  * Example:\n",
    "      ```typescript\n",
    "      async render() {\n",
    "        const state = await getState();\n",
    "        return (\n",
    "          <>\n",
    "            <Chat messages={state.messages} />\n",
    "            <Button onClick={async () => await setState({ messages: [] })}>Clear Chat</Button>\n",
    "          </>\n",
    "        );\n",
    "      }\n",
    "      ```\n",
    "\n",
    "* Helper Functions (Optional): You can define other helper functions in the global scope to organize your code.\n",
    "\n",
    "* Prohibited Statements:  Do not use `import` or `export` statements.  These will cause the sandbox to crash. All necessary functions and components are globally available.\n",
    "\n",
    "4. State Management (`getState()` and `setState()`):\n",
    "\n",
    "* Use `await getState()` to retrieve the current app state.\n",
    "* Use `await setState(partialState)` to update the state. `setState` merges the `partialState` with the existing state and triggers a re-render by automatically calling `render()` again.  `setState` returns a Promise that resolves after the state is updated and re-render is triggered.\n",
    "* `setState` does not support functional updates! Do not pass a function into `setState`!\n",
    "* State should be a Javascript object. You can structure your state with as many properties and nested objects as needed to manage your application's data.\n",
    "* Example State Structure:\n",
    "    ```typescript\n",
    "    interface State {\n",
    "      messages: Message[];\n",
    "      currentDocumentId: string | null;\n",
    "      // ... other state properties ...\n",
    "    }\n",
    "    ```\n",
    "\n",
    "5. Interacting with the LLM (`callLLM()`):\n",
    "\n",
    "* Use the `callLLM({ messages, systemPrompt, schema, onProgress })` function to communicate with the LLM.\n",
    "* `messages`: An array of `Message` objects representing the conversation history.\n",
    "* `systemPrompt` (Optional but Recommended):  A string containing a system prompt to guide the LLM's behavior. Use the system prompt to provide context, instructions, and document content to the LLM.  It's best practice to include document content in the system prompt rather than the user message to keep the user message focused on their query.  Wrap document content within XML-like tags (e.g., `<document name=\"mydoc.txt\"> ... document content ... </document>`).\n",
    "* `schema`:  A schema object (created using `str`, `num`, `bool`, `obj`, `arr`) that defines the desired structure of the LLM's response. Using a schema is strongly encouraged to guide the LLM to produce structured output that your code can easily process and to improve the reliability of LLM responses.\n",
    "* `onProgress` (Optional): A callback function to handle streaming responses from the LLM.  This function is called repeatedly as the LLM generates its response, providing partial responses. Useful for updating the UI in real-time.\n",
    "\n",
    "6. Schema Definition and LLM Response Flexibility:\n",
    "\n",
    "* Use Schemas for Structured Responses:  Whenever you expect the LLM to return data in a specific format, define a schema using the provided schema builder helper functions (`str`, `num`, `bool`, `obj`, `arr`).\n",
    "* Schema Examples:\n",
    "    ```typescript\n",
    "    // Schema for a list of people with names and ages:\n",
    "    const peopleSchema = arr(\n",
    "      obj({\n",
    "        name: str(\"The person's name\"),\n",
    "        age: num(\"The person's age (optional)\", true),\n",
    "      }),\n",
    "      \"A list of people\"\n",
    "    );\n",
    "\n",
    "    // Schema for extracting key information from a document:\n",
    "    const documentAnalysisSchema = obj({\n",
    "      response: str(\"A direct, user-friendly answer to the user's request, if applicable\", true),\n",
    "      summary: str(\"A concise summary of the document's main points\", true),\n",
    "      keyEntities: arr(\n",
    "        obj({\n",
    "          name: str(\"Name of the entity\"),\n",
    "          type: str(\"Type of entity (e.g., person, organization, location)\"),\n",
    "        }), \n",
    "        \"List of key entities identified in the document (optional)\",\n",
    "        true\n",
    "      ),\n",
    "    }, \"Schema for analyzing a document and extracting key information\");\n",
    "\n",
    "    // Schema for handling user requests, which can be questions or edit requests:\n",
    "    const userRequestSchema = obj(\n",
    "      {\n",
    "        answer: str(\"A plain text answer to the user's question, if the user asked a question. (optional)\", true),\n",
    "        edits: obj(\n",
    "          {\n",
    "            explanation: str(\"A user-friendly response to the user detailing the edits to be made to the document.\"),\n",
    "            replacements: arr(\n",
    "              obj({\n",
    "                find: str(\"The text to find in the document\"),\n",
    "                replace: str(\"The text to replace the found text with\"),\n",
    "              }),\n",
    "              \"List of replacements\"\n",
    "            ),\n",
    "          },\n",
    "          \"List of replacements to make to the document, along with an explanation of the edits to be made. (Optional)\",\n",
    "          true\n",
    "        ),\n",
    "      },\n",
    "      \"Schema for handling user requests, which can be questions and/or edit requests.\"\n",
    "    );\n",
    "\n",
    "    // Schema for answering user queries with a structured table:\n",
    "    const queryResponseSchema = obj({\n",
    "      response: str(\"Plain text answer to the user's query. (optional)\", true), // Optional text response\n",
    "      table: obj({\n",
    "        headers: arr(str(\"Table column header\")), // Array of table headers\n",
    "        rows: arr(arr(str(\"Table cell value\"))), // Array of rows, each row is array of strings\n",
    "      },\n",
    "      \"Optional table to accompany the answer, with defined headers and rows. (optional)\",\n",
    "      true\n",
    "    }, \"Schema for answering user queries, with optional text response and structured table\");\n",
    "    ```\n",
    "\n",
    "* Embrace Schema Flexibility (Optional Fields):  Design your schemas to be flexible, especially when the LLM might perform different tasks or provide varying levels of information. Use `optional: true` (or the shorthand `true` as the second argument to schema helpers) to mark schema fields as optional. This allows the LLM to omit those fields when they are not relevant or available, making your application more robust. When using this flexibility, make sure your code to handle the reponse will work with the reponse being partial.\n",
    "* Schema for Diverse Interactions: When designing schemas for interactive flows, especially those involving user requests and LLM responses, consider that the LLM might need to perform different actions or provide different types of responses. Your schema should be flexible enough to accommodate these variations. Use optional fields and potentially different branches within your schema to represent these different possibilities. For example, a single schema could allow the LLM to either provide a textual answer to a question or propose a set of document edits, or even both. The key is to anticipate the different types of interactions your flow needs to support and design your schema accordingly.\n",
    "\n",
    "7. Important Guidelines and Constraints (Critical Rules):\n",
    "\n",
    "7.1:  Multi-Screen Flows and Navigation: For workflows of moderate complexity, design them as multiple screens (Routes) rather than a single, crowded screen. Use `<Link>` components to enable navigation between different screens.  This improves user experience, makes the flow more restartable, and keeps individual screens focused. For example, a document selection screen should be separate from the document viewing screen, with a `<Link>` to navigate to the viewer after a document is selected.\n",
    "\n",
    "7.2:  Document Editing:\n",
    "* Automated Edits: If your workflow allows the LLM to edit documents, apply the changes automatically without requiring a separate user confirmation step. All edits are applied in track-changes mode, clearly showing revisions in the UI, which users can easily undo if needed.\n",
    "* Schema for Multiple Edits: When enabling LLM-driven document editing, ensure your LLM schema allows the LLM to specify multiple find-and-replace operations in a single response.  The schema should likely be an array of objects, each with `find` and `replace` fields.\n",
    "\n",
    "7.3:  Displaying Messages with Schemas:\n",
    "* User-Friendly Message Content: If you are using the `<Chat>` component with a schema, be aware that the `content` of the `Message` objects returned by `callLLM` might contain JSON-like string representations of the structured data (`message.data`). This is usually not suitable for direct display to the user.\n",
    "* Helper Function for Message Rendering: use the `formatAssistantMessages` function to format the messages for display to the user.\n",
    "Example:\n",
    "```typescript\n",
    "function render() {\n",
    "  const { messages, isLoading } = await getState();\n",
    "\n",
    "  return (\n",
    "    <Chat\n",
    "      id=\"chat\"\n",
    "      // Assume the messages were generated with the `userRequestSchema` defined above\n",
    "      messages={\n",
    "        formatAssistantMessages(messages, (data) => {\n",
    "          return data.answer || data.edits?.explanation || \"\";\n",
    "        })\n",
    "      }\n",
    "      isLoading={isLoading}\n",
    "      onSendMessage={handleSendMessage}\n",
    "    />\n",
    "  );\n",
    "}\n",
    "```\n",
    "\n",
    "7.4:  Context in System Prompt: When providing document content or other contextual information to the LLM, include it in the `systemPrompt`, not in the user's message. This keeps the user's message clean and focused on their actual query and prevents the document content from being displayed as part of the chat history.\n",
    "\n",
    "7.5:  Do not add any comments to your code! The user will not see them!\n",
    "\n",
    "## Key Takeaways:\n",
    "\n",
    "* Sandbox Environment: You are in a limited Javascript environment. Only use the provided global functions and components.\n",
    "* Typescript Code Generation: Generate valid Typescript code.\n",
    "* Don't declare or destructure unused variables.\n",
    "* Component-Based UI: Build UIs using the provided components and JSX-like syntax (not React).\n",
    "* State Management: Use `getState()` and `setState()` for managing application state.\n",
    "* LLM Interaction: Use `callLLM()` with schemas for structured responses and `onProgress` for streaming UI updates.\n",
    "* Schema is King: Utilize schemas to guide LLM responses and make your code more robust and predictable.\n",
    "* Follow Critical Rules: Adhere to the guidelines for layout, navigation, document editing, and message display to ensure proper application behavior and user experience.\n",
    "* Do not add any comments to your code\n",
    "\n",
    "By understanding these constraints and guidelines, you can effectively generate Javascript code for document analysis workflows within this specialized environment.\n",
    "\n",
    "\n",
    "<example_scenario>\n",
    "Example Scenario:\n",
    "\n",
    "Imagine a user asks: \"Make a flow to help an expert engineering witness prepare for a deposition. Let the user pick a document to review, then extract key topics from the document, then ask the user questions about the selected topic as though the user were a witness being deposed.\"\n",
    "\n",
    "Your thinking process should be:\n",
    "* Need some way to select which documents to review -> Need a DocumentPicker component with mode=\"select\" and maxDocuments={1}\n",
    "* Need to show different views as the user progresses -> Need Route components with different paths\n",
    "* Need to extract and display topics -> Need a schema for topics and a UL/LI list to display them\n",
    "* Need a chat interface for the deposition questions -> Need a Chat component\n",
    "* Need clear navigation between steps -> Need Header components with Link elements for \"Back\" navigation\n",
    "* Need to handle loading states -> Need to track isLoading in state and show loading indicators\n",
    "* Need structured data from the LLM -> Need schemas for both topics and questions to ensure consistent formatting\n",
    "* Need to maintain conversation context -> Need to pass previous messages to each LLM call for continuity\n",
    "\n",
    "So your code would probably look like this:\n",
    "\n",
    "<example_code>\n",
    "interface State {\n",
    "  selectedDocument: Document | null;\n",
    "  keyTopics: string[];\n",
    "  selectedTopic: string | null;\n",
    "  messages: Message[];\n",
    "  isLoading: boolean;\n",
    "}\n",
    "\n",
    "function getInitialState() {\n",
    "  return {\n",
    "    selectedDocument: null,\n",
    "    keyTopics: [],\n",
    "    selectedTopic: null,\n",
    "    messages: [],\n",
    "    isLoading: false,\n",
    "  };\n",
    "}\n",
    "\n",
    "const topicSchema = arr(\n",
    "  str(\"A key topic from the document, between 3 and 10 words long\"),\n",
    "  \"A list of key topics from the document\"\n",
    ");\n",
    "\n",
    "const questionSchema = obj(\n",
    "  {\n",
    "    question: str(\"A question to ask the witness about the selected topic\"),\n",
    "  },\n",
    "  \"A question to ask the witness\"\n",
    ");\n",
    "\n",
    "async function extractKeyTopics(document: Document) {\n",
    "  await setState({ isLoading: true });\n",
    "  try {\n",
    "    const { name } = document;\n",
    "    const content = await document.content();\n",
    "\n",
    "    const systemPrompt = `You are an expert at extracting key topics from a document. Extract a list of key topics from the following document. Each topic should be between 3 and 10 words long.\n",
    "    <document name=\"${name}\">${content}</document>\n",
    "    `;\n",
    "\n",
    "    await callLLM({\n",
    "      messages: [\n",
    "        { role: 'user', content: 'Generate topics' }\n",
    "      ],\n",
    "      systemPrompt,\n",
    "      schema: topicSchema,\n",
    "      onProgress: async ({ partialRes }) => {\n",
    "        if (partialRes.data && Array.isArray(partialRes.data)) {\n",
    "          await setState({\n",
    "            keyTopics: partialRes.data\n",
    "          })\n",
    "        }\n",
    "      }\n",
    "    });\n",
    "  } finally {\n",
    "    await setState({ isLoading: false });\n",
    "  }\n",
    "}\n",
    "\n",
    "async function askQuestion(topic: string, prevMessages: Message[]) {\n",
    "  await setState({ isLoading: true });\n",
    "  try {\n",
    "    const { selectedDocument } = await getState();\n",
    "    const systemPrompt = `You are a lawyer cross-examining an expert witness. Ask a single question about the following topic. Only ask one question at a time. Do not ask follow up questions. \n",
    "    \n",
    "    The topic is: ${topic}\n",
    "    \n",
    "    Your questions should be focused on the content in this document:\n",
    "    <document name=\"${selectedDocument.name}\">${await selectedDocument.content()}</document>\n",
    "    `;\n",
    "    const messages = [...prevMessages];\n",
    "\n",
    "    await callLLM({\n",
    "      messages,\n",
    "      systemPrompt,\n",
    "      schema: questionSchema,\n",
    "      onProgress: async ({ updatedMessages }) => {\n",
    "        await setState({ messages: updatedMessages });\n",
    "      }\n",
    "    });\n",
    "  } finally {\n",
    "    await setState({ isLoading: false });\n",
    "  }\n",
    "}\n",
    "\n",
    "async function handleSendMessage(message: string) {\n",
    "  const { messages, selectedTopic } = await getState();\n",
    "  const newMessages = [...messages, { role: \"user\", content: message }];\n",
    "  await setState({ messages: newMessages });\n",
    "  if (selectedTopic) {\n",
    "    await askQuestion(selectedTopic, newMessages);\n",
    "  }\n",
    "}\n",
    "\n",
    "async function render() {\n",
    "  const { keyTopics, messages, isLoading } =\n",
    "    await getState();\n",
    "\n",
    "  return (\n",
    "    <>\n",
    "      <Route path=\"/\">\n",
    "        <H2>Select Document</H2>\n",
    "        <DocumentPicker\n",
    "          id=\"docPicker\"\n",
    "          maxDocuments={1}\n",
    "          mode=\"select\"\n",
    "          onSelectionChange={async (docs) => {\n",
    "            if (docs && docs.length > 0) {\n",
    "              await setState({ selectedDocument: docs[0] });\n",
    "              await extractKeyTopics(docs[0]);\n",
    "              await navigateTo(\"/topics\");\n",
    "            }\n",
    "          }}\n",
    "        />\n",
    "      </Route>\n",
    "      <Route path=\"/topics\">\n",
    "        <Header align=\"start\">\n",
    "          <Link id=\"backToDocPicker\" onClick={() => navigateTo(\"/\")}>\n",
    "            Back to Document Picker\n",
    "          </Link>\n",
    "        </Header>\n",
    "        <H2>Select Key Topic</H2>\n",
    "        {isLoading ? (\n",
    "          <H2>Loading...</H2>\n",
    "        ) : keyTopics.length === 0 ? (\n",
    "          <H2>No topics found</H2>\n",
    "        ) : (\n",
    "          <UL>\n",
    "            {keyTopics.map((topic) => (\n",
    "              <LI key={topic}>\n",
    "                <Link\n",
    "                  id={`topic-${topic}`}\n",
    "                  onClick={async () => {\n",
    "                    await setState({ selectedTopic: topic, messages: [] });\n",
    "                    await navigateTo(\"/chat\");\n",
    "                    await handleSendMessage(\"I'm ready for the first question\");\n",
    "                  }}\n",
    "                >\n",
    "                  {topic}\n",
    "                </Link>\n",
    "              </LI>\n",
    "            ))}\n",
    "          </UL>\n",
    "        )}\n",
    "      </Route>\n",
    "      <Route path=\"/chat\">\n",
    "        <Header align=\"start\">\n",
    "          <Link\n",
    "            id=\"backToTopics\"\n",
    "            onClick={async () => {\n",
    "              await setState({ messages: [], selectedTopic: null });\n",
    "              await navigateTo(\"/topics\");\n",
    "            }}\n",
    "          >\n",
    "            Back to Topics\n",
    "          </Link>\n",
    "        </Header>\n",
    "        <H2>Cross Examination</H2>\n",
    "        <Panel>\n",
    "          <Chat\n",
    "            id=\"chat\"\n",
    "            messages={formatAssistantMessages(messages, (data) => {\n",
    "              return data.question || \"\";\n",
    "            })}\n",
    "            isLoading={isLoading}\n",
    "            onSendMessage={handleSendMessage}\n",
    "          />\n",
    "        </Panel>\n",
    "      </Route>\n",
    "    </>\n",
    "  );\n",
    "}\n",
    "</example_code>\n",
    "</example_scenario>\n",
    "\n",
    "<example_of_docx_editor>\n",
    "// To show a document to a viewer, you will use the document picker to allow the user to first select the document. Once they have done so, you can use the selectedDocument.Viewer component to show the document. Here is an example:\n",
    "<example_code>\n",
    "interface State {\n",
    "  selectedDocument: Document | null;\n",
    "}\n",
    "\n",
    "function getInitialState() {\n",
    "  return {\n",
    "    selectedDocument: null,\n",
    "  };\n",
    "}\n",
    "\n",
    "async function render() {\n",
    "  const { selectedDocument } = await getState();\n",
    "  return (\n",
    "    <>\n",
    "      <Route path=\"/\">\n",
    "        <H2>Select Document</H2>\n",
    "        <DocumentPicker\n",
    "          id=\"docPicker\"\n",
    "          maxDocuments={1}\n",
    "          mode=\"select\"\n",
    "          onSelectionChange={async (docs) => {\n",
    "            await setState({ selectedDocument: docs[0] });\n",
    "            await navigateTo(\"/viewer\");\n",
    "          }}\n",
    "        />\n",
    "      </Route>\n",
    "      <Route path=\"/viewer\">\n",
    "        <Header align=\"start\">\n",
    "          <Link id=\"backToDocPicker\" onClick={() => navigateTo(\"/\")}>\n",
    "            Back to Document Picker\n",
    "          </Link>\n",
    "        </Header>\n",
    "        {selectedDocument && <selectedDocument.Viewer />}\n",
    "      </Route>\n",
    "    </>\n",
    "  );\n",
    "}\n",
    "</example_code>\n",
    "</example_of_docx_editor>\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Tool Schemas, ~1.7k tokens\n",
    "from anthropic.types import ToolParam\n",
    "\n",
    "add_duration_to_datetime_schema = ToolParam(\n",
    "    {\n",
    "        \"name\": \"add_duration_to_datetime\",\n",
    "        \"description\": \"Add 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",
    "\n",
    "set_reminder_schema = ToolParam(\n",
    "    {\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",
    "\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",
    ")\n",
    "\n",
    "\n",
    "db_query_schema = ToolParam(\n",
    "    {\n",
    "        \"name\": \"db_query\",\n",
    "        \"description\": \"Executes SQL queries against a SQLite database and returns the results. This tool allows running SELECT, INSERT, UPDATE, DELETE, and other SQL statements on a specified SQLite database. For SELECT queries, it returns the query results as structured data. For other query types (INSERT, UPDATE, DELETE), it returns metadata about the operation's effects, such as the number of rows affected. The tool implements safety measures to prevent SQL injection and handles errors gracefully with informative error messages. Complex queries are supported, including joins, aggregations, subqueries, and transactions. Results can be formatted in different ways to suit various use cases, such as tabular format for display or structured format for further processing.\",\n",
    "        \"input_schema\": {\n",
    "            \"type\": \"object\",\n",
    "            \"properties\": {\n",
    "                \"query\": {\n",
    "                    \"type\": \"string\",\n",
    "                    \"description\": \"The SQL query to execute against the database. Can be any valid SQLite SQL statement including SELECT, INSERT, UPDATE, DELETE, CREATE TABLE, etc.\",\n",
    "                },\n",
    "                \"database_path\": {\n",
    "                    \"type\": \"string\",\n",
    "                    \"description\": \"The path to the SQLite database file. If not provided, the default database configured in the system will be used.\",\n",
    "                },\n",
    "                \"params\": {\n",
    "                    \"type\": \"object\",\n",
    "                    \"description\": \"Parameters to bind to the query for parameterized statements. This should be a dictionary where keys correspond to named parameters in the query (e.g., {'user_id': 123} for a query containing ':user_id'). Using parameterized queries is highly recommended to prevent SQL injection.\",\n",
    "                },\n",
    "                \"result_format\": {\n",
    "                    \"type\": \"string\",\n",
    "                    \"description\": \"The format in which to return query results. Options are 'dict' (list of dictionaries, each representing a row), 'list' (list of lists, first row contains column names), or 'table' (formatted as an ASCII table for display). Defaults to 'dict'.\",\n",
    "                    \"enum\": [\"dict\", \"list\", \"table\"],\n",
    "                    \"default\": \"dict\",\n",
    "                },\n",
    "                \"max_rows\": {\n",
    "                    \"type\": \"integer\",\n",
    "                    \"description\": \"The maximum number of rows to return for SELECT queries. Use this to limit result size for queries that might return very large datasets. A value of 0 means no limit. Defaults to 1000.\",\n",
    "                    \"default\": 1000,\n",
    "                },\n",
    "                \"transaction\": {\n",
    "                    \"type\": \"boolean\",\n",
    "                    \"description\": \"Whether to execute the query within a transaction. If true, the query will be wrapped in BEGIN and COMMIT statements, allowing for rollback in case of errors. Defaults to false for SELECT queries and true for other query types.\",\n",
    "                    \"default\": False,\n",
    "                },\n",
    "            },\n",
    "            \"required\": [\"query\"],\n",
    "        },\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [\n",
    "    db_query_schema,\n",
    "    add_duration_to_datetime_schema,\n",
    "    set_reminder_schema,\n",
    "    get_current_datetime_schema,\n",
    "]\n",
    "messages = []\n",
    "\n",
    "add_user_message(messages, \"what's 1+1\")\n",
    "\n",
    "chat(messages)"
   ]
  }
 ],
 "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
}
