From b632fd8c5421f749cc89bd8a3a8bfcde7d20b751 Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:06:05 -0700 Subject: [PATCH] Add cleaned up csbot chatbot example (#971) --- examples/chatbots/customer-support.ipynb | 733 ++++++++++++++++++++++- 1 file changed, 731 insertions(+), 2 deletions(-) diff --git a/examples/chatbots/customer-support.ipynb b/examples/chatbots/customer-support.ipynb index 47b3bf808..90d55e455 100644 --- a/examples/chatbots/customer-support.ipynb +++ b/examples/chatbots/customer-support.ipynb @@ -2,10 +2,739 @@ "cells": [ { "cell_type": "markdown", - "id": "4c37bb65-6e2c-42e4-bfa7-9df10e2652a0", + "id": "d9d1a28b-c2a1-4246-b1c2-c58d6938f798", "metadata": {}, "source": [ - "This example has moved! Check out the [Customer Support Tutorial](../customer-support/customer-support.ipynb) for more information." + "# Customer Support\n", + "\n", + "Here, we show an example of building a customer support chatbot.\n", + "\n", + "This customer support chatbot interacts with SQL database to answer questions.\n", + "We will use a mock SQL database to get started: the [Chinook](https://www.sqlitetutorial.net/sqlite-sample-database/) database.\n", + "This database is about sales from a music store: what songs and album exists, customer orders, things like that.\n", + "\n", + "This chatbot has two different states: \n", + "1. Music: the user can inquire about different songs and albums present in the store\n", + "2. Account: the user can ask questions about their account\n", + "\n", + "Under the hood, this is handled by two separate agents. \n", + "Each has a specific prompt and tools related to their objective. \n", + "There is also a generic agent who is responsible for routing between these two agents as needed.\n", + "\n", + "Note: This is a very simple example! For a more complete tutorial on building a customer support bot, check out the [Customer Support Tutorial](../customer-support/customer-support.ipynb) for more information." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35abc013-2613-4a49-a806-939dcf13ccf3", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install -U langgraph langchain-community langchain-openai scikit-learn" + ] + }, + { + "cell_type": "markdown", + "id": "9431e7f1-07fa-49d9-ac45-29613703dcc1", + "metadata": {}, + "source": [ + "## Load the data" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "3d1ef253-6b0c-4481-868c-e1fe84f2c8ff", + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "\n", + "url = \"https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db\"\n", + "response = requests.get(url)\n", + "\n", + "with open(\"Chinook.db\", \"wb\") as file:\n", + " file.write(response.content)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "61f7ef9c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Album',\n", + " 'Artist',\n", + " 'Customer',\n", + " 'Employee',\n", + " 'Genre',\n", + " 'Invoice',\n", + " 'InvoiceLine',\n", + " 'MediaType',\n", + " 'Playlist',\n", + " 'PlaylistTrack',\n", + " 'Track']" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_community.utilities import SQLDatabase\n", + "\n", + "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")\n", + "db.get_usable_table_names()" + ] + }, + { + "cell_type": "markdown", + "id": "1cf668e4-8cb4-4de1-bc5e-c90284bf74bc", + "metadata": {}, + "source": [ + "## Load an LLM\n", + "\n", + "We will load a language model to use.\n", + "For this demo we will use OpenAI." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d9ea4e80-30e6-4d46-b480-35f0be2fb055", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_openai import ChatOpenAI\n", + "\n", + "model = ChatOpenAI(temperature=0, streaming=True, model=\"gpt-4o\")" + ] + }, + { + "cell_type": "markdown", + "id": "73907422-7e05-431e-b06d-256c9ec1f6f6", + "metadata": {}, + "source": [ + "## Load Other Modules\n", + "\n", + "Load other modules we will use.\n", + "\n", + "All of the tools our agents will use will be custom tools. As such, we will use the `@tool` decorator to create custom tools.\n", + "\n", + "We will pass in messages to the agent, so we load `HumanMessage` and `SystemMessage`" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ea958e9f-ab1f-49b5-bd85-16332055297c", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.messages import HumanMessage, SystemMessage" + ] + }, + { + "cell_type": "markdown", + "id": "35271d4d-2a1c-41be-9359-a3a7c3fed3d9", + "metadata": {}, + "source": [ + "## Define the Customer Agent\n", + "\n", + "This agent is responsible for looking up customer information.\n", + "It will have a specific prompt as well a specific tool to look up information about that customer (after asking for their user id)." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "975b039a", + "metadata": {}, + "outputs": [], + "source": [ + "# This tool is given to the agent to look up information about a customer\n", + "def get_customer_info(customer_id: int):\n", + " \"\"\"Look up customer info given their ID. ALWAYS make sure you have the customer ID before invoking this.\"\"\"\n", + " return db.run(f\"SELECT * FROM Customer WHERE CustomerID = {customer_id};\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "1d5fa446", + "metadata": {}, + "outputs": [], + "source": [ + "customer_prompt = \"\"\"Your job is to help a user update their profile.\n", + "\n", + "You only have certain tools you can use. These tools require specific input. If you don't know the required input, then ask the user for it.\n", + "\n", + "If you are unable to help the user, you can \"\"\"\n", + "\n", + "\n", + "def get_customer_messages(messages):\n", + " return [SystemMessage(content=customer_prompt)] + messages\n", + "\n", + "\n", + "customer_chain = get_customer_messages | model.bind_tools([get_customer_info])" + ] + }, + { + "cell_type": "markdown", + "id": "904a9485-3857-458e-8b9d-33bc33842bc9", + "metadata": {}, + "source": [ + "## Define the Music Agent\n", + "\n", + "This agent is responsible for figuring out information about music. To do that, we will create a prompt and various tools for looking up information about music\n", + "\n", + "First, we will create indexes for looking up artists and track names.\n", + "This will allow us to look up artists and tracks without having to spell their names exactly right." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a8604a3b-b484-4b2b-a914-4236cb98c524", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.vectorstores import SKLearnVectorStore\n", + "from langchain_openai import OpenAIEmbeddings\n", + "\n", + "artists = db._execute(\"select * from Artist\")\n", + "songs = db._execute(\"select * from Track\")\n", + "artist_retriever = SKLearnVectorStore.from_texts(\n", + " [a[\"Name\"] for a in artists], OpenAIEmbeddings(), metadatas=artists\n", + ").as_retriever()\n", + "song_retriever = SKLearnVectorStore.from_texts(\n", + " [a[\"Name\"] for a in songs], OpenAIEmbeddings(), metadatas=songs\n", + ").as_retriever()" + ] + }, + { + "cell_type": "markdown", + "id": "ac7eb264-c572-4925-ad55-a1d52a18b1c0", + "metadata": {}, + "source": [ + "First, let's create a tool for getting albums by artist." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "0a2a2b74", + "metadata": {}, + "outputs": [], + "source": [ + "def get_albums_by_artist(artist):\n", + " \"\"\"Get albums by an artist (or similar artists).\"\"\"\n", + " docs = artist_retriever.get_relevant_documents(artist)\n", + " artist_ids = \", \".join([str(d.metadata[\"ArtistId\"]) for d in docs])\n", + " return db.run(\n", + " f\"SELECT Title, Name FROM Album LEFT JOIN Artist ON Album.ArtistId = Artist.ArtistId WHERE Album.ArtistId in ({artist_ids});\",\n", + " include_columns=True,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "45e85066-f2fc-490e-992d-cd66c9cd6486", + "metadata": {}, + "source": [ + "Next, lets create a tool for getting tracks by an artist" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "da533f50", + "metadata": {}, + "outputs": [], + "source": [ + "def get_tracks_by_artist(artist):\n", + " \"\"\"Get songs by an artist (or similar artists).\"\"\"\n", + " docs = artist_retriever.invoke(artist)\n", + " artist_ids = \", \".join([str(d.metadata[\"ArtistId\"]) for d in docs])\n", + " return db.run(\n", + " f\"SELECT Track.Name as SongName, Artist.Name as ArtistName FROM Album LEFT JOIN Artist ON Album.ArtistId = Artist.ArtistId LEFT JOIN Track ON Track.AlbumId = Album.AlbumId WHERE Album.ArtistId in ({artist_ids});\",\n", + " include_columns=True,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "bb0e50ab-b059-427c-924b-f8072d8db23c", + "metadata": {}, + "source": [ + "Finally, let's create a tool for looking up songs by their name." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "b3c07010", + "metadata": {}, + "outputs": [], + "source": [ + "def check_for_songs(song_title):\n", + " \"\"\"Check if a song exists by its name.\"\"\"\n", + " return song_retriever.invoke(song_title)" + ] + }, + { + "cell_type": "markdown", + "id": "88388ff8-38b5-4e4e-a24d-de8c3670bd2b", + "metadata": {}, + "source": [ + "Create the chain to call the relevant tools" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "72a14d5c", + "metadata": {}, + "outputs": [], + "source": [ + "song_system_message = \"\"\"Your job is to help a customer find any songs they are looking for. \n", + "\n", + "You only have certain tools you can use. If a customer asks you to look something up that you don't know how, politely tell them what you can help with.\n", + "\n", + "When looking up artists and songs, sometimes the artist/song will not be found. In that case, the tools will return information \\\n", + "on similar songs and artists. This is intentional, it is not the tool messing up.\"\"\"\n", + "\n", + "\n", + "def get_song_messages(messages):\n", + " return [SystemMessage(content=song_system_message)] + messages\n", + "\n", + "\n", + "song_recc_chain = get_song_messages | model.bind_tools(\n", + " [get_albums_by_artist, get_tracks_by_artist, check_for_songs]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "cff15eb0-62c7-451d-a5f9-4576b24c879e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_aXa9rSRXvTCJabrMY6AqkSV8', 'function': {'arguments': '{\"artist\":\"Amy Winehouse\"}', 'name': 'get_tracks_by_artist'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f'}, id='run-60633269-fd02-43e8-b434-28e3b8b69fb1-0', tool_calls=[{'name': 'get_tracks_by_artist', 'args': {'artist': 'Amy Winehouse'}, 'id': 'call_aXa9rSRXvTCJabrMY6AqkSV8'}])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "msgs = [HumanMessage(content=\"hi! can you help me find songs by amy whinehouse?\")]\n", + "song_recc_chain.invoke(msgs)" + ] + }, + { + "cell_type": "markdown", + "id": "0a42c293-0816-4f3c-b4a3-5b9f3a0665d1", + "metadata": {}, + "source": [ + "## Define the Generic Agent\n", + "\n", + "We now define a generic agent that is responsible for handling initial inquiries and routing to the right sub agent." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "73e74268", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.messages import AIMessage, HumanMessage, SystemMessage\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "\n", + "\n", + "class Router(BaseModel):\n", + " \"\"\"Call this if you are able to route the user to the appropriate representative.\"\"\"\n", + "\n", + " choice: str = Field(description=\"should be one of: music, customer\")\n", + "\n", + "\n", + "system_message = \"\"\"Your job is to help as a customer service representative for a music store.\n", + "\n", + "You should interact politely with customers to try to figure out how you can help. You can help in a few ways:\n", + "\n", + "- Updating user information: if a customer wants to update the information in the user database. Call the router with `customer`\n", + "- Recommending music: if a customer wants to find some music or information about music. Call the router with `music`\n", + "\n", + "If the user is asking or wants to ask about updating or accessing their information, send them to that route.\n", + "If the user is asking or wants to ask about music, send them to that route.\n", + "Otherwise, respond.\"\"\"\n", + "\n", + "\n", + "def get_messages(messages):\n", + " return [SystemMessage(content=system_message)] + messages" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ddf27314", + "metadata": {}, + "outputs": [], + "source": [ + "chain = get_messages | model.bind_tools([Router])" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "3c896f34", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_0aaFPPCWDiAoPyXQX2PS8TcJ', 'function': {'arguments': '{\"choice\":\"music\"}', 'name': 'Router'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f'}, id='run-73d51d75-b7c5-49fe-b558-105ede7c75d1-0', tool_calls=[{'name': 'Router', 'args': {'choice': 'music'}, 'id': 'call_0aaFPPCWDiAoPyXQX2PS8TcJ'}])" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "msgs = [HumanMessage(content=\"hi! can you help me find a good song?\")]\n", + "chain.invoke(msgs)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "40d86f59", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_Okla9DfMHIPs5TslS6KPaoBA', 'function': {'arguments': '{\"choice\":\"customer\"}', 'name': 'Router'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719'}, id='run-0d82d4f8-f4eb-4b16-add8-3e7fdffd6332-0', tool_calls=[{'name': 'Router', 'args': {'choice': 'customer'}, 'id': 'call_Okla9DfMHIPs5TslS6KPaoBA'}])" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "msgs = [HumanMessage(content=\"hi! what's the email you have for me?\")]\n", + "chain.invoke(msgs)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "bd6ddd8b-7500-46a7-811d-3bcb937bda51", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.messages import AIMessage\n", + "\n", + "\n", + "def add_name(message, name):\n", + " _dict = message.dict()\n", + " _dict[\"name\"] = name\n", + " return AIMessage(**_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "27494de5-8345-4c23-bc0e-81e0dd5d47d8", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "from langgraph.graph import END\n", + "\n", + "\n", + "def _get_last_ai_message(messages):\n", + " for m in messages[::-1]:\n", + " if isinstance(m, AIMessage):\n", + " return m\n", + " return None\n", + "\n", + "\n", + "def _is_tool_call(msg):\n", + " return hasattr(msg, \"additional_kwargs\") and \"tool_calls\" in msg.additional_kwargs\n", + "\n", + "\n", + "def _route(messages):\n", + " last_message = messages[-1]\n", + " if isinstance(last_message, AIMessage):\n", + " if not last_message.tool_calls:\n", + " return END\n", + " else:\n", + " if last_message.name == \"general\":\n", + " if len(last_message.tool_calls) > 1:\n", + " raise ValueError(\"Too many tools\")\n", + " return last_message.tool_calls[0][\"args\"][\"choice\"]\n", + " else:\n", + " return \"tools\"\n", + " last_m = _get_last_ai_message(messages)\n", + " if last_m is None:\n", + " return \"general\"\n", + " if last_m.name == \"music\":\n", + " return \"music\"\n", + " elif last_m.name == \"customer\":\n", + " return \"customer\"\n", + " else:\n", + " return \"general\"" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "8aec704a-46fe-4fb3-bdee-11c3bbffc370", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.prebuilt import ToolNode\n", + "\n", + "tools = [get_albums_by_artist, get_tracks_by_artist, check_for_songs, get_customer_info]\n", + "tool_node = ToolNode(tools)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "4d5b75c6-73e0-4922-a765-a15be63f869e", + "metadata": {}, + "outputs": [], + "source": [ + "def _filter_out_routes(messages):\n", + " ms = []\n", + " for m in messages:\n", + " if _is_tool_call(m):\n", + " if m.name == \"general\":\n", + " continue\n", + " ms.append(m)\n", + " return ms" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "fd4dbf98-dbb3-411a-bad6-2bb334072aaf", + "metadata": {}, + "outputs": [], + "source": [ + "from functools import partial\n", + "\n", + "general_node = _filter_out_routes | chain | partial(add_name, name=\"general\")\n", + "music_node = _filter_out_routes | song_recc_chain | partial(add_name, name=\"music\")\n", + "customer_node = _filter_out_routes | customer_chain | partial(add_name, name=\"customer\")" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "dcade924", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.checkpoint.sqlite import SqliteSaver\n", + "\n", + "from langgraph.graph import MessageGraph\n", + "\n", + "memory = SqliteSaver.from_conn_string(\":memory:\")\n", + "graph = MessageGraph()\n", + "nodes = {\n", + " \"general\": \"general\",\n", + " \"music\": \"music\",\n", + " END: END,\n", + " \"tools\": \"tools\",\n", + " \"customer\": \"customer\",\n", + "}\n", + "# Define a new graph\n", + "workflow = MessageGraph()\n", + "workflow.add_node(\"general\", general_node)\n", + "workflow.add_node(\"music\", music_node)\n", + "workflow.add_node(\"customer\", customer_node)\n", + "workflow.add_node(\"tools\", tool_node)\n", + "workflow.add_conditional_edges(\"general\", _route, nodes)\n", + "workflow.add_conditional_edges(\"tools\", _route, nodes)\n", + "workflow.add_conditional_edges(\"music\", _route, nodes)\n", + "workflow.add_conditional_edges(\"customer\", _route, nodes)\n", + "workflow.set_conditional_entry_point(_route, nodes)\n", + "graph = workflow.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "ac65d6d2", + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "User (q/Q to quit): what music do you have?\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Output from node 'general':\n", + "---\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_iste6NuKvZou8O9QudOectOU', 'function': {'arguments': '{\"choice\":\"music\"}', 'name': 'Router'}, 'type': 'function'}]} response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719'} name='general' id='run-9eb940ff-6592-43ae-aa34-22c3d630ac65-0' tool_calls=[{'name': 'Router', 'args': {'choice': 'music'}, 'id': 'call_iste6NuKvZou8O9QudOectOU'}]\n", + "\n", + "---\n", + "\n", + "Output from node 'music':\n", + "---\n", + "content=\"I can help you find songs and albums by specific artists, or check if a particular song exists. Just let me know the name of the artist or song you're interested in!\" response_metadata={'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719'} name='music' id='run-91f560e7-ffa5-437f-afda-27490cbd1efe-0'\n", + "\n", + "---\n", + "\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "User (q/Q to quit): how about shakira?\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Output from node 'general':\n", + "---\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_fH4oKyA3U9aQy3p31MYXv2VP', 'function': {'arguments': '{\"choice\":\"music\"}', 'name': 'Router'}, 'type': 'function'}]} response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_d576307f90'} name='general' id='run-6f2eee09-9e3e-4011-a045-196cc5baa1ee-0' tool_calls=[{'name': 'Router', 'args': {'choice': 'music'}, 'id': 'call_fH4oKyA3U9aQy3p31MYXv2VP'}]\n", + "\n", + "---\n", + "\n", + "Output from node 'music':\n", + "---\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_qivZqsI8zQAqSDP2jsvHyR7T', 'function': {'arguments': '{\"artist\": \"Shakira\"}', 'name': 'get_albums_by_artist'}, 'type': 'function'}, {'index': 1, 'id': 'call_GER0B3vlAjxcvYOYq1NGlV4r', 'function': {'arguments': '{\"artist\": \"Shakira\"}', 'name': 'get_tracks_by_artist'}, 'type': 'function'}]} response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f'} name='music' id='run-38e2eca8-771c-48a2-83be-023fd17fc6f6-0' tool_calls=[{'name': 'get_albums_by_artist', 'args': {'artist': 'Shakira'}, 'id': 'call_qivZqsI8zQAqSDP2jsvHyR7T'}, {'name': 'get_tracks_by_artist', 'args': {'artist': 'Shakira'}, 'id': 'call_GER0B3vlAjxcvYOYq1NGlV4r'}]\n", + "\n", + "---\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/wfh/code/lc/langchain/libs/core/langchain_core/_api/deprecation.py:139: LangChainDeprecationWarning: The method `BaseRetriever.get_relevant_documents` was deprecated in langchain-core 0.1.46 and will be removed in 0.3.0. Use invoke instead.\n", + " warn_deprecated(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Output from node 'tools':\n", + "---\n", + "[ToolMessage(content=\"[{'Title': 'Supernatural', 'Name': 'Santana'}, {'Title': 'Santana - As Years Go By', 'Name': 'Santana'}, {'Title': 'Santana Live', 'Name': 'Santana'}, {'Title': 'Lulu Santos - RCA 100 Anos De Música - Álbum 01', 'Name': 'Lulu Santos'}, {'Title': 'Lulu Santos - RCA 100 Anos De Música - Álbum 02', 'Name': 'Lulu Santos'}]\", name='get_albums_by_artist', id='14ad12f3-afa1-4375-a89d-e878babf2d95', tool_call_id='call_qivZqsI8zQAqSDP2jsvHyR7T'), ToolMessage(content='[{\\'SongName\\': \\'(Da Le) Yaleo\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Love Of My Life\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Put Your Lights On\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Africa Bamba\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Smooth\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Do You Like The Way\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Maria Maria\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Migra\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Corazon Espinado\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Wishing It Was\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'El Farol\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Primavera\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'The Calling\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Jingo\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'El Corazon Manda\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'La Puesta Del Sol\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Persuasion\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'As The Years Go by\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Soul Sacrifice\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Fried Neckbones And Home Fries\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Santana Jam\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Evil Ways\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \"We\\'ve Got To Get Together/Jingo\", \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Rock Me\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \"Just Ain\\'t Good Enough\", \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Funky Piano\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'The Way You Do To Mer\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Assim Caminha A Humanidade\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Um Pro Outro\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Casa\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Condição\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Satisfação\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Brumário\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Sábado À Noite\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'A Cura\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Atrás Do Trio Elétrico\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Tudo Bem\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Toda Forma De Amor\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Sereia\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Se Você Pensa\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Lá Vem O Sol (Here Comes The Sun)\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Honolulu\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Dancin´Days\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Aviso Aos Navegantes\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Hyperconectividade\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'O Descobridor Dos Sete Mares\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Um Certo Alguém\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Fullgás\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Aquilo\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Senta A Pua\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Ro-Que-Se-Da-Ne\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Tudo Igual\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Fogo De Palha\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Assaltaram A Gramática\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'O Último Romântico (Ao Vivo)\\', \\'ArtistName\\': \\'Lulu Santos\\'}]', name='get_tracks_by_artist', id='4ddcebc3-8e4e-42d4-8ae4-3ce3a62b548c', tool_call_id='call_GER0B3vlAjxcvYOYq1NGlV4r')]\n", + "\n", + "---\n", + "\n", + "Output from node 'music':\n", + "---\n", + "content=\"It seems I couldn't find specific albums or songs by Shakira, but I did find some related artists and their works. Here are some albums and songs by Santana and Lulu Santos:\\n\\n### Albums:\\n- **Santana:**\\n - Supernatural\\n - Santana - As Years Go By\\n - Santana Live\\n\\n- **Lulu Santos:**\\n - Lulu Santos - RCA 100 Anos De Música - Álbum 01\\n - Lulu Santos - RCA 100 Anos De Música - Álbum 02\\n\\n### Songs:\\n- **Santana:**\\n - (Da Le) Yaleo\\n - Love Of My Life\\n - Put Your Lights On\\n - Africa Bamba\\n - Smooth\\n - Maria Maria\\n - Corazon Espinado\\n - Jingo\\n - Evil Ways\\n\\n- **Lulu Santos:**\\n - Assim Caminha A Humanidade\\n - Um Pro Outro\\n - Casa\\n - Condição\\n - Satisfação\\n - A Cura\\n - Atrás Do Trio Elétrico\\n - Toda Forma De Amor\\n - Sereia\\n - Se Você Pensa\\n\\nIf you have any other specific artists or songs in mind, feel free to let me know!\" response_metadata={'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_d576307f90'} name='music' id='run-3d90d7cc-e7f7-48dc-bffd-00765c3f5d13-0'\n", + "\n", + "---\n", + "\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "User (q/Q to quit): hm cool\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Output from node 'general':\n", + "---\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_Nc5d0TWNbpnVJFeYJdQFuFGd', 'function': {'arguments': '{\"choice\":\"music\"}', 'name': 'Router'}, 'type': 'function'}]} response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719'} name='general' id='run-19a3588e-f04c-48db-a54f-95d6b930ee3e-0' tool_calls=[{'name': 'Router', 'args': {'choice': 'music'}, 'id': 'call_Nc5d0TWNbpnVJFeYJdQFuFGd'}]\n", + "\n", + "---\n", + "\n", + "Output from node 'music':\n", + "---\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_weMHkgu3GYaMwXZM6hnCZd0z', 'function': {'arguments': '{\"artist\": \"Shakira\"}', 'name': 'get_albums_by_artist'}, 'type': 'function'}, {'index': 1, 'id': 'call_rXlVbPiEHbt10CNDUJ5GA2ZQ', 'function': {'arguments': '{\"artist\": \"Shakira\"}', 'name': 'get_tracks_by_artist'}, 'type': 'function'}]} response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f'} name='music' id='run-71bf8550-b5a8-423f-b463-83d9ff51391b-0' tool_calls=[{'name': 'get_albums_by_artist', 'args': {'artist': 'Shakira'}, 'id': 'call_weMHkgu3GYaMwXZM6hnCZd0z'}, {'name': 'get_tracks_by_artist', 'args': {'artist': 'Shakira'}, 'id': 'call_rXlVbPiEHbt10CNDUJ5GA2ZQ'}]\n", + "\n", + "---\n", + "\n", + "Output from node 'tools':\n", + "---\n", + "[ToolMessage(content=\"[{'Title': 'Supernatural', 'Name': 'Santana'}, {'Title': 'Santana - As Years Go By', 'Name': 'Santana'}, {'Title': 'Santana Live', 'Name': 'Santana'}, {'Title': 'Lulu Santos - RCA 100 Anos De Música - Álbum 01', 'Name': 'Lulu Santos'}, {'Title': 'Lulu Santos - RCA 100 Anos De Música - Álbum 02', 'Name': 'Lulu Santos'}]\", name='get_albums_by_artist', id='52ada997-83f6-4500-b0a7-1104d4d38eb9', tool_call_id='call_weMHkgu3GYaMwXZM6hnCZd0z'), ToolMessage(content='[{\\'SongName\\': \\'(Da Le) Yaleo\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Love Of My Life\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Put Your Lights On\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Africa Bamba\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Smooth\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Do You Like The Way\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Maria Maria\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Migra\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Corazon Espinado\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Wishing It Was\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'El Farol\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Primavera\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'The Calling\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Jingo\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'El Corazon Manda\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'La Puesta Del Sol\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Persuasion\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'As The Years Go by\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Soul Sacrifice\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Fried Neckbones And Home Fries\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Santana Jam\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Evil Ways\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \"We\\'ve Got To Get Together/Jingo\", \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Rock Me\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \"Just Ain\\'t Good Enough\", \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Funky Piano\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'The Way You Do To Mer\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Assim Caminha A Humanidade\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Um Pro Outro\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Casa\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Condição\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Satisfação\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Brumário\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Sábado À Noite\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'A Cura\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Atrás Do Trio Elétrico\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Tudo Bem\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Toda Forma De Amor\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Sereia\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Se Você Pensa\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Lá Vem O Sol (Here Comes The Sun)\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Honolulu\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Dancin´Days\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Aviso Aos Navegantes\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Hyperconectividade\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'O Descobridor Dos Sete Mares\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Um Certo Alguém\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Fullgás\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Aquilo\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Senta A Pua\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Ro-Que-Se-Da-Ne\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Tudo Igual\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Fogo De Palha\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Assaltaram A Gramática\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'O Último Romântico (Ao Vivo)\\', \\'ArtistName\\': \\'Lulu Santos\\'}]', name='get_tracks_by_artist', id='7583e077-2fb4-44bf-bb00-b89d599ae84d', tool_call_id='call_rXlVbPiEHbt10CNDUJ5GA2ZQ')]\n", + "\n", + "---\n", + "\n", + "Output from node 'music':\n", + "---\n", + "content=\"It seems like I couldn't find specific albums or songs by Shakira. However, I did find some related artists and their works. Here are some albums and songs by Santana and Lulu Santos:\\n\\n### Albums:\\n1. **Santana**\\n - Supernatural\\n - Santana - As Years Go By\\n - Santana Live\\n\\n2. **Lulu Santos**\\n - RCA 100 Anos De Música - Álbum 01\\n - RCA 100 Anos De Música - Álbum 02\\n\\n### Songs:\\n1. **Santana**\\n - (Da Le) Yaleo\\n - Love Of My Life\\n - Put Your Lights On\\n - Africa Bamba\\n - Smooth\\n - Maria Maria\\n - Corazon Espinado\\n - Jingo\\n - Evil Ways\\n\\n2. **Lulu Santos**\\n - Assim Caminha A Humanidade\\n - Um Pro Outro\\n - Casa\\n - Condição\\n - Satisfação\\n - A Cura\\n - Atrás Do Trio Elétrico\\n - Toda Forma De Amor\\n - Sereia\\n\\nIf you have any other artists or songs in mind, feel free to let me know!\" response_metadata={'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_d576307f90'} name='music' id='run-2418b45b-2762-49ca-b166-a0fee309ed9e-0'\n", + "\n", + "---\n", + "\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "User (q/Q to quit): q\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AI: Byebye\n" + ] + } + ], + "source": [ + "import uuid\n", + "\n", + "from langchain_core.messages import HumanMessage\n", + "\n", + "from langgraph.graph.graph import START\n", + "\n", + "history = []\n", + "while True:\n", + " user = input(\"User (q/Q to quit): \")\n", + " if user in {\"q\", \"Q\"}:\n", + " print(\"AI: Byebye\")\n", + " break\n", + " history.append(HumanMessage(content=user))\n", + " async for output in graph.astream(history):\n", + " for key, value in output.items():\n", + " print(f\"Output from node '{key}':\")\n", + " print(\"---\")\n", + " print(value)\n", + " print(\"\\n---\\n\")" ] } ],