fix: process embeddings sequentially, shorten prompts, fix tests
- main.ts: process files sequentially (not Promise.all) to avoid concurrent embedding requests hammering Ollama; batch size reduced to 1 - vectorization.ts: shorten embedding prompts from 1000 to 500 chars, limit headings to 5, remove frontmatter from prompt to stay well within embedding model context window - Update vectorization and indexing-pipeline tests for new prompt format
This commit is contained in:
@@ -9507,10 +9507,10 @@ var ContentVectorizer = class {
|
|||||||
const parts = [
|
const parts = [
|
||||||
chunk.title,
|
chunk.title,
|
||||||
chunk.firstParagraph,
|
chunk.firstParagraph,
|
||||||
chunk.content.substring(0, 1e3),
|
chunk.content.substring(0, 500),
|
||||||
// Limit content to avoid long prompts
|
// Limit content to avoid long prompts
|
||||||
chunk.headings.join(" "),
|
chunk.headings.slice(0, 5).join(" ")
|
||||||
JSON.stringify(chunk.frontmatter)
|
// Limit headings
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
return parts.join("\n\n");
|
return parts.join("\n\n");
|
||||||
}
|
}
|
||||||
@@ -10033,7 +10033,7 @@ var OllamaPlugin = class extends import_obsidian4.Plugin {
|
|||||||
const files = this.app.vault.getMarkdownFiles();
|
const files = this.app.vault.getMarkdownFiles();
|
||||||
Logger.info(`Starting background vault indexing for ${files.length} files...`, "main");
|
Logger.info(`Starting background vault indexing for ${files.length} files...`, "main");
|
||||||
let indexed = 0;
|
let indexed = 0;
|
||||||
const BATCH_SIZE = 2;
|
const BATCH_SIZE = 1;
|
||||||
const DELAY_MS = 500;
|
const DELAY_MS = 500;
|
||||||
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
@@ -10041,20 +10041,18 @@ var OllamaPlugin = class extends import_obsidian4.Plugin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const batch = files.slice(i, i + BATCH_SIZE);
|
const batch = files.slice(i, i + BATCH_SIZE);
|
||||||
await Promise.all(
|
for (const file of batch) {
|
||||||
batch.map(async (file) => {
|
if (signal.aborted) break;
|
||||||
if (signal.aborted) return;
|
|
||||||
try {
|
try {
|
||||||
const content = await this.app.vault.read(file);
|
const content = await this.app.vault.read(file);
|
||||||
if (signal.aborted) return;
|
if (signal.aborted) break;
|
||||||
await this.vaultVectorStore.indexFile(file, content);
|
await this.vaultVectorStore.indexFile(file, content);
|
||||||
indexed++;
|
indexed++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
Logger.warn(`Failed to index ${file.path}: ${errorMessage}`, "main");
|
Logger.warn(`Failed to index ${file.path}: ${errorMessage}`, "main");
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
);
|
|
||||||
if (i + BATCH_SIZE < files.length) {
|
if (i + BATCH_SIZE < files.length) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, DELAY_MS));
|
await new Promise((resolve) => setTimeout(resolve, DELAY_MS));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,13 +92,13 @@ export class ContentVectorizer {
|
|||||||
* Creates a prompt from content chunk for embedding
|
* Creates a prompt from content chunk for embedding
|
||||||
*/
|
*/
|
||||||
private createPrompt(chunk: ContentChunk): string {
|
private createPrompt(chunk: ContentChunk): string {
|
||||||
// Combine important elements for embedding
|
// Combine important elements for embedding, keeping it concise
|
||||||
|
// to avoid exceeding the embedding model's context window
|
||||||
const parts = [
|
const parts = [
|
||||||
chunk.title,
|
chunk.title,
|
||||||
chunk.firstParagraph,
|
chunk.firstParagraph,
|
||||||
chunk.content.substring(0, 1000), // Limit content to avoid long prompts
|
chunk.content.substring(0, 500), // Limit content to avoid long prompts
|
||||||
chunk.headings.join(' '),
|
chunk.headings.slice(0, 5).join(' '), // Limit headings
|
||||||
JSON.stringify(chunk.frontmatter),
|
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
return parts.join('\n\n');
|
return parts.join('\n\n');
|
||||||
|
|||||||
+6
-7
@@ -166,7 +166,7 @@ export default class OllamaPlugin extends Plugin {
|
|||||||
Logger.info(`Starting background vault indexing for ${files.length} files...`, 'main');
|
Logger.info(`Starting background vault indexing for ${files.length} files...`, 'main');
|
||||||
|
|
||||||
let indexed = 0;
|
let indexed = 0;
|
||||||
const BATCH_SIZE = 2;
|
const BATCH_SIZE = 1;
|
||||||
const DELAY_MS = 500;
|
const DELAY_MS = 500;
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
||||||
@@ -176,20 +176,19 @@ export default class OllamaPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const batch = files.slice(i, i + BATCH_SIZE);
|
const batch = files.slice(i, i + BATCH_SIZE);
|
||||||
await Promise.all(
|
// Process files sequentially to avoid concurrent embedding requests
|
||||||
batch.map(async (file) => {
|
for (const file of batch) {
|
||||||
if (signal.aborted) return;
|
if (signal.aborted) break;
|
||||||
try {
|
try {
|
||||||
const content = await this.app.vault.read(file);
|
const content = await this.app.vault.read(file);
|
||||||
if (signal.aborted) return;
|
if (signal.aborted) break;
|
||||||
await this.vaultVectorStore!.indexFile(file, content);
|
await this.vaultVectorStore!.indexFile(file, content);
|
||||||
indexed++;
|
indexed++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
Logger.warn(`Failed to index ${file.path}: ${errorMessage}`, 'main');
|
Logger.warn(`Failed to index ${file.path}: ${errorMessage}`, 'main');
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
);
|
|
||||||
|
|
||||||
// Delay between batches to avoid overloading Ollama
|
// Delay between batches to avoid overloading Ollama
|
||||||
if (i + BATCH_SIZE < files.length) {
|
if (i + BATCH_SIZE < files.length) {
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ Content`;
|
|||||||
firstParagraph: 'First paragraph',
|
firstParagraph: 'First paragraph',
|
||||||
wordCount: 2,
|
wordCount: 2,
|
||||||
chunkIndex: 0,
|
chunkIndex: 0,
|
||||||
chunkSize: 100
|
chunkSize: 100,
|
||||||
};
|
};
|
||||||
|
|
||||||
const prompt = (vectorizer as any).createPrompt(mockChunk);
|
const prompt = (vectorizer as any).createPrompt(mockChunk);
|
||||||
@@ -215,7 +215,7 @@ Content`;
|
|||||||
expect(prompt).toContain('Test');
|
expect(prompt).toContain('Test');
|
||||||
expect(prompt).toContain('First paragraph');
|
expect(prompt).toContain('First paragraph');
|
||||||
expect(prompt).toContain('Heading');
|
expect(prompt).toContain('Heading');
|
||||||
expect(prompt).toContain('tags');
|
// Frontmatter is no longer included in embedding prompts
|
||||||
});
|
});
|
||||||
|
|
||||||
// Note: Actual embedding tests would require mocking fetch or integration testing
|
// Note: Actual embedding tests would require mocking fetch or integration testing
|
||||||
@@ -233,7 +233,7 @@ Content`;
|
|||||||
firstParagraph: 'First paragraph',
|
firstParagraph: 'First paragraph',
|
||||||
wordCount: 2,
|
wordCount: 2,
|
||||||
chunkIndex: 0,
|
chunkIndex: 0,
|
||||||
chunkSize: 100
|
chunkSize: 100,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock fetch to simulate an error
|
// Mock fetch to simulate an error
|
||||||
@@ -290,12 +290,12 @@ This is a test document for pipeline processing.`;
|
|||||||
it('should process files in batches', async () => {
|
it('should process files in batches', async () => {
|
||||||
const files: MockVaultFile[] = [
|
const files: MockVaultFile[] = [
|
||||||
{ basename: 'file1', path: 'file1.md' },
|
{ basename: 'file1', path: 'file1.md' },
|
||||||
{ basename: 'file2', path: 'file2.md' }
|
{ basename: 'file2', path: 'file2.md' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const fileContents = {
|
const fileContents = {
|
||||||
'file1.md': '# File 1\n\nContent 1',
|
'file1.md': '# File 1\n\nContent 1',
|
||||||
'file2.md': '# File 2\n\nContent 2'
|
'file2.md': '# File 2\n\nContent 2',
|
||||||
};
|
};
|
||||||
|
|
||||||
const results = await pipeline.processFilesInBatches(files, fileContents, 1);
|
const results = await pipeline.processFilesInBatches(files, fileContents, 1);
|
||||||
|
|||||||
@@ -200,8 +200,7 @@ describe('ContentVectorizer', () => {
|
|||||||
expect(prompt).toContain('This is the first paragraph');
|
expect(prompt).toContain('This is the first paragraph');
|
||||||
expect(prompt).toContain('Main Heading');
|
expect(prompt).toContain('Main Heading');
|
||||||
expect(prompt).toContain('Sub Heading');
|
expect(prompt).toContain('Sub Heading');
|
||||||
expect(prompt).toContain('test');
|
// Frontmatter is no longer included in embedding prompts
|
||||||
expect(prompt).toContain('2024-01-01');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle empty content fields gracefully', () => {
|
it('should handle empty content fields gracefully', () => {
|
||||||
@@ -221,8 +220,7 @@ describe('ContentVectorizer', () => {
|
|||||||
|
|
||||||
const prompt = (vectorizer as any).createPrompt(chunk);
|
const prompt = (vectorizer as any).createPrompt(chunk);
|
||||||
expect(prompt).toContain('Only content');
|
expect(prompt).toContain('Only content');
|
||||||
// JSON.stringify({}) produces "{}", which is truthy so it's included
|
// Frontmatter is no longer included in embedding prompts
|
||||||
expect(prompt).toContain('{}');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should limit content length', () => {
|
it('should limit content length', () => {
|
||||||
@@ -243,7 +241,7 @@ describe('ContentVectorizer', () => {
|
|||||||
|
|
||||||
const prompt = (vectorizer as any).createPrompt(chunk);
|
const prompt = (vectorizer as any).createPrompt(chunk);
|
||||||
expect(prompt).not.toContain('a'.repeat(1500));
|
expect(prompt).not.toContain('a'.repeat(1500));
|
||||||
expect(prompt).toContain('a'.repeat(1000));
|
expect(prompt).toContain('a'.repeat(500));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle missing frontmatter gracefully', () => {
|
it('should handle missing frontmatter gracefully', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user