Create email assistant

This commit is contained in:
Lance Martin
2025-02-04 14:09:02 -08:00
parent 93605e414e
commit 65b7b97110
22 changed files with 955 additions and 1984 deletions
+12 -8
View File
@@ -2,13 +2,17 @@
## Motivation
Memory in LLM applications enables structured extraction and organization of information over time. A practical example is [AI-assisted journaling](https://x.com/patrick_oshag/status/1876455619516911720), where an LLM can extract structured data (sentiment, focus topics, action items) from unstructured journal entries. This course is a step-by-step guide to building an LLM-assisted journaling app using LangGraph that 1) gives you an actual application that you can deploy (even locally for private use) and 2) shows you the different types of memories that you can use in AI applications.
Memory in LLM applications enables intelligent processing and organization of communication over time. A practical example is an AI email assistant that can effectively triage and respond to emails based on learned patterns and preferences. This course is a step-by-step guide to building an LLM-powered email assistant using LangGraph that 1) gives you an actual application that you can deploy and 2) shows you the different types of memories that you can use in AI applications.
## End result
We'll build an app that looks like this, allowing you to journal and have the AI extract structured data from your journal entries for later retrieval / analysis.
We'll build an app that looks like this, allowing you to process incoming emails with an AI assistant that can:
- Automatically classify emails (respond, ignore, or notify)
- Draft appropriate responses
- Schedule meetings and manage your calendar
- Learn from your communication patterns over time
![Memory Course App](notebooks/img/memory_course_app.png)
![Memory Course App](img/memory_course_email.png)
## Organization of the course
@@ -16,11 +20,11 @@ The lessons are shown in the `notebooks` folder, structured as follows:
```
- Lesson 1: Concepts
- Lesson 2: Procedural Memory
- Lesson 3: Episodic Memory
- Lesson 4: Semantic Memory Collection
- Lesson 5: Semantic Memory Profile
- Lesson 6: Building the App
- Lesson 2: Baseline Email Assistant
- Lesson 3: Adding Semantic Memory
- Lesson 4: Adding Episodic Memory
- Lesson 5: Adding Procedural Memory
- Lesson 6: Deploying the App
```
These give you the conceptual foundations for the app in interactive notebooks for exploration and experimentation. The final code needed to run the resulting application is shown in `src/memory_course/` folder, which can be run as a standalone application using [LangGraph server](https://langchain-ai.github.io/langgraph/concepts/langgraph_server/).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

+46 -76
View File
@@ -1,94 +1,64 @@
# Concept Overview (WIP / Outline)
### Motivation
### Slide 1: The Challenge of Email Management
Memory in LLM applications enables structured extraction and organization of information over time. A practical example is [AI-assisted journaling](https://x.com/patrick_oshag/status/1876455619516911720), where an LLM can process daily input (e.g. audio, text) and extract structured data (sentiment, focus topics, action items) that can be used to generate actionable insights and follow-ups.
- Email management is a common pain point
- Information overload leads to:
- Missed important messages
- Delayed responses
- Poor prioritization
- Calendar scheduling difficulties
![Memory Course App](img/example.png)
### Slide 2: Email Assistant
In this course, we will build an LLM assisted journaling app for this use case from scratch, fully open source, and simple enough to run privately / locally using LangGraph.
An AI email assistant:
- Triage incoming emails
- Draft responses
- Manage calendar
### Memory Types
### Slide 3: Types of Memory
[Human long-term memory is divided into different types:](https://en.wikipedia.org/wiki/Long-term_memory)
[Three key types of memory in cognitive systems:](https://en.wikipedia.org/wiki/Long-term_memory)
- Procedural: Skills, habits, routines
- Episodic: Events, experiences
- Semantic: Facts, concepts, relationships
1. **Semantic Memory**
- Facts and knowledge
- Understanding relationships
- General concepts
We can [map these onto different memory types in AI applications:](https://arxiv.org/pdf/2309.02427)
2. **Episodic Memory**
- Specific past experiences
- Context-based learning
- Historical patterns
- Procedural: Prompts
- Episodic: Few shot examples
- Semantic: Long-term facts we want to remember
3. **Procedural Memory**
- Skills and procedures
- How to perform tasks
- Learned behaviors
In particular, semantic memories can be [organized into collections or a fixed profile](https://langchain-ai.github.io/langgraph/concepts/memory/#semantic-memory).
### Slide 4: Mapping this to AI systems
### Updating Modes
https://arxiv.org/abs/2309.02427
Memories can be updates in different ways, incluing:
Semantic memories can be [organized into collections or a fixed profile](https://langchain-ai.github.io/langgraph/concepts/memory/#semantic-memory).
* Hot Path: Creating memories during runtime
* Background: Creating memories as a separate background task
### Slide 5: Mapping Memory Types to Email Assistant
### Memory Types in Our Journalling App
![System Diagram](img/memory_course_email.png)
* If the app detects an entry, it will enter the top flow of the diagram. Everything in this branch can be done "in the background" without blocking the user.
* If the user asks a question, it will enter the bottom flow of the diagram where memories are retrieved and used to condition immediate responses for the user.
1. **Semantic Memory** → User Profile & Facts
- Contact information
- Preferences
- Important relationships
- Regular commitments
![Memory Course App](img/memory_course_app.png)
## 1. Procedural Memory (System Prompts)
- Shown in RED in the diagram
- Acts as the "how to" instructions for the AI
- Contains extraction rules and processing guidelines
- Can be updated through direct feedback
- Example: Instructions for extracting todos, sentiment, and themes from journal entries
## 2. Episodic Memory (Few Shot Examples)
- Shown in GREEN in the diagram
- Specific examples of successful memory extractions
- Helps the AI understand patterns through concrete cases
- Can be refined based on user feedback
- Example: Sample journal entries and their correctly extracted structures
## 3. Semantic Memory (Long-term Storage)
### a) Semantic Memory Profile
- Fixed structure with predefined fields
- Stores user-specific information
- Example fields:
- name
- family
- location
- preferences
- patterns
### b) Semantic Memory Collections
- Open-ended lists of extracted memories
- Organized by type (todos, ideas, etc.)
- Searchable using natural language
- Example collections:
- Todo items
- Ideas
- Insights
- Messages to draft
## System Flow
1. User inputs either a Journal Entry or Ask Question
2. Router directs the input appropriately
3. For journal entries:
- Uses procedural and episodic memory to process input
- Updates semantic memory (both profile and collections)
4. For questions:
- Queries the semantic memory
- Uses profile for context
- Returns relevant memories
## Summary
This creates a complete system where:
- Instructions (procedural) guide the extraction
- Examples (episodic) improve accuracy
- Collections and profile (semantic) store the knowledge
- All components can be updated and refined through feedback
2. **Episodic Memory** → Email Triage
- Past email interactions
- Previous handling of similar messages
- Meeting history
- Response patterns
3. **Procedural Memory** → Email Actions
- Email drafting templates
- Calendar scheduling procedures
- Response workflows
- Priority handling rules
File diff suppressed because one or more lines are too long
-396
View File
@@ -1,396 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"! pip install langchain-anthropic langgraph"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import os, getpass\n",
"\n",
"def _set_env(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
"\n",
"_set_env(\"ANTHROPIC_API_KEY\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Instructions\n",
"\n",
"You can think of this as \"procedural\" memory. \n",
"\n",
"These are the instructions that the LLM will follow to extract memories from the journal entry.\n",
"\n",
"First, we define a general schema for any memory.\n",
"\n",
"The schema has a `memory_type` and a `memory_content`.\n",
"\n",
"![](./img/memory_course_procedural.png)\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"from typing import List\n",
"from pydantic import BaseModel, Field\n",
"from langchain_anthropic import ChatAnthropic\n",
"\n",
"# Schema for a memory\n",
"class Memory(BaseModel):\n",
" memory_type: str = Field(None, description=\"Type of memory to extract.\")\n",
" memory_content: str = Field(None, description=\"Specific content of the memory.\")\n",
"\n",
"# List of memories \n",
"class Memories(BaseModel):\n",
" memories: List[Memory] = Field(None, description=\"List of memories to extract.\")\n",
"\n",
"# Define and augment LLM with structured output\n",
"llm = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")\n",
"structured_llm = llm.with_structured_output(Memories)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's load our instructions for memory extraction."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"sys.path.append('..')\n",
"import src.memory_course.prompts\n",
"\n",
"# Reload the module in case any changes were made\n",
"import importlib\n",
"importlib.reload(src.memory_course.prompts)\n",
"\n",
"# Then import the instructions \n",
"from src.memory_course.prompts import memory_collection_extraction_instructions, update_memory_collection_extraction_instructions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we want to store these in a way that our application can easily access to modify them. \n",
" \n",
"The [LangGraph store](https://docs.langchain.com/docs/components/store/memory) is a simple key-value store that we can use for long-term storage. \n",
"\n",
"We get it for \"free\" with any LangGraph deployment (local or hosted)."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"# Save instructions to the store\n",
"from langgraph.store.memory import InMemoryStore\n",
"in_memory_store = InMemoryStore()\n",
"namespace = (\"journal\",\"instructions\")\n",
"key = \"instructions_extraction\"\n",
"in_memory_store.put(namespace, key, {\"instructions\": memory_collection_extraction_instructions})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we can create a function that will extract memories from a journal entry."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.messages import HumanMessage, SystemMessage\n",
"\n",
"def extract_memories(journal_entry: str) -> Memories:\n",
" \"\"\"Extract memories from a journal entry.\n",
" \n",
" This function analyzes a journal entry and extracts memories\n",
" \n",
" Args:\n",
" journal_entry (str): The text of the journal entry to analyze\n",
" \n",
" Returns:\n",
" Memories (list): A structured list of Memory objects\"\"\"\n",
"\n",
" # Get the instructions from the store\n",
" namespace = (\"journal\",\"instructions\")\n",
" key = \"instructions_extraction\"\n",
" procedural_memory = in_memory_store.get(namespace, key)\n",
"\n",
" # Extract memories\n",
" memories = structured_llm.invoke([SystemMessage(content=procedural_memory.value['instructions']),\n",
" HumanMessage(content=f\"Extract memory from <context> {journal_entry} </context>\")])\n",
" \n",
" return memories"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we can test extraction from a fake journal entry.\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=== Extracted Memory ===\n",
"\n",
"SENTIMENT:\n",
"- Mixed emotions with overall optimistic tone. Started energized, experienced afternoon overwhelm with deadlines, but ended positively about team progress. Key phrases: 'energized', 'overwhelmed', 'optimistic', 'team's coming together'.\n",
"\n",
"=== Extracted Memory ===\n",
"\n",
"TODO:\n",
"- Email Sarah about the client presentation (Deadline: Thursday)\n",
"\n",
"=== Extracted Memory ===\n",
"\n",
"TODO:\n",
"- Schedule dentist appointment\n",
"\n",
"=== Extracted Memory ===\n",
"\n",
"TODO:\n",
"- Pick up groceries for weekend dinner party\n",
"\n",
"=== Extracted Memory ===\n",
"\n",
"IDEA:\n",
"- Implement AI in customer feedback system for automated initial response and categorization. Required: Research existing solutions first.\n",
"\n"
]
}
],
"source": [
"journal_entry = \"\"\"\n",
"Today was a mix of ups and downs. Started the morning feeling energized after my new morning routine, but got a bit overwhelmed with the project deadlines in the afternoon. \n",
"\n",
"Need to remember to:\n",
"- Email Sarah about the client presentation by Thursday\n",
"- Schedule dentist appointment\n",
"- Pick up groceries for weekend dinner party\n",
"\n",
"Had an interesting thought during my walk - what if we incorporated AI into our customer feedback system? Could potentially automate the initial response and categorization. Would need to research existing solutions first.\n",
"\n",
"Despite the stress, I'm optimistic about where things are heading. The team's really coming together on the new initiative.\"\"\"\n",
"\n",
"# Run extraction\n",
"memories = extract_memories(journal_entry)\n",
"\n",
"# Review\n",
"for memory in memories.memories:\n",
" print(\"=== Extracted Memory ===\")\n",
" print(f\"\\n{memory.memory_type.upper()}:\")\n",
" print(f\"- {memory.memory_content}\\n\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In a journal, we want the ability to update instructions directly based upon user feedback. \n",
"\n",
"For example, above notice that we create `IDEA:` memories with a `Required` prerequisite:\n",
"\n",
"```\n",
"IDEA:\n",
"- Implement AI in customer feedback system for automated initial response and categorization. Required: Research existing solutions first.\n",
"```\n",
"\n",
"Let's assume that we don't want this.\n",
"\n",
"We can easily udpate our instructions, simply by fetching the current instructions from the store and updating them."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"# Function for updating the instructions\n",
"def update_extract_memories_instructions(user_feedback: str) -> str:\n",
" \"\"\"Update instructions for memory extraction based on user feedback.\n",
" \n",
" Args:\n",
" user_feedback (str): User feedback about the memory extraction process\n",
" \n",
" Returns:\n",
" None\"\"\"\n",
"\n",
" # Get the current instructions\n",
" namespace = (\"journal\",\"instructions\")\n",
" key = \"instructions_extraction\"\n",
" instructions = in_memory_store.get(namespace, key)\n",
"\n",
" # Extract memories\n",
" updated_instructions = llm.invoke([SystemMessage(content=update_memory_collection_extraction_instructions.format(instructions=instructions.value['instructions'])),\n",
" HumanMessage(content=f\"<Feedback> {user_feedback} </Feedback>\")])\n",
" \n",
" # Add the new instructions to the store\n",
" in_memory_store.put(namespace, key, {\"instructions\": updated_instructions.content})\n",
"\n",
"update_extract_memories_instructions(\"\"\"When saving ideas dont include Required or Prerequisites.\"\"\")"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=== Extracted Memory ===\n",
"\n",
"SENTIMENT:\n",
"- Mixed emotions with overall optimistic tone. Started energized, experienced afternoon overwhelm/stress, ended with positive outlook. Key phrases: 'feeling energized', 'got overwhelmed', 'optimistic about where things are heading'. Notable positive mention of team cohesion.\n",
"\n",
"=== Extracted Memory ===\n",
"\n",
"TODO:\n",
"- Email Sarah about client presentation - Due Thursday\n",
"\n",
"=== Extracted Memory ===\n",
"\n",
"TODO:\n",
"- Schedule dentist appointment\n",
"\n",
"=== Extracted Memory ===\n",
"\n",
"TODO:\n",
"- Pick up groceries for weekend dinner party\n",
"\n",
"=== Extracted Memory ===\n",
"\n",
"IDEA:\n",
"- Integrate AI into customer feedback system to automate initial response and categorization\n",
"\n"
]
}
],
"source": [
"# Run extraction\n",
"memories = extract_memories(journal_entry)\n",
"\n",
"# Review\n",
"for memory in memories.memories:\n",
" print(\"=== Extracted Memory ===\")\n",
" print(f\"\\n{memory.memory_type.upper()}:\")\n",
" print(f\"- {memory.memory_content}\\n\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We ca see that the `Required` prerequisite is no longer present:\n",
"\n",
"```\n",
"IDEA:\n",
"- Integrate AI into customer feedback system to automate initial response and categorization\n",
"````\n",
"\n",
"We can see that this is because the instructions were updated:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'<Instructions>\\nGiven a journal entry, analyze and extract the following elements:\\n\\n1. SENTIMENT:\\n - Identify the overall emotional tone (positive, negative, neutral)\\n - Note any significant emotional shifts or patterns\\n - Extract key emotional words or phrases\\n Output as a single memory with memory_type \"SENTIMENT\"\\n\\n2. TODOS:\\n - Extract each task, action, or commitment as a SEPARATE memory\\n - Each TODO should be its own Memory object with memory_type \"TODO\"\\n - Include deadline in the memory_content if mentioned\\n - Format each todo concisely and clearly\\n\\n3. IDEAS:\\n - Extract each creative thought, insight, or potential project as a SEPARATE memory\\n - Each idea should be its own Memory object with memory_type \"IDEA\"\\n - Keep each idea focused and specific\\n - Include only the core concept and relevant details, without listing requirements or prerequisites\\n\\nImportant: Create a new Memory object for EACH individual todo and idea. Do not combine multiple todos or ideas into a single memory.\\n</Instructions>'"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Get the current instructions\n",
"procedural_memory = in_memory_store.get(namespace, key)\n",
"procedural_memory.value['instructions']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So now we can:\n",
"\n",
"1. Extract memories from a journal entry given a set of instructions (system message)\n",
"2. Update those instructions based upon user feedback directly \n",
"\n",
"This is a simple example of using and editing \"procedural memory\". \n",
"\n",
"In the next lesson, we will look at how to add in \"episodic memory\" or few-shot-example to improve the quality of the extraction."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "memory-course-env",
"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.13.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
-435
View File
@@ -1,435 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"! pip install langchain-anthropic langgraph"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os, getpass\n",
"\n",
"def _set_env(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
"\n",
"_set_env(\"ANTHROPIC_API_KEY\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Few Shot Examples\n",
"\n",
"You can think of this as \"episodic\" memory. \n",
"\n",
"These are the examples that the LLM can use to help it extract memories correctly.\n",
"\n",
"Let's build on what we did in the previous lesson.\n",
"\n",
"First, we define a general schema for any memory.\n",
"\n",
"The schema has a `memory_type` and a `memory_content`.\n",
"\n",
"![](./img/memory_course_episodic.png)\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [],
"source": [
"from pydantic import BaseModel, Field\n",
"from typing import List\n",
"from langchain_anthropic import ChatAnthropic\n",
"\n",
"# Schema for structured output\n",
"class Memory(BaseModel):\n",
" memory_type: str = Field(None, description=\"Type of memory to extract.\")\n",
" memory_content: str = Field(None, description=\"Specific content of the memory.\")\n",
"\n",
"class Memories(BaseModel):\n",
" memories: List[Memory] = Field(None, description=\"List of memories to extract.\")\n",
"\n",
"# Define and augment LLM with structured output\n",
"llm = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")\n",
"structured_llm = llm.with_structured_output(Memories)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's load our instructions for memory extraction, as we did before.\n",
"\n",
"But, we'll also load some examples that explain how to extract memories correctly."
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"sys.path.append('..')\n",
"import src.memory_course.prompts\n",
"import src.memory_course.examples\n",
"import src.memory_course.utils\n",
"\n",
"# Reload the module in case any changes were made\n",
"import importlib\n",
"importlib.reload(src.memory_course.prompts)\n",
"importlib.reload(src.memory_course.examples)\n",
"importlib.reload(src.memory_course.utils)\n",
"\n",
"# Then import the instructions and examples\n",
"from src.memory_course.utils import format_few_shot_examples\n",
"from src.memory_course.examples import example_input, example_output\n",
"from src.memory_course.prompts import memory_collection_extraction_instructions, update_few_shot_examples_instructions, collection_extraction_input"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As before, we'll use the store to store our instructions and examples."
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {},
"outputs": [],
"source": [
"# Save instructions to the store\n",
"from datetime import datetime\n",
"from langgraph.store.memory import InMemoryStore\n",
"\n",
"in_memory_store = InMemoryStore()\n",
"namespace = (\"journal\",\"instructions\")\n",
"key = \"instructions_extraction\"\n",
"in_memory_store.put(namespace, key, {\"instructions\": memory_collection_extraction_instructions})\n",
"\n",
"namespace = (\"journal\",\"examples\")\n",
"key = f\"example_{datetime.now().strftime('%Y%m%d_%H%M%S')}\"\n",
"in_memory_store.put(namespace, key, {\"input\": example_input, \"output\": example_output})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can now fetch examples from the store."
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Input:\\n\\nToday was a mix of ups and downs. Started the morning feeling energized after my new morning routine, but got a bit overwhelmed with the project deadlines in the afternoon. \\n\\nNeed to remember to:\\n- Email Sarah about the client presentation by Thursday\\n- Schedule dentist appointment\\n- Pick up groceries for weekend dinner party\\n\\nHad an interesting thought during my walk - what if we incorporated AI into our customer feedback system? Could potentially automate the initial response and categorization. Would need to research existing solutions first.\\n\\nDespite the stress, I\\'m optimistic about where things are heading. The team\\'s really coming together on the new initiative.\\n\\nOutput:\\n\\n{\\n \"memories\": [\\n {\\n \"memory_type\": \"SENTIMENT\",\\n \"memory_content\": \"Mixed emotions with overall optimistic tone. Started energized, experienced afternoon overwhelm with deadlines, but ended positively about team progress. Key phrases: \\'energized\\', \\'overwhelmed\\', \\'optimistic\\', \\'team\\'s coming together\\'\"\\n },\\n {\\n \"memory_type\": \"TODO\",\\n \"memory_content\": \"Email Sarah about the client presentation (Deadline: Thursday)\"\\n },\\n {\\n \"memory_type\": \"TODO\",\\n \"memory_content\": \"Schedule dentist appointment\"\\n },\\n {\\n \"memory_type\": \"TODO\",\\n \"memory_content\": \"Pick up groceries for weekend dinner party\"\\n },\\n {\\n \"memory_type\": \"IDEA\",\\n \"memory_content\": \"Integrate AI into customer feedback system to automate initial response and categorization.\"\\n }\\n ]\\n}\\n'"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from src.memory_course.utils import format_few_shot_examples\n",
"few_shot_examples = in_memory_store.search((\"journal\", \"examples\"))\n",
"\n",
"# Format the examples for the LLM\n",
"episodic_memory_formatted = format_few_shot_examples(few_shot_examples)\n",
"episodic_memory_formatted"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As before, we define a function to extract memories from a journal entry that uses both instruction and few shot examples."
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.messages import HumanMessage, SystemMessage\n",
"\n",
"def extract_memories(journal_entry: str) -> Memories:\n",
" \"\"\"Extract memories from a journal entry.\n",
" \n",
" This function analyzes a journal entry and extracts memories\n",
" \n",
" Args:\n",
" journal_entry (str): The text of the journal entry to analyze\n",
" \n",
" Returns:\n",
" Memories (list): A structured list of Memory objects\"\"\"\n",
"\n",
" # Get the instructions from the store\n",
" namespace = (\"journal\",\"instructions\")\n",
" key = \"instructions_extraction\"\n",
" instructions = in_memory_store.get(namespace, key)\n",
"\n",
" # Get the examples from the store\n",
" few_shot_examples = in_memory_store.search((\"journal\", \"examples\"))\n",
" few_shot_examples_formatted = format_few_shot_examples(few_shot_examples)\n",
"\n",
" # Format the instructions\n",
" memory_extraction_instructions_formatted = collection_extraction_input.format(\n",
" procedural_memory_instructions=instructions.value['instructions'],\n",
" few_shot_examples_formatted=few_shot_examples_formatted\n",
" )\n",
"\n",
" # Extract memories\n",
" memories = structured_llm.invoke([SystemMessage(content=memory_extraction_instructions_formatted),\n",
" HumanMessage(content=f\"Extract memory from <context> {journal_entry} </context>\")])\n",
" \n",
" return memories"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we can test extraction from a fake journal entry."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=== Extracted Memory ===\n",
"\n",
"SENTIMENT:\n",
"- Overall positive and confident mood. Productive coding session and optimistic about sprint goals. Some minor challenges with database migrations but maintaining positive outlook. Key phrases: 'working better than expected', 'feeling pretty confident', 'showing positive results'\n",
"\n",
"=== Extracted Memory ===\n",
"\n",
"TODO:\n",
"- Set up meeting with DevOps team about deployment strategy\n",
"\n",
"=== Extracted Memory ===\n",
"\n",
"TODO:\n",
"- Fix unit tests for payment module - Deadline: Friday\n",
"\n",
"=== Extracted Memory ===\n",
"\n",
"TODO:\n",
"- Order new laptop charger\n",
"\n",
"=== Extracted Memory ===\n",
"\n",
"TODO:\n",
"- Update documentation for API changes\n",
"\n",
"=== Extracted Memory ===\n",
"\n",
"IDEA:\n",
"- Implement automatic test generation using LLMs to reduce manual test writing time. Requirements: Need to evaluate cost and accuracy implications\n",
"\n"
]
}
],
"source": [
"# Test extraction of memories\n",
"journal_entry = \"\"\"\n",
"Really productive coding session this morning! The new refactoring approach is working better than expected, though I hit a few snags with the database migrations.\n",
"\n",
"Important tasks for this week:\n",
"- Set up meeting with DevOps team about deployment strategy\n",
"- Fix unit tests for the payment module by Friday\n",
"- Order new laptop charger\n",
"- Update documentation for API changes\n",
"\n",
"During the team standup, had a fascinating idea about improving our testing workflow. What if we implemented automatic test generation using LLMs? Could save hours of manual test writing. Would need to evaluate cost and accuracy first.\n",
"\n",
"Even though there's a lot on my plate, feeling pretty confident about the sprint goals. The recent architecture changes are already showing positive results.\"\"\"\n",
"\n",
"# Run extraction\n",
"memories = extract_memories(journal_entry)\n",
"\n",
"# Review\n",
"for memory in memories.memories:\n",
" print(\"=== Extracted Memory ===\")\n",
" print(f\"\\n{memory.memory_type.upper()}:\")\n",
" print(f\"- {memory.memory_content}\\n\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In a journal, we want the ability to update examples directly based upon user feedback. \n",
"\n",
"Let's look at the current examples.\n"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Input:\\n\\nToday was a mix of ups and downs. Started the morning feeling energized after my new morning routine, but got a bit overwhelmed with the project deadlines in the afternoon. \\n\\nNeed to remember to:\\n- Email Sarah about the client presentation by Thursday\\n- Schedule dentist appointment\\n- Pick up groceries for weekend dinner party\\n\\nHad an interesting thought during my walk - what if we incorporated AI into our customer feedback system? Could potentially automate the initial response and categorization. Would need to research existing solutions first.\\n\\nDespite the stress, I\\'m optimistic about where things are heading. The team\\'s really coming together on the new initiative.\\n\\nOutput:\\n\\n{\\n \"memories\": [\\n {\\n \"memory_type\": \"SENTIMENT\",\\n \"memory_content\": \"Mixed emotions with overall optimistic tone. Started energized, experienced afternoon overwhelm with deadlines, but ended positively about team progress. Key phrases: \\'energized\\', \\'overwhelmed\\', \\'optimistic\\', \\'team\\'s coming together\\'\"\\n },\\n {\\n \"memory_type\": \"TODO\",\\n \"memory_content\": \"Email Sarah about the client presentation (Deadline: Thursday)\"\\n },\\n {\\n \"memory_type\": \"TODO\",\\n \"memory_content\": \"Schedule dentist appointment\"\\n },\\n {\\n \"memory_type\": \"TODO\",\\n \"memory_content\": \"Pick up groceries for weekend dinner party\"\\n },\\n {\\n \"memory_type\": \"IDEA\",\\n \"memory_content\": \"Integrate AI into customer feedback system to automate initial response and categorization.\"\\n }\\n ]\\n}\\n'"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Get the examples from the store\n",
"few_shot_examples = in_memory_store.search((\"journal\", \"examples\"))\n",
"few_shot_examples_formatted = format_few_shot_examples(few_shot_examples)\n",
"few_shot_examples_formatted"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [],
"source": [
"# Schema for a example\n",
"class Example(BaseModel):\n",
" example_input: str = Field(None, description=\"Input for the example.\")\n",
" example_output: str = Field(None, description=\"Output for the example.\")\n",
"\n",
"# List of examples \n",
"class Examples(BaseModel):\n",
" examples: List[Example] = Field(None, description=\"List examples.\")"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [],
"source": [
"# Function for updating the instructions\n",
"def update_few_shot_examples(user_feedback: str) -> str:\n",
" \"\"\"Few shot examples can be updated based on user feedback.\n",
" \n",
" Args:\n",
" user_feedback (str): User feedback about the memory extraction process\n",
" \n",
" Returns:\n",
" None\"\"\"\n",
"\n",
" # Get the examples from the store\n",
" namespace = (\"journal\",\"examples\")\n",
" few_shot_examples = in_memory_store.search(namespace)\n",
" few_shot_examples_formatted = format_few_shot_examples(few_shot_examples)\n",
"\n",
" # Create new few shot examples from the user feedback\n",
" structured_llm = llm.with_structured_output(Examples)\n",
" updated_few_shot_examples = structured_llm.invoke([SystemMessage(content=update_few_shot_examples_instructions.format(current_examples=few_shot_examples_formatted)),\n",
" HumanMessage(content=f\"<Feedback> {user_feedback} </Feedback>\")])\n",
" \n",
" # Add the new few shot examples to the store\n",
" for example in updated_few_shot_examples.examples:\n",
" key = f\"example_{datetime.now().strftime('%Y%m%d_%H%M%S')}\"\n",
" in_memory_store.put(namespace, key, {\"input\": example.example_input, \"output\": example.example_output})\n",
"\n",
"update_few_shot_examples(\"If I talk about something 'interesting' related to work, then classify it and save it as an idea.\")\n"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Item(namespace=['journal', 'examples'], key='example_20250203_133145', value={'input': 'Interesting insight from the sales meeting - our competitors are all moving towards subscription models.', 'output': '{\"memories\": [{\"memory_type\": \"IDEA\", \"memory_content\": \"Consider subscription model strategy in response to competitor market trends.\"}]}'}, created_at='2025-02-03T21:31:45.669267+00:00', updated_at='2025-02-03T21:31:45.669268+00:00', score=None)"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Get the examples from the store\n",
"few_shot_examples = in_memory_store.search((\"journal\", \"examples\"))\n",
"few_shot_examples[-1]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So now we can:\n",
"\n",
"1. Extract memories from a journal entry given a set of instructions (system message)\n",
"2. Update those instructions based upon user feedback directly \n",
"3. Add few shot examples to improve the quality of the extraction\n",
"4. Update those few shot examples based upon user feedback directly \n",
"\n",
"This is a simple example of using and editing both \"procedural memory\" and \"episodic memory\". \n",
"\n",
"In the next lesson, we will look at how how to save the extracted memories and retrieve them later. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "memory-course-env",
"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.13.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+74
View File
@@ -0,0 +1,74 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"! pip install langchain-anthropic langgraph"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os, getpass\n",
"\n",
"def _set_env(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
"\n",
"_set_env(\"ANTHROPIC_API_KEY\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Add Semantic Memory\n",
"\n",
"Add step to save (details?) from email to semantic memory"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "memory-course-env",
"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.13.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+64
View File
@@ -0,0 +1,64 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"! pip install langchain-anthropic langgraph"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import os, getpass\n",
"\n",
"def _set_env(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
"\n",
"_set_env(\"ANTHROPIC_API_KEY\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Add Procedural Memory\n",
"\n",
"* Update draft tool that uses procedural memory\n",
"* Update calendar tool to use procedural memory "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "memory-course-env",
"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.13.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
-277
View File
@@ -1,277 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"! pip install langchain-anthropic langgraph"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import os, getpass\n",
"\n",
"def _set_env(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
"\n",
"_set_env(\"ANTHROPIC_API_KEY\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Memory Profile\n",
"\n",
"You can think of this as a fixed profile of facts (\"semantic\" memory) about the user.\n",
"\n",
"These are typically user attributes that fall within a pre-defined schema. \n",
"\n",
"You'll see that this gives us a useful, general context to have in all of our interactions with the user.\n",
"\n",
"Let's build on what we did in the previous lessons.\n",
"\n",
"First, we define a profile schema for the user.\n",
"\n",
"![](./img/memory_course_semantic_profile.png)\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from pydantic import BaseModel, Field\n",
"from typing import List\n",
"from langchain_anthropic import ChatAnthropic\n",
"\n",
"# Schema for user profile\n",
"class UserProfile(BaseModel):\n",
" name: str = Field(None, description=\"User's full name\")\n",
" location: str = Field(None, description=\"User's primary location/city\")\n",
" workplace: str = Field(None, description=\"User's place of work\")\n",
" relationships: list[dict] = Field(None, description=\"User's relationships\")\n",
"\n",
"# Define and augment LLM with structured output\n",
"llm = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")\n",
"structured_llm = llm.with_structured_output(UserProfile)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# We can store the instructions for easy access and updating\n",
"from langgraph.store.memory import InMemoryStore\n",
"in_memory_store = InMemoryStore()"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"sys.path.append('..')\n",
"import src.memory_course.prompts\n",
"import src.memory_course.examples\n",
"import src.memory_course.utils\n",
"\n",
"# Reload the module in case any changes were made\n",
"import importlib\n",
"importlib.reload(src.memory_course.prompts)\n",
"importlib.reload(src.memory_course.examples)\n",
"importlib.reload(src.memory_course.utils)\n",
"\n",
"# Then import the instructions and examples\n",
"from src.memory_course.utils import format_few_shot_examples\n",
"from src.memory_course.examples import example_input, example_output\n",
"from src.memory_course.prompts import memory_collection_extraction_instructions, memory_search_instructions, memory_profile_extraction_instructions, profile_extraction_input"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"# Profile extraction instructions\n",
"namespace = (\"journal\",\"instructions\")\n",
"key = \"instructions_profile_extraction\"\n",
"in_memory_store.put(namespace, key, {\"instructions\": memory_profile_extraction_instructions})"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"def extract_profile(journal_entry):\n",
" \"\"\"Extract a profile from a journal entry.\n",
" \n",
" Args:\n",
" journal_entry (str): The text of the journal entry to analyze\n",
" \n",
" Returns:\n",
" dict: A dictionary containing extracted profile\n",
" \"\"\"\n",
" # Get instructions\n",
" memory_profile_extraction_instructions = in_memory_store.get((\"journal\",\"instructions\"), \"instructions_profile_extraction\")\n",
"\n",
" # Get existing profile\n",
" namespace = (\"journal\", \"memory\", \"profile\")\n",
" key = \"profile\"\n",
" profile = in_memory_store.get(namespace, key)\n",
"\n",
" # Format input\n",
" input = profile_extraction_input.format(\n",
" memory_profile_extraction_instructions=memory_profile_extraction_instructions,\n",
" profile=profile,\n",
" journal_entry=journal_entry\n",
" )\n",
"\n",
" # Run extraction\n",
" profile = structured_llm.invoke(input)\n",
"\n",
" return profile\n",
"\n",
"def store_user_profile(profile):\n",
" \"\"\"Store the user's profile in the memory store.\n",
" \n",
" Args:\n",
" profile (dict): The profile information to store, containing extracted\n",
" personality traits, preferences, and characteristics\n",
" \n",
" Returns:\n",
" None\n",
" \"\"\"\n",
" # Iterate through each memory and store in appropriate collection\n",
" namespace = (\"journal\", \"memory\", \"profile\")\n",
" key = \"profile\"\n",
" value = profile\n",
" in_memory_store.put(namespace, key, value)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"UserProfile(name='Lance', location='San Francisco', workplace='LangChain', relationships=[{'type': 'spouse', 'description': 'married'}, {'type': 'daughter', 'description': '16 months old'}])"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Test extraction of memories\n",
"journal_entry = \"\"\"\n",
"I'll first introduce myself before I start the journal.\n",
"\n",
"I'm Lance, I work at LangChain, I live in San Francisco, and I'm married with a 16 month old daughter.\n",
"\"\"\"\n",
"\n",
"profile=extract_profile(journal_entry)\n",
"profile"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"store_user_profile(profile)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"UserProfile(name='Lance', location='San Francisco', workplace='LangChain', relationships=[{'type': 'spouse', 'description': 'married'}, {'type': 'daughter', 'description': '16 months old'}, {'type': 'co-worker', 'description': 'Will'}])"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Test updating profile\n",
"journal_entry = \"\"\"\n",
"I need to ping Will (co-worker) about the new LangChain memory docs.\n",
"\"\"\"\n",
"\n",
"profile=extract_profile(journal_entry)\n",
"profile"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So now we can:\n",
"\n",
"1. Extract memories (collections) and user profile from a journal entry given a set of instructions (system message)\n",
"2. Update those instructions based upon user feedback directly \n",
"3. Add few shot examples to improve the quality of the extraction\n",
"4. Update those few shot examples based upon user feedback directly \n",
"5. Save a collection of extracted memories to the store\n",
"6. Search for memories in a given collection in natural language\n",
"\n",
"This is a simple example of using and editing both \"procedural memory\" (system prompt/instructions) and \"episodic memory\" (few shot examples). \n",
"\n",
"In addition, we save \"semantic memories\" (fact collections as well as a single profile) about the user.\n",
"\n",
"And we can search for memories in a given collection in natural language. \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "memory-course-env",
"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.13.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
File diff suppressed because one or more lines are too long
+2 -36
View File
@@ -1,37 +1,3 @@
example_input = """
Today was a mix of ups and downs. Started the morning feeling energized after my new morning routine, but got a bit overwhelmed with the project deadlines in the afternoon.
example_input = """ . """
Need to remember to:
- Email Sarah about the client presentation by Thursday
- Schedule dentist appointment
- Pick up groceries for weekend dinner party
Had an interesting thought during my walk - what if we incorporated AI into our customer feedback system? Could potentially automate the initial response and categorization. Would need to research existing solutions first.
Despite the stress, I'm optimistic about where things are heading. The team's really coming together on the new initiative."""
example_output = """
{
"memories": [
{
"memory_type": "SENTIMENT",
"memory_content": "Mixed emotions with overall optimistic tone. Started energized, experienced afternoon overwhelm with deadlines, but ended positively about team progress. Key phrases: 'energized', 'overwhelmed', 'optimistic', 'team's coming together'"
},
{
"memory_type": "TODO",
"memory_content": "Email Sarah about the client presentation (Deadline: Thursday)"
},
{
"memory_type": "TODO",
"memory_content": "Schedule dentist appointment"
},
{
"memory_type": "TODO",
"memory_content": "Pick up groceries for weekend dinner party"
},
{
"memory_type": "IDEA",
"memory_content": "Integrate AI into customer feedback system to automate initial response and categorization."
}
]
}"""
example_output = """ . """
+46 -136
View File
@@ -1,150 +1,60 @@
# Agent prompt
agent_system_prompt = """
< Role >
You are {full_name}'s executive assistant. You are a top-notch executive assistant who cares about {name} performing as well as possible.
</ Role >
< Tools >
You have access to the following tools to help manage {name}'s communications and schedule:
# Instructions for memory collection extraction
memory_collection_extraction_instructions = """
You are a helpful assistant that extracts memories from a journal entry for a user.
1. write_email(to, subject, content) - Send emails to specified recipients
2. schedule_meeting(attendees, subject, duration_minutes, preferred_day) - Schedule calendar meetings
3. check_calendar_availability(day) - Check available time slots for a given day
</ Tools >
<Instructions>
Given a journal entry, analyze and extract the following elements:
1. SENTIMENT:
- Identify the overall emotional tone (positive, negative, neutral)
- Note any significant emotional shifts or patterns
- Extract key emotional words or phrases
Output as a single memory with memory_type "SENTIMENT"
2. TODOS:
- Extract each task, action, or commitment as a SEPARATE memory
- Each TODO should be its own Memory object with memory_type "TODO"
- Include deadline in the memory_content if mentioned
- Format each todo concisely and clearly
3. IDEAS:
- Extract each creative thought, insight, or potential project as a SEPARATE memory
- Each idea should be its own Memory object with memory_type "IDEA"
- Keep each idea focused and specific
- Include any relevant details or requirements with that specific idea
Important: Create a new Memory object for EACH individual todo and idea. Do not combine multiple todos or ideas into a single memory.
</Instructions>"""
# Define the prompt for memory collection extraction input
collection_extraction_input = """
<Instructions>
{procedural_memory_instructions}
</Instructions>
<Few Shot Examples>
{few_shot_examples_formatted}
</Few Shot Examples>"""
# Define the prompt for updating the memory extraction instructions per feedback
update_memory_collection_extraction_instructions = """
You goal is examine user feedback on an extraction task and use that to update the instructions.
<Current Instructions>
{instructions}
</Current Instructions>
Produce new instructions that incorporate the user feedback. Add no preamble or postamble.
Retain the original instructions as a base and only update the instructions that need to be changed."""
# Define the prompt for updating the few shot examples per feedback
update_few_shot_examples_instructions="""
You are an expert at creating few-shot examples based on user feedback.
Here are the current few shot examples to use for formatting (if they are present):
<Current Few-Shot Examples>
{current_examples}
</Current Few-Shot Examples>
<Instructions>
1. Review the user feedback:
2. Create new examples based upon the user feedack
3. Maintain the same format as the existing examples, if they are present
Do not:
- Add explanatory text or commentar
</Instructions>
Output only the new examples:
< Instructions >
Use these tools when appropriate to help manage {name}'s tasks efficiently.
</ Instructions >
"""
# Define the prompt for searching for memories in a given collection
memory_search_instructions = """
# Triage prompt
triage_system_prompt = """
< Role >
You are {full_name}'s executive assistant. You are a top-notch executive assistant who cares about {name} performing as well as possible.
</ Role >
<Instructions>
You are a helpful assistant that can search for memories in a given collection.
< Background >
{user_profile_background}.
</ Background >
Your goal is to determine which collection the user is interested in searching.
< Instructions >
You will be given a list of available collections, a user input, and the prompt used for memory classification so that you have full context on how memories are classified.
{name} gets lots of emails. Your job is to categorize each email into one of three categories:
Your job is to return the name of the collection that best matches the user input.
</Instructions>
1. IGNORE - Emails that are not worth responding to or tracking
2. NOTIFY - Important information that {name} should know about but doesn't require a response
3. RESPOND - Emails that need a direct response from {name}
<Available Collections>
{available_collections}
</Available Collections>
Classify the below email into one of these categories.
<Memory Classification Prompt>
{memory_classification_prompt}
</Memory Classification Prompt>
</ Instructions >
< Examples >
Emails that are not worth responding to:
{triage_no}
There are also other things that {name} should know about, but don't require an email response. For these, you should notify {name} (using the `notify` response). Examples of this include:
{triage_notify}
Emails that are worth responding to:
{triage_email}
</ Examples >
"""
# Define the prompt for extracting a user profile from a journal entry
profile_extraction_input = """
triage_user_prompt = """
Please determine how to handle the below email thread:
<Instructions>
{memory_profile_extraction_instructions}
</Instructions>
<Existing Profile>
{profile}
</Existing Profile>
<USER JOURNAL ENTRY>
{journal_entry}
</USER JOURNAL ENTRY>
"""
# Define the prompt for extracting a user profile from a journal entry
memory_profile_extraction_instructions = """
You are a helpful assistant that extracts a user profile.
<Instructions>
Given a journal entry and, optionally, an existing user profile, create or update the fields of the user profile.
Keep any existing fields of the user profile that are not mentioned in the journal entry.
Do not forget information.
If the journal entry mentions new information relevant to the fields, then populate them.
If the journal entry updates information in any of the fields, then update them.
</Instructions>"""
# Define the prompt for routing a user request
routing_instructions = """
<Instructions>
Take a user request and classify it into one of the following categories:
- search: The user is asking to retrieve or find information from their existing journal entries
Example: "Show me entries about project work"
Example: "Find times when I mentioned feeling stuck"
- extract: The user is submitting a new journal entry that needs to be processed
Example: "Today I made great progress on the API design"
Example: "Dear journal, just finished a productive meeting"
- update_instructions: The user is providing general rules about what information to extract from entries
Example: "Extract each creative thought, insight, or potential project as a SEPARATE memory and classify it as IDEA"
Example: "Identify the overall emotional tone (positive, negative, neutral) and classify it as SENTIMENT"
- update_examples: The user is providing specific classification examples
Example: "It would be cool to build an open source journal as an example of an IDEA"
Example: "I need to take out the trash tomorrow is an example of a TODO"
</Instructions>
"""
From: {author}
To: {to}
Subject: {subject}
{email_thread}"""
+17
View File
@@ -0,0 +1,17 @@
from pydantic import BaseModel, Field
from typing import List, TypedDict, Literal, Annotated
import operator
class Router(BaseModel):
classification: Literal["ignore", "respond", "notify"] = Field(
None,
description="The classification of an email: 'ignore' for irrelevant emails, "
"'notify' for important information that doesn't need a response, "
"'respond' for emails that need a reply"
)
class State(TypedDict):
email_input: str
routing_decision: str
messages: Annotated[list, operator.add]
-30
View File
@@ -1,30 +0,0 @@
from pydantic import BaseModel, Field
from typing import List, TypedDict
class State(TypedDict):
user_input: str
routing_decision: str
journal_output: str
class UserProfile(BaseModel):
name: str = Field(None, description="User's full name")
location: str = Field(None, description="User's primary location/city")
workplace: str = Field(None, description="User's place of work")
relationships: list[dict] = Field(None, description="User's relationships")
class Memory(BaseModel):
memory_type: str = Field(None, description="Type of memory to extract.")
memory_content: str = Field(None, description="Specific content of the memory.")
class Memories(BaseModel):
memories: List[Memory] = Field(None, description="List of memories to extract.")
class MemorySearch(BaseModel):
collection: str=Field(None, description="Name of the memory collection to search" )
class Example(BaseModel):
example_input: str = Field(None, description="Input for the example.")
example_output: str = Field(None, description="Output for the example.")
class Examples(BaseModel):
examples: List[Example] = Field(None, description="List examples.")
+23 -9
View File
@@ -1,9 +1,23 @@
def format_few_shot_examples(examples):
""" Format episodic memories for few shot examples"""
formatted_examples = []
for example in examples:
formatted_examples.append(
f"Input:\n{example.value['input']}\n\n"
f"Output:\n{example.value['output']}\n"
)
return "\n---\n".join(formatted_examples)
def parse_email(email_input: dict) -> dict:
"""Parse an email input dictionary.
Args:
email_input (dict): Dictionary containing email fields:
- author: Sender's name and email
- to: Recipient's name and email
- subject: Email subject line
- email_thread: Full email content
Returns:
tuple[str, str, str, str]: Tuple containing:
- author: Sender's name and email
- to: Recipient's name and email
- subject: Email subject line
- email_thread: Full email content
"""
return (
email_input["author"],
email_input["to"],
email_input["subject"],
email_input["email_thread"]
)