-: Hey, welcome back.
And in this short video,
what we're gonna cover and start introducing
is this concept called agents inside a LangChain.
Now, agents are really fascinating,
and they're very good at doing task planning, for example,
and executing lots of different things
in a very dynamic way.
But before we go into any code,
I think it's worthwhile us thinking
about some high-level concepts that LangChain provides.
So you've got this idea of an agent action,
which is what you might decide a tool that's gonna be used,
and we'll talk about tools in a bit
when the agent is finished.
So there's this idea of the eventually,
the agent is gonna finish,
and that it will return some result.
Now there's some series of intermediate steps.
And if I scroll down a bit and show you,
you can almost think of an agent like this,
where the agent gets an action,
and when the action isn't a finish,
we then run the next action.
And then we get an observation, and with the observation,
we then decide to get another action.
So we've provided it almost like an endless loop of actions,
observations, getting the next action,
and then whilst the next action isn't a finish,
we keep running in this endless loop.
So that's a little bit on agents
and the concepts that are happening here.
Now, as you're probably all aware of,
agents use something called tools.
And tools, if you can think of it,
are basically just Python functions,
or some way to interact with the environment.
Now if we have a look at tools,
there's a variety of different ways
that you can define tools.
You've got some default tools that are provided to you
from the LangChain documentation,
but essentially, in essence,
a tool is basically a function
that you can have an input into and there's an output
that gets added, right, from using the tool.
A LangChain provides some really great ways for us
to create our own tools.
And so what we're gonna do is have a look at
how we can create tools,
and how we can attach those tools to LangChain agents.
And then the agent can then use those tools
to answer questions.
So let's have a look at this notebook.
And to start with,
we're gonna use the LangChain_community.tools,
and you've got two tools here,
so I'm just gonna make sure my notebook's running
and I'll just run that.
So let's have a look at this.
So what we're gonna do is have a look at this one here.
All right, so in this example, we're using a community tool
which allows us to query Wikipedia.
And you can see there's this API wrapper.
And then we have this tool
that's being made from the Wikipedia query run.
And this eventually will basically make a tool.
Now we know it's a tool in LangChain
because if it's got a name, a description,
and some input arguments, you can see,
the tool name is called Wikipedia.
This is the description, you've got the query.
So this is the args that will go in
and this bit here is whether the tool will automatically
be returned to the user,
or whether the agent will take the output of the tool
and then decide what to return to the user.
Now how do you call a tool?
Well, you can just do the tool.invoke,
and you can see if I run this,
it's gonna start using that tool
and it gives us some information about digital marketing.
Now you can see it's got a doc content chars_max,
so we could also set this to 1,000,
and then we're gonna get a lot more content back
about that example.
Now one thing that's often the case
is you'll want to create your own tools.
And your tools are gonna need a name, a description,
and an arg schema, which is optional.
The name is required and so the two optional ones
are the description and the argschema,
but it can be really nice to include those.
Now the first way that you can easily create a tool
is you could have a Python function,
like where you gonna do a Google search?
And we could take a query in here now.
For now we're just returning LangChain,
which is a type of string,
and we wrap that specific Python function
with this tool decorator.
And then based on that, you will then from the doc string,
it will add that into the description.
So your doc strings,
you want to make them very descriptive
about what kind of tools your agents can use.
The name of the tool comes from the Python function
that you can see here.
And the argschema or the .args in this case,
get associated with the query.
And this is basically the function signature.
So you've got one input parameter here, which is a query,
which is the type of string.
Now you could also modify your inputs.
And so you can see here we've got a didactic model,
and we have one input which is a query type of string,
but we've also added on a description
to make it a bit more descriptive.
And you can now see we've also changed the name
of the tools to search-tool.
And we've also said that this output
should be immediately returned to the user,
and not the agent if it's the final finish.
So you can also make tools from the search function
in terms of a structured tool .from function,
where the function is here, you give it the name,
and the description and you can even add on the argschema
and all that good stuff.
Now this shows you how to make tools,
but let's see how do we add these onto agents?
The easiest way to do that is you would have some function.
So for example, we've got the get_word_length,
we'd make our list of tools and we'd call that a tools list.
And so what you end up here with is you've got
that tools-list, which has a bunch of tools.
Now once you have your bunch of tools,
I personally find that the best way to use
these LangChain agents is to write your own custom agents.
Now it's quite easy to do that.
There's a couple of different things that you'll need.
So the first is you'll need a chat prompt template,
and then you can have whatever you want
in the system message.
You need some form of input to take the original query.
And then this is the most important bit
that you have to have if you're building
your own custom LangChain expression language agents.
And basically this agent scratch pad
is how LangChain will store all of those actions
and observations so that it can use that in memory
to then decide what to do next.
'Cause remember there was that endless feedback loop
of do an action, get an observation,
decide on the next action, have we finished?
Oh, we haven't finished.
Let's go and run that next action again,
until you end up finishing.
So that agent scratch pad is really important
because that stores all of the kind of intermediate steps
that LangChain uses.
And remember the input here is basically
what you can use to specifically decide on the first query
that hits this agent.
Now after defining the chat prompt template,
you'll then need to bind tools to your agent.
And basically OpenAI have recently moved away
from function calling,
and they've moved to binding tools
and using tools directly.
So we use the .bind_tools function
and that takes a list of tools and you've got a model
and your model also needs to be capable of calling tools.
Now once you've got your model
and you've bound the tools to that specific model,
it knows about those tools,
but we still need to set up the formatting
to easily convert the intermediate steps
that are inside the agent scratch pad
into something that OpenAI understands.
So what's happening is these intermediate steps
that the agents are scratch pad down,
I've taken this action and run these tools,
it's formatting those to the OpenAI tool messages
and then after that gets put into the prompt,
we then have the model that is with the tools
and we also pass the tools outputs
back into these intermediate steps
so that the agent executor then can easily understand those
and see what's happening with those.
So after you've got your agent set up in an LCL chain,
you would then add that onto the agent executor.
You also pass the tools here
and you can also put in verbose-True
to make sure that comes across
and you can see all the various steps.
Now if I just .stream the output
and you can see the input
is gonna be mapped if we scroll back up to this input here
in the user message, and we're saying something like,
how many letters in the word data.
And by doing agent_executed.stream,
we can actually see all the different steps
that happen inside of the agency.
You've got the actions,
it decided to take a tool agent action,
and it used this get_word_length tool
and we can also see what the final output is,
so the word data is four letters
and this is the messages that ended with.
Now you wouldn't always stream these
and the way that you would normally use these
if you just wanted to get the final output
is you would use that .invoke function.
And by changing that to the .invoke,
you'll see that we just get the final output,
which will come through in just a second.
So you get this input, how many letters is in the word data,
and you get the output, the word data has four letters.
Now that's great,
but we don't currently have any memory inside of this agent.
It can just use a tool,
and it can use that to help solve a question.
Now one thing that we can do to add memory in
is we can add another messages placeholder,
and you'll see we've got two messages placeholders.
So one for the memory key,
which is we've called chat history.
And that chat history, that key name,
the variable name here is very important.
And you'll see this has also got our agent scratch pad.
Now we create a Python list called Chat History,
and we updated the LCL chain.
So basically we're getting the chat history
and then that's then being formatted into the prompt.
So you've got both the chat history
and the agent scratch pad
that are now being associated
with these message placeholders, yeah?
So you can add as many message placeholders as you want,
but you have to make sure that
before that enters the prompt,
you've got some output for those
inside of your prompt template that goes into the prompt.
Then it goes into the model
and we pass back the agent tools agent
using an output passer.
The agent executor's exactly the same.
But you'll see we've got this first input,
how many letters in the word data,
and we've now had to add on that chat history
as the extra one, which is just a Python list.
Now we then extend the chat history with a human message
with the input one,
and the AI's message that it came back with a result output.
And then we can say, is that a real word?
And so we can have a look. So firstly it's gonna figure out
that it needs to invoke this tool called the get_word_length
and it uses the input for that as data
and then it basically work,
this is the output it came back with,
so the word data is four letters.
And then after that,
then it entered a new agent executed chain.
But rather than deciding to invoke a tool,
it decided to just reply with text.
And that's quite an interesting thing,
because you don't always have to invoke tools
when using agents.
They can use a mixture of just replying in text
or using a tool.
And in OpenAI tools,
you can also run multiple tools in parallel,
which can make agents incredibly versatile
at not only being able to handle certain queries,
but also being able to handle a mixture of queries.
And we can see that the second response
is saying yes, data is a real word.
It refers to facts and statistics collected together
for reference or analysis.
So it is actually referring back to the fact
that we asked it a question about what's the data?
And so what's the count for that word.
Now one thing that can be quite nice to do
is obviously we've done this,
but I just wanna show you another way of doing the memory.
You could create, so when we're building these chains,
they are something called a runnable.
And a runnable is a something that can be ran.
It's a LangChain expression
that's basically compiled into a graph.
Now if we go back down here,
we can also do something like this,
where we can create an empty store,
which is gonna store our chat messages
and we can do something with runnable with message history,
and we can specify a session ID.
And so what you'll see here is
we've changed the variable name to history,
just to show you how this works.
The agent chain therefore has to update
and it has to use history.
The agent scratch pad stays the same.
And what we've also got here is a get_session history
that takes a session ID,
looks inside of that Python dictionary, which is the store.
And then after we've looked inside
of that Python dictionary, we add on that session ID
and we create this chat message history.
And then we return the session ID.
Now we can then wrap the agent executor
with that Python function
and the input messages key is gonna be the input.
So remember that's our input here,
and you can see that in the ACL inside of this agent chain.
And the history messages key is the history.
Now once we've done that,
now what we can do is we can invoke,
put our input, the history,
set the config for the runnable config,
and we say it's configurable with a session ID,
some_session_ID.
And what's happening now is the store,
if we have a look at it,
is actually gonna start to be populated with a session ID
which has a chat message history attached to it.
So this is an in-memory way
of doing configurable chat message histories,
which is being associated with this specific dictionary,
this Python dictionary, this store Python dictionary.
Now if we then say, I said, I've told it my name is James,
and now we can also say what is my name?
And we're using the same session ID,
which we've called some_session_ID,
and the agent executor hasn't used any tools yet.
It's still got the ability to count with the word length,
but it's decided to just reply in text.
And I think that's an important point.
And you can also see here
that because we are using the same session ID
in our Runnable config,
we've then also been able to then get the LLM
to store that chat history in a in-memory Python dictionary.
And the output then says, your name is James.
So it is able to remember who we are
by using that with message history runnable.
Now if I change the session ID to something different,
you'll see that we then get a very different response
from the agent,
where it decides you haven't mentioned your name yet,
please could you tell me your name, and I'll remember it.
So hopefully this gives you a good introduction to agents.
In future videos,
we'll be exploring how you can build agents
directly inside of things like line graph,
and also how you could build multiple tools
and looking at parallel tool usage.