-: Hey, welcome back.
And in this video we're gonna have a look at
how you can use tools,
and also persistence, inside of LangGraph.
Firstly, we're gonna just import a couple of packages
and then after that we are also going
to see if you have your open AI, API key
specifically for that, as well.
So I'm just gonna run this Python.
And the other thing we're gonna need
is something called Tavily.
And Tavily is a great search engine that you can use,
they offer a free amount specifically of credits.
And the main benefit of using Tavily is
that you can easily connect your large language models
with text-based results that come back from Google search.
So, rather than you having to use a Google search,
API wrapper and get the text from the webpages,
all of that is handled automatically
for you using a package like Tavily.
So that's also one of the requirements.
And then I'm also gonna set my API key for Tavily.
So I'm just gonna copy that in
and then we're gonna use the Tavily results,
and you can see this is the tool that we'll be using,
and we've set a max results of two
and then we wrap that in a Python list
'cause we're gonna use a list of tools
and you can even invoke a tool.
So you can say, like, "What's a node in LangGraph?"
And you'll see that it gives us a couple
of different results and you've got the URL
and the content specifically of those.
Now, the next thing that we need to do
is we need to annotate our state.
And you can see we have a state graph being set up
and that is being set up
using this state-typed dictionary we have,
which has a list of messages
which are also being appended to using
that add messages that will merge in the left
and right messages easily.
So that's a nice helper function
and we're gonna be using the GPT-4 O model
and then also making sure that we've got these tools.
So we have a list of tools that you can see
with the Tavily search results,
and What we're gonna do is then take our chat model,
which is this LLM, and we're gonna bind those tools to it.
And different types of providers like Anthropic
and Grok will also provide you with the ability
to add tools to your large language models.
We've got a function here called chatbot,
which is basically what we had before,
and it returns a key of messages
with the invoked state of the messages with the tools.
So we're gonna add that as a node.
Now we need to create a function
to run the tools if they are called,
just to show you that the sort of
the lower level implementation,
remember that your chat bot can return
that it wants to call several tools in one message.
And so what we need is something
that will have all of the tools
with the names of those tools
and we also have a call here which basically goes
and tries to get the messages.
And if there aren't any messages then it will raise an error
and it makes the outputs,
and for each message doc tool calls,
it will get the tool call,
and it will find that tool by its name
and invoke it with a tool call with the ARGs of that
and it will append the outputs as tool messages
with the content from the json.dumps is the content
and the name and the tool call ID.
The tool call IDs are quite important
because that tells that this, you know,
that what message this tool call is responding to.
And then we return the messages with the outputs
and then you can see we've got a basic tool node
which takes in a bunch of tools
and we can use our original graph builder
to add on this new tool node.
Now once, because we've got two nodes,
now we've got the chat bot node and the tools node,
the next thing we have to do is we have to link these things
and recall that edges are the way that we can connect nodes
inside of a state machine.
So, if we're thinking about a state machine
and how these kinds of nodes, you know, might look,
you might have something that looks a little bit like this
where you've got H for Human
and LLM for Large Language Model
and you could have some types of nodes
where you've got these sort of bubbles and black boxes
and you can see the arrows pointing to the directional flow.
And we can think of these black arrows
in the middle here as the edges.
Okay? And the great thing about LangGraph
is we can have these cyclical loops where, you know,
and we can also have conditional edges.
So, a conditional edge might look like this
where we have a node here
and we decide whether we should progress to this next one.
So I'll do blue for conditional.
We might progress here,
or we might go and we might go and do something else
and then go back into here, right?
So this might be something that we do.
So, you can think of it like a little bit like this,
so, we might go into here,
so this is kind of what I'm thinking.
So we've got a node
and we might conditionally go into this node.
You can think of this as the conditional node.
(keyboard keys rapidly clicking)
And we can think of this as if we've passed
a certain check continue, right?
And we'll go to the continue node.
And what's important about this is we essentially have
between this node here and this continue node,
we have a conditional edge, right?
Sometimes we're going back in, sometimes we're going here.
So, this edge is fairly conditional
and you can think of it like maybe
you will have two different conditions.
One goes to the blue and one goes to the green
and decides to continue.
So that's what we're gonna have a look at now.
It's pretty simple, it's just mapping, you know,
this node to that node based on a condition
and this node to the conditional node,
based on a condition, as well.
So, let's have a look at that.
So we've got this route tools function
and you can see we are returning a string of literal,
so basically just a string of Veeva tools
or this underscore, underscore, end, underscore, underscore.
Now, what you'll see is if we have a list,
get the AI message.
If the messages exist there, we're gonna get that instead.
And if we don't have any of those, we raise an error,
and we basically have this bit where we say,
"If this AI message has tool calls,
and the tool calls is greater than zero,
I want to go into this tools node.
Else, I want to just return with this end."
Now the end is a very special node
because it represents the end of the state machine.
And what we then do is with our route tools function,
we are then going to map the route tools
with the outputs of these.
So you can see we've got the return of tools
and the return of end, the return of tools gets matched
to our tools node that we created earlier, right?
And I'll just scroll up to just remind you,
this is where we created the tools node, right?
So if I was to make this more explicit
I would call this the tools node
and then if I was to go down here,
then this tools would then map to the tools node here.
Okay?
It's this bit which is actually mapping it.
'Cause you can think of it, like,
we have this route tools function,
the outputs of these are the keys inside of the dictionary.
So, tools and end,
which get mapped to either tools or tools node,
depending upon what you decided
to call your tools node.
So, now that I've just gone and update that
and I'll just run this and, yeah,
this has given me an error 'cause that node already exists,
which is fine,
then what we can do is we can compile this graph
and this probably already gives us an error
because we've already compiled the graph.
So ,you can only run these types of things once
because, otherwise, you will get an error.
But if we have a look at our graph, you have the start,
we've set the entry point as the chat bot,
and we've got the different types of of nodes.
So we could also have a look at the nodes,
and you can see we have a start node,
we have a chat bot node and we have a tool node.
Yeah, and the conditional edges is basically going
to do something like this.
If we look, so we'll start here,
we'll go into the chat bot node,
if there are tools, we will go and do those,
and we'll go back into chat bot.
And if there are no more tool calls,
then we will also then just end the chat bot.
And now we can ask the bot questions
outside of its training data.
So, I've got a little while true here
and we have, if the user enters quit or exit or queue,
it will break and print goodbye.
Otherwise, it will stream the graph with the messages
of a two-pull object in the list
where you've got the user and the user input.
And we're just printing out the event values
inside of the LangGraph.
So if I now run this and say,
"Hey, how are you?" (keyboard keys tapping)
There aren't any tool calls here.
So, the assistant has basically gone straight
from the chat bot node directly to the end node, right?
So, there's a very simple architecture we have
here with this conditional edge
where we're either going into the tools node
or we're going to the end node
after we've invoked the model so it can say,
"I am good, thanks. (keyboard keys clicking)
What tools do you have available?"
And this probably also won't call a tool.
It says we've got Tavily search
and we also have multi-tool use.
Now, one thing we might wanna say is, "Can you search
on Google for digital marketing leads?"
And then what it's gonna do is it's then gonna go through
and it's gonna use this chat bot and use the tools
and then it's gonna go back here
and if there aren't any more tools to call,
it's then gonna end.
And so what you can see here is it decided
to call Tavily search here,
and then it also then provided the actual answer here.
So it gave us a marketing deep dive
and so that's basically it.
So, congratulations, you've figured out how
to add a tool in a way which works inside of LangGraph,
which is really, really cool.
Now the next thing that we wanna cover
is adding memory to this chat bot.
So we've got a conditional edge,
which will either call the tool,
or it will just end the chat bot.
Now, the next thing we have to look at
is how do we specifically save the state
of which our graph is currently in?
This is important because you might want to
save the state of the graph,
you might want to reload the graph,
you might wanna update parts of that graph,
and then, you know, getting some human feedback.
And, so, where you can use this concept
called a check pointer,
and we're using the thread ID as the unique identifier
for a specific type of graph state,
which will be automatically saved to a database.
Now, we're using the SQL lite saver
and we're deciding to store that directly inside of memory,
but there are different types of persistence
that you might want to know about.
So there is a persistence notebook
inside of LangChain's documentation
that provides for PostgreSQL.
So I'd recommend having a look at that
and I'll leave a link in this lecture
for how they've implemented the PostgreSQL check pointer
for if you want to use that for your production systems.
Let's have a look at how we could do this
purely using the memory check pointer.
And we are then going to replace the basic tool node
with LangChain's built-in tool node and tools condition,
which basically do the exact same things
with the parallel tool cooling,
but it's basically wrapped up
and you don't have to worry about that.
So, you can see here we're now importing
a couple of prebuilt things.
So you've got this tool node
that basically does the same thing as this.
Right?
So you're getting the tools by the name,
you're deciding what tools to call.
You are then appending the results into the messages,
as tool messages.
And we've also got this tools condition
and this tools condition, you'll see,
if you look at the literal of that,
we'll just either route to tools or it will route to end,
based on whether there are some tools to to see.
So, it's basically that bit there,
You've got that tools condition.
So, then we've got our chat bots, we add our chat bot node,
we are setting up the graph and we have a tool node.
And notice how the tool node now is much slower,
sorry, much of reduced amount of sort of code,
and we add that to the tools node.
Then we add our conditional edges on the chat bot
where it's over routing directly to tools
or it's routing to end.
We add a edge from the tools back
to the chat bot just in case tools are called.
And we also set the entry 0.2 chat bots.
Right.
So, I'm gonna just get rid of this user session
to which is happening above. (keyboard keys clicking)
So I'm just gonna run these.
And, now, instead of just compiling the graph,
we also add a check pointer here.
And that check pointer will basically,
it's an optional check pointer which will save that
to either an online database or local memory.
If we have a look here,
we haven't changed anything inside
of this LangGraph state machine.
We still have the start node,
which is basically chat bot,
and then we go into tools.
If we have any more tools, we then call those
and add into them until we're finished and then we end.
And if there aren't any tools then we directly end.
Now, we set up a runnable config,
and this is the bit that gives you the ability
to load previous checkpoints
and also update previous checkpoints.
So, it's very important that you have a thread ID
inside of your runnable configuration as you pass
that into the app.stream or the graph.stream.
So you can see here, "Hi, my name is James Phoenix,"
we can pass in a message and we pass in the config
and the stream mode just to have a look at the values.
And then we're pretty printing that so you can see
that we've passed in this message
and we then also have, you know,
"How can I help you today?"
And then if we look at a different,
if we look at, like, the same config,
so using the same config as before.
So if we're just having a look at that,
which is the thread ID of one,
then what we can do is say, "Remember my name,"
and you can see that it does know
that my name is James Phoenix, which is a good thing.
Now, the problem that we run into
is if we change this thread ID,
you're gonna see the previous message.
It doesn't really know who I am anymore
because it's not using that thread ID, right?
So, the thread ID is the way that the graph is able
to determine specifically what, you know, who you are.
And that's coming from earlier when we loaded the graph,
we actually used a check pointer, right?
So in real time that check pointer is being loaded
and it's being differently attributed to the thread IDs
inside of the SQL lite database.
Now, if we're having a look, we can also modify,
yeah, so the only change that we've made to the thread ID,
but we can also have a look at a few checkpoints
across these different threads.
So, we can get the state of a graph
by taking the configuration.
So, we've got the thread idea of one
and you can see this is the snapshot.
So you get access to things like the DOT values,
which is all the messages.
And there's also a lot more things
that you can have a look at inside of there.
There's, for example, the.next,
which we'll just have a look at over here.
So the snapshot.next shows that there's nothing else
to do on on the graph, right?
But there's lots of different ones, as well.
You can look at the parent configuration,
there's lots of different attributes
that you might find interesting.
There's metadata, for example,
that you can attach specifically to the graph.
And the.next is quite useful for determining
what nodes would be useful to run next
inside of the state machine.
Now, the next node, you know, obviously, has reached an end.
So, next is empty,
but that's basically a little bit on the persistence
and tool usage inside of LangGraph.
Next, we're gonna have a look at
how we can add human in a loop.
So, we've got a chat bot now,
we've added on the Tavily search tool,
and we've also talked around persistence
and how you can use persistence
to specifically remember previous conversations you've had
using a check pointer with the thread ID
inside of LangGraph.