From 74f1edda7a6480a9fdda7f896bb839c14b30723d Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Thu, 27 Jun 2024 12:12:02 -0700 Subject: [PATCH] Rm Chat Deployment You typically wouldn't deploy both the memory service and the chatbot in the same instance. --- example.ipynb | 81 ++++++-------- langgraph.json | 3 +- memory_service/chatbot.py | 218 -------------------------------------- 3 files changed, 32 insertions(+), 270 deletions(-) delete mode 100644 memory_service/chatbot.py diff --git a/example.ipynb b/example.ipynb index 0b87318..98db577 100644 --- a/example.ipynb +++ b/example.ipynb @@ -31,16 +31,14 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 25, "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", + "deployment_url = \"\"\n", "\n", "client = get_client(url=deployment_url)" ] @@ -56,7 +54,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ @@ -257,7 +255,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ @@ -281,7 +279,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 28, "metadata": {}, "outputs": [], "source": [ @@ -290,7 +288,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 29, "metadata": {}, "outputs": [], "source": [ @@ -301,20 +299,20 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 30, "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", + "{'thread_id': '3ff82998-b622-421f-8c8c-4b14d10c17b1',\n", + " 'created_at': '2024-06-28T00:42:23.884229+00:00',\n", + " 'updated_at': '2024-06-28T00:42:23.884229+00:00',\n", " 'metadata': {},\n", " 'status': 'idle'}" ] }, - "execution_count": 9, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -326,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 31, "metadata": {}, "outputs": [], "source": [ @@ -362,7 +360,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 32, "metadata": {}, "outputs": [], "source": [ @@ -371,19 +369,9 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 33, "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", @@ -398,7 +386,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 34, "metadata": {}, "outputs": [ { @@ -418,14 +406,14 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 35, "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?" + "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? That could help you get started with planning." ] } ], @@ -437,14 +425,14 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 36, "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?" + "Whoa, that's cool! Capoeira is such a dynamic and energetic activity. You could definitely incorporate elements of it into the party to make it more exciting. Maybe you could hire a capoeira instructor to lead a short workshop or demo, or even have a mini \"roda\" (that's the circle where capoeiristas play, right?) set up for guests to try out some moves. What do you think Steve would think of that?" ] } ], @@ -454,14 +442,14 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 37, "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?" + "That's a great connection to have! It's always helpful to get recommendations from people who have experience with a particular activity or business. You could reach out to the studio and ask if they know of any instructors who might be available to lead a workshop or demo at the party. They might even have some suggestions for how to incorporate capoeira into the celebration in a way that would be fun and engaging for Steve and the other guests. Do you think you'll reach out to them today, or wait until later in the planning process?" ] } ], @@ -473,14 +461,14 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 38, "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?" + "I'm doing great, thanks for asking! I'm just happy to be chatting with you and helping with your party planning. It's always exciting to see people come together to celebrate special occasions. But enough about me - let's get back to Steve's party! What do you think about serving some Brazilian-inspired food and drinks to tie in with the capoeira theme?" ] } ], @@ -490,14 +478,14 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 39, "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!" + "Nice to meet you, Ken! I'm glad we could chat about Steve's party and get some ideas going. If you need any more help or just want to bounce some ideas off me, feel free to reach out anytime. Good luck with the planning, and I hope Steve has an amazing time!" ] } ], @@ -517,7 +505,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 40, "metadata": {}, "outputs": [], "source": [ @@ -528,7 +516,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 41, "metadata": {}, "outputs": [], "source": [ @@ -537,7 +525,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 42, "metadata": {}, "outputs": [], "source": [ @@ -546,7 +534,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 43, "metadata": {}, "outputs": [ { @@ -563,7 +551,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 44, "metadata": {}, "outputs": [ { @@ -580,7 +568,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 45, "metadata": {}, "outputs": [ { @@ -594,13 +582,6 @@ "source": [ "_ = await chat2(\"Oh planning is going alright!\")" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/langgraph.json b/langgraph.json index 91c6b21..d3bd6fd 100644 --- a/langgraph.json +++ b/langgraph.json @@ -1,8 +1,7 @@ { "dependencies": ["."], "graphs": { - "memory": "./memory_service/graph.py:memgraph", - "chat": "./memory_service/chatbot.py:chat_graph" + "memory": "./memory_service/graph.py:memgraph" }, "env": ".env" } diff --git a/memory_service/chatbot.py b/memory_service/chatbot.py deleted file mode 100644 index 8f7af25..0000000 --- a/memory_service/chatbot.py +++ /dev/null @@ -1,218 +0,0 @@ -"""Example chatbot that incorporates user memories.""" - -import os -import uuid -from datetime import datetime, timezone -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 langgraph.graph import START, StateGraph, add_messages -from langgraph_sdk import get_client -from typing_extensions import Annotated, TypedDict - -from memory_service import _constants as constants -from memory_service import _settings as settings -from memory_service import _utils as utils - - -class ChatState(TypedDict): - """The state of the chatbot.""" - - messages: Annotated[List[AnyMessage], add_messages] - user_memories: List[dict] - - -class ChatConfigurable(TypedDict): - """The configurable fields for the chatbot.""" - - user_id: str - thread_id: str - memory_service_url: str = "" - model: str - delay: Optional[float] - - -def _ensure_configurable(config: RunnableConfig) -> ChatConfigurable: - """Ensure the configuration is valid.""" - 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), - ) - - -PROMPT = ChatPromptTemplate.from_messages( - [ - ( - "system", - "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"), -) - - -@langsmith.traceable -def format_query(messages: List[AnyMessage]) -> str: - """Format the query for the user's memories.""" - # This is quite naive :) - return " ".join([str(m.content) for m in messages if m.type == "human"][-5:]) - - -async def query_memories(state: ChatState, config: RunnableConfig) -> ChatState: - """Query the user's memories.""" - configurable: ChatConfigurable = config["configurable"] - user_id = configurable["user_id"] - index = utils.get_index() - embeddings = utils.get_embeddings() - - query = format_query(state["messages"]) - vec = await embeddings.aembed_query(query) - # You can also filter by memory type, etc. here. - 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"][constants.PAYLOAD_KEY] for m in matches] - return { - "user_memories": memories, - } - - -@langsmith.traceable -def format_memories(memories: List[dict]) -> str: - """Format the user's memories.""" - if not memories: - return "" - # Note Bene: You can format better than this.... - memories = "\n".join(str(m) for m in memories) - return f""" - -## Memories - -You have noted the following memorable events from previous interactions with the user. - -{memories} - -""" - - -async def bot(state: ChatState, config: RunnableConfig) -> ChatState: - """Prompt the bot to resopnd to the user, incorporating memories (if provided).""" - configurable = _ensure_configurable(config) - model = init_chat_model(configurable["model"]) - chain = PROMPT | model - memories = format_memories(state["user_memories"]) - m = await chain.ainvoke( - { - "messages": state["messages"], - "user_info": memories, - }, - config, - ) - - return { - "messages": [m], - } - - -async def post_messages(state: ChatState, config: RunnableConfig) -> ChatState: - """Query the user's memories.""" - 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: - await langgraph_client.threads.get(thread_id=memory_thread_id) - except Exception: - await langgraph_client.threads.create(thread_id=memory_thread_id) - - await langgraph_client.runs.create( - memory_thread_id, - assistant_id=configurable["mem_assistant_id"], - input={ - "messages": state["messages"], # the service dedupes messages - }, - config={ - "configurable": { - "user_id": configurable["user_id"], - "delay": configurable["delay"], - "schemas": { - "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": "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", - }, - }, - "required": ["description", "participants"], - "title": "memorable_event", - "type": "object", - }, - }, - }, - }, - }, - }, - multitask_strategy="rollback", - ) - - return { - "messages": [], - } - - -builder = StateGraph(ChatState, ChatConfigurable) -builder.add_node(query_memories) -builder.add_node(bot) -builder.add_node(post_messages) -builder.add_edge(START, "query_memories") -builder.add_edge("query_memories", "bot") -builder.add_edge("bot", "post_messages") - -chat_graph = builder.compile()