mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-21 07:02:25 +02:00
38 KiB
38 KiB
In [1]:
# !pip install -U scikit-learnIn [24]:
!pip install -U langgraphRequirement already satisfied: langgraph in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (0.0.24) Collecting langgraph Downloading langgraph-0.0.26-py3-none-any.whl.metadata (34 kB) Collecting langchain-core<0.2.0,>=0.1.25 (from langgraph) Downloading langchain_core-0.1.26-py3-none-any.whl.metadata (6.0 kB) Requirement already satisfied: PyYAML>=5.3 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from langchain-core<0.2.0,>=0.1.25->langgraph) (6.0.1) Requirement already satisfied: anyio<5,>=3 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from langchain-core<0.2.0,>=0.1.25->langgraph) (3.7.1) Requirement already satisfied: jsonpatch<2.0,>=1.33 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from langchain-core<0.2.0,>=0.1.25->langgraph) (1.33) Collecting langsmith<0.2.0,>=0.1.0 (from langchain-core<0.2.0,>=0.1.25->langgraph) Downloading langsmith-0.1.8-py3-none-any.whl.metadata (13 kB) Requirement already satisfied: packaging<24.0,>=23.2 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from langchain-core<0.2.0,>=0.1.25->langgraph) (23.2) Requirement already satisfied: pydantic<3,>=1 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from langchain-core<0.2.0,>=0.1.25->langgraph) (2.4.2) Requirement already satisfied: requests<3,>=2 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from langchain-core<0.2.0,>=0.1.25->langgraph) (2.31.0) Requirement already satisfied: tenacity<9.0.0,>=8.1.0 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from langchain-core<0.2.0,>=0.1.25->langgraph) (8.2.3) Requirement already satisfied: idna>=2.8 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from anyio<5,>=3->langchain-core<0.2.0,>=0.1.25->langgraph) (3.4) Requirement already satisfied: sniffio>=1.1 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from anyio<5,>=3->langchain-core<0.2.0,>=0.1.25->langgraph) (1.3.0) Requirement already satisfied: jsonpointer>=1.9 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from jsonpatch<2.0,>=1.33->langchain-core<0.2.0,>=0.1.25->langgraph) (2.4) Collecting orjson<4.0.0,>=3.9.14 (from langsmith<0.2.0,>=0.1.0->langchain-core<0.2.0,>=0.1.25->langgraph) Downloading orjson-3.9.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl.metadata (49 kB) [2K [38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m49.5/49.5 kB[0m [31m3.7 MB/s[0m eta [36m0:00:00[0m [?25hRequirement already satisfied: annotated-types>=0.4.0 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from pydantic<3,>=1->langchain-core<0.2.0,>=0.1.25->langgraph) (0.6.0) Requirement already satisfied: pydantic-core==2.10.1 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from pydantic<3,>=1->langchain-core<0.2.0,>=0.1.25->langgraph) (2.10.1) Requirement already satisfied: typing-extensions>=4.6.1 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from pydantic<3,>=1->langchain-core<0.2.0,>=0.1.25->langgraph) (4.8.0) Requirement already satisfied: charset-normalizer<4,>=2 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from requests<3,>=2->langchain-core<0.2.0,>=0.1.25->langgraph) (3.3.0) Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from requests<3,>=2->langchain-core<0.2.0,>=0.1.25->langgraph) (2.0.7) Requirement already satisfied: certifi>=2017.4.17 in /Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages (from requests<3,>=2->langchain-core<0.2.0,>=0.1.25->langgraph) (2023.7.22) Downloading langgraph-0.0.26-py3-none-any.whl (44 kB) [2K [38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m44.4/44.4 kB[0m [31m3.6 MB/s[0m eta [36m0:00:00[0m [?25hDownloading langchain_core-0.1.26-py3-none-any.whl (246 kB) [2K [38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m246.4/246.4 kB[0m [31m3.3 MB/s[0m eta [36m0:00:00[0m[31m5.9 MB/s[0m eta [36m0:00:01[0m [?25hDownloading langsmith-0.1.8-py3-none-any.whl (62 kB) [2K [38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m62.2/62.2 kB[0m [31m5.6 MB/s[0m eta [36m0:00:00[0m [?25hDownloading orjson-3.9.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl (248 kB) [2K [38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m248.6/248.6 kB[0m [31m14.3 MB/s[0m eta [36m0:00:00[0m [?25hInstalling collected packages: orjson, langsmith, langchain-core, langgraph Attempting uninstall: langsmith Found existing installation: langsmith 0.0.87 Uninstalling langsmith-0.0.87: Successfully uninstalled langsmith-0.0.87 Attempting uninstall: langchain-core Found existing installation: langchain-core 0.1.22 Uninstalling langchain-core-0.1.22: Successfully uninstalled langchain-core-0.1.22 Attempting uninstall: langgraph Found existing installation: langgraph 0.0.24 Uninstalling langgraph-0.0.24: Successfully uninstalled langgraph-0.0.24 [31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts. langchain-community 0.0.19 requires langsmith<0.1,>=0.0.83, but you have langsmith 0.1.8 which is incompatible. langchain 0.1.6 requires langsmith<0.1,>=0.0.83, but you have langsmith 0.1.8 which is incompatible.[0m[31m [0mSuccessfully installed langchain-core-0.1.26 langgraph-0.0.26 langsmith-0.1.8 orjson-3.9.15 [1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m A new release of pip is available: [0m[31;49m23.3.2[0m[39;49m -> [0m[32;49m24.0[0m [1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m To update, run: [0m[32;49mpython3.11 -m pip install --upgrade pip[0m
In [1]:
from langchain_community.utilities import SQLDatabase
db = SQLDatabase.from_uri("sqlite:///Chinook.db")In [2]:
db.get_table_names()Out [2]:
/Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The function `get_table_names` was deprecated in LangChain 0.0.1 and will be removed in 0.2.0. Use get_usable_table_name instead. warn_deprecated(
['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']
In [3]:
from langchain_openai import ChatOpenAI
# We will set streaming=True so that we can stream tokens
# See the streaming section for more information on this.
model = ChatOpenAI(temperature=0, streaming=True, model="gpt-4-turbo-preview")In [4]:
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, SystemMessageIn [5]:
# This tool is given to the agent to look up information about a customer
@tool
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])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()In [8]:
@tool
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)In [9]:
@tool
def get_tracks_by_artist(artist):
"""Get songs 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 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)In [10]:
@tool
def check_for_songs(song_title):
"""Check if a song exists by its name."""
return song_retriever.get_relevant_documents(song_title)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 simliar 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_LxRLQdYKVzGHMGgwGTIIMvBO', 'function': {'arguments': '{"artist":"amy winehouse"}', 'name': 'get_tracks_by_artist'}, 'type': 'function'}]})In [13]:
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
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`
- Recomending 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)] + messagesIn [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_RpE3v45nw65Cx0RcgUXVbU8M', 'function': {'arguments': '{"choice":"music"}', 'name': 'Router'}, 'type': 'function'}]})In [16]:
msgs = [HumanMessage(content="hi! whats the email you have for me?")]
chain.invoke(msgs)Out [16]:
AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_GoXNLA8goAQJraVW3dMF1Uze', 'function': {'arguments': '{"choice":"customer"}', 'name': 'Router'}, 'type': 'function'}]})In [17]:
from langchain_core.messages import AIMessage
def add_name(message, name):
_dict = message.dict()
_dict["name"] = name
return AIMessage(**_dict)In [18]:
from langgraph.graph import END
import json
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 _is_tool_call(last_message):
return END
else:
if last_message.name == "general":
tool_calls = last_message.additional_kwargs['tool_calls']
if len(tool_calls) > 1:
raise ValueError
tool_call = tool_calls[0]
return json.loads(tool_call['function']['arguments'])['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 ToolExecutor, ToolInvocation
tools = [get_albums_by_artist, get_tracks_by_artist, check_for_songs, get_customer_info]
tool_executor = ToolExecutor(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 msIn [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 langchain_core.messages import ToolMessage
async def call_tool(messages):
actions = []
# Based on the continue condition
# we know the last message involves a function call
last_message = messages[-1]
for tool_call in last_message.additional_kwargs["tool_calls"]:
function = tool_call["function"]
function_name = function["name"]
_tool_input = json.loads(function["arguments"] or "{}")
# We construct an ToolInvocation from the function_call
actions.append(
ToolInvocation(
tool=function_name,
tool_input=_tool_input,
)
)
# We call the tool_executor and get back a response
responses = await tool_executor.abatch(actions)
# We use the response to create a ToolMessage
tool_messages = [
ToolMessage(
tool_call_id=tool_call["id"],
content=str(response),
additional_kwargs={"name": tool_call["function"]["name"]},
)
for tool_call, response in zip(
last_message.additional_kwargs["tool_calls"], responses
)
]
return tool_messagesIn [23]:
from langgraph.graph import MessageGraph
from langgraph.checkpoint.sqlite import SqliteSaver
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", call_tool)
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 [24]:
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):
if END in output or START in output:
continue
# stream() yields dictionaries with output keyed by node name
for key, value in output.items():
print(f"Output from node '{key}':")
print("---")
print(value)
print("\n---\n")
history = output[END]User (q/Q to quit): hi!
Output from node 'general': --- content='Hello! How can I assist you today?' name='general' ---
User (q/Q to quit): what songs do you have?
Output from node 'general':
---
content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_1IfQfrOHQXRuqz20GBr0GB7p', 'function': {'arguments': '{"choice":"music"}', 'name': 'Router'}, 'type': 'function'}]} name='general'
---
Output from node 'music':
---
content="I can help you find songs by specific artists or songs with particular titles. If you have a favorite artist or a song in mind, let me know, and I'll do my best to find information for you." name='music'
---
User (q/Q to quit): anything by t swift?
Output from node 'music':
---
content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_HLdMla4vn6g3SZulKZRtNJdp', 'function': {'arguments': '{"artist": "Taylor Swift"}', 'name': 'get_albums_by_artist'}, 'type': 'function'}, {'index': 1, 'id': 'call_ZtC6LQpaieVentCQaujm5Oam', 'function': {'arguments': '{"artist": "Taylor Swift"}', 'name': 'get_tracks_by_artist'}, 'type': 'function'}]} name='music'
---
Output from node 'tools':
---
[ToolMessage(content="[{'Title': 'International Superhits', 'Name': 'Green Day'}, {'Title': 'American Idiot', 'Name': 'Green Day'}]", additional_kwargs={'name': 'get_albums_by_artist'}, tool_call_id='call_HLdMla4vn6g3SZulKZRtNJdp'), ToolMessage(content='[{\'SongName\': \'Maria\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Poprocks And Coke\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Longview\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Welcome To Paradise\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Basket Case\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'When I Come Around\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'She\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'J.A.R. (Jason Andrew Relva)\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Geek Stink Breath\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Brain Stew\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Jaded\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Walking Contradiction\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Stuck With Me\', \'ArtistName\': \'Green Day\'}, {\'SongName\': "Hitchin\' A Ride", \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Good Riddance (Time Of Your Life)\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Redundant\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Nice Guys Finish Last\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Minority\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Warning\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Waiting\', \'ArtistName\': \'Green Day\'}, {\'SongName\': "Macy\'s Day Parade", \'ArtistName\': \'Green Day\'}, {\'SongName\': \'American Idiot\', \'ArtistName\': \'Green Day\'}, {\'SongName\': "Jesus Of Suburbia / City Of The Damned / I Don\'t Care / Dearly Beloved / Tales Of Another Broken Home", \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Holiday\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Boulevard Of Broken Dreams\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Are We The Waiting\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'St. Jimmy\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Give Me Novacaine\', \'ArtistName\': \'Green Day\'}, {\'SongName\': "She\'s A Rebel", \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Extraordinary Girl\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Letterbomb\', \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Wake Me Up When September Ends\', \'ArtistName\': \'Green Day\'}, {\'SongName\': "Homecoming / The Death Of St. Jimmy / East 12th St. / Nobody Likes You / Rock And Roll Girlfriend / We\'re Coming Home Again", \'ArtistName\': \'Green Day\'}, {\'SongName\': \'Whatsername\', \'ArtistName\': \'Green Day\'}]', additional_kwargs={'name': 'get_tracks_by_artist'}, tool_call_id='call_ZtC6LQpaieVentCQaujm5Oam')]
---
Output from node 'music':
---
content='It seems there was a mix-up in the search, and I received information related to Green Day instead of Taylor Swift. Unfortunately, I can\'t directly access or correct this error in real-time. However, Taylor Swift has a vast discography with many popular albums and songs. Some of her well-known albums include "Fearless," "1989," "Reputation," "Lover," "Folklore," and "Evermore." Her music spans across various genres, including country, pop, and indie folk.\n\nIf you\'re looking for specific songs or albums by Taylor Swift, please let me know, and I\'ll do my best to provide you with the information you\'re seeking!' name='music'
---
User (q/Q to quit): q
AI: Byebye
In [36]:
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):
if END in output or START in output:
continue
# stream() yields dictionaries with output keyed by node name
for key, value in output.items():
print(f"Output from node '{key}':")
print("---")
print(value)
print("\n---\n")
history = output[END]User (q/Q to quit): hi! whats the email you have on file?
Output from node 'general':
---
content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_fA4APR8G3SIxHLN8Sfv2F6si', 'function': {'arguments': '{"choice":"customer"}', 'name': 'Router'}, 'type': 'function'}]} name='general'
---
Output from node 'customer':
---
content="To help you with that, I'll need your customer ID. Could you provide it, please?" name='customer'
---
User (q/Q to quit): 1
Output from node 'customer':
---
content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_pPUA6QaH2kQG9MRLjRn0PCDY', 'function': {'arguments': '{"customer_id":1}', 'name': 'get_customer_info'}, 'type': 'function'}]} name='customer'
---
Output from node 'tools':
---
[ToolMessage(content="[(1, 'Luís', 'Gonçalves', 'Embraer - Empresa Brasileira de Aeronáutica S.A.', 'Av. Brigadeiro Faria Lima, 2170', 'São José dos Campos', 'SP', 'Brazil', '12227-000', '+55 (12) 3923-5555', '+55 (12) 3923-5566', 'luisg@embraer.com.br', 3)]", additional_kwargs={'name': 'get_customer_info'}, tool_call_id='call_pPUA6QaH2kQG9MRLjRn0PCDY')]
---
Output from node 'customer':
---
content='The email we have on file for you is luisg@embraer.com.br. Is there anything else I can assist you with?' name='customer'
---
User (q/Q to quit): q
AI: Byebye