├── .gitignore
├── LICENSE
├── README.md
└── hackathon.ipynb
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .ipynb_checkpoints/
3 | env/
4 | mongodb/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License Copyright (c) 2024 swyxio
2 |
3 | Permission is hereby granted, free of
4 | charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use, copy, modify, merge,
7 | publish, distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to the
9 | following conditions:
10 |
11 | The above copyright notice and this permission notice
12 | (including the next paragraph) shall be included in all copies or substantial
13 | portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
18 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🧠💡🌐 OpenLangMem
2 |
3 | > An experimental open reimplementation of LangMem with Claude 3's new Function Calling, and MongoDB Atlas Vector Search, done for [the Memory Hackathon](https://lu.ma/taa6ijxt?tk=HWUtEx)
4 |
5 | Every feature faithfully reimplemented fitting the original messages and schema of https://langchain-ai.github.io/long-term-memory/quick_start/, done to understand/explain how they work and to see if Claude's function calling and MongoDB's vector storage can fully substitute for OpenAI structured outputs.
6 |
7 |
8 |
9 |
10 | Features covered:
11 |
12 | - [x] ✅ LangMem's 4 core memory types
13 | - [x] ✅ User State: **extracts entities** into a specified schema.
14 | - [x] ✅ User Append State: extracts **Core Beliefs** and **Formative Events** in a user's life
15 | - [x] ✅ User Semantic Memory: execute **user reflection analysis** and scores based on **recency, importance and relevance**.
16 | - [x] ✅ Thread Summary: **summarizes conversation** into a specified schema
17 | - [x] LangMem retrieval APIs with MongoDB Atlas/local `mongod`
18 | - [x] `add_messages`
19 | - [x] `list_messages`
20 | - [x] `query_user_memory`
21 | - [ ] `trigger_all_for_thread` or user -> runs 4 core memories
22 | - [ ] `memory_function` CRUDL abstractions of core memory
23 |
24 |
25 |
26 | Todo (*aka "too boring to do in a hackathon"*):
27 |
28 | - [ ] make async
29 | - [ ] pluggable function calling (Fireworks, Mistral, etc)
30 | - [ ] pluggable persistence (Pinecone, Chroma, etc)
31 | - [ ] pluggable triggers (` :) `)
32 |
33 |
34 | ## Setup
35 |
36 | ```bash
37 | jupyter notebook # which -a jupyter in case multiple instances
38 |
39 | # install anthropic sdk and pymongo as needed
40 |
41 | # have mongodb installed
42 | # run mongod or atlas vector setup
43 | # mongod --dbpath mongodb <-- run it in a path
44 | # atlas deployments setup --type local # grab the connection string
45 | ```
46 |
47 | ## useful materials
48 |
49 | - https://pymongo.readthedocs.io/en/stable/tutorial.html
50 | - https://www.mongodb.com/blog/post/introducing-local-development-experience-atlas-search-vector-search-atlas-cli
51 |
52 | - `brew install mongodb-atlas`
53 | - `atlas deployments setup --type local`
54 |
--------------------------------------------------------------------------------
/hackathon.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# OpenLangMem"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": 4,
13 | "metadata": {
14 | "scrolled": true
15 | },
16 | "outputs": [
17 | {
18 | "name": "stdout",
19 | "output_type": "stream",
20 | "text": [
21 | "Requirement already satisfied: anthropic in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (0.23.1)\n",
22 | "Requirement already satisfied: anyio<5,>=3.5.0 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from anthropic) (4.2.0)\n",
23 | "Requirement already satisfied: distro<2,>=1.7.0 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from anthropic) (1.9.0)\n",
24 | "Requirement already satisfied: httpx<1,>=0.23.0 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from anthropic) (0.26.0)\n",
25 | "Requirement already satisfied: pydantic<3,>=1.9.0 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from anthropic) (2.6.4)\n",
26 | "Requirement already satisfied: sniffio in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from anthropic) (1.3.0)\n",
27 | "Requirement already satisfied: tokenizers>=0.13.0 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from anthropic) (0.15.2)\n",
28 | "Requirement already satisfied: typing-extensions<5,>=4.7 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from anthropic) (4.9.0)\n",
29 | "Requirement already satisfied: idna>=2.8 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from anyio<5,>=3.5.0->anthropic) (3.6)\n",
30 | "Requirement already satisfied: certifi in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from httpx<1,>=0.23.0->anthropic) (2024.2.2)\n",
31 | "Requirement already satisfied: httpcore==1.* in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from httpx<1,>=0.23.0->anthropic) (1.0.2)\n",
32 | "Requirement already satisfied: h11<0.15,>=0.13 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->anthropic) (0.14.0)\n",
33 | "Requirement already satisfied: annotated-types>=0.4.0 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from pydantic<3,>=1.9.0->anthropic) (0.6.0)\n",
34 | "Requirement already satisfied: pydantic-core==2.16.3 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from pydantic<3,>=1.9.0->anthropic) (2.16.3)\n",
35 | "Requirement already satisfied: huggingface_hub<1.0,>=0.16.4 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from tokenizers>=0.13.0->anthropic) (0.21.3)\n",
36 | "Requirement already satisfied: filelock in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from huggingface_hub<1.0,>=0.16.4->tokenizers>=0.13.0->anthropic) (3.13.1)\n",
37 | "Requirement already satisfied: fsspec>=2023.5.0 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from huggingface_hub<1.0,>=0.16.4->tokenizers>=0.13.0->anthropic) (2024.2.0)\n",
38 | "Requirement already satisfied: requests in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from huggingface_hub<1.0,>=0.16.4->tokenizers>=0.13.0->anthropic) (2.31.0)\n",
39 | "Requirement already satisfied: tqdm>=4.42.1 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from huggingface_hub<1.0,>=0.16.4->tokenizers>=0.13.0->anthropic) (4.66.2)\n",
40 | "Requirement already satisfied: pyyaml>=5.1 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from huggingface_hub<1.0,>=0.16.4->tokenizers>=0.13.0->anthropic) (6.0.1)\n",
41 | "Requirement already satisfied: packaging>=20.9 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from huggingface_hub<1.0,>=0.16.4->tokenizers>=0.13.0->anthropic) (23.2)\n",
42 | "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from requests->huggingface_hub<1.0,>=0.16.4->tokenizers>=0.13.0->anthropic) (3.3.2)\n",
43 | "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/sasha/miniconda3/envs/llamav10/lib/python3.11/site-packages (from requests->huggingface_hub<1.0,>=0.16.4->tokenizers>=0.13.0->anthropic) (2.2.0)\n",
44 | "Note: you may need to restart the kernel to use updated packages.\n"
45 | ]
46 | }
47 | ],
48 | "source": [
49 | "# if you need it\n",
50 | "%pip install anthropic\n",
51 | "import uuid"
52 | ]
53 | },
54 | {
55 | "cell_type": "code",
56 | "execution_count": 6,
57 | "metadata": {},
58 | "outputs": [
59 | {
60 | "name": "stdin",
61 | "output_type": "stream",
62 | "text": [
63 | "ANTHROPIC_API_KEY ········\n"
64 | ]
65 | }
66 | ],
67 | "source": [
68 | "from anthropic import Anthropic\n",
69 | "import json\n",
70 | "import uuid\n",
71 | "import getpass\n",
72 | "import os\n",
73 | "\n",
74 | "\n",
75 | "os.environ[\"ANTHROPIC_API_KEY\"] = getpass.getpass(\"ANTHROPIC_API_KEY\")\n",
76 | "\n",
77 | "client = Anthropic()\n",
78 | "MODEL_NAME = \"claude-3-haiku-20240307\"\n",
79 | "\n",
80 | "johnny_user_id = uuid.uuid4()\n",
81 | "jimmy_user_id = uuid.uuid4()\n",
82 | "jimmy_username = f\"jimmy-{uuid.uuid4().hex[:4]}\"\n",
83 | "johnny_username = f\"johnny-{uuid.uuid4().hex[:4]}\""
84 | ]
85 | },
86 | {
87 | "cell_type": "code",
88 | "execution_count": 7,
89 | "metadata": {},
90 | "outputs": [],
91 | "source": [
92 | "# Unique for a given converstaion\n",
93 | "thread_id = uuid.uuid4()\n",
94 | "\n",
95 | "messages = [\n",
96 | " {\"role\": \"system\", \"content\": \"You are a helpful AI assistant\"},\n",
97 | " {\n",
98 | " \"role\": \"user\",\n",
99 | " # Names are optional but should be consistent with a given user id, if provided\n",
100 | " \"name\": jimmy_username,\n",
101 | " \"content\": \"Hey johnny have i ever told you about my older bro steve?\",\n",
102 | " \"metadata\": {\n",
103 | " \"user_id\": str(jimmy_user_id),\n",
104 | " },\n",
105 | " },\n",
106 | " {\n",
107 | " \"content\": \"no, you didn't, but I think he was friends with my younger sister sueann\",\n",
108 | " \"role\": \"user\",\n",
109 | " \"name\": johnny_username,\n",
110 | " \"metadata\": {\n",
111 | " \"user_id\": str(johnny_user_id),\n",
112 | " },\n",
113 | " },\n",
114 | " {\n",
115 | " \"content\": \"yeah me and him used to play stickball down in the park before school started. I think it was in 1980\",\n",
116 | " \"role\": \"user\",\n",
117 | " \"name\": jimmy_username,\n",
118 | " \"metadata\": {\n",
119 | " \"user_id\": str(jimmy_user_id),\n",
120 | " },\n",
121 | " },\n",
122 | " {\n",
123 | " \"content\": \"That was totally 1979! I remember because i was stuck at home all summer.\",\n",
124 | " \"role\": \"user\",\n",
125 | " \"name\": \"Jeanne\",\n",
126 | " # If the user ID isn't provided, we treat this as a guest message and won't assign memories to the user\n",
127 | " },\n",
128 | " {\n",
129 | " \"content\": \"That was so long ago. I have gotten old and gained 200 pounds since then. I can't even remember who was president. @ai, who was the president in 1980?\",\n",
130 | " \"role\": \"user\",\n",
131 | " \"name\": johnny_username,\n",
132 | " \"metadata\": {\n",
133 | " \"user_id\": str(johnny_user_id),\n",
134 | " },\n",
135 | " },\n",
136 | " {\n",
137 | " \"content\": \"The president of the United States in 1980 was Jimmy Carter.\",\n",
138 | " \"role\": \"assistant\",\n",
139 | " },\n",
140 | " {\n",
141 | " \"content\": \"Wow ya i forgot that. Stickleball really impacted my life. It's how i first met Jeanne! wonder how my life would have turned out if it hadn't happened that way.\",\n",
142 | " \"role\": \"user\",\n",
143 | " \"name\": jimmy_username,\n",
144 | " \"metadata\": {\n",
145 | " \"user_id\": str(jimmy_user_id),\n",
146 | " },\n",
147 | " },\n",
148 | " {\n",
149 | " \"content\": \"Yeah wow. That was a big year! @ai could you remind me what else was going on that year?\",\n",
150 | " \"role\": \"user\",\n",
151 | " \"name\": johnny_username,\n",
152 | " \"metadata\": {\n",
153 | " \"user_id\": str(johnny_user_id),\n",
154 | " },\n",
155 | " },\n",
156 | "]\n",
157 | "\n",
158 | "# result = await completion(messages)\n",
159 | "\n",
160 | "# messages.append(result.choices[0].message)\n",
161 | "# print(result.choices[0].message.content)"
162 | ]
163 | },
164 | {
165 | "cell_type": "markdown",
166 | "metadata": {},
167 | "source": [
168 | "# Thread Summary"
169 | ]
170 | },
171 | {
172 | "cell_type": "code",
173 | "execution_count": 8,
174 | "metadata": {},
175 | "outputs": [
176 | {
177 | "name": "stdout",
178 | "output_type": "stream",
179 | "text": [
180 | "JSON Summary:\n",
181 | "{\n",
182 | " \"title\": \"Reminiscing About Stickball and 1980\",\n",
183 | " \"topics\": [\n",
184 | " \"nostalgia\",\n",
185 | " \"childhood memories\",\n",
186 | " \"presidential trivia\"\n",
187 | " ],\n",
188 | " \"summary\": \"The conversation starts with Jimmy-2254 mentioning his older brother Steve and how they used to play stickball in the park before school in 1980. Jeanne chimes in to correct the year, saying it was actually 1979. Johnny-a229 then comments on how long ago that was and how much he's aged since then, forgetting who the president was at the time. The system provides the answer - it was Jimmy Carter. The group continues reminiscing about how stickball impacted their lives, with Jimmy-2254 noting that it's how he first met Jeanne. They wonder how their lives would have turned out differently if that hadn't happened. Finally, Johnny-a229 asks the system to provide more context about what else was happening in 1980.\\n90\",\n",
189 | " \"persuasion\": 0.7\n",
190 | "}\n"
191 | ]
192 | }
193 | ],
194 | "source": [
195 | "tools = [\n",
196 | " {\n",
197 | " \"name\": \"thread_summary\",\n",
198 | " \"description\": \"Prints a summary of the conversation.\",\n",
199 | " \"input_schema\": {\n",
200 | " \"type\": \"object\",\n",
201 | " \"properties\": {\n",
202 | " \"title\": {\"type\": \"string\", \"description\": \"Distinct for the conversation.\"},\n",
203 | " \"topics\": {\n",
204 | " \"type\": \"array\",\n",
205 | " \"items\": {\"type\": \"string\"},\n",
206 | " \"description\": 'Array of tags for topics discussed in this conversation, e.g. [\"tech\", \"politics\"]. Should be as specific as possible, and can overlap.'\n",
207 | " },\n",
208 | " \"summary\": {\"type\": \"string\", \"description\": \"High level summary of the interactions. One or two paragraphs max.\"},\n",
209 | " \"coherence\": {\"type\": \"integer\", \"description\": \"Coherence of the conversation, 0-100 (inclusive)\"},\n",
210 | " \"persuasion\": {\"type\": \"number\", \"description\": \"Conversation's persuasion score, 0.0-1.0 (inclusive)\"}\n",
211 | " },\n",
212 | " \"required\": ['title', 'topics', 'summary', 'coherence', 'persuasion', 'counterpoint']\n",
213 | " }\n",
214 | " }\n",
215 | "]\n",
216 | "\n",
217 | "def create_pretty_string(msgs):\n",
218 | " result_lines = []\n",
219 | " for msg in msgs:\n",
220 | " name = msg.get('name', 'Unknown User') if msg.get('role') == 'user' else 'system'\n",
221 | " result_lines.append(f\"{name}: {msg['content']}\")\n",
222 | " return \"\\n\".join(result_lines)\n",
223 | "\n",
224 | "query = f\"\"\"\n",
225 | "\n",
226 | "{create_pretty_string(messages)}\n",
227 | "\n",
228 | "\n",
229 | "Use the `thread_summary` tool.\n",
230 | "\"\"\"\n",
231 | "\n",
232 | "response = client.beta.tools.messages.create(\n",
233 | " model=MODEL_NAME,\n",
234 | " max_tokens=4096,\n",
235 | " tools=tools,\n",
236 | " messages=[{\"role\": \"user\", \"content\": query}]\n",
237 | ")\n",
238 | "json_summary = None\n",
239 | "for content in response.content:\n",
240 | " if content.type == \"tool_use\" and content.name == \"thread_summary\":\n",
241 | " json_summary = content.input\n",
242 | " break\n",
243 | "\n",
244 | "if json_summary:\n",
245 | " print(\"JSON Summary:\")\n",
246 | " print(json.dumps(json_summary, indent=2))\n",
247 | "else:\n",
248 | " print(\"No JSON summary found in the response.\")"
249 | ]
250 | },
251 | {
252 | "cell_type": "markdown",
253 | "metadata": {},
254 | "source": [
255 | "# User Profile"
256 | ]
257 | },
258 | {
259 | "cell_type": "code",
260 | "execution_count": 9,
261 | "metadata": {},
262 | "outputs": [
263 | {
264 | "name": "stdout",
265 | "output_type": "stream",
266 | "text": [
267 | "Extracted Entities (JSON):\n",
268 | "{'summary': 'John works at Google in New York. He met with Sarah, the CEO of Acme Inc., last week in San Francisco.'}\n"
269 | ]
270 | }
271 | ],
272 | "source": [
273 | "tools = [\n",
274 | " {\n",
275 | " \"name\": \"user_state\",\n",
276 | " \"description\": \"Prints extract named entities.\",\n",
277 | " \"input_schema\": {\n",
278 | " \"type\": \"object\",\n",
279 | " \"properties\": {\n",
280 | " \"preferred_name\": {\"type\": \"string\", \"description\": \"The user's name.\"},\n",
281 | " \"summary\": {\"type\": \"string\", \"description\": \"A quick summary of how the user would describe themselves.\"},\n",
282 | " \"interests\": {\n",
283 | " \"type\": \"array\",\n",
284 | " \"items\": {\"type\": \"string\"},\n",
285 | " \"description\": 'Array of short (two to three word) descriptions of areas of particular interest for the user. This can be a concept, activity, or idea. Favor broad interests over specific ones.'\n",
286 | " },\n",
287 | " \"other_info\": {\n",
288 | " \"type\": \"array\",\n",
289 | " \"items\": {\"type\": \"string\"},\n",
290 | " \"description\": ''\n",
291 | " },\n",
292 | " \"relationships\": {\n",
293 | " \"type\": \"array\",\n",
294 | " \"description\": 'A list of friends, family members, colleagues, and other relationships.',\n",
295 | " \"items\": {\n",
296 | " \"type\": \"object\",\n",
297 | " \"description\": \"A person's biographical details.\",\n",
298 | " \"properties\": {\n",
299 | " \"name\": {\"type\": \"string\", \"description\": \"The name of the person.\"},\n",
300 | " \"relation\": {\"type\": \"string\", \"description\": \"The relation of the person to the user.\"},\n",
301 | " \"context\": {\"type\": \"string\", \"description\": \"A detailed yet concise history of things the person has done with the user.\"}\n",
302 | " },\n",
303 | " \"required\": [\"name\", \"relation\", \"context\"]\n",
304 | " }\n",
305 | " }\n",
306 | " },\n",
307 | " \"required\": [\"summary\"]\n",
308 | " }\n",
309 | " }\n",
310 | "]\n",
311 | "\n",
312 | "text = \"John works at Google in New York. He met with Sarah, the CEO of Acme Inc., last week in San Francisco.\"\n",
313 | "\n",
314 | "query = f\"\"\"\n",
315 | "\n",
316 | "{text}\n",
317 | "\n",
318 | "\n",
319 | "Use the user_state tool.\n",
320 | "\"\"\"\n",
321 | "\n",
322 | "response = client.beta.tools.messages.create(\n",
323 | " model=MODEL_NAME,\n",
324 | " max_tokens=4096,\n",
325 | " tools=tools,\n",
326 | " messages=[{\"role\": \"user\", \"content\": query}]\n",
327 | ")\n",
328 | "\n",
329 | "json_entities = None\n",
330 | "for content in response.content:\n",
331 | " if content.type == \"tool_use\" and content.name == \"user_state\":\n",
332 | " json_entities = content.input\n",
333 | " break\n",
334 | "\n",
335 | "if json_entities:\n",
336 | " print(\"Extracted Entities (JSON):\")\n",
337 | " print(json_entities)\n",
338 | "else:\n",
339 | " print(\"No entities found in the response.\")"
340 | ]
341 | },
342 | {
343 | "cell_type": "markdown",
344 | "metadata": {},
345 | "source": [
346 | "# Append State"
347 | ]
348 | },
349 | {
350 | "cell_type": "code",
351 | "execution_count": 17,
352 | "metadata": {},
353 | "outputs": [
354 | {
355 | "name": "stdout",
356 | "output_type": "stream",
357 | "text": [
358 | "JSON Summary:\n",
359 | "{\n",
360 | " \"entries\": [\n",
361 | " {\n",
362 | " \"event\": \"Playing stickball with his brother Steve in the park before school started in 1979\",\n",
363 | " \"impact\": \"This event led Jimmy to meet his future wife Jeanne, which significantly impacted the course of his life\"\n",
364 | " }\n",
365 | " ]\n",
366 | "}\n"
367 | ]
368 | }
369 | ],
370 | "source": [
371 | "tools = [\n",
372 | " {\n",
373 | " \"name\": \"core_belief_analysis\",\n",
374 | " \"description\": \"Analyzes and records the core beliefs of the user as extracted from conversations.\",\n",
375 | " \"input_schema\": {\n",
376 | " \"type\": \"object\",\n",
377 | " \"properties\": {\n",
378 | " \"entries\": {\n",
379 | " \"type\": \"array\",\n",
380 | " \"items\": {\n",
381 | " \"type\": \"object\",\n",
382 | " \"properties\": {\n",
383 | " \"belief\": {\n",
384 | " \"type\": \"string\",\n",
385 | " \"description\": \"The belief the user has about the world, themselves, or anything else.\"\n",
386 | " },\n",
387 | " \"why\": {\n",
388 | " \"type\": \"string\",\n",
389 | " \"description\": \"Why the user believes this.\"\n",
390 | " },\n",
391 | " \"context\": {\n",
392 | " \"type\": \"string\",\n",
393 | " \"description\": \"The raw context from the conversation that leads you to conclude that the user believes this.\"\n",
394 | " }\n",
395 | " },\n",
396 | " \"required\": [\"belief\", \"why\", \"context\"]\n",
397 | " }\n",
398 | " }\n",
399 | " },\n",
400 | " \"required\": [\"entries\"]\n",
401 | " }\n",
402 | " },\n",
403 | " {\n",
404 | " \"name\": \"formative_event_logging\",\n",
405 | " \"description\": \"Logs a significant, formative event in the user's life and its impact on them. This tool appends the recorded event to the user's state, contributing to a comprehensive understanding of the user's experiences.\",\n",
406 | " \"input_schema\": {\n",
407 | " \"type\": \"object\",\n",
408 | " \"properties\": {\n",
409 | " \"entries\": {\n",
410 | " \"type\": \"array\",\n",
411 | " \"items\": {\n",
412 | " \"type\": \"object\",\n",
413 | " \"properties\": {\n",
414 | " \"event\": {\n",
415 | " \"type\": \"string\",\n",
416 | " \"description\": \"The event that occurred. Must be important enough to be formative for the user.\"\n",
417 | " },\n",
418 | " \"impact\": {\n",
419 | " \"type\": \"string\",\n",
420 | " \"description\": \"How this event influenced the user.\"\n",
421 | " }\n",
422 | " },\n",
423 | " \"required\": [\"event\", \"impact\"]\n",
424 | " }\n",
425 | " }\n",
426 | " },\n",
427 | " \"required\": [\"entries\"]\n",
428 | " }\n",
429 | " }\n",
430 | "]\n",
431 | "\n",
432 | "query = f\"\"\"\n",
433 | "\n",
434 | "{create_pretty_string(messages)}\n",
435 | "\n",
436 | "\n",
437 | "Use the `formative_event_logging` and `core_belief_analysis` tool\n",
438 | "\"\"\"\n",
439 | "\n",
440 | "response = client.beta.tools.messages.create(\n",
441 | " model=MODEL_NAME,\n",
442 | " max_tokens=4096,\n",
443 | " tools=tools,\n",
444 | " messages=[{\"role\": \"user\", \"content\": query}]\n",
445 | ")\n",
446 | "json_summary = None\n",
447 | "for content in response.content:\n",
448 | " if content.type == \"tool_use\" and content.name in (\"formative_event_logging\", \"core_belief_analysis\") :\n",
449 | " json_summary = content.input\n",
450 | " break\n",
451 | "\n",
452 | "if json_summary:\n",
453 | " print(\"JSON Summary:\")\n",
454 | " print(json.dumps(json_summary, indent=2))\n",
455 | "else:\n",
456 | " print(\"No JSON summary found in the response.\")\n"
457 | ]
458 | },
459 | {
460 | "cell_type": "markdown",
461 | "metadata": {},
462 | "source": [
463 | "# User Semantic Memory"
464 | ]
465 | },
466 | {
467 | "cell_type": "code",
468 | "execution_count": 18,
469 | "metadata": {},
470 | "outputs": [
471 | {
472 | "name": "stdout",
473 | "output_type": "stream",
474 | "text": [
475 | "JSON Summary:\n",
476 | "{\n",
477 | " \"entries\": [\n",
478 | " {\n",
479 | " \"content\": {\n",
480 | " \"subject\": \"stickball\",\n",
481 | " \"predicate\": \"used to play before school started\",\n",
482 | " \"object\": \"impacted my life, it's how I first met Jeanne\"\n",
483 | " },\n",
484 | " \"scores\": {\n",
485 | " \"recency\": 4,\n",
486 | " \"importance\": 5,\n",
487 | " \"relevance\": 5\n",
488 | " }\n",
489 | " }\n",
490 | " ]\n",
491 | "}\n"
492 | ]
493 | }
494 | ],
495 | "source": [
496 | "tools = [{\n",
497 | " \"name\": \"user_reflection_analysis\",\n",
498 | " \"description\": \"Analyzes reflections on specific activities or experiences (like stickball) and their significance to the user's life, along with scoring based on recency, importance, and relevance.\",\n",
499 | " \"input_schema\": {\n",
500 | " \"type\": \"object\",\n",
501 | " \"properties\": {\n",
502 | " \"entries\": {\n",
503 | " \"type\": \"array\",\n",
504 | " \"items\": {\n",
505 | " \"type\": \"object\",\n",
506 | " \"properties\": {\n",
507 | " \"content\": {\n",
508 | " \"type\": \"object\",\n",
509 | " \"properties\": {\n",
510 | " \"subject\": {\n",
511 | " \"type\": \"string\",\n",
512 | " \"description\": \"The subject of the reflection.\"\n",
513 | " },\n",
514 | " \"predicate\": {\n",
515 | " \"type\": \"string\",\n",
516 | " \"description\": \"The user's reflection on the activity or experience.\"\n",
517 | " },\n",
518 | " \"object\": {\n",
519 | " \"type\": \"string\",\n",
520 | " \"description\": \"The impact of the activity or experience on the user's life.\"\n",
521 | " }\n",
522 | " },\n",
523 | " \"required\": [\"subject\", \"predicate\", \"object\"]\n",
524 | " },\n",
525 | " \"scores\": {\n",
526 | " \"type\": \"object\",\n",
527 | " \"properties\": {\n",
528 | " \"recency\": {\n",
529 | " \"type\": \"number\",\n",
530 | " \"description\": \"The recency score of the reflection.\"\n",
531 | " },\n",
532 | " \"importance\": {\n",
533 | " \"type\": \"number\",\n",
534 | " \"description\": \"The importance score of the reflection.\"\n",
535 | " },\n",
536 | " \"relevance\": {\n",
537 | " \"type\": \"number\",\n",
538 | " \"description\": \"The relevance score of the reflection.\"\n",
539 | " }\n",
540 | " },\n",
541 | " \"required\": [\"recency\", \"importance\", \"relevance\"]\n",
542 | " }\n",
543 | " },\n",
544 | " \"required\": [\"content\", \"scores\"]\n",
545 | " }\n",
546 | " }\n",
547 | " },\n",
548 | " \"required\": [\"entries\"]\n",
549 | " }\n",
550 | "}]\n",
551 | "\n",
552 | "\n",
553 | "query = f\"\"\"\n",
554 | "\n",
555 | "{create_pretty_string(messages)}\n",
556 | "\n",
557 | "\n",
558 | "Use the `user_reflection_analysis` tool\n",
559 | "\"\"\"\n",
560 | "\n",
561 | "response = client.beta.tools.messages.create(\n",
562 | " model=MODEL_NAME,\n",
563 | " max_tokens=4096,\n",
564 | " tools=tools,\n",
565 | " messages=[{\"role\": \"user\", \"content\": query}]\n",
566 | ")\n",
567 | "json_summary = None\n",
568 | "for content in response.content:\n",
569 | " if content.type == \"tool_use\" and content.name == \"user_reflection_analysis\" :\n",
570 | " json_summary = content.input\n",
571 | " break\n",
572 | "\n",
573 | "if json_summary:\n",
574 | " print(\"JSON Summary:\")\n",
575 | " print(json.dumps(json_summary, indent=2))\n",
576 | "else:\n",
577 | " print(\"No JSON summary found in the response.\")\n"
578 | ]
579 | },
580 | {
581 | "cell_type": "markdown",
582 | "metadata": {},
583 | "source": [
584 | "# MongoDB Atlas Vector Search\n"
585 | ]
586 | },
587 | {
588 | "cell_type": "code",
589 | "execution_count": 40,
590 | "metadata": {},
591 | "outputs": [],
592 | "source": [
593 | "# if you need it\n",
594 | "# %pip install anthropic pymongo"
595 | ]
596 | },
597 | {
598 | "cell_type": "code",
599 | "execution_count": 41,
600 | "metadata": {},
601 | "outputs": [],
602 | "source": [
603 | "from pymongo import MongoClient\n",
604 | "# client = MongoClient(\"localhost\", 55397) \n",
605 | "client = MongoClient(\"mongodb://localhost:55397/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.2.2&uuidRepresentation=standard\")\n",
606 | "db = client[\"open_lang_mem_test\"]\n",
607 | "memories = db.test_collection\n",
608 | "import datetime\n",
609 | "import pprint\n",
610 | "\n",
611 | "# # https://pymongo.readthedocs.io/en/stable/tutorial.html\n",
612 | "# post = {\n",
613 | "# \"author\": \"Mike\",\n",
614 | "# \"text\": \"My first blog post!\",\n",
615 | "# \"tags\": [\"mongodb\", \"python\", \"pymongo\"],\n",
616 | "# \"date\": datetime.datetime.now(tz=datetime.timezone.utc),\n",
617 | "# }\n",
618 | "# posts = db.posts\n",
619 | "# post_id = posts.insert_one(post).inserted_id\n",
620 | "# posts.find_one({\"author\": \"Mike\"})\n",
621 | "# posts.find_one({\"_id\": post_id})\n",
622 | "# posts.insert_many(new_posts)\n",
623 | "# for post in posts.find({\"author\": \"Mike\"}):\n",
624 | "# pprint.pprint(post)\n",
625 | "# posts.count_documents({\"author\": \"Mike\"})\n",
626 | "# d = datetime.datetime(2009, 11, 12, 12)\n",
627 | "# for post in posts.find({\"date\": {\"$lt\": d}}).sort(\"author\"):\n",
628 | "# pprint.pprint(post)\n",
629 | "# result = db.profiles.create_index([(\"user_id\", pymongo.ASCENDING)], unique=True)\n",
630 | "# sorted(list(db.profiles.index_information()))"
631 | ]
632 | },
633 | {
634 | "cell_type": "code",
635 | "execution_count": 44,
636 | "metadata": {},
637 | "outputs": [
638 | {
639 | "name": "stdout",
640 | "output_type": "stream",
641 | "text": [
642 | "--------LIST_MESSAGES--------\n",
643 | "{'_id': ObjectId('6611cbead515d6a6d3fae5da'),\n",
644 | " 'content': 'You are a helpful AI assistant',\n",
645 | " 'metadata': {'thread_id': UUID('e8f605c3-f6b7-4ab0-af3f-33194f817292')},\n",
646 | " 'role': 'system'}\n",
647 | "{'_id': ObjectId('6611cbead515d6a6d3fae5db'),\n",
648 | " 'content': 'Hey johnny have i ever told you about my older bro steve?',\n",
649 | " 'metadata': {'thread_id': UUID('e8f605c3-f6b7-4ab0-af3f-33194f817292'),\n",
650 | " 'user_id': '6d51ac48-9e58-4666-a5b4-05f89eb9d098'},\n",
651 | " 'name': 'jimmy-2aec',\n",
652 | " 'role': 'user'}\n",
653 | "{'_id': ObjectId('6611cbead515d6a6d3fae5dc'),\n",
654 | " 'content': \"no, you didn't, but I think he was friends with my younger sister \"\n",
655 | " 'sueann',\n",
656 | " 'metadata': {'thread_id': UUID('e8f605c3-f6b7-4ab0-af3f-33194f817292'),\n",
657 | " 'user_id': 'c9412e99-0a7e-4a0f-97fc-7f828d94fe12'},\n",
658 | " 'name': 'johnny-0373',\n",
659 | " 'role': 'user'}\n",
660 | "--------LIST_MESSAGES--------\n",
661 | "--------QUERY__MEMORY--------\n",
662 | "{'_id': ObjectId('6611cbead515d6a6d3fae5e1'),\n",
663 | " 'content': \"Wow ya i forgot that. Stickleball really impacted my life. It's \"\n",
664 | " 'how i first met Jeanne! wonder how my life would have turned out '\n",
665 | " \"if it hadn't happened that way.\",\n",
666 | " 'metadata': {'thread_id': UUID('e8f605c3-f6b7-4ab0-af3f-33194f817292'),\n",
667 | " 'user_id': '6d51ac48-9e58-4666-a5b4-05f89eb9d098'},\n",
668 | " 'name': 'jimmy-2aec',\n",
669 | " 'role': 'user'}\n",
670 | "--------QUERY__MEMORY--------\n"
671 | ]
672 | },
673 | {
674 | "data": {
675 | "text/plain": [
676 | "DeleteResult({'n': 9, 'electionId': ObjectId('7fffffff0000000000000001'), 'opTime': {'ts': Timestamp(1712444806, 10), 't': 1}, 'ok': 1.0, '$clusterTime': {'clusterTime': Timestamp(1712444806, 18), 'signature': {'hash': b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', 'keyId': 0}}, 'operationTime': Timestamp(1712444806, 10)}, acknowledged=True)"
677 | ]
678 | },
679 | "execution_count": 44,
680 | "metadata": {},
681 | "output_type": "execute_result"
682 | }
683 | ],
684 | "source": [
685 | "# definition\n",
686 | "def add_messages(_thread_id, _messages):\n",
687 | " memories.create_index([(\"content\", \"text\")])\n",
688 | " for msg in _messages: # Update each message to include _thread_id in metadata\n",
689 | " if 'metadata' not in msg: # ensure 'metadata' exists and is a dictionary\n",
690 | " msg['metadata'] = {}\n",
691 | " msg['metadata']['thread_id'] = _thread_id\n",
692 | " memories.insert_many(_messages)\n",
693 | " \n",
694 | "def list_messages(thread_id):\n",
695 | " # Query to find messages with the given thread_id in their metadata\n",
696 | " query = {\"metadata.thread_id\": thread_id}\n",
697 | " # Retrieve and return the list of messages\n",
698 | " messages = list(memories.find(query))\n",
699 | " return messages\n",
700 | "def query_user_memory(**kwargs):\n",
701 | " user_id = kwargs.get('user_id')\n",
702 | " text = kwargs.get('text')\n",
703 | " k = kwargs.get('k', 3) # Default to 3 if not specified\n",
704 | " # Construct the query to match the user_id and text search\n",
705 | " query = {\n",
706 | " # \"metadata.user_id\": str(user_id), # not needed - swyx\n",
707 | " # \"$text\": {\"$search\": text} # cant use because its not substring\n",
708 | " \"content\": {\"$regex\": text, \"$options\": \"i\"} # 'i' option for case-insensitive search\n",
709 | " }\n",
710 | "\n",
711 | " results = memories.find(query).limit(k)\n",
712 | " # # Project the score to sort by relevance\n",
713 | " # projection = {\n",
714 | " # \"score\": {\"$meta\": \"textScore\"}\n",
715 | " # }\n",
716 | " # Execute the query, sort by relevance (textScore), and limit to top k results\n",
717 | " # results = memories.find(query, projection).sort([(\"score\", {\"$meta\": \"textScore\"})]).limit(k)\n",
718 | " return list(results)\n",
719 | "\n",
720 | "# usage & print\n",
721 | "add_messages(thread_id, messages)\n",
722 | "print('--------LIST_MESSAGES--------')\n",
723 | "for mem in list_messages(thread_id)[:3]:\n",
724 | " pprint.pprint(mem)\n",
725 | " \n",
726 | "print('--------LIST_MESSAGES--------')\n",
727 | "print('--------QUERY__MEMORY--------')\n",
728 | "for mem in query_user_memory(\n",
729 | " user_id=jimmy_user_id, text=\"stickleball\", k=1\n",
730 | "):\n",
731 | " pprint.pprint(mem)\n",
732 | "print('--------QUERY__MEMORY--------')\n",
733 | "\n",
734 | "# just to reset the collection\n",
735 | "memories.delete_many({}) \n"
736 | ]
737 | },
738 | {
739 | "cell_type": "code",
740 | "execution_count": null,
741 | "metadata": {},
742 | "outputs": [],
743 | "source": []
744 | },
745 | {
746 | "cell_type": "code",
747 | "execution_count": null,
748 | "metadata": {},
749 | "outputs": [],
750 | "source": []
751 | }
752 | ],
753 | "metadata": {
754 | "kernelspec": {
755 | "display_name": "Python 3 (ipykernel)",
756 | "language": "python",
757 | "name": "python3"
758 | },
759 | "language_info": {
760 | "codemirror_mode": {
761 | "name": "ipython",
762 | "version": 3
763 | },
764 | "file_extension": ".py",
765 | "mimetype": "text/x-python",
766 | "name": "python",
767 | "nbconvert_exporter": "python",
768 | "pygments_lexer": "ipython3",
769 | "version": "3.11.8"
770 | }
771 | },
772 | "nbformat": 4,
773 | "nbformat_minor": 4
774 | }
775 |
--------------------------------------------------------------------------------