-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Recursive k similarity search (#2410)
* feat(vectorstores/base.ts): add support to recursive similarity searches * chore(docs): update language and formatting in the Recursive Similarity Search section * fix: fix docs * feat: move to SimilarityScoreThresholdVectorStoreRetriever * docs(vector_stores): remove unused import CodeBlock docs(vector_stores): remove similarityFilter parameter from asRetriever method * refactor(conversational_retrieval_chain.int.test.ts): remove eslint-disable comment * refactor(vectorstores/base.ts): remove unused import and similarityFilter parameter in VectorStoreRetrieverInput refactor(vectorstores/base.ts): simplify object creation in VectorStoreRetriever method * refactor(similarity-score-threshold.ts): remove debug console.log statement * fix: use the right name convention * fix: use the right name convention * fix(similarity_score_threshold.ts): fix infinite loop condition in SimilarityScoreThresholdVectorStoreRetriever * Refactor * fix(vectorstores/index.ts): rename SimilarityScoreThresholdVectorStoreRetriever to ScoreThresholdVectorStoreRetriever * Rename, fix entrypoints * Add similarity score threshold retriever example * Fix name * Fix formatting * Update docs --------- Co-authored-by: João Melo <jopcmelo@gmail.com>
- Loading branch information
1 parent
23c20da
commit c6899ae
Showing
16 changed files
with
268 additions
and
4 deletions.
There are no files selected for viewing
16 changes: 16 additions & 0 deletions
16
...ules/data_connection/retrievers/how_to/similarity-score-threshold-retriever.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Similarity Score Threshold | ||
|
||
A problem some people may face is that when doing a similarity search, you have to supply a `k` value. This value is responsible for bringing N similar results back to you. But what if you don't know the `k` value? What if you want the system to return all the possible results? | ||
|
||
In a real-world scenario, let's imagine a super long document created by a product manager which describes a product. In this document, we could have 10, 15, 20, 100 or more features described. How to know the correct `k` value so the system returns all the possible results to the question "What are all the features that product X has?". | ||
|
||
To solve this problem, LangChain offers a feature called Recursive Similarity Search. With it, you can do a similarity search without having to rely solely on the `k` value. The system will return all the possible results to your question, based on the minimum similarity percentage you want. | ||
|
||
It is possible to use the Recursive Similarity Search by using a vector store as retriever. | ||
|
||
## Usage | ||
|
||
import CodeBlock from "@theme/CodeBlock"; | ||
import Example from "@examples/retrievers/similarity_score_threshold.ts"; | ||
|
||
<CodeBlock language="typescript">{Example}</CodeBlock> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { MemoryVectorStore } from "langchain/vectorstores/memory"; | ||
import { OpenAIEmbeddings } from "langchain/embeddings/openai"; | ||
import { ScoreThresholdRetriever } from "langchain/retrievers/score_threshold"; | ||
|
||
const vectorStore = await MemoryVectorStore.fromTexts( | ||
[ | ||
"Buildings are made out of brick", | ||
"Buildings are made out of wood", | ||
"Buildings are made out of stone", | ||
"Buildings are made out of atoms", | ||
"Buildings are made out of building materials", | ||
"Cars are made out of metal", | ||
"Cars are made out of plastic", | ||
], | ||
[{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }], | ||
new OpenAIEmbeddings() | ||
); | ||
|
||
const retriever = ScoreThresholdRetriever.fromVectorStore(vectorStore, { | ||
minSimilarityScore: 0.9, // Finds results with at least this similarity score | ||
maxK: 100, // The maximum K value to use. Use it based to your chunk size to make sure you don't run out of tokens | ||
kIncrement: 2, // How much to increase K by each time. It'll fetch N results, then N + kIncrement, then N + kIncrement * 2, etc. | ||
}); | ||
|
||
const result = await retriever.getRelevantDocuments( | ||
"What are buildings made out of?" | ||
); | ||
|
||
console.log(result); | ||
|
||
/* | ||
[ | ||
Document { | ||
pageContent: 'Buildings are made out of building materials', | ||
metadata: { id: 5 } | ||
}, | ||
Document { | ||
pageContent: 'Buildings are made out of wood', | ||
metadata: { id: 2 } | ||
}, | ||
Document { | ||
pageContent: 'Buildings are made out of brick', | ||
metadata: { id: 1 } | ||
}, | ||
Document { | ||
pageContent: 'Buildings are made out of stone', | ||
metadata: { id: 3 } | ||
}, | ||
Document { | ||
pageContent: 'Buildings are made out of atoms', | ||
metadata: { id: 4 } | ||
} | ||
] | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { | ||
VectorStore, | ||
VectorStoreRetriever, | ||
VectorStoreRetrieverInput, | ||
} from "../vectorstores/base.js"; | ||
import { Document } from "../document.js"; | ||
|
||
export type ScoreThresholdRetrieverInput<V extends VectorStore> = Omit< | ||
VectorStoreRetrieverInput<V>, | ||
"k" | ||
> & { | ||
maxK?: number; | ||
kIncrement?: number; | ||
minSimilarityScore: number; | ||
}; | ||
|
||
export class ScoreThresholdRetriever< | ||
V extends VectorStore | ||
> extends VectorStoreRetriever<V> { | ||
minSimilarityScore: number; | ||
|
||
kIncrement = 10; | ||
|
||
maxK = 100; | ||
|
||
constructor(input: ScoreThresholdRetrieverInput<V>) { | ||
super(input); | ||
this.maxK = input.maxK ?? this.maxK; | ||
this.minSimilarityScore = | ||
input.minSimilarityScore ?? this.minSimilarityScore; | ||
this.kIncrement = input.kIncrement ?? this.kIncrement; | ||
} | ||
|
||
async getRelevantDocuments(query: string): Promise<Document[]> { | ||
let currentK = 0; | ||
let filteredResults: [Document, number][] = []; | ||
do { | ||
currentK += this.kIncrement; | ||
const results = await this.vectorStore.similaritySearchWithScore( | ||
query, | ||
currentK, | ||
this.filter | ||
); | ||
filteredResults = results.filter( | ||
([, score]) => score >= this.minSimilarityScore | ||
); | ||
} while (filteredResults.length >= currentK && currentK < this.maxK); | ||
return filteredResults.map((documents) => documents[0]).slice(0, this.maxK); | ||
} | ||
|
||
static fromVectorStore<V extends VectorStore>( | ||
vectorStore: V, | ||
options: Omit<ScoreThresholdRetrieverInput<V>, "vectorStore"> | ||
) { | ||
return new this<V>({ ...options, vectorStore }); | ||
} | ||
} |
111 changes: 111 additions & 0 deletions
111
langchain/src/retrievers/tests/score_threshold.int.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/* eslint-disable no-process-env */ | ||
import { expect, test } from "@jest/globals"; | ||
import { ConversationalRetrievalQAChain } from "../../chains/conversational_retrieval_chain.js"; | ||
import { OpenAIEmbeddings } from "../../embeddings/openai.js"; | ||
import { ChatOpenAI } from "../../chat_models/openai.js"; | ||
import { BufferMemory } from "../../memory/buffer_memory.js"; | ||
import { MemoryVectorStore } from "../../vectorstores/memory.js"; | ||
import { ScoreThresholdRetriever } from "../score_threshold.js"; | ||
|
||
test("ConversationalRetrievalQAChain.fromLLM should use its vector store recursively until it gets all the similar results with the minimum similarity score provided", async () => { | ||
const vectorStore = await MemoryVectorStore.fromTexts( | ||
[ | ||
"Buildings are made out of brick", | ||
"Buildings are made out of wood", | ||
"Buildings are made out of stone", | ||
"Cars are made out of metal", | ||
"Cars are made out of plastic", | ||
], | ||
[{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }], | ||
new OpenAIEmbeddings() | ||
); | ||
|
||
const model = new ChatOpenAI({ | ||
modelName: "gpt-3.5-turbo", | ||
temperature: 0, | ||
}); | ||
|
||
const chain = ConversationalRetrievalQAChain.fromLLM( | ||
model, | ||
ScoreThresholdRetriever.fromVectorStore(vectorStore, { | ||
minSimilarityScore: 0.9, | ||
kIncrement: 1, | ||
}), | ||
{ | ||
returnSourceDocuments: true, | ||
memory: new BufferMemory({ | ||
memoryKey: "chat_history", | ||
inputKey: "question", | ||
outputKey: "text", | ||
}), | ||
} | ||
); | ||
const res = await chain.call({ | ||
question: "Buildings are made out of what?", | ||
}); | ||
|
||
console.log("response:", res); | ||
|
||
expect(res).toEqual( | ||
expect.objectContaining({ | ||
text: expect.any(String), | ||
sourceDocuments: expect.arrayContaining([ | ||
expect.objectContaining({ | ||
metadata: expect.objectContaining({ | ||
id: 1, | ||
}), | ||
}), | ||
expect.objectContaining({ | ||
metadata: expect.objectContaining({ | ||
id: 2, | ||
}), | ||
}), | ||
expect.objectContaining({ | ||
metadata: expect.objectContaining({ | ||
id: 3, | ||
}), | ||
}), | ||
]), | ||
}) | ||
); | ||
}); | ||
|
||
test("ConversationalRetrievalQAChain.fromLLM should use its vector store to get up to X results that matches the provided similarity score", async () => { | ||
const vectorStore = await MemoryVectorStore.fromTexts( | ||
[ | ||
"Buildings are made out of brick", | ||
"Buildings are made out of wood", | ||
"Buildings are made out of stone", | ||
"Cars are made out of metal", | ||
"Cars are made out of plastic", | ||
], | ||
[{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }], | ||
new OpenAIEmbeddings() | ||
); | ||
|
||
const model = new ChatOpenAI({ | ||
modelName: "gpt-3.5-turbo", | ||
temperature: 0, | ||
}); | ||
|
||
const chain = ConversationalRetrievalQAChain.fromLLM( | ||
model, | ||
ScoreThresholdRetriever.fromVectorStore(vectorStore, { | ||
minSimilarityScore: 0.9, | ||
maxK: 2, | ||
}), | ||
{ | ||
returnSourceDocuments: true, | ||
memory: new BufferMemory({ | ||
memoryKey: "chat_history", | ||
inputKey: "question", | ||
outputKey: "text", | ||
}), | ||
} | ||
); | ||
const res = await chain.call({ | ||
question: "Buildings are made out of what?", | ||
}); | ||
|
||
expect(res.sourceDocuments).toHaveLength(2); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
c6899ae
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
langchainjs-docs – ./
langchainjs-docs-git-main-langchain.vercel.app
langchainjs-docs-ruddy.vercel.app
langchainjs-docs-langchain.vercel.app
js.langchain.com