mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	feat(llm): add utils for excluding notes from LLM
This commit is contained in:
		| @@ -211,5 +211,10 @@ export const LLM_CONSTANTS = { | |||||||
|     CONTENT: { |     CONTENT: { | ||||||
|         MAX_NOTE_CONTENT_LENGTH: 1500, |         MAX_NOTE_CONTENT_LENGTH: 1500, | ||||||
|         MAX_TOTAL_CONTENT_LENGTH: 10000 |         MAX_TOTAL_CONTENT_LENGTH: 10000 | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     // AI Feature Exclusion | ||||||
|  |     AI_EXCLUSION: { | ||||||
|  |         LABEL_NAME: 'aiExclude'  // Label used to exclude notes from all AI/LLM features | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ import cacheManager from '../modules/cache_manager.js'; | |||||||
| import type { NoteSearchResult } from '../../interfaces/context_interfaces.js'; | import type { NoteSearchResult } from '../../interfaces/context_interfaces.js'; | ||||||
| import type { LLMServiceInterface } from '../../interfaces/agent_tool_interfaces.js'; | import type { LLMServiceInterface } from '../../interfaces/agent_tool_interfaces.js'; | ||||||
| import { SEARCH_CONSTANTS } from '../../constants/search_constants.js'; | import { SEARCH_CONSTANTS } from '../../constants/search_constants.js'; | ||||||
|  | import { isNoteExcludedFromAI } from '../../utils/ai_exclusion_utils.js'; | ||||||
|  |  | ||||||
| export interface VectorSearchOptions { | export interface VectorSearchOptions { | ||||||
|     maxResults?: number; |     maxResults?: number; | ||||||
| @@ -118,6 +119,11 @@ export class VectorSearchService { | |||||||
|                         return null; |                         return null; | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|  |                     // Check if this note is excluded from AI features | ||||||
|  |                     if (isNoteExcludedFromAI(note)) { | ||||||
|  |                         return null; // Skip this note if it has the AI exclusion label | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                     // Get note content - full or summarized based on option |                     // Get note content - full or summarized based on option | ||||||
|                     let content: string | null = null; |                     let content: string | null = null; | ||||||
|  |  | ||||||
| @@ -289,6 +295,12 @@ export class VectorSearchService { | |||||||
|  |  | ||||||
|             for (const noteId of noteIds) { |             for (const noteId of noteIds) { | ||||||
|                 try { |                 try { | ||||||
|  |                     // Check if this note is excluded from AI features | ||||||
|  |                     const note = becca.getNote(noteId); | ||||||
|  |                     if (!note || isNoteExcludedFromAI(note)) { | ||||||
|  |                         continue; // Skip this note if it doesn't exist or has the AI exclusion label | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                     // Get note embedding |                     // Get note embedding | ||||||
|                     const embeddingResult = await vectorStore.getEmbeddingForNote( |                     const embeddingResult = await vectorStore.getEmbeddingForNote( | ||||||
|                         noteId, |                         noteId, | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import { deleteNoteEmbeddings } from "./storage.js"; | |||||||
| import type { QueueItem } from "./types.js"; | import type { QueueItem } from "./types.js"; | ||||||
| import { getChunkingOperations } from "./chunking/chunking_interface.js"; | import { getChunkingOperations } from "./chunking/chunking_interface.js"; | ||||||
| import indexService from '../index_service.js'; | import indexService from '../index_service.js'; | ||||||
|  | import { isNoteExcludedFromAIById } from "../utils/ai_exclusion_utils.js"; | ||||||
|  |  | ||||||
| // Track which notes are currently being processed | // Track which notes are currently being processed | ||||||
| const notesInProcess = new Set<string>(); | const notesInProcess = new Set<string>(); | ||||||
| @@ -261,6 +262,17 @@ export async function processEmbeddingQueue() { | |||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             // Check if this note is excluded from AI features | ||||||
|  |             if (isNoteExcludedFromAIById(noteId)) { | ||||||
|  |                 log.info(`Note ${noteId} excluded from AI features, removing from embedding queue`); | ||||||
|  |                 await sql.execute( | ||||||
|  |                     "DELETE FROM embedding_queue WHERE noteId = ?", | ||||||
|  |                     [noteId] | ||||||
|  |                 ); | ||||||
|  |                 await deleteNoteEmbeddings(noteId); // Also remove any existing embeddings | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             if (noteData.operation === 'DELETE') { |             if (noteData.operation === 'DELETE') { | ||||||
|                 await deleteNoteEmbeddings(noteId); |                 await deleteNoteEmbeddings(noteId); | ||||||
|                 await sql.execute( |                 await sql.execute( | ||||||
|   | |||||||
| @@ -8,6 +8,9 @@ import entityChangesService from "../../../services/entity_changes.js"; | |||||||
| import type { EntityChange } from "../../../services/entity_changes_interface.js"; | import type { EntityChange } from "../../../services/entity_changes_interface.js"; | ||||||
| import { EMBEDDING_CONSTANTS } from "../constants/embedding_constants.js"; | import { EMBEDDING_CONSTANTS } from "../constants/embedding_constants.js"; | ||||||
| import { SEARCH_CONSTANTS } from '../constants/search_constants.js'; | import { SEARCH_CONSTANTS } from '../constants/search_constants.js'; | ||||||
|  | import type { NoteEmbeddingContext } from "./embeddings_interface.js"; | ||||||
|  | import becca from "../../../becca/becca.js"; | ||||||
|  | import { isNoteExcludedFromAIById } from "../utils/ai_exclusion_utils.js"; | ||||||
|  |  | ||||||
| interface Similarity { | interface Similarity { | ||||||
|     noteId: string; |     noteId: string; | ||||||
| @@ -452,6 +455,11 @@ async function processEmbeddings(queryEmbedding: Float32Array, embeddings: any[] | |||||||
|             : ''; |             : ''; | ||||||
|  |  | ||||||
|         for (const e of embeddings) { |         for (const e of embeddings) { | ||||||
|  |             // Check if this note is excluded from AI features | ||||||
|  |             if (isNoteExcludedFromAIById(e.noteId)) { | ||||||
|  |                 continue; // Skip this note if it has the AI exclusion label | ||||||
|  |             } | ||||||
|  |  | ||||||
|             const embVector = bufferToEmbedding(e.embedding, e.dimension); |             const embVector = bufferToEmbedding(e.embedding, e.dimension); | ||||||
|  |  | ||||||
|             // Detect content type from mime type if available |             // Detect content type from mime type if available | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ import sql from "../sql.js"; | |||||||
| import sqlInit from "../sql_init.js"; | import sqlInit from "../sql_init.js"; | ||||||
| import { CONTEXT_PROMPTS } from './constants/llm_prompt_constants.js'; | import { CONTEXT_PROMPTS } from './constants/llm_prompt_constants.js'; | ||||||
| import { SEARCH_CONSTANTS } from './constants/search_constants.js'; | import { SEARCH_CONSTANTS } from './constants/search_constants.js'; | ||||||
|  | import { isNoteExcludedFromAI } from "./utils/ai_exclusion_utils.js"; | ||||||
|  |  | ||||||
| export class IndexService { | export class IndexService { | ||||||
|     private initialized = false; |     private initialized = false; | ||||||
| @@ -803,6 +804,12 @@ export class IndexService { | |||||||
|                 throw new Error(`Note ${noteId} not found`); |                 throw new Error(`Note ${noteId} not found`); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             // Check if this note is excluded from AI features | ||||||
|  |             if (isNoteExcludedFromAI(note)) { | ||||||
|  |                 log.info(`Note ${noteId} (${note.title}) excluded from AI indexing due to exclusion label`); | ||||||
|  |                 return true; // Return true to indicate successful handling (exclusion is intentional) | ||||||
|  |             } | ||||||
|  |  | ||||||
|             // Check where embedding generation should happen |             // Check where embedding generation should happen | ||||||
|             const embeddingLocation = await options.getOption('embeddingGenerationLocation') || 'client'; |             const embeddingLocation = await options.getOption('embeddingGenerationLocation') || 'client'; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										94
									
								
								apps/server/src/services/llm/utils/ai_exclusion_utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								apps/server/src/services/llm/utils/ai_exclusion_utils.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | import becca from '../../../becca/becca.js'; | ||||||
|  | import type BNote from '../../../becca/entities/bnote.js'; | ||||||
|  | import { LLM_CONSTANTS } from '../constants/provider_constants.js'; | ||||||
|  | import log from '../../log.js'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Check if a note should be excluded from all AI/LLM features | ||||||
|  |  * | ||||||
|  |  * @param note - The note to check (BNote object) | ||||||
|  |  * @returns true if the note should be excluded from AI features | ||||||
|  |  */ | ||||||
|  | export function isNoteExcludedFromAI(note: BNote): boolean { | ||||||
|  |     if (!note) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |         // Check if the note has the AI exclusion label | ||||||
|  |         const hasExclusionLabel = note.hasLabel(LLM_CONSTANTS.AI_EXCLUSION.LABEL_NAME); | ||||||
|  |  | ||||||
|  |         if (hasExclusionLabel) { | ||||||
|  |             log.info(`Note ${note.noteId} (${note.title}) excluded from AI features due to ${LLM_CONSTANTS.AI_EXCLUSION.LABEL_NAME} label`); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return false; | ||||||
|  |     } catch (error) { | ||||||
|  |         log.error(`Error checking AI exclusion for note ${note.noteId}: ${error}`); | ||||||
|  |         return false; // Default to not excluding on error | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Check if a note should be excluded from AI features by noteId | ||||||
|  |  * | ||||||
|  |  * @param noteId - The ID of the note to check | ||||||
|  |  * @returns true if the note should be excluded from AI features | ||||||
|  |  */ | ||||||
|  | export function isNoteExcludedFromAIById(noteId: string): boolean { | ||||||
|  |     if (!noteId) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |         const note = becca.getNote(noteId); | ||||||
|  |         if (!note) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         return isNoteExcludedFromAI(note); | ||||||
|  |     } catch (error) { | ||||||
|  |         log.error(`Error checking AI exclusion for note ID ${noteId}: ${error}`); | ||||||
|  |         return false; // Default to not excluding on error | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Filter out notes that are excluded from AI features | ||||||
|  |  * | ||||||
|  |  * @param notes - Array of notes to filter | ||||||
|  |  * @returns Array of notes with AI-excluded notes removed | ||||||
|  |  */ | ||||||
|  | export function filterAIExcludedNotes(notes: BNote[]): BNote[] { | ||||||
|  |     return notes.filter(note => !isNoteExcludedFromAI(note)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Filter out note IDs that are excluded from AI features | ||||||
|  |  * | ||||||
|  |  * @param noteIds - Array of note IDs to filter | ||||||
|  |  * @returns Array of note IDs with AI-excluded notes removed | ||||||
|  |  */ | ||||||
|  | export function filterAIExcludedNoteIds(noteIds: string[]): string[] { | ||||||
|  |     return noteIds.filter(noteId => !isNoteExcludedFromAIById(noteId)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Check if any notes in an array are excluded from AI features | ||||||
|  |  * | ||||||
|  |  * @param notes - Array of notes to check | ||||||
|  |  * @returns true if any note should be excluded from AI features | ||||||
|  |  */ | ||||||
|  | export function hasAIExcludedNotes(notes: BNote[]): boolean { | ||||||
|  |     return notes.some(note => isNoteExcludedFromAI(note)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Get the AI exclusion label name from constants | ||||||
|  |  * This can be used in UI components or other places that need to reference the label | ||||||
|  |  * | ||||||
|  |  * @returns The label name used for AI exclusion | ||||||
|  |  */ | ||||||
|  | export function getAIExclusionLabelName(): string { | ||||||
|  |     return LLM_CONSTANTS.AI_EXCLUSION.LABEL_NAME; | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user