Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 0 additions & 84 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
},
"dependencies": {
"@langchain/core": "1.1.38",
"@langchain/openai": "1.4.1",
"@langchain/textsplitters": "1.0.1",
"@modelcontextprotocol/sdk": "1.29.0",
"@xenova/transformers": "2.17.2",
Expand Down
20 changes: 4 additions & 16 deletions src/docs/store/DocumentStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest';

// --- Mocking Setup ---

// Mock OpenAIEmbeddings
const mockEmbedDocuments = vi.fn().mockResolvedValue([[0.1, 0.2, 0.3]]); // Keep this if addDocuments is tested elsewhere

// Mock the module to export a mock function for the class constructor
vi.mock('@langchain/openai', () => ({
OpenAIEmbeddings: vi.fn(), // Mock the class export as a vi.fn()
// Mock the embeddings utility
vi.mock('../../utils/embeddings', () => ({
embedDocument: vi.fn().mockResolvedValue([0.1, 0.2, 0.3]),
embedDocuments: vi.fn().mockResolvedValue([[0.1, 0.2, 0.3]]),
}));

// Mock better-sqlite3
Expand Down Expand Up @@ -36,28 +34,18 @@ vi.mock('sqlite-vec', () => ({

// --- Test Suite ---

// Import the mocked constructor function
import { OpenAIEmbeddings } from '@langchain/openai';
// Import DocumentStore AFTER mocks are defined
import { DocumentStore } from './DocumentStore';

// Cast OpenAIEmbeddings to the correct Vitest mock type for configuration
const MockedOpenAIEmbeddingsConstructor = OpenAIEmbeddings as ReturnType<typeof vi.fn>;

describe('DocumentStore', () => {
let documentStore: DocumentStore;

beforeEach(async () => {
vi.clearAllMocks(); // Clear call history etc.

// Configure the mock constructor's implementation for THIS test run
MockedOpenAIEmbeddingsConstructor.mockImplementation(() => ({
embedDocuments: mockEmbedDocuments,
}));
mockPrepare.mockReturnValue(mockStatement); // <-- Re-configure prepare mock return value

// Now create the store and initialize.
// initialize() will call 'new OpenAIEmbeddings()', which uses our fresh mock implementation.
documentStore = new DocumentStore(':memory:');
await documentStore.initialize();
});
Expand Down
73 changes: 73 additions & 0 deletions src/utils/embeddings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';

// 1. Create the mock extractor function using vi.hoisted to ensure it is initialized before imports
const { mockExtractor } = vi.hoisted(() => ({
mockExtractor: vi.fn().mockImplementation((input: string | string[]) => {
const count = Array.isArray(input) ? input.length : 1;
const vectors = Array.from({ length: count }, (_, i) => [
Math.round((0.1 + i * 0.1) * 10) / 10,
Math.round((0.2 + i * 0.1) * 10) / 10,
Math.round((0.3 + i * 0.1) * 10) / 10,
]);
return Promise.resolve({
tolist: () => vectors,
});
}),
}));

// 2. Mock @xenova/transformers pipeline
vi.mock('@xenova/transformers', () => ({
pipeline: vi.fn().mockResolvedValue(mockExtractor),
}));

// 3. Import functions after the mock has been configured
import { embedDocument, embedDocuments } from './embeddings';

describe('embeddings utility', () => {
beforeEach(() => {
vi.clearAllMocks();
});

it('should embed a single document using Xenova feature extraction', async () => {
const result = await embedDocument('test query');

expect(result).toEqual([0.1, 0.2, 0.3]);
expect(mockExtractor).toHaveBeenCalledTimes(1);
expect(mockExtractor).toHaveBeenCalledWith('test query', {
pooling: 'mean',
normalize: true,
});
});

it('should embed an empty string successfully', async () => {
const result = await embedDocument('');

expect(result).toEqual([0.1, 0.2, 0.3]);
expect(mockExtractor).toHaveBeenCalledTimes(1);
expect(mockExtractor).toHaveBeenCalledWith('', {
pooling: 'mean',
normalize: true,
});
});

it('should embed multiple documents and return a 2D array of the same length', async () => {
const result = await embedDocuments(['doc1', 'doc2']);

expect(result).toEqual([
[0.1, 0.2, 0.3],
[0.2, 0.3, 0.4],
]);
expect(mockExtractor).toHaveBeenCalledTimes(1);
expect(mockExtractor).toHaveBeenCalledWith(['doc1', 'doc2'], {
pooling: 'mean',
normalize: true,
});
});

it('should return an empty array without calling the extractor if empty array is passed', async () => {
const result = await embedDocuments([]);

expect(result).toEqual([]);
expect(mockExtractor).not.toHaveBeenCalled();
});
});
3 changes: 3 additions & 0 deletions src/utils/embeddings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export async function embedDocument(q: string): Promise<number[]> {
}

export async function embedDocuments(q: string[]): Promise<number[][]> {
if (q.length === 0) {
return [];
}
const out = await extractor(q, { pooling: 'mean', normalize: true });
return out.tolist();
}