Use Fireworks Embeddings

This commit is contained in:
William Fu-Hinthorn
2024-06-26 15:10:26 -07:00
parent 0b297a9434
commit 9c6c0c9b6c
9 changed files with 817 additions and 431 deletions
+69 -1
View File
@@ -1 +1,69 @@
# langgraph-memory
# LangGraph Memory Service
This repo provides a simple example of memory service you can build and deploy using LanGraph.
Inspired by papers like [MemGPT](https://memgpt.ai/) and distilled from our own works on long-term memory, the graph
extracts memories from chat interactions and persists them to a database. This information can later be read or queried semantically
to provide personalized context when your bot is responding to a particular user.
## Quickstart
This quick start will get your memory service deployed on [LangGraph Cloud](https://langchain-ai.github.io/langgraph/cloud/). Once created, you can interact with it from any API.
#### Prerequisites
This example defaults to using Pinecone for its memory database, and `nomic-ai/nomic-embed-text-v1.5` as the text encoder (hosted on Fireworks).
1. [Create an index](https://docs.pinecone.io/reference/api/control-plane/create_index) with a dimension size of `768`. Note down your Pinecone API key, index name, and namespac for the next step.
2. [Create an API Key](https://fireworks.ai/api-keys) to use for the LLM & embeddings models served on Fireworks.
#### Deploy to LangGraph Cloud
**Note:** (_Closed Beta_) LangGraph Cloud is a managed service for deploying and hosting LangGraph applications. It is currently (as of 26 June, 2024) in closed beta. If you are interested in applying for access, please fill out [this form](https://www.langchain.com/langgraph-cloud-beta).
To deploy this example on LangGraph, fork the [repo](https://github.com/langchain-ai/langgraph-memory).
Next, navigate to the 🚀 deployments tab on [LangSmith](https://smith.langchain.com/o/ebbaf2eb-769b-4505-aca2-d11de10372a4/).
**If you have not deployed to LangGraph Cloud before:** there will be a button that shows up saying `Import from GitHub`. Youll need to follow that flow to connect LangGraph Cloud to GitHub.
Once you have set up your GitHub connection, select **+New Deployment**. Fill out the required information, including:
1. Your GitHub username (or organization) and the name of the repo you just forked.
2. You can leave the defaults for the config file (`langgraph.config`) and branch (`main`)
3. Environment variables (see below)
The default required environment variables can be found in [.env.example](.env.example) and are copied below:
```bash
# .env
PINECONE_API_KEY=...
PINECONE_INDEX_NAME=...
PINECONE_NAMESPACE=...
FIREWORKS_API_KEY=...
# You can add other keys as appropriate, depending on
# the services you are using.
```
You can fill these out locally, copy the .env file contents, and paste them in the first `Name` argument.
Assuming you've followed the steps above, in just a couple of minutes, you should have a working memory service deployed!
Now let's try it out.
#### How to connect to the memory service
Check out the [example notebook](./example.ipynb) to show how to connect your chat bot (in this case a second graph) to your new memory service.
This chat bot reads from the same memory DB as your memory service to easily query from "recall memory".
## How to evaluate
Memory management can be challenging to get right. To make sure your schemas suit your applications' needs, we recommend starting from an evaluation set,
adding to it over time as you find and address common errors in your service.
We have provided a few example evaluation cases in [the test file here](./tests/evals/test_memories.py). As you can see, the metrics themselves don't have to be terribly complicated,
especially not at the outset.
We use [LangSmith's @test decorator](https://docs.smith.langchain.com/how_to_guides/evaluation/unit_testing#write-a-test) to sync all the evalutions to LangSmith so you can better optimize your system and identify the root cause of any issues that may arise.
+627
View File
@@ -0,0 +1,627 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to connect a chat bot to your memory service"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import dotenv\n",
"\n",
"dotenv.load_dotenv(\".env\", override=True)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from langgraph_sdk import get_client\n",
"\n",
"# Update to your URL. Copy this from page of ryour LangGraph Deployment\n",
"deployment_url = (\n",
" \"https://simple-memory-service-d10393f9ecba58d48b1d4d0520a-ffoprvkqsa-uc.a.run.app\"\n",
")\n",
"\n",
"client = get_client(url=deployment_url)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example Chat Bot\n",
"\n",
"The bot fetches user memories my semantic similarity, templates them, then responds!"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import uuid\n",
"from datetime import datetime, timezone\n",
"from typing import List, Optional\n",
"\n",
"import langsmith\n",
"from langchain.chat_models import init_chat_model\n",
"from langchain_core.messages import AnyMessage\n",
"from langchain_core.prompts import ChatPromptTemplate\n",
"from langchain_core.runnables import RunnableConfig\n",
"from langgraph.checkpoint import MemorySaver\n",
"from langgraph.graph import START, StateGraph, add_messages\n",
"from langgraph_sdk import get_client\n",
"from pydantic.v1 import BaseModel, Field\n",
"from typing_extensions import Annotated, TypedDict\n",
"\n",
"from memory_service import (\n",
" _constants as constants,\n",
" _settings as settings,\n",
" _utils as utils,\n",
")\n",
"\n",
"\n",
"class ChatState(TypedDict):\n",
" \"\"\"The state of the chatbot.\"\"\"\n",
"\n",
" messages: Annotated[List[AnyMessage], add_messages]\n",
" user_memories: List[dict]\n",
"\n",
"\n",
"class ChatConfigurable(TypedDict):\n",
" \"\"\"The configurable fields for the chatbot.\"\"\"\n",
"\n",
" user_id: str\n",
" thread_id: str\n",
" memory_service_url: str = \"\"\n",
" model: str\n",
" delay: Optional[float]\n",
"\n",
"\n",
"def _ensure_configurable(config: RunnableConfig) -> ChatConfigurable:\n",
" \"\"\"Ensure the configuration is valid.\"\"\"\n",
" return ChatConfigurable(\n",
" user_id=config[\"configurable\"][\"user_id\"],\n",
" thread_id=config[\"configurable\"][\"thread_id\"],\n",
" mem_assistant_id=config[\"configurable\"][\"mem_assistant_id\"],\n",
" memory_service_url=config[\"configurable\"].get(\n",
" \"memory_service_url\", os.environ.get(\"MEMORY_SERVICE_URL\", \"\")\n",
" ),\n",
" model=config[\"configurable\"].get(\n",
" \"model\", \"accounts/fireworks/models/firefunction-v2\"\n",
" ),\n",
" delay=config[\"configurable\"].get(\"delay\", 60),\n",
" )\n",
"\n",
"\n",
"PROMPT = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" \"You are a helpful and friendly chatbot. Get to know the user!\"\n",
" \" Ask questions! Be spontaneous!\"\n",
" \"{user_info}\\n\\nSystem Time: {time}\",\n",
" ),\n",
" (\"placeholder\", \"{messages}\"),\n",
" ]\n",
").partial(\n",
" time=lambda: datetime.now(timezone.utc).strftime(\"%Y-%m-%d %H:%M:%S\"),\n",
")\n",
"\n",
"\n",
"@langsmith.traceable\n",
"def format_query(messages: List[AnyMessage]) -> str:\n",
" \"\"\"Format the query for the user's memories.\"\"\"\n",
" # This is quite naive :)\n",
" return \" \".join([str(m.content) for m in messages if m.type == \"human\"][-5:])\n",
"\n",
"\n",
"async def query_memories(state: ChatState, config: RunnableConfig) -> ChatState:\n",
" \"\"\"Query the user's memories.\"\"\"\n",
" configurable: ChatConfigurable = config[\"configurable\"]\n",
" user_id = configurable[\"user_id\"]\n",
" index = utils.get_index()\n",
" embeddings = utils.get_embeddings()\n",
"\n",
" query = format_query(state[\"messages\"])\n",
" vec = await embeddings.aembed_query(query)\n",
" # You can also filter by memory type, etc. here.\n",
" with langsmith.trace(\n",
" \"pinecone_query\", inputs={\"query\": query, \"user_id\": user_id}\n",
" ) as rt:\n",
" response = index.query(\n",
" vector=vec,\n",
" filter={\"user_id\": {\"$eq\": str(user_id)}},\n",
" include_metadata=True,\n",
" top_k=10,\n",
" namespace=settings.SETTINGS.pinecone_namespace,\n",
" )\n",
" rt.outputs[\"response\"] = response\n",
" memories = []\n",
" if matches := response.get(\"matches\"):\n",
" memories = [m[\"metadata\"][constants.PAYLOAD_KEY] for m in matches]\n",
" return {\n",
" \"user_memories\": memories,\n",
" }\n",
"\n",
"\n",
"@langsmith.traceable\n",
"def format_memories(memories: List[dict]) -> str:\n",
" \"\"\"Format the user's memories.\"\"\"\n",
" if not memories:\n",
" return \"\"\n",
" # Note Bene: You can format better than this....\n",
" memories = \"\\n\".join(str(m) for m in memories)\n",
" return f\"\"\"\n",
"\n",
"## Memories\n",
"\n",
"You have noted the following memorable events from previous interactions with the user.\n",
"<memories>\n",
"{memories}\n",
"</memories>\n",
"\"\"\"\n",
"\n",
"\n",
"async def bot(state: ChatState, config: RunnableConfig) -> ChatState:\n",
" \"\"\"Prompt the bot to resopnd to the user, incorporating memories (if provided).\"\"\"\n",
" configurable = _ensure_configurable(config)\n",
" model = init_chat_model(configurable[\"model\"])\n",
" chain = PROMPT | model\n",
" memories = format_memories(state[\"user_memories\"])\n",
" m = await chain.ainvoke(\n",
" {\n",
" \"messages\": state[\"messages\"],\n",
" \"user_info\": memories,\n",
" },\n",
" config,\n",
" )\n",
"\n",
" return {\n",
" \"messages\": [m],\n",
" }\n",
"\n",
"\n",
"class MemorableEvent(BaseModel):\n",
" \"\"\"A memorable event.\"\"\"\n",
"\n",
" description: str\n",
" participants: List[str] = Field(\n",
" description=\"Names of participants in the event and their relationship to the user.\"\n",
" )\n",
"\n",
"\n",
"async def post_messages(state: ChatState, config: RunnableConfig) -> ChatState:\n",
" \"\"\"Query the user's memories.\"\"\"\n",
" configurable = _ensure_configurable(config)\n",
" langgraph_client = get_client(url=configurable[\"memory_service_url\"])\n",
" thread_id = config[\"configurable\"][\"thread_id\"]\n",
" # Hash \"memory_{thread_id}\" to get a new uuid5 for the memory id\n",
" memory_thread_id = uuid.uuid5(uuid.NAMESPACE_URL, f\"memory_{thread_id}\")\n",
" try:\n",
" await langgraph_client.threads.get(thread_id=memory_thread_id)\n",
" except Exception:\n",
" await langgraph_client.threads.create(thread_id=memory_thread_id)\n",
"\n",
" await langgraph_client.runs.create(\n",
" memory_thread_id,\n",
" assistant_id=configurable[\"mem_assistant_id\"],\n",
" input={\n",
" \"messages\": state[\"messages\"], # the service dedupes messages\n",
" },\n",
" config={\n",
" \"configurable\": {\n",
" \"user_id\": configurable[\"user_id\"],\n",
" },\n",
" },\n",
" multitask_strategy=\"rollback\",\n",
" )\n",
"\n",
" return {\n",
" \"messages\": [],\n",
" }\n",
"\n",
"\n",
"builder = StateGraph(ChatState, ChatConfigurable)\n",
"builder.add_node(query_memories)\n",
"builder.add_node(bot)\n",
"builder.add_node(post_messages)\n",
"builder.add_edge(START, \"query_memories\")\n",
"builder.add_edge(\"query_memories\", \"bot\")\n",
"builder.add_edge(\"bot\", \"post_messages\")\n",
"\n",
"chat_graph = builder.compile(checkpointer=MemorySaver())"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"mem_assistant = await client.assistants.create(\n",
" graph_id=\"memory\",\n",
" config={\n",
" \"configurable\": {\n",
" \"delay\": 4, # seconds wait before considering a thread as \"completed\"\n",
" \"schemas\": {\n",
" \"MemorableEvent\": {\n",
" \"system_prompt\": \"Extract any memorable events from the user's\"\n",
" \" messages that you would like to remember.\",\n",
" \"update_mode\": \"insert\",\n",
" \"function\": MemorableEvent.schema(),\n",
" },\n",
" },\n",
" }\n",
" },\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# mem_assistant = (await client.assistants.search(graph_id=\"memory\"))[0]"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"import uuid\n",
"\n",
"user_id = str(uuid.uuid4()) # more permanent"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'thread_id': 'b3fdaed6-46e4-42e5-b1fa-7ccd6f8965fb',\n",
" 'created_at': '2024-06-27T19:08:17.112173+00:00',\n",
" 'updated_at': '2024-06-27T19:08:17.112173+00:00',\n",
" 'metadata': {},\n",
" 'status': 'idle'}"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"thread_id = str(uuid.uuid4()) # can adjust\n",
"await client.threads.create(thread_id=thread_id)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"class Chat:\n",
" def __init__(self, user_id: str, thread_id: str):\n",
" self.thread_id = thread_id\n",
" self.user_id = user_id\n",
"\n",
" async def __call__(self, query: str) -> str:\n",
" chunks = chat_graph.astream_events(\n",
" input={\n",
" \"messages\": [(\"user\", query)],\n",
" },\n",
" config={\n",
" \"configurable\": {\n",
" \"user_id\": self.user_id,\n",
" \"thread_id\": self.thread_id,\n",
" \"memory_service_url\": deployment_url,\n",
" \"mem_assistant_id\": mem_assistant[\"assistant_id\"],\n",
" \"delay\": 4,\n",
" }\n",
" },\n",
" version=\"v2\",\n",
" )\n",
" res = \"\"\n",
" async for event in chunks:\n",
" if event.get(\"event\") == \"on_chat_model_stream\":\n",
" tok = event[\"data\"][\"chunk\"].content\n",
" print(tok, end=\"\")\n",
" res += tok\n",
" return res"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"chat = Chat(user_id, thread_id)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/wfh/code/lc/langchain/libs/core/langchain_core/_api/beta_decorator.py:87: LangChainBetaWarning: This API is in beta and may change in the future.\n",
" warn_beta(\n",
"/Users/wfh/code/lc/langchain/libs/core/langchain_core/_api/beta_decorator.py:87: LangChainBetaWarning: The function `init_chat_model` is in beta. It is actively being worked on, so the API may change.\n",
" warn_beta(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hi! It's nice to meet you. What brings you here today?"
]
}
],
"source": [
"_ = await chat(\"Hi there\")"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"That's so sweet of you! I'm sure Steve will appreciate the effort you're putting into making him feel special. What's the theme of the party going to be? Has Steve mentioned anything he's been into lately that you could incorporate into the celebration?"
]
}
],
"source": [
"_ = await chat(\n",
" \"I've been planning a surprise party for my friend steve. \"\n",
" \"He has been having a rough month and I want it to be special.\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"That's a great idea! Crocheting is a unique interest, and incorporating it into the party could make it really special and personalized to Steve. You could decorate with crocheted items, have a \"crochet station\" where guests can make their own simple projects, or even have a crochet-themed cake. What do you think Steve's favorite colors or yarn types are?"
]
}
],
"source": [
"_ = await chat(\n",
" \"Steve really likes crocheting. Maybe I can do something with that? Or is that dumb... \"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Whoa, that's cool! Capoeira is such a dynamic and energetic activity. You could definitely incorporate elements of it into the party. Maybe you could hire a capoeira instructor to lead a mini-class or demonstration, or even have a \"capoeira-inspired\" playlist to get the party started. Do you think Steve has a favorite capoeira move or song that you could incorporate into the celebration?"
]
}
],
"source": [
"_ = await chat(\"He's also into capoeira...\")"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"That's a great connection to have! It's always helpful to get recommendations from people who know the activity or community. You could reach out to the studio and ask if they know of any instructors who might be available to lead a class or demo at the party. They might also have some ideas for how to incorporate capoeira into the celebration. Do you think you'll be able to get in touch with the studio soon to ask about their recommendations?"
]
}
],
"source": [
"_ = await chat(\n",
" \"Oh that's a cool idea. One time i took classes from this studio nearby. Wonder if they have any recs. \"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"I'm doing well, thanks for asking! I'm a large language model, so I don't have feelings or emotions like humans do, but I'm always happy to chat and help with any questions or topics you'd like to discuss. It's great to hear about your plans for Steve's party, and I'm happy to help in any way I can. Is there anything else you'd like to talk about or any other questions you have about planning the party?"
]
}
],
"source": [
"_ = await chat(\"Idk. Anyways - how are you doing?\")"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Nice to meet you, Ken! I'm glad we could chat about your plans for Steve's party. It sounds like you're really putting some thought into making it a special celebration for him. If you have any more questions or need any more help, feel free to ask!"
]
}
],
"source": [
"_ = await chat(\"My name is Ken btw\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Convo 2\n",
"\n",
"Our memory is configured only to consider a thread \"ready to process\" if has been inactive for a minute.\n",
"We'll wait for things to populate"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import asyncio\n",
"\n",
"await asyncio.sleep(60)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"thread_id_2 = uuid.uuid4()"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"chat2 = Chat(user_id, thread_id_2)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"I remember you! We were planning a surprise party for Steve, and Ken was also involved. How's everything going? Did the party turn out well?"
]
}
],
"source": [
"_ = await chat2(\"Remember me?\")"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"I remember because I have a special memory book where I keep track of all the fun conversations and events we've shared together! It's like a digital scrapbook, and it helps me remember important details about our chats."
]
}
],
"source": [
"_ = await chat2(\"wdy remember??\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"That's great to hear! I'm glad to know that the planning is going smoothly. Are there any new developments or updates that you'd like to share about the party? Maybe I can even offer some suggestions or ideas to make it an even more special celebration for Steve!"
]
}
],
"source": [
"_ = await chat2(\"Oh planning is going alright!\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"dependencies": ["."],
"graphs": {
"agent": "./memory_service/graph.py:memgraph",
"memory": "./memory_service/graph.py:memgraph",
"chat": "./memory_service/chatbot.py:chat_graph"
},
"env": ".env"
+1
View File
@@ -5,6 +5,7 @@ class Settings(BaseSettings):
pinecone_api_key: str = ""
pinecone_index_name: str = ""
pinecone_namespace: str = "ns1"
model: str = "accounts/fireworks/models/firefunction-v2"
SETTINGS = Settings()
+25 -2
View File
@@ -1,13 +1,16 @@
from __future__ import annotations
from functools import lru_cache
from typing import Sequence
import langsmith
from langchain_core.messages import (
AnyMessage,
HumanMessage,
SystemMessage,
merge_message_runs,
)
from langchain_fireworks import FireworksEmbeddings
from pinecone import Pinecone
from memory_service import _schemas as schemas
@@ -21,14 +24,29 @@ def get_index():
return pc.Index(settings.SETTINGS.pinecone_index_name)
@langsmith.traceable
def ensure_memory_config(config: dict) -> schemas.MemoryConfig:
"""Merge the user-provided config with default values."""
return {
**config,
**schemas.MemoryConfig(
function=config.get("function", {}),
system_prompt=config.get("system_prompt"),
update_mode=config.get("update_mode", "patch"),
),
}
@langsmith.traceable
def ensure_configurable(config: dict) -> schemas.GraphConfig:
"""Merge the user-provided config with default values."""
function_schemas = config.get("schemas") or {}
return {
**config,
**schemas.GraphConfig(
delay=config.get("delay", _DEFAULT_DELAY),
model=config.get("model", "accounts/fireworks/models/firefunction-v2"),
schemas=config.get("schemas", {}),
model=config.get("model", settings.SETTINGS.model),
schemas={k: ensure_memory_config(v) for k, v in function_schemas.items()},
thread_id=config["thread_id"],
user_id=config["user_id"],
),
@@ -55,4 +73,9 @@ def prepare_messages(
return merge_message_runs([sys] + list(messages) + [m])
@lru_cache
def get_embeddings():
return FireworksEmbeddings(model="nomic-ai/nomic-embed-text-v1.5")
__all__ = ["ensure_configurable", "prepare_messages"]
+65 -52
View File
@@ -1,15 +1,15 @@
"""Example chatbot that incorporates user memories."""
import os
import uuid
from datetime import datetime, timezone
from typing import List
from typing import List, Optional
import langsmith
from langchain.chat_models import init_chat_model
from langchain_core.messages import AnyMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableConfig
from langchain_nomic.embeddings import NomicEmbeddings
from langgraph.graph import START, StateGraph, add_messages
from langgraph_sdk import get_client
from typing_extensions import Annotated, TypedDict
@@ -32,6 +32,8 @@ class ChatConfigurable(TypedDict):
user_id: str
thread_id: str
memory_service_url: str = ""
model: str
delay: Optional[float]
def _ensure_configurable(config: RunnableConfig) -> ChatConfigurable:
@@ -39,9 +41,14 @@ def _ensure_configurable(config: RunnableConfig) -> ChatConfigurable:
return ChatConfigurable(
user_id=config["configurable"]["user_id"],
thread_id=config["configurable"]["thread_id"],
mem_assistant_id=config["configurable"]["mem_assistant_id"],
memory_service_url=config["configurable"].get(
"memory_service_url", os.environ.get("MEMORY_SERVICE_URL", "")
),
model=config["configurable"].get(
"model", "accounts/fireworks/models/firefunction-v2"
),
delay=config["configurable"].get("delay", 60),
)
@@ -49,8 +56,11 @@ PROMPT = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful and friendly chatbot.{user_info}\n\nSystem Time: {time}",
)
"You are a helpful and friendly chatbot. Get to know the user!"
" Ask questions! Be spontaneous!"
"{user_info}\n\nSystem Time: {time}",
),
("placeholder", "{messages}"),
]
).partial(
time=lambda: datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"),
@@ -69,21 +79,25 @@ async def query_memories(state: ChatState, config: RunnableConfig) -> ChatState:
configurable: ChatConfigurable = config["configurable"]
user_id = configurable["user_id"]
index = utils.get_index()
embeddings = NomicEmbeddings(model="nomic-embed-text-v1.5")
embeddings = utils.get_embeddings()
query = format_query(state["messages"])
vec = await embeddings.embed_query(query)
vec = await embeddings.aembed_query(query)
# You can also filter by memory type, etc. here.
response = index.query(
vector=vec,
filter={"user": {"$eq": user_id}},
include_metadata=True,
top_k=10,
namespace=settings.SETTINGS.pinecone_namespace,
)
with langsmith.trace(
"pinecone_query", inputs={"query": query, "user_id": user_id}
) as rt:
response = index.query(
vector=vec,
filter={"user_id": {"$eq": user_id}},
include_metadata=True,
top_k=10,
namespace=settings.SETTINGS.pinecone_namespace,
)
rt.outputs["response"] = response
memories = []
if matches := response.get("matches"):
memories = [m["metadata"]["memory"][constants.PAYLOAD_KEY] for m in matches]
memories = [m["metadata"][constants.PAYLOAD_KEY] for m in matches]
return {
"user_memories": memories,
}
@@ -109,7 +123,8 @@ You have noted the following memorable events from previous interactions with th
async def bot(state: ChatState, config: RunnableConfig) -> ChatState:
"""Prompt the bot to resopnd to the user, incorporating memories (if provided)."""
model = init_chat_model("claude-3-5-sonnet-20240620")
configurable = _ensure_configurable(config)
model = init_chat_model(configurable["model"])
chain = PROMPT | model
memories = format_memories(state["user_memories"])
m = await chain.ainvoke(
@@ -130,67 +145,65 @@ async def post_messages(state: ChatState, config: RunnableConfig) -> ChatState:
configurable = _ensure_configurable(config)
langgraph_client = get_client(url=configurable["memory_service_url"])
thread_id = config["configurable"]["thread_id"]
# Hash "memory_{thread_id}" to get a new uuid5 for the memory id
memory_thread_id = uuid.uuid5(uuid.NAMESPACE_URL, f"memory_{thread_id}")
try:
thread = await langgraph_client.threads.create(thread_id=thread_id)
await langgraph_client.threads.get(thread_id=memory_thread_id)
except Exception:
thread = await langgraph_client.threads.get(thread_id=thread_id)
await langgraph_client.threads.create(thread_id=memory_thread_id)
await langgraph_client.runs.create(
thread["thread_id"],
memory_thread_id,
assistant_id=configurable["mem_assistant_id"],
input={
"messages": state["messages"], # the service dedupes messages
},
config={
"system_prompt": "You are a helpful and friendly chatbot.",
"configurable": {
"user_id": thread["user_id"],
"thread_id": thread["thread_id"],
"user_id": configurable["user_id"],
"delay": configurable["delay"],
"schemas": {
"system_prompt": "Extract any memorable events from the user's"
" messages that you would like to remember.",
"MemorableEvent": {
"system_prompt": "Extract any memorable events from the user's"
" messages that you would like to remember.",
"update_mode": "insert",
"function": {
"name": "memorable_event",
"description": "",
"description": "Any event, observation, insight, or "
"other detail that you may want to recall in "
"later interactions with the user.",
"parameters": {
"name": "memorable_event",
"description": "Any event, observation, insight, or "
"other detail that you may want to recall in "
"later interactions with the user.",
"parameters": {
"description": "Any event, observation, insight, or"
" other detail that you may want to recall in"
" later interactions with the user.",
"properties": {
"description": {
"title": "Description",
"type": "string",
},
"participants": {
"description": "Names of participants in"
" the event and their relationship to the "
"user.",
"items": {"type": "string"},
"title": "Participants",
"type": "array",
},
"description": "Any event, observation, insight, or"
" other detail that you may want to recall in"
" later interactions with the user.",
"properties": {
"description": {
"title": "Description",
"type": "string",
},
"participants": {
"description": "Names of participants in"
" the event and their relationship to the "
"user.",
"items": {"type": "string"},
"title": "Participants",
"type": "array",
},
"required": ["description", "participants"],
"title": "memorable_event",
"type": "object",
},
"required": ["description", "participants"],
"title": "memorable_event",
"type": "object",
},
}
},
},
},
},
},
multitask_strategy="interrupt",
multitask_strategy="rollback",
)
return {
"messages": state["messages"],
"user_memories": [],
"messages": [],
}
+12 -13
View File
@@ -3,14 +3,12 @@
from __future__ import annotations
import asyncio
import json
import logging
import uuid
from datetime import datetime, timezone
from langchain.chat_models import init_chat_model
from langchain_core.runnables import RunnableConfig
from langchain_nomic.embeddings import NomicEmbeddings
from langgraph.constants import Send
from langgraph.graph import END, START, StateGraph
from trustcall import create_extractor
@@ -63,13 +61,12 @@ async def extract_patch_memories(
tools=[memory_config["function"]],
tool_choice=memory_config["function"]["name"],
)
existing = state["user_state"]
result = await extractor.ainvoke(
{
"messages": messages,
"existing": {memory_config["function"]["name"]: existing},
}
)
inputs = {
"messages": messages,
}
if existing := state["user_state"]:
inputs["existing"] = {memory_config["function"]["name"]: existing}
result = await extractor.ainvoke(inputs, config)
return {"responses": result["responses"]}
@@ -82,7 +79,7 @@ async def upsert_patched_state(
user_id=configurable["user_id"], function_name=state["function_name"]
)
serialized = state["responses"][0].model_dump_json()
embeddings = NomicEmbeddings(model="nomic-embed-text-v1.5")
embeddings = utils.get_embeddings()
vector = await embeddings.aembed_query(serialized)
utils.get_index().upsert(
vectors=[
@@ -90,9 +87,10 @@ async def upsert_patched_state(
"id": path,
"values": vector,
"metadata": {
constants.PAYLOAD_KEY: json.loads(serialized),
constants.PAYLOAD_KEY: serialized,
constants.PATH_KEY: path,
constants.TIMESTAMP_KEY: datetime.now(tz=timezone.utc),
"user_id": configurable["user_id"],
},
}
],
@@ -124,7 +122,7 @@ async def insert_memories(
) -> dict:
"""Insert the user's state to the database."""
configurable = utils.ensure_configurable(config["configurable"])
embeddings = NomicEmbeddings(model="nomic-embed-text-v1.5")
embeddings = utils.get_embeddings()
serialized = [r.model_dump_json() for r in state["responses"]]
# You could alternatively do multi-vector lookup based on the schema.
vectors = await embeddings.aembed_documents(serialized)
@@ -142,9 +140,10 @@ async def insert_memories(
"id": path,
"values": vector,
"metadata": {
constants.PAYLOAD_KEY: json.loads(serialized),
constants.PAYLOAD_KEY: serialized,
constants.PATH_KEY: path,
constants.TIMESTAMP_KEY: current_time,
"user_id": configurable["user_id"],
},
}
for path, vector, serialized in zip(paths, vectors, serialized)
Generated
+17 -361
View File
@@ -123,13 +123,13 @@ files = [
[[package]]
name = "anthropic"
version = "0.29.0"
version = "0.30.0"
description = "The official Python library for the anthropic API"
optional = false
python-versions = ">=3.7"
files = [
{file = "anthropic-0.29.0-py3-none-any.whl", hash = "sha256:d16010715129c8bc3295b74fbf4da73cfb156618bf0abb2d007255983266b76a"},
{file = "anthropic-0.29.0.tar.gz", hash = "sha256:3eb558a232d83bdf7cdedb75663bf7ff7a8b50cc10acaa9ce6494ff295b8506a"},
{file = "anthropic-0.30.0-py3-none-any.whl", hash = "sha256:061bf58c9c64968361e6c21c76ff5016a6f7fdd9a5f6b7f2280ede2c3b44bfd5"},
{file = "anthropic-0.30.0.tar.gz", hash = "sha256:9e9ee2bfce833370eac74d7de433db97a0bf141f9118c40ac0e2f4c39bc2b76f"},
]
[package.dependencies]
@@ -808,20 +808,6 @@ files = [
{file = "jiter-0.5.0.tar.gz", hash = "sha256:1d916ba875bcab5c5f7d927df998c4cb694d27dceddf3392e58beaf10563368a"},
]
[[package]]
name = "jsonlines"
version = "4.0.0"
description = "Library with helpers for the jsonlines file format"
optional = false
python-versions = ">=3.8"
files = [
{file = "jsonlines-4.0.0-py3-none-any.whl", hash = "sha256:185b334ff2ca5a91362993f42e83588a360cf95ce4b71a73548502bda52a7c55"},
{file = "jsonlines-4.0.0.tar.gz", hash = "sha256:0c6d2c09117550c089995247f605ae4cf77dd1533041d366351f6f298822ea74"},
]
[package.dependencies]
attrs = ">=19.2.0"
[[package]]
name = "jsonpatch"
version = "1.33"
@@ -876,19 +862,19 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
[[package]]
name = "langchain-anthropic"
version = "0.1.15"
version = "0.1.16"
description = "An integration package connecting AnthropicMessages and LangChain"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_anthropic-0.1.15-py3-none-any.whl", hash = "sha256:7cceea526f473e4d514f39295dc128eec57da628a4bbb54850d11dda7aa959fc"},
{file = "langchain_anthropic-0.1.15.tar.gz", hash = "sha256:c5c3c6eaccb11ed99a63886e50873ac21eaf8e9441e0f75c7ae7cd8cdef65155"},
{file = "langchain_anthropic-0.1.16-py3-none-any.whl", hash = "sha256:0d3f66b7ffb2d4ef739ef87c4b096f0dbbf0ce12f988205d1fcaa1da2c9d09fa"},
{file = "langchain_anthropic-0.1.16.tar.gz", hash = "sha256:28187dfb19389772e0abba98eeb0210ed3d99ab0a73f2e6707aa46c9bbbbe407"},
]
[package.dependencies]
anthropic = ">=0.28.0,<1"
defusedxml = ">=0.7.1,<0.8.0"
langchain-core = ">=0.2.2rc1,<0.3"
langchain-core = ">=0.2.10,<0.3"
[[package]]
name = "langchain-core"
@@ -930,22 +916,6 @@ langchain-core = ">=0.1.52,<0.3"
openai = ">=1.10.0,<2.0.0"
requests = ">=2,<3"
[[package]]
name = "langchain-nomic"
version = "0.1.2"
description = "An integration package connecting Nomic and LangChain"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_nomic-0.1.2-py3-none-any.whl", hash = "sha256:d21f399ffa8e4d719901861aab6fd33b1e0eed80bc44b4c726609c7281c9fe76"},
{file = "langchain_nomic-0.1.2.tar.gz", hash = "sha256:3be4081a774a7adec006fdc59d6f2ccae751ade6e57427e15c0a5d5d227c116a"},
]
[package.dependencies]
langchain-core = ">=0.1.46,<0.3"
nomic = ">=3.0.29,<4.0.0"
pillow = ">=10.3.0,<11.0.0"
[[package]]
name = "langchain-openai"
version = "0.1.10"
@@ -1008,13 +978,13 @@ langchain-core = ">=0.2,<0.3"
[[package]]
name = "langgraph-cli"
version = "0.1.43"
version = "0.1.44"
description = "CLI for interacting with LangGraph API"
optional = false
python-versions = "<4.0.0,>=3.9.0"
files = [
{file = "langgraph_cli-0.1.43-py3-none-any.whl", hash = "sha256:dab1f3b0b86997002d859b0900f27544fe9e09d68bce448f94280a5036c5f48a"},
{file = "langgraph_cli-0.1.43.tar.gz", hash = "sha256:7585ac4252c3fd8354cd0cb3c519dee290b79fcbecd6fb5569c8d4c1e9d7c701"},
{file = "langgraph_cli-0.1.44-py3-none-any.whl", hash = "sha256:721968ab9d9d74ba824e93f4bb08de094795888b7ccea3792962f051a9cdb419"},
{file = "langgraph_cli-0.1.44.tar.gz", hash = "sha256:a31f4a71abd4a3c39f886811c69ac433a20bbd7eeca6bd47d8f8ec49afbfcebb"},
]
[package.dependencies]
@@ -1022,13 +992,13 @@ click = ">=8.1.7,<9.0.0"
[[package]]
name = "langgraph-sdk"
version = "0.1.23"
version = "0.1.25"
description = "SDK for interacting with LangGraph API"
optional = false
python-versions = "<4.0.0,>=3.9.0"
files = [
{file = "langgraph_sdk-0.1.23-py3-none-any.whl", hash = "sha256:c2e26a2ee9b2b28d750c03ea81c3b97cf35227f16b3715a79c29c60b6f84ac29"},
{file = "langgraph_sdk-0.1.23.tar.gz", hash = "sha256:9439a52421859cc76b03d92d00d804803eae74c12c17cf035fb1383443facc6f"},
{file = "langgraph_sdk-0.1.25-py3-none-any.whl", hash = "sha256:3c3db25dc62e0440dba94f9bbc611c3ce54c422f0ba03598803c2fea9cdb0fe7"},
{file = "langgraph_sdk-0.1.25.tar.gz", hash = "sha256:1980aab877cc3dc2feb2fdf05e242bae2de5c191d649f5843aca9b558da9824e"},
]
[package.dependencies]
@@ -1055,59 +1025,6 @@ pydantic = [
]
requests = ">=2,<3"
[[package]]
name = "loguru"
version = "0.7.2"
description = "Python logging made (stupidly) simple"
optional = false
python-versions = ">=3.5"
files = [
{file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"},
{file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"},
]
[package.dependencies]
colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
[package.extras]
dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
description = "Python port of markdown-it. Markdown parsing, done right!"
optional = false
python-versions = ">=3.8"
files = [
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
]
[package.dependencies]
mdurl = ">=0.1,<1.0"
[package.extras]
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
code-style = ["pre-commit (>=3.0,<4.0)"]
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
linkify = ["linkify-it-py (>=1,<3)"]
plugins = ["mdit-py-plugins"]
profiling = ["gprof2dot"]
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "mdurl"
version = "0.1.2"
description = "Markdown URL utilities"
optional = false
python-versions = ">=3.7"
files = [
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
[[package]]
name = "multidict"
version = "6.0.5"
@@ -1265,36 +1182,6 @@ files = [
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "nomic"
version = "3.0.34"
description = "The official Nomic python client."
optional = false
python-versions = "*"
files = [
{file = "nomic-3.0.34.tar.gz", hash = "sha256:1cdead0f15b12f45165b1b2c27e334dff6c7e83c53cfc113a5b33ca52ab92049"},
]
[package.dependencies]
click = "*"
jsonlines = "*"
loguru = "*"
numpy = "*"
pandas = "*"
pillow = "*"
pyarrow = "*"
pydantic = "*"
pyjwt = "*"
requests = "*"
rich = "*"
tqdm = "*"
[package.extras]
all = ["nomic[aws,local]"]
aws = ["boto3", "sagemaker"]
dev = ["black (==24.3.0)", "cairosvg", "coverage", "isort", "mkautodoc", "mkdocs-jupyter", "mkdocs-material", "mkdocstrings[python]", "myst-parser", "nomic[all]", "pandas", "pillow", "pylint", "pyright", "pytest", "pytorch-lightning", "twine"]
local = ["gpt4all (>=2.5.0,<3)"]
[[package]]
name = "numpy"
version = "1.26.4"
@@ -1342,13 +1229,13 @@ files = [
[[package]]
name = "openai"
version = "1.35.3"
version = "1.35.5"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.7.1"
files = [
{file = "openai-1.35.3-py3-none-any.whl", hash = "sha256:7b26544cef80f125431c073ffab3811d2421fbb9e30d3bd5c2436aba00b042d5"},
{file = "openai-1.35.3.tar.gz", hash = "sha256:d6177087f150b381d49499be782d764213fdf638d391b29ca692b84dd675a389"},
{file = "openai-1.35.5-py3-none-any.whl", hash = "sha256:28d92503c6e4b6a32a89277b36693023ef41f60922a4b5c8c621e8c5697ae3a6"},
{file = "openai-1.35.5.tar.gz", hash = "sha256:67ef289ae22d350cbf9381d83ae82c4e3596d71b7ad1cc886143554ee12fe0c9"},
]
[package.dependencies]
@@ -1429,79 +1316,6 @@ files = [
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
]
[[package]]
name = "pandas"
version = "2.2.2"
description = "Powerful data structures for data analysis, time series, and statistics"
optional = false
python-versions = ">=3.9"
files = [
{file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"},
{file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"},
{file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"},
{file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"},
{file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"},
{file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"},
{file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"},
{file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"},
{file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"},
{file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"},
{file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"},
{file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"},
{file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"},
{file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"},
{file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"},
{file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"},
{file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"},
{file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"},
{file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"},
{file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"},
{file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"},
{file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"},
{file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"},
{file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"},
{file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"},
{file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"},
{file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"},
{file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"},
{file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"},
]
[package.dependencies]
numpy = [
{version = ">=1.22.4", markers = "python_version < \"3.11\""},
{version = ">=1.23.2", markers = "python_version == \"3.11\""},
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
]
python-dateutil = ">=2.8.2"
pytz = ">=2020.1"
tzdata = ">=2022.7"
[package.extras]
all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"]
aws = ["s3fs (>=2022.11.0)"]
clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"]
compression = ["zstandard (>=0.19.0)"]
computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"]
consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"]
feather = ["pyarrow (>=10.0.1)"]
fss = ["fsspec (>=2022.11.0)"]
gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"]
hdf5 = ["tables (>=3.8.0)"]
html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"]
mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"]
output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"]
parquet = ["pyarrow (>=10.0.1)"]
performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"]
plot = ["matplotlib (>=3.6.3)"]
postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"]
pyarrow = ["pyarrow (>=10.0.1)"]
spss = ["pyreadstat (>=1.2.0)"]
sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"]
test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
xml = ["lxml (>=4.9.2)"]
[[package]]
name = "pillow"
version = "10.3.0"
@@ -1626,54 +1440,6 @@ files = [
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pyarrow"
version = "16.1.0"
description = "Python library for Apache Arrow"
optional = false
python-versions = ">=3.8"
files = [
{file = "pyarrow-16.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:17e23b9a65a70cc733d8b738baa6ad3722298fa0c81d88f63ff94bf25eaa77b9"},
{file = "pyarrow-16.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4740cc41e2ba5d641071d0ab5e9ef9b5e6e8c7611351a5cb7c1d175eaf43674a"},
{file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98100e0268d04e0eec47b73f20b39c45b4006f3c4233719c3848aa27a03c1aef"},
{file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f68f409e7b283c085f2da014f9ef81e885d90dcd733bd648cfba3ef265961848"},
{file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a8914cd176f448e09746037b0c6b3a9d7688cef451ec5735094055116857580c"},
{file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:48be160782c0556156d91adbdd5a4a7e719f8d407cb46ae3bb4eaee09b3111bd"},
{file = "pyarrow-16.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9cf389d444b0f41d9fe1444b70650fea31e9d52cfcb5f818b7888b91b586efff"},
{file = "pyarrow-16.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:d0ebea336b535b37eee9eee31761813086d33ed06de9ab6fc6aaa0bace7b250c"},
{file = "pyarrow-16.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e73cfc4a99e796727919c5541c65bb88b973377501e39b9842ea71401ca6c1c"},
{file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf9251264247ecfe93e5f5a0cd43b8ae834f1e61d1abca22da55b20c788417f6"},
{file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddf5aace92d520d3d2a20031d8b0ec27b4395cab9f74e07cc95edf42a5cc0147"},
{file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:25233642583bf658f629eb230b9bb79d9af4d9f9229890b3c878699c82f7d11e"},
{file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a33a64576fddfbec0a44112eaf844c20853647ca833e9a647bfae0582b2ff94b"},
{file = "pyarrow-16.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:185d121b50836379fe012753cf15c4ba9638bda9645183ab36246923875f8d1b"},
{file = "pyarrow-16.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:2e51ca1d6ed7f2e9d5c3c83decf27b0d17bb207a7dea986e8dc3e24f80ff7d6f"},
{file = "pyarrow-16.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06ebccb6f8cb7357de85f60d5da50e83507954af617d7b05f48af1621d331c9a"},
{file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b04707f1979815f5e49824ce52d1dceb46e2f12909a48a6a753fe7cafbc44a0c"},
{file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d32000693deff8dc5df444b032b5985a48592c0697cb6e3071a5d59888714e2"},
{file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8785bb10d5d6fd5e15d718ee1d1f914fe768bf8b4d1e5e9bf253de8a26cb1628"},
{file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e1369af39587b794873b8a307cc6623a3b1194e69399af0efd05bb202195a5a7"},
{file = "pyarrow-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:febde33305f1498f6df85e8020bca496d0e9ebf2093bab9e0f65e2b4ae2b3444"},
{file = "pyarrow-16.1.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b5f5705ab977947a43ac83b52ade3b881eb6e95fcc02d76f501d549a210ba77f"},
{file = "pyarrow-16.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0d27bf89dfc2576f6206e9cd6cf7a107c9c06dc13d53bbc25b0bd4556f19cf5f"},
{file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d07de3ee730647a600037bc1d7b7994067ed64d0eba797ac74b2bc77384f4c2"},
{file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbef391b63f708e103df99fbaa3acf9f671d77a183a07546ba2f2c297b361e83"},
{file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19741c4dbbbc986d38856ee7ddfdd6a00fc3b0fc2d928795b95410d38bb97d15"},
{file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f2c5fb249caa17b94e2b9278b36a05ce03d3180e6da0c4c3b3ce5b2788f30eed"},
{file = "pyarrow-16.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:e6b6d3cd35fbb93b70ade1336022cc1147b95ec6af7d36906ca7fe432eb09710"},
{file = "pyarrow-16.1.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:18da9b76a36a954665ccca8aa6bd9f46c1145f79c0bb8f4f244f5f8e799bca55"},
{file = "pyarrow-16.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:99f7549779b6e434467d2aa43ab2b7224dd9e41bdde486020bae198978c9e05e"},
{file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f07fdffe4fd5b15f5ec15c8b64584868d063bc22b86b46c9695624ca3505b7b4"},
{file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddfe389a08ea374972bd4065d5f25d14e36b43ebc22fc75f7b951f24378bf0b5"},
{file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3b20bd67c94b3a2ea0a749d2a5712fc845a69cb5d52e78e6449bbd295611f3aa"},
{file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ba8ac20693c0bb0bf4b238751d4409e62852004a8cf031c73b0e0962b03e45e3"},
{file = "pyarrow-16.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:31a1851751433d89a986616015841977e0a188662fcffd1a5677453f1df2de0a"},
{file = "pyarrow-16.1.0.tar.gz", hash = "sha256:15fbb22ea96d11f0b5768504a3f961edab25eaf4197c341720c4a387f6c60315"},
]
[package.dependencies]
numpy = ">=1.16.6"
[[package]]
name = "pydantic"
version = "2.7.4"
@@ -1803,37 +1569,6 @@ python-dotenv = ">=0.21.0"
toml = ["tomli (>=2.0.1)"]
yaml = ["pyyaml (>=6.0.1)"]
[[package]]
name = "pygments"
version = "2.18.0"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
files = [
{file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
{file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pyjwt"
version = "2.8.0"
description = "JSON Web Token implementation in Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"},
{file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"},
]
[package.extras]
crypto = ["cryptography (>=3.4.0)"]
dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
[[package]]
name = "pytest"
version = "8.2.2"
@@ -1874,20 +1609,6 @@ pytest = ">=7.0.0,<9"
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
description = "Extensions to the standard Python datetime module"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
files = [
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
]
[package.dependencies]
six = ">=1.5"
[[package]]
name = "python-dotenv"
version = "1.0.1"
@@ -1902,17 +1623,6 @@ files = [
[package.extras]
cli = ["click (>=5.0)"]
[[package]]
name = "pytz"
version = "2024.1"
description = "World timezone definitions, modern and historical"
optional = false
python-versions = "*"
files = [
{file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"},
{file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"},
]
[[package]]
name = "pyyaml"
version = "6.0.1"
@@ -2071,24 +1781,6 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "rich"
version = "13.7.1"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"},
{file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
]
[package.dependencies]
markdown-it-py = ">=2.2.0"
pygments = ">=2.13.0,<3.0.0"
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
version = "0.4.10"
@@ -2115,17 +1807,6 @@ files = [
{file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"},
]
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
[[package]]
name = "sniffio"
version = "1.3.1"
@@ -2466,17 +2147,6 @@ files = [
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "tzdata"
version = "2024.1"
description = "Provider of IANA time zone data"
optional = false
python-versions = ">=2"
files = [
{file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"},
{file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"},
]
[[package]]
name = "urllib3"
version = "2.2.2"
@@ -2494,20 +2164,6 @@ h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "win32-setctime"
version = "1.1.0"
description = "A small Python utility to set file creation time on Windows"
optional = false
python-versions = ">=3.5"
files = [
{file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
]
[package.extras]
dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[[package]]
name = "yarl"
version = "1.9.4"
@@ -2614,4 +2270,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = ">=3.9.0,<3.13"
content-hash = "56269de92f8bc9a1f408e9975dd15433919bbb156e8f74cae03534a8966f15b1"
content-hash = "be77dfc6f180873373bd7fd6583e16e00743c1a97eef9fe4a5491394ddf4e8ee"
-1
View File
@@ -18,7 +18,6 @@ trustcall = "^0.0.4"
langchain = "^0.2.6"
langchain-openai = "^0.1.10"
langchain-anthropic = "^0.1.15"
langchain-nomic = "^0.1.2"
pydantic-settings = "^2.3.4"
langgraph-sdk = "^0.1.23"