Conversational Retrieval Agents
This is an agent specifically optimized for doing retrieval when necessary while holding a conversation and being able to answer questions based on previous dialogue in the conversation.
To start, we will set up the retriever we want to use, then turn it into a retriever tool. Next, we will use the high-level constructor for this type of agent. Finally, we will walk through how to construct a conversational retrieval agent from components.
The Retriever
To start, we need a retriever to use! The code here is mostly just example code. Feel free to use your own retriever and skip to the next section on creating a retriever tool.
import { FaissStore } from "langchain/vectorstores/faiss";
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
import { TextLoader } from "langchain/document_loaders/fs/text";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
const loader = new TextLoader("state_of_the_union.txt");
const docs = await loader.load();
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 0,
});
const texts = await splitter.splitDocuments(docs);
const vectorStore = await FaissStore.fromDocuments(
texts,
new OpenAIEmbeddings()
);
const retriever = vectorStore.asRetriever();
Retriever Tool
Now we need to create a tool for our retriever. The main things we need to pass in are a name
for the retriever as well as a description
. These will both be used by the language model, so they should be informative.
import { createRetrieverTool } from "langchain/agents/toolkits";
const tool = createRetrieverTool(retriever, {
name: "search_state_of_union",
description:
"Searches and returns documents regarding the state-of-the-union.",
});
Agent Constructor
Here, we will use the high level create_conversational_retrieval_agent
API to construct the agent.
Notice that beside the list of tools, the only thing we need to pass in is a language model to use.
Under the hood, this agent is using the OpenAIFunctionsAgent, so we need to use an ChatOpenAI model.
import { createConversationalRetrievalAgent } from "langchain/agents/toolkits";
import { ChatOpenAI } from "langchain/chat_models/openai";
const model = new ChatOpenAI({
temperature: 0,
});
const executor = await createConversationalRetrievalAgent(model, [tool], {
verbose: true,
});
We can now try it out!
const result = await executor.invoke({
input: "Hi, I'm Bob!",
});
console.log(result);
/*
{
output: 'Hello Bob! How can I assist you today?',
intermediateSteps: []
}
*/
const result2 = await executor.invoke({
input: "What's my name?",
});
console.log(result2);
/*
{ output: 'Your name is Bob.', intermediateSteps: [] }
*/
const result3 = await executor.invoke({
input:
"What did the president say about Ketanji Brown Jackson in the most recent state of the union?",
});
console.log(result3);
/*
{
output: "In the most recent state of the union, President Biden mentioned Ketanji Brown Jackson. He nominated her as a Circuit Court of Appeals judge and described her as one of the nation's top legal minds who will continue Justice Breyer's legacy of excellence. He mentioned that she has received a broad range of support, including from the Fraternal Order of Police and former judges appointed by Democrats and Republicans.",
intermediateSteps: [
{...}
]
}
*/
const result4 = await executor.invoke({
input: "How long ago did he nominate her?",
});
console.log(result4);
/*
{
output: 'President Biden nominated Ketanji Brown Jackson four days before the most recent state of the union address.',
intermediateSteps: []
}
*/
Note that for the final call, the agent used previously retrieved information to answer the query and did not need to call the tool again!
Here's a trace showing how the agent fetches documents to answer the question with the retrieval tool:
https://smith.langchain.com/public/1e2b1887-ca44-4210-913b-a69c1b8a8e7e/r
Creating from components
What actually is going on underneath the hood? Let's take a look so we can understand how to modify things going forward.
Memory
In this example, we want the agent to remember not only previous conversations, but also previous intermediate steps.
For that, we can use OpenAIAgentTokenBufferMemory
. Note that if you want to change whether the agent remembers intermediate steps,
how the long the retained buffer is, or anything like that you should change this part.
import { OpenAIAgentTokenBufferMemory } from "langchain/agents/toolkits";
const memory = new OpenAIAgentTokenBufferMemory({
llm: model,
memoryKey: "chat_history",
outputKey: "output",
});
You should make sure memoryKey
is set to "chat_history"
and outputKey
is set to "output"
for the OpenAI functions agent.
This memory also has returnMessages
set to true
by default.
You can also load messages from prior conversations into this memory by initializing it with a pre-loaded chat history:
import { ChatOpenAI } from "langchain/chat_models/openai";
import { OpenAIAgentTokenBufferMemory } from "langchain/agents/toolkits";
import { HumanMessage, AIMessage } from "langchain/schema";
import { ChatMessageHistory } from "langchain/memory";
const previousMessages = [
new HumanMessage("My name is Bob"),
new AIMessage("Nice to meet you, Bob!"),
];
const chatHistory = new ChatMessageHistory(previousMessages);
const memory = new OpenAIAgentTokenBufferMemory({
llm: new ChatOpenAI({}),
memoryKey: "chat_history",
outputKey: "output",
chatHistory,
});
Agent executor
We can recreate the agent executor directly with the initializeAgentExecutorWithOptions
method.
This allows us to customize the agent's system message by passing in a prefix
into agentArgs
.
Importantly, we must pass in return_intermediate_steps: true
since we are recording that with our memory object.
import { initializeAgentExecutorWithOptions } from "langchain/agents";
const executor = await initializeAgentExecutorWithOptions(tools, llm, {
agentType: "openai-functions",
memory,
returnIntermediateSteps: true,
agentArgs: {
prefix:
prefix ??
`Do your best to answer the questions. Feel free to use any tools available to look up relevant information, only if necessary.`,
},
});