Merge pull request #1 from langchain-ai/rlm/email-assistant
Email Assistant Course Topic
@@ -0,0 +1,55 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
*.ipynb
|
||||
|
||||
# Virtual Environment
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
.env
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
|
||||
# Testing
|
||||
.coverage
|
||||
htmlcov/
|
||||
.pytest_cache/
|
||||
.tox/
|
||||
|
||||
# Distribution
|
||||
*.tar.gz
|
||||
*.whl
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
@@ -2,29 +2,28 @@
|
||||
|
||||
## 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.
|
||||
TO ADD
|
||||
|
||||
## 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.
|
||||
TO ADD
|
||||
|
||||

|
||||

|
||||
|
||||
## Organization of the course
|
||||
|
||||
The first lesson is a conceptual introduction (slides [here](https://docs.google.com/presentation/d/1zdVyTUydRkgrSx_ZzlNuuKYapy6dTkHKkqYLdcxtTIQ/edit?usp=sharing)) to the different types of memories that we will use in this course.
|
||||
|
||||
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/).
|
||||
|
||||
## Running the Application
|
||||
|
||||
TODO
|
||||
|
Before Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 296 KiB |
|
After Width: | Height: | Size: 326 KiB |
|
Before Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 259 KiB |
|
Before Width: | Height: | Size: 278 KiB |
@@ -1,94 +0,0 @@
|
||||
# Concept Overview (WIP / Outline)
|
||||
|
||||
### 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 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
### Memory Types
|
||||
|
||||
[Human long-term memory is divided into different types:](https://en.wikipedia.org/wiki/Long-term_memory)
|
||||
|
||||
- Procedural: Skills, habits, routines
|
||||
- Episodic: Events, experiences
|
||||
- Semantic: Facts, concepts, relationships
|
||||
|
||||
We can [map these onto different memory types in AI applications:](https://arxiv.org/pdf/2309.02427)
|
||||
|
||||
- Procedural: Prompts
|
||||
- Episodic: Few shot examples
|
||||
- Semantic: Long-term facts we want to remember
|
||||
|
||||
In particular, semantic memories can be [organized into collections or a fixed profile](https://langchain-ai.github.io/langgraph/concepts/memory/#semantic-memory).
|
||||
|
||||
### Updating Modes
|
||||
|
||||
Memories can be updates in different ways, incluing:
|
||||
|
||||
* Hot Path: Creating memories during runtime
|
||||
* Background: Creating memories as a separate background task
|
||||
|
||||
### Memory Types in Our Journalling App
|
||||
|
||||
* 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. 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
|
||||
|
||||
@@ -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",
|
||||
"\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
|
||||
}
|
||||
@@ -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",
|
||||
"\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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"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": {
|
||||
"vscode": {
|
||||
"languageId": "shellscript"
|
||||
}
|
||||
},
|
||||
"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": [
|
||||
"## Episodic Memory\n",
|
||||
"\n",
|
||||
"* Add few shot examples to triage\n",
|
||||
"* Add few shot examples to agent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"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
|
||||
}
|
||||
@@ -1,501 +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": {
|
||||
"vscode": {
|
||||
"languageId": "shellscript"
|
||||
}
|
||||
},
|
||||
"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 Collection\n",
|
||||
"\n",
|
||||
"You can think of this as a collection of facts or \"semantic\" memory. \n",
|
||||
"\n",
|
||||
"These are things that the LLM extracts over time from the user's journal. \n",
|
||||
"\n",
|
||||
"Let's build on what we did in the previous lessons.\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",
|
||||
"\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 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 and examples."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"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, collection_extraction_input"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"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": [
|
||||
"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": 5,
|
||||
"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": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"=== Extracted Memory ===\n",
|
||||
"\n",
|
||||
"SENTIMENT:\n",
|
||||
"- Highly positive and confident mood despite workload. Enthusiasm about technical progress and sprint goals. Key phrases: 'productive', 'working better than expected', 'feeling pretty confident', '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 the 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: Evaluate cost and accuracy feasibility\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": [
|
||||
"With memories extracted from the journal entry, we can store them easily."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import uuid\n",
|
||||
"\n",
|
||||
"def store_memory_collection(memories):\n",
|
||||
" \"\"\"Store a collection of extracted memories in the memory store.\n",
|
||||
" \n",
|
||||
" Args:\n",
|
||||
" memories (List[Memory]): List of Memory objects, where each Memory has:\n",
|
||||
" - memory_type: str, one of [\"SENTIMENT\", \"TODO\", \"IDEA\"]\n",
|
||||
" - memory_content: str, the extracted content\n",
|
||||
" - timestamp: str, ISO format timestamp\n",
|
||||
" \n",
|
||||
" Returns:\n",
|
||||
" None: Memories are stored in the in-memory store as a side effect\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Iterate through each memory and store in appropriate collection\n",
|
||||
" for memory in memories.memories:\n",
|
||||
"\n",
|
||||
" # Generate a unique UUID for each memory\n",
|
||||
" memory_id = str(uuid.uuid4())\n",
|
||||
" \n",
|
||||
" # Create namespace tuple with the specific collection type (memory_type)\n",
|
||||
" namespace = (\"journal\", \"memory\", \"collection\", memory.memory_type.lower())\n",
|
||||
" \n",
|
||||
" # Create the key using the UUID\n",
|
||||
" key = f\"memory_{memory_id}\"\n",
|
||||
" \n",
|
||||
" # Create the value object according to the schema\n",
|
||||
" value = {\n",
|
||||
" \"memory\": memory.memory_content, #\n",
|
||||
" \"timestamp\": datetime.now().isoformat(),\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" # Store in the database\n",
|
||||
" in_memory_store.put(namespace, key, value)\n",
|
||||
"\n",
|
||||
"store_memory_collection(memories)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[Item(namespace=['journal', 'semantic', 'collection', 'todo'], key='memory_5951f267-9b73-4198-9b17-c017ffc71808', value={'memory': 'Set up meeting with DevOps team about deployment strategy', 'timestamp': '2025-02-03T13:48:57.451657'}, created_at='2025-02-03T21:48:57.451663+00:00', updated_at='2025-02-03T21:48:57.451664+00:00', score=None),\n",
|
||||
" Item(namespace=['journal', 'semantic', 'collection', 'todo'], key='memory_9337cc2e-d7b3-4351-90f0-902acd7dffb1', value={'memory': 'Fix unit tests for the payment module (Deadline: Friday)', 'timestamp': '2025-02-03T13:48:57.451675'}, created_at='2025-02-03T21:48:57.451680+00:00', updated_at='2025-02-03T21:48:57.451681+00:00', score=None),\n",
|
||||
" Item(namespace=['journal', 'semantic', 'collection', 'todo'], key='memory_0fb65069-c8ea-4b22-b06e-7c83e15ca681', value={'memory': 'Order new laptop charger', 'timestamp': '2025-02-03T13:48:57.451695'}, created_at='2025-02-03T21:48:57.451700+00:00', updated_at='2025-02-03T21:48:57.451700+00:00', score=None),\n",
|
||||
" Item(namespace=['journal', 'semantic', 'collection', 'todo'], key='memory_8465d5cf-945d-4b9a-9810-4bdfe75a56ce', value={'memory': 'Update documentation for API changes', 'timestamp': '2025-02-03T13:48:57.451707'}, created_at='2025-02-03T21:48:57.451711+00:00', updated_at='2025-02-03T21:48:57.451712+00:00', score=None)]"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Search\n",
|
||||
"namespace = (\"journal\", \"memory\", \"collection\", \"todo\")\n",
|
||||
"in_memory_store.search(namespace)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now, we also want the ability to search for memories across all collections in natural language."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Schema for structured output\n",
|
||||
"class MemorySearch(BaseModel):\n",
|
||||
" collection: str=Field(None, description=\"Name of the memory collection to search\" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'todo'"
|
||||
]
|
||||
},
|
||||
"execution_count": 23,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def get_collection_to_search(user_input):\n",
|
||||
" \"\"\"Get the collection to search based on user input.\"\"\"\n",
|
||||
"\n",
|
||||
" # Get the last element of each namespace tuple which represents the collection type\n",
|
||||
" namespace = (\"journal\", \"memory\", \"collection\")\n",
|
||||
" all_memories = in_memory_store.search(namespace)\n",
|
||||
" unique_collections = sorted({item.namespace[-1] for item in all_memories})\n",
|
||||
"\n",
|
||||
" # Format the prompt\n",
|
||||
" structured_llm = llm.with_structured_output(MemorySearch)\n",
|
||||
" search_instructions_formatted = memory_search_instructions.format(available_collections=unique_collections, \n",
|
||||
" memory_classification_prompt=memory_collection_extraction_instructions)\n",
|
||||
" # Get the collection to search\n",
|
||||
" collection = structured_llm.invoke([SystemMessage(content=search_instructions_formatted), HumanMessage(content=f\"<User Input>{user_input}</User Input>\")])\n",
|
||||
" return collection.collection\n",
|
||||
"\n",
|
||||
"get_collection_to_search(\"What are my ToDos for the day?\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now, we can easily search for memories in the given collection."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Extract semantic collection\n",
|
||||
"def search_and_format_semantic_collection(collection_type):\n",
|
||||
" \"\"\" Format episodic memories for few shot examples\"\"\"\n",
|
||||
"\n",
|
||||
" # Search the collection\n",
|
||||
" collection = in_memory_store.search((\"journal\", \"semantic\", \"collection\", collection_type))\n",
|
||||
"\n",
|
||||
" # Sort items by creation timestamp\n",
|
||||
" sorted_items = sorted(collection, key=lambda x: x.created_at)\n",
|
||||
" \n",
|
||||
" # Extract memory contents\n",
|
||||
" memories = []\n",
|
||||
" for item in sorted_items:\n",
|
||||
" if isinstance(item.value.get('memory'), Memory):\n",
|
||||
" memories.append(item.value['memory'].memory_content)\n",
|
||||
" else:\n",
|
||||
" memories.append(item.value.get('memory'))\n",
|
||||
" \n",
|
||||
" output = [\n",
|
||||
" f\"Collection Type: {collection_type.upper()}\",\n",
|
||||
" \"Sorted by: Creation Time (Oldest to Newest)\",\n",
|
||||
" \"\\nItems:\",\n",
|
||||
" *[f\"• {memory}\" for memory in memories]\n",
|
||||
" ]\n",
|
||||
" \n",
|
||||
" return \"\\n\".join(output)\n",
|
||||
"\n",
|
||||
"collection_type = get_collection_to_search(\"What are my ToDos for the day?\")\n",
|
||||
"collection_formatted = search_and_format_semantic_collection(collection_type)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'Collection Type: TODO\\nSorted by: Creation Time (Oldest to Newest)\\n\\nItems:\\n• Set up meeting with DevOps team about deployment strategy\\n• Fix unit tests for the payment module (Deadline: Friday)\\n• Order new laptop charger\\n• Update documentation for API changes'"
|
||||
]
|
||||
},
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"collection_formatted"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"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\" (facts) about the users in specific collections.\n",
|
||||
"\n",
|
||||
"And we can search for memories in a given collection in natural language. \n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
"\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
|
||||
}
|
||||
@@ -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 = """ . """
|
||||
|
||||
@@ -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}"""
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing_extensions import TypedDict, Literal, Annotated
|
||||
from langgraph.graph import add_messages
|
||||
|
||||
|
||||
class Router(BaseModel):
|
||||
"""Analyze the unread email and route it according to its content."""
|
||||
|
||||
reasoning: str = Field(
|
||||
description="Step-by-step reasoning behind the classification."
|
||||
)
|
||||
classification: Literal["ignore", "respond", "notify"] = Field(
|
||||
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, add_messages]
|
||||
@@ -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.")
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||