Handle Cases Where No Queries are Generated
Sometimes, a query analysis technique may allow for any number of queries to be generated - including no queries! In this case, our overall chain will need to inspect the result of the query analysis before deciding whether to call the retriever or not.
We will use mock data for this example.
Setupβ
Install dependenciesβ
- npm
- yarn
- pnpm
npm i @langchain/core @langchain/community @langchain/openai zod chromadb
yarn add @langchain/core @langchain/community @langchain/openai zod chromadb
pnpm add @langchain/core @langchain/community @langchain/openai zod chromadb
Set environment variablesβ
OPENAI_API_KEY=your-api-key
# Optional, use LangSmith for best-in-class observability
LANGSMITH_API_KEY=your-api-key
LANGCHAIN_TRACING_V2=true
Create Indexβ
We will create a vectorstore over fake information.
import { Chroma } from "@langchain/community/vectorstores/chroma";
import { OpenAIEmbeddings } from "@langchain/openai";
import "chromadb";
const texts = ["Harrison worked at Kensho"];
const embeddings = new OpenAIEmbeddings({ model: "text-embedding-3-small" });
const vectorstore = await Chroma.fromTexts(texts, {}, embeddings, {
collectionName: "harrison",
});
const retriever = vectorstore.asRetriever(1);
[Module: null prototype] {
AdminClient: [class AdminClient],
ChromaClient: [class ChromaClient],
CloudClient: [class CloudClient extends ChromaClient],
CohereEmbeddingFunction: [class CohereEmbeddingFunction],
Collection: [class Collection],
DefaultEmbeddingFunction: [class _DefaultEmbeddingFunction],
GoogleGenerativeAiEmbeddingFunction: [class _GoogleGenerativeAiEmbeddingFunction],
HuggingFaceEmbeddingServerFunction: [class HuggingFaceEmbeddingServerFunction],
IncludeEnum: {
Documents: "documents",
Embeddings: "embeddings",
Metadatas: "metadatas",
Distances: "distances"
},
JinaEmbeddingFunction: [class JinaEmbeddingFunction],
OpenAIEmbeddingFunction: [class _OpenAIEmbeddingFunction],
TransformersEmbeddingFunction: [class _TransformersEmbeddingFunction]
}
Query analysisβ
We will use function calling to structure the output. However, we will configure the LLM such that is doesnβt NEED to call the function representing a search query (should it decide not to). We will also then use a prompt to do query analysis that explicitly lays when it should and shouldnβt make a search.
import { z } from "zod";
const searchSchema = z.object({
query: z.string().describe("Similarity search query applied to job record."),
});
Pick your chat model:
- OpenAI
- Anthropic
- FireworksAI
- MistralAI
Install dependencies
- npm
- yarn
- pnpm
npm i @langchain/openai
yarn add @langchain/openai
pnpm add @langchain/openai
Add environment variables
OPENAI_API_KEY=your-api-key
Instantiate the model
import { ChatOpenAI } from "@langchain/openai";
const llm = new ChatOpenAI({
model: "gpt-3.5-turbo-0125",
temperature: 0
});
Install dependencies
- npm
- yarn
- pnpm
npm i @langchain/anthropic
yarn add @langchain/anthropic
pnpm add @langchain/anthropic
Add environment variables
ANTHROPIC_API_KEY=your-api-key
Instantiate the model
import { ChatAnthropic } from "@langchain/anthropic";
const llm = new ChatAnthropic({
model: "claude-3-sonnet-20240229",
temperature: 0
});
Install dependencies
- npm
- yarn
- pnpm
npm i @langchain/community
yarn add @langchain/community
pnpm add @langchain/community
Add environment variables
FIREWORKS_API_KEY=your-api-key
Instantiate the model
import { ChatFireworks } from "@langchain/community/chat_models/fireworks";
const llm = new ChatFireworks({
model: "accounts/fireworks/models/firefunction-v1",
temperature: 0
});
Install dependencies
- npm
- yarn
- pnpm
npm i @langchain/mistralai
yarn add @langchain/mistralai
pnpm add @langchain/mistralai
Add environment variables
MISTRAL_API_KEY=your-api-key
Instantiate the model
import { ChatMistralAI } from "@langchain/mistralai";
const llm = new ChatMistralAI({
model: "mistral-large-latest",
temperature: 0
});
import { zodToJsonSchema } from "zod-to-json-schema";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import {
RunnableSequence,
RunnablePassthrough,
} from "@langchain/core/runnables";
const system = `You have the ability to issue search queries to get information to help answer user information.
You do not NEED to look things up. If you don't need to, then just respond normally.`;
const prompt = ChatPromptTemplate.fromMessages([
["system", system],
["human", "{question}"],
]);
const llmWithTools = llm.bind({
tools: [
{
type: "function" as const,
function: {
name: "search",
description: "Search over a database of job records.",
parameters: zodToJsonSchema(searchSchema),
},
},
],
});
const queryAnalyzer = RunnableSequence.from([
{
question: new RunnablePassthrough(),
},
prompt,
llmWithTools,
]);
We can see that by invoking this we get an message that sometimes - but not always - returns a tool call.
await queryAnalyzer.invoke("where did Harrison Work");
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: "",
additional_kwargs: {
function_call: undefined,
tool_calls: [
{
id: "call_uqHm5OMbXBkmqDr7Xzj8EMmd",
type: "function",
function: [Object]
}
]
}
},
lc_namespace: [ "langchain_core", "messages" ],
content: "",
name: undefined,
additional_kwargs: {
function_call: undefined,
tool_calls: [
{
id: "call_uqHm5OMbXBkmqDr7Xzj8EMmd",
type: "function",
function: { name: "search", arguments: '{"query":"Harrison"}' }
}
]
}
}
await queryAnalyzer.invoke("hi!");
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: "Hello! How can I assist you today?",
additional_kwargs: { function_call: undefined, tool_calls: undefined }
},
lc_namespace: [ "langchain_core", "messages" ],
content: "Hello! How can I assist you today?",
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: undefined }
}
Retrieval with query analysisβ
So how would we include this in a chain? Letβs look at an example below.
import { JsonOutputKeyToolsParser } from "@langchain/core/output_parsers/openai_tools";
const outputParser = new JsonOutputKeyToolsParser({
keyName: "search",
});
import { RunnableConfig, RunnableLambda } from "@langchain/core/runnables";
const chain = async (question: string, config?: RunnableConfig) => {
const response = await queryAnalyzer.invoke(question, config);
if (
"tool_calls" in response.additional_kwargs &&
response.additional_kwargs.tool_calls !== undefined
) {
const query = await outputParser.invoke(response, config);
return retriever.invoke(query[0].query, config);
} else {
return response;
}
};
const customChain = new RunnableLambda({ func: chain });
await customChain.invoke("where did Harrison Work");
[ Document { pageContent: "Harrison worked at Kensho", metadata: {} } ]
await customChain.invoke("hi!");
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: "Hello! How can I assist you today?",
additional_kwargs: { function_call: undefined, tool_calls: undefined }
},
lc_namespace: [ "langchain_core", "messages" ],
content: "Hello! How can I assist you today?",
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: undefined }
}