By: Samuel Chan · October 10, 2024
Table of Content
AgentExecutor
(in chapter 1 and chapter 2).LangGraph
library and
a prebuilt ReAct agent that comes with LangGraph.
To make use of the LangGraph
library, you will need to install it:
@tool
decoratorcreate_react_agent
to create our agent equipped with tools we created from (2)@tool
decorator, we turn our functions into LangChain’s structured tools, of the type <class 'langchain_core.tools.structured.StructuredTool'>
, and
these tools can be used, either directly or indirectly by our agent.
Just to see how the tools work, let us invoke them directly:
ReAct
agent yet. In fact, we don’t even have a Language Model (neither Llama3.1 nor GPT-4) to work with.
Here is the result of the above code:
prompt
object and bind our tools to a LLM model of our choice.
Instead of invoking each tool directly like we did earlier, we will create a runnable
that chains the prompt with the tool-use LLM model.
Our expectation is to be able to prompt the runnable with something like “overview of BBRI” and have the agent invoke the correct tool — in this case, get_company_overview
— along with
the correct parameters for us.
.tool_calls
.
At this point, you might be tempted to chain the output of tool_calls
to further runnables, thus actually calling the API for information retrieval
and then using structuring the output into the desired format. However, LangChain provides some utility functions to make this process easier. Recall from
chapter 2 we have the AgentExecutor
class that helps us orchestrate the tools and the LLM model:
ReAct
agent instead, as it is
also now the recommended way to create agents in LangChain. The AgentExecutor
class is still available,
but its official documentation now recommends the use of ReAct
agents instead.
We explore the use of LLMs to generate both reasoning traces and task-specific actions in an interleaved manner, allowing for greater synergy between the two: reasoning traces help the model induce, track, and update action plans as well as handle exceptions, while actions allow it to interface with external sources, such as knowledge bases or environments, to gather additional information. ReAct outperforms imitation and reinforcement learning methods by an absolute success rate of 34% and 10% respectively, while being prompted with only one or two in-context examples.
create_react_agent
function from langgraph.prebuilt
. This function requires two arguments:
model
: The LLM model to usetools
: A list of tools to bind to the agent, essentially replacing the llm.bind_tools(tools)
set-up we did earlierstate_modifier
: A function that modifies the state of the agent. This is useful for adding system messages or other state changes to the agent.create_react_agent
is of type <class 'langgraph.graph.state.CompiledStateGraph'>
which conveniently also implements the
invoke
method. The most basic usage of the agent is as follows:
.invoke()
method takes a dictionary with a key messages
and a value that is a string. The output is a dictionary that would contain, among other things,
a messages
key with a list of HumanMessage
and AIMessage
objects.
state_modifier
and another utility function
to simplify the invocation of the agent.
get_company_overview
tool. Even then, the response
would be in the original format returned by the API, i.e. a JSON object — making it less readable for the end-user.
With the ReAct agent, we’re now querying in natural language, and getting a far more human-friendly response, generated
by the agent.
We can also query the agent for the top 5 companies ranked by a certain metric:
state_modifier
were also
being followed as the agent responded with a markdown-formatted answer (ordered lists, along with bolded text syntax).
print
ing the
the response as they come in, rather than waiting for the entire response to be generated before displaying it.
.invoke()
gets us a final response;.stream()
let us stream messages as they occur (useful if agent takes a while, i.e. multiple steps)flush
the buffer!flush=True
argument in print
forces the buffer to be written to disk immediately, thus flushing
the buffer. In the case of streaming responses from our AI agent, this has the benefit of ensuring
our users see the output as soon as it is generated, rather than waiting for the buffer to fill up.json.loads
(or json.dumps
) on the partial JSON, the parsing would fail due to
the incomplete syntax.
The solution is to apply the parser on the input stream so that it could attempt to auto-complete the partial json into
a valid and complete JSON object.
Here is a simple example of one such implementation: