Files
langgraph/examples/chatbots/customer-support.ipynb
T

37 KiB
Raw Blame History

Customer Support

Here, we show an example of building a customer support chatbot.

This customer support chatbot interacts with SQL database to answer questions. We will use a mock SQL database to get started: the Chinook database. This database is about sales from a music store: what songs and album exists, customer orders, things like that.

This chatbot has two different states:

  1. Music: the user can inquire about different songs and albums present in the store
  2. Account: the user can ask questions about their account

Under the hood, this is handled by two separate agents. Each has a specific prompt and tools related to their objective. There is also a generic agent who is responsible for routing between these two agents as needed.

Note: This is a very simple example! For a more complete tutorial on building a customer support bot, check out the Customer Support Tutorial for more information.

In [ ]:
%%capture --no-stderr
%pip install -U langgraph langchain-community langchain-openai scikit-learn

Load the data

In [1]:
import requests

url = "https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db"
response = requests.get(url)

with open("Chinook.db", "wb") as file:
    file.write(response.content)
In [2]:
from langchain_community.utilities import SQLDatabase

db = SQLDatabase.from_uri("sqlite:///Chinook.db")
db.get_usable_table_names()
Out [2]:
['Album',
 'Artist',
 'Customer',
 'Employee',
 'Genre',
 'Invoice',
 'InvoiceLine',
 'MediaType',
 'Playlist',
 'PlaylistTrack',
 'Track']

Load an LLM

We will load a language model to use. For this demo we will use OpenAI.

In [3]:
from langchain_openai import ChatOpenAI

model = ChatOpenAI(temperature=0, streaming=True, model="gpt-4o")

Load Other Modules

Load other modules we will use.

All of the tools our agents will use will be custom tools. As such, we will use the @tool decorator to create custom tools.

We will pass in messages to the agent, so we load HumanMessage and SystemMessage

In [4]:
from langchain_core.messages import HumanMessage, SystemMessage

Define the Customer Agent

This agent is responsible for looking up customer information. It will have a specific prompt as well a specific tool to look up information about that customer (after asking for their user id).

In [5]:
# This tool is given to the agent to look up information about a customer
def get_customer_info(customer_id: int):
    """Look up customer info given their ID. ALWAYS make sure you have the customer ID before invoking this."""
    return db.run(f"SELECT * FROM Customer WHERE CustomerID = {customer_id};")
In [6]:
customer_prompt = """Your job is to help a user update their profile.

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.

If you are unable to help the user, you can """


def get_customer_messages(messages):
    return [SystemMessage(content=customer_prompt)] + messages


customer_chain = get_customer_messages | model.bind_tools([get_customer_info])

Define the Music Agent

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

First, we will create indexes for looking up artists and track names. This will allow us to look up artists and tracks without having to spell their names exactly right.

In [7]:
from langchain_community.vectorstores import SKLearnVectorStore
from langchain_openai import OpenAIEmbeddings

artists = db._execute("select * from Artist")
songs = db._execute("select * from Track")
artist_retriever = SKLearnVectorStore.from_texts(
    [a["Name"] for a in artists], OpenAIEmbeddings(), metadatas=artists
).as_retriever()
song_retriever = SKLearnVectorStore.from_texts(
    [a["Name"] for a in songs], OpenAIEmbeddings(), metadatas=songs
).as_retriever()

First, let's create a tool for getting albums by artist.

In [8]:
def get_albums_by_artist(artist):
    """Get albums by an artist (or similar artists)."""
    docs = artist_retriever.get_relevant_documents(artist)
    artist_ids = ", ".join([str(d.metadata["ArtistId"]) for d in docs])
    return db.run(
        f"SELECT Title, Name FROM Album LEFT JOIN Artist ON Album.ArtistId = Artist.ArtistId WHERE Album.ArtistId in ({artist_ids});",
        include_columns=True,
    )

Next, lets create a tool for getting tracks by an artist

In [9]:
def get_tracks_by_artist(artist):
    """Get songs by an artist (or similar artists)."""
    docs = artist_retriever.invoke(artist)
    artist_ids = ", ".join([str(d.metadata["ArtistId"]) for d in docs])
    return db.run(
        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});",
        include_columns=True,
    )

Finally, let's create a tool for looking up songs by their name.

In [10]:
def check_for_songs(song_title):
    """Check if a song exists by its name."""
    return song_retriever.invoke(song_title)

Create the chain to call the relevant tools

In [11]:
song_system_message = """Your job is to help a customer find any songs they are looking for. 

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.

When looking up artists and songs, sometimes the artist/song will not be found. In that case, the tools will return information \
on similar songs and artists. This is intentional, it is not the tool messing up."""


def get_song_messages(messages):
    return [SystemMessage(content=song_system_message)] + messages


song_recc_chain = get_song_messages | model.bind_tools(
    [get_albums_by_artist, get_tracks_by_artist, check_for_songs]
)
In [12]:
msgs = [HumanMessage(content="hi! can you help me find songs by amy whinehouse?")]
song_recc_chain.invoke(msgs)
Out [12]:
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'}])

Define the Generic Agent

We now define a generic agent that is responsible for handling initial inquiries and routing to the right sub agent.

In [13]:
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langchain_core.pydantic_v1 import BaseModel, Field


class Router(BaseModel):
    """Call this if you are able to route the user to the appropriate representative."""

    choice: str = Field(description="should be one of: music, customer")


system_message = """Your job is to help as a customer service representative for a music store.

You should interact politely with customers to try to figure out how you can help. You can help in a few ways:

- Updating user information: if a customer wants to update the information in the user database. Call the router with `customer`
- Recommending music: if a customer wants to find some music or information about music. Call the router with `music`

If the user is asking or wants to ask about updating or accessing their information, send them to that route.
If the user is asking or wants to ask about music, send them to that route.
Otherwise, respond."""


def get_messages(messages):
    return [SystemMessage(content=system_message)] + messages
In [14]:
chain = get_messages | model.bind_tools([Router])
In [15]:
msgs = [HumanMessage(content="hi! can you help me find a good song?")]
chain.invoke(msgs)
Out [15]:
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'}])
In [16]:
msgs = [HumanMessage(content="hi! what's the email you have for me?")]
chain.invoke(msgs)
Out [16]:
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'}])
In [17]:
from langchain_core.messages import AIMessage


def add_name(message, name):
    _dict = message.dict()
    _dict["name"] = name
    return AIMessage(**_dict)
In [18]:
import json

from langgraph.graph import END


def _get_last_ai_message(messages):
    for m in messages[::-1]:
        if isinstance(m, AIMessage):
            return m
    return None


def _is_tool_call(msg):
    return hasattr(msg, "additional_kwargs") and "tool_calls" in msg.additional_kwargs


def _route(messages):
    last_message = messages[-1]
    if isinstance(last_message, AIMessage):
        if not last_message.tool_calls:
            return END
        else:
            if last_message.name == "general":
                if len(last_message.tool_calls) > 1:
                    raise ValueError("Too many tools")
                return last_message.tool_calls[0]["args"]["choice"]
            else:
                return "tools"
    last_m = _get_last_ai_message(messages)
    if last_m is None:
        return "general"
    if last_m.name == "music":
        return "music"
    elif last_m.name == "customer":
        return "customer"
    else:
        return "general"
In [19]:
from langgraph.prebuilt import ToolNode

tools = [get_albums_by_artist, get_tracks_by_artist, check_for_songs, get_customer_info]
tool_node = ToolNode(tools)
In [20]:
def _filter_out_routes(messages):
    ms = []
    for m in messages:
        if _is_tool_call(m):
            if m.name == "general":
                continue
        ms.append(m)
    return ms
In [21]:
from functools import partial

general_node = _filter_out_routes | chain | partial(add_name, name="general")
music_node = _filter_out_routes | song_recc_chain | partial(add_name, name="music")
customer_node = _filter_out_routes | customer_chain | partial(add_name, name="customer")
In [22]:
from langgraph.checkpoint.sqlite import SqliteSaver

from langgraph.graph import MessageGraph

memory = SqliteSaver.from_conn_string(":memory:")
graph = MessageGraph()
nodes = {
    "general": "general",
    "music": "music",
    END: END,
    "tools": "tools",
    "customer": "customer",
}
# Define a new graph
workflow = MessageGraph()
workflow.add_node("general", general_node)
workflow.add_node("music", music_node)
workflow.add_node("customer", customer_node)
workflow.add_node("tools", tool_node)
workflow.add_conditional_edges("general", _route, nodes)
workflow.add_conditional_edges("tools", _route, nodes)
workflow.add_conditional_edges("music", _route, nodes)
workflow.add_conditional_edges("customer", _route, nodes)
workflow.set_conditional_entry_point(_route, nodes)
graph = workflow.compile()
In [23]:
import uuid

from langchain_core.messages import HumanMessage

from langgraph.graph.graph import START

history = []
while True:
    user = input("User (q/Q to quit): ")
    if user in {"q", "Q"}:
        print("AI: Byebye")
        break
    history.append(HumanMessage(content=user))
    async for output in graph.astream(history):
        for key, value in output.items():
            print(f"Output from node '{key}':")
            print("---")
            print(value)
        print("\n---\n")
User (q/Q to quit):  what music do you have?
Output from node 'general':
---
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'}]

---

Output from node 'music':
---
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'

---

User (q/Q to quit):  how about shakira?
Output from node 'general':
---
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'}]

---

Output from node 'music':
---
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'}]

---

/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.
  warn_deprecated(
Output from node 'tools':
---
[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')]

---

Output from node 'music':
---
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'

---

User (q/Q to quit):  hm cool
Output from node 'general':
---
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'}]

---

Output from node 'music':
---
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'}]

---

Output from node 'tools':
---
[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')]

---

Output from node 'music':
---
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'

---

User (q/Q to quit):  q
AI: Byebye