Skip to content

Commit 6446b56

Browse files
authored
@jakmro/filtering feature (#10)
* feat: result filtering based on predicate function * chore: bump version
1 parent a6c63b3 commit 6446b56

10 files changed

Lines changed: 94 additions & 57 deletions

File tree

README.md

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Private, local RAGs. Supercharge LLMs with your own knowledge base.
2121
- [Interfaces (for Custom Components)](#interfaces-for-custom-components)
2222
- [Text Splitters](#text-splitters)
2323
- [Utilities](#utilities)
24+
- [Types](#types)
2425
- [:jigsaw: Using Custom Components](#jigsaw-using-custom-components)
2526
- [:electric_plug: Plugins](#electric_plug-plugins)
2627
- [:handshake: Contributing](#handshake-contributing)
@@ -272,11 +273,12 @@ The core class for managing the RAG workflow.
272273

273274
* `async load(): Promise<this>`: Initializes the vector store and loads the LLM.
274275
* `async unload(): Promise<void>`: Unloads the vector store and LLM.
275-
* `async generate(input: Message[] | string, options?: { augmentedGeneration?: boolean; k?: number; questionGenerator?: Function; promptGenerator?: Function; callback?: (token: string) => void }): Promise<string>` Generates a response.
276+
* `async generate(input: Message[] | string, options?: { augmentedGeneration?: boolean; k?: number; predicate?: (value: SearchResult) => boolean; questionGenerator?: Function; promptGenerator?: Function; callback?: (token: string) => void }): Promise<string>` Generates a response.
276277
* `input` (`Message[] | string`): A string or an array of `Message` objects.
277278
* `options` (object, optional): Generation options.
278279
* `augmentedGeneration` (`boolean`, optional): If `true` (default), retrieves context from the vector store to augment the prompt.
279280
* `k` (`number`, optional): Number of documents to retrieve (default: `3`).
281+
* `predicate` (`function`, optional): Function to filter retrieved documents (default: includes all).
280282
* `questionGenerator` (`function`, optional): Custom question generator.
281283
* `promptGenerator` (`function`, optional): Custom prompt generator.
282284
* `callback` (`function`, optional): A function that receives tokens as they are generated.
@@ -322,7 +324,7 @@ These interfaces define the contracts for creating your own custom components.
322324
* `add(document: string, metadata?: Record<string, any>): Promise<string>`: Adds a document.
323325
* `update(id: string, document?: string, metadata?: Record<string, any>): Promise<void>`: Updates a document.
324326
* `delete(id: string): Promise<void>`: Deletes a document.
325-
* `similaritySearch(query: string, k?: number): Promise<{ id: string; content: string; ... }[]>`: Searches for `k` similar documents.
327+
* `similaritySearch(query: string, k?: number, predicate?: (value: SearchResult) => boolean): Promise<SearchResult[]>`: Searches for `k` similar documents. Which can be filtered with an optional `predicate` function.
326328

327329
#### `TextSplitter`
328330

@@ -345,6 +347,24 @@ The library provides wrappers around common `langchain` text splitters. All spli
345347
* `dotProduct(a: number[], b: number[]): number`: Calculates the dot product of two vectors.
346348
* `magnitude(a: number[]): number`: Calculates the Euclidean magnitude of a vector.
347349

350+
### Types
351+
352+
```typescript
353+
interface Message {
354+
role: 'user' | 'assistant' | 'system';
355+
content: string;
356+
}
357+
358+
type ResourceSource = string | number | object;
359+
360+
interface SearchResult {
361+
id: string;
362+
content: string;
363+
metadata?: Record<string, any>;
364+
similarity: number;
365+
}
366+
```
367+
348368
## :jigsaw: Using Custom Components
349369

350370
Bring your own components by creating classes that implement the `LLM`, `Embeddings`, `VectorStore` and `TextSplitter` interfaces. This allows you to use any model or service that fits your needs.
@@ -382,15 +402,9 @@ interface VectorStore {
382402
delete(id: string): Promise<void>;
383403
similaritySearch(
384404
query: string,
385-
k?: number
386-
): Promise<
387-
{
388-
id: string;
389-
content: string;
390-
metadata?: Record<string, any>;
391-
similarity: number;
392-
}[]
393-
>;
405+
k?: number,
406+
predicate?: (value: SearchResult) => boolean
407+
): Promise<SearchResult[]>;
394408
}
395409
```
396410

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-rag",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"description": "Private, local RAGs. Supercharge LLMs with your own knowledge base.",
55
"main": "./lib/module/index.js",
66
"types": "./lib/typescript/src/index.d.ts",

packages/op-sqlite/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,13 @@ The `OPSQLiteVectorStore` class implements the `VectorStore` interface from `rea
9494

9595
* **Throws**: An error if the document with the specified `id` does not exist.
9696

97-
* #### `similaritySearch(query: string, k?: number): Promise<object[]>`
97+
* #### `similaritySearch(query: string, k?: number, predicate?: (value: SearchResult) => boolean): Promise<SearchResult[]>`
9898

9999
Finds the most relevant documents in the store based on a query string. It generates an embedding for the query and returns the top `k` documents that are most similar.
100100

101101
* `query`: The text to search for.
102102
* `k`: The number of similar documents to return. Defaults to `3`.
103+
* `predicate`: An optional function to filter the search results. It receives each result object and should return `true` to include the result or `false` to exclude it. If not provided, all results are included.
103104
* **Returns**: A `Promise` that resolves with an array of result objects, each containing `{ id, content, metadata, similarity }`. The `similarity` score ranges from 0 to 1, where 1 means a perfect match.
104105

105106
* #### `deleteVectorStore(): Promise<void>`

packages/op-sqlite/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@react-native-rag/op-sqlite",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"main": "src/index.ts",
55
"scripts": {
66
"test": "echo \"Error: no test specified\" && exit 1"

packages/op-sqlite/src/wrappers/op-sqlite.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { type VectorStore, type Embeddings, uuidv4 } from 'react-native-rag';
1+
import {
2+
type VectorStore,
3+
type Embeddings,
4+
uuidv4,
5+
type SearchResult,
6+
} from 'react-native-rag';
27
import { open, type DB } from '@op-engineering/op-sqlite';
38

49
export interface OPSQLiteVectorStoreParams {
@@ -107,27 +112,35 @@ export class OPSQLiteVectorStore implements VectorStore {
107112
}
108113
}
109114

110-
async similaritySearch(query: string, k: number = 3) {
115+
async similaritySearch(
116+
query: string,
117+
k: number = 3,
118+
predicate: (value: SearchResult) => boolean = () => true
119+
): Promise<SearchResult[]> {
111120
const queryEmbedding = await this.embeddings.embed(query);
112121
const vectorStr = `[${queryEmbedding.join(',')}]`;
113-
const results = await this.db.execute(
122+
const queryResult = await this.db.execute(
114123
`
115124
SELECT id, content, vector_distance_cos(embedding, vector(?)) as cosine_distance, metadata
116125
FROM vectors
117-
ORDER BY cosine_distance ASC
118-
LIMIT ?;
126+
ORDER BY cosine_distance ASC;
119127
`,
120-
[vectorStr, k]
128+
[vectorStr]
121129
);
122-
return results.rows.map((row) => {
123-
const id = row.id as string;
124-
const content = row.content as string;
125-
const metadata = row.metadata
126-
? (JSON.parse(row.metadata as string) as Record<string, any>)
127-
: undefined;
128-
const similarity = 1 - (row.cosine_distance as number); // Convert cosine distance to similarity
129-
return { id, content, metadata, similarity };
130-
});
130+
const results = Array.from<SearchResult>(
131+
queryResult.rows.map((row: any) => {
132+
const id = row.id as string;
133+
const content = row.content as string;
134+
const metadata = row.metadata
135+
? (JSON.parse(row.metadata as string) as Record<string, any>)
136+
: undefined;
137+
const similarity = 1 - (row.cosine_distance as number); // Convert cosine distance to similarity
138+
return { id, content, metadata, similarity };
139+
})
140+
)
141+
.filter(predicate)
142+
.slice(0, k);
143+
return results;
131144
}
132145

133146
async deleteVectorStore() {

src/hooks/rag.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect, useState, useCallback, useMemo } from 'react';
22
import { RAG, type RAGParams } from '../rag/rag';
3-
import type { Message } from '../types/common';
3+
import type { Message, SearchResult } from '../types/common';
44
import type { TextSplitter } from '../interfaces/textSplitter';
55

66
/**
@@ -75,7 +75,7 @@ export function useRAG({ vectorStore, llm, preventLoad }: UseRAGParams) {
7575
/**
7676
* Generates a text response.
7777
* @param {Message[] | string} input - Input messages or query.
78-
* @param {object} [options={}] - Generation options (augmentedGeneration, k, questionGenerator, promptGenerator).
78+
* @param {object} [options={}] - Generation options (augmentedGeneration, k, predicate, questionGenerator, promptGenerator).
7979
* @returns {Promise<string>} Complete generated string.
8080
* @throws {Error} If not ready or busy.
8181
*/
@@ -85,6 +85,7 @@ export function useRAG({ vectorStore, llm, preventLoad }: UseRAGParams) {
8585
options: {
8686
augmentedGeneration?: boolean;
8787
k?: number;
88+
predicate?: (value: SearchResult) => boolean;
8889
questionGenerator?: (messages: Message[]) => string;
8990
promptGenerator?: (
9091
messages: Message[],

src/interfaces/vectorStore.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { SearchResult } from '../types/common';
2+
13
/**
24
* Defines the essential operations for a vector store.
35
* A vector store efficiently stores and retrieves high-dimensional vectors,
@@ -49,18 +51,13 @@ export interface VectorStore {
4951
/**
5052
* Performs a similarity search against the stored vectors.
5153
* @param query The query string to search for.
52-
* @param k The number of top similar results to return. Defaults to 3.
54+
* @param k The number of top similar results to return.
55+
* @param predicate An optional function to filter results based on custom criteria.
5356
* @returns An array of objects containing the ID, content, metadata, and similarity score for each result.
5457
*/
5558
similaritySearch(
5659
query: string,
57-
k?: number
58-
): Promise<
59-
{
60-
id: string;
61-
content: string;
62-
metadata?: Record<string, any>;
63-
similarity: number;
64-
}[]
65-
>;
60+
k?: number,
61+
predicate?: (value: SearchResult) => boolean
62+
): Promise<SearchResult[]>;
6663
}

src/rag/rag.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { RecursiveCharacterTextSplitter } from '../text-splitters/langchain';
2-
import type { Message } from '../types/common';
2+
import type { Message, SearchResult } from '../types/common';
33
import type { LLM } from '../interfaces/llm';
44
import type { TextSplitter } from '../interfaces/textSplitter';
55
import type { VectorStore } from '../interfaces/vectorStore';
@@ -127,6 +127,7 @@ Context: ${retrievedDocs.map((doc) => doc.content).join('\n')}`;
127127
* @param options Optional parameters for generation:
128128
* - `augmentedGeneration`: Whether to use augmented generation with retrieved documents (default: true).
129129
* - `k`: Number of documents to retrieve (default: 3).
130+
* - `predicate`: Function to filter retrieved documents (default: includes all).
130131
* - `questionGenerator`: Function to generate the question from messages for the document retrieval step. (default: uses last user message).
131132
* - `promptGenerator`: Function to generate the prompt from messages and retrieved documents (default: uses last user message and retrieved docs).
132133
* - `callback`: Optional callback function to handle token generation.
@@ -137,6 +138,7 @@ Context: ${retrievedDocs.map((doc) => doc.content).join('\n')}`;
137138
options: {
138139
augmentedGeneration?: boolean;
139140
k?: number;
141+
predicate?: (value: SearchResult) => boolean;
140142
questionGenerator?: (messages: Message[]) => string;
141143
promptGenerator?: (
142144
messages: Message[],
@@ -155,6 +157,7 @@ Context: ${retrievedDocs.map((doc) => doc.content).join('\n')}`;
155157
const {
156158
augmentedGeneration = true,
157159
k = 3,
160+
predicate = () => true,
158161
questionGenerator = this.questionGenerator,
159162
promptGenerator = this.promptGenerator,
160163
callback = () => {},
@@ -170,7 +173,8 @@ Context: ${retrievedDocs.map((doc) => doc.content).join('\n')}`;
170173
}
171174
const retrievedDocs = await this.vectorStore.similaritySearch(
172175
questionGenerator(input),
173-
k
176+
k,
177+
predicate
174178
);
175179
const prompt = promptGenerator(input, retrievedDocs);
176180
const augmentedInput: Message[] = [

src/types/common.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,13 @@ export interface Message {
2121
* This type is flexible to cover different model loading strategies.
2222
*/
2323
export type ResourceSource = string | number | object;
24+
25+
/**
26+
* Defines the structure of a search result from a vector store.
27+
*/
28+
export interface SearchResult {
29+
id: string;
30+
content: string;
31+
metadata?: Record<string, any>;
32+
similarity: number;
33+
}

src/vector_stores/memoryVectorStore.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Embeddings } from '../interfaces/embeddings';
22
import type { VectorStore } from '../interfaces/vectorStore';
3+
import type { SearchResult } from '../types/common';
34
import { uuidv4 } from '../utils/uuidv4';
45
import { cosine } from '../utils/vectorMath';
56

@@ -86,24 +87,20 @@ export class MemoryVectorStore implements VectorStore {
8687

8788
async similaritySearch(
8889
query: string,
89-
k: number = 3
90-
): Promise<
91-
{
92-
id: string;
93-
content: string;
94-
metadata?: Record<string, any>;
95-
similarity: number;
96-
}[]
97-
> {
90+
k: number = 3,
91+
predicate: (value: SearchResult) => boolean = () => true
92+
): Promise<SearchResult[]> {
9893
const queryEmbedding = await this.embeddings.embed(query);
99-
const results = Array.from(this.memoryVectors.values()).map(
100-
(memoryVector) => ({
94+
const results = Array.from(this.memoryVectors.values())
95+
.map((memoryVector) => ({
10196
id: memoryVector.id,
10297
content: memoryVector.content,
10398
metadata: memoryVector.metadata,
10499
similarity: cosine(queryEmbedding, memoryVector.embedding),
105-
})
106-
);
107-
return results.sort((a, b) => b.similarity - a.similarity).slice(0, k);
100+
}))
101+
.filter(predicate)
102+
.sort((a, b) => b.similarity - a.similarity)
103+
.slice(0, k);
104+
return results;
108105
}
109106
}

0 commit comments

Comments
 (0)