Merge pull request #2205 from TriliumNext/feat/llm-remove-embeddings

Remove Embeddings from LLM feature
This commit is contained in:
Elian Doran
2025-06-07 22:54:05 +03:00
committed by GitHub
84 changed files with 543 additions and 10463 deletions

View File

@@ -48,17 +48,6 @@ interface AnthropicModel {
* type: string
* type:
* type: string
* embeddingModels:
* type: array
* items:
* type: object
* properties:
* id:
* type: string
* name:
* type: string
* type:
* type: string
* '500':
* description: Error listing models
* security:
@@ -90,14 +79,10 @@ async function listModels(req: Request, res: Response) {
type: 'chat'
}));
// Anthropic doesn't currently have embedding models
const embeddingModels: AnthropicModel[] = [];
// Return the models list
return {
success: true,
chatModels,
embeddingModels
chatModels
};
} catch (error: any) {
log.error(`Error listing Anthropic models: ${error.message || 'Unknown error'}`);

View File

@@ -1,843 +0,0 @@
import options from "../../services/options.js";
import vectorStore from "../../services/llm/embeddings/index.js";
import providerManager from "../../services/llm/providers/providers.js";
import indexService from "../../services/llm/index_service.js";
import becca from "../../becca/becca.js";
import type { Request, Response } from "express";
import log from "../../services/log.js";
import sql from "../../services/sql.js";
/**
* @swagger
* /api/llm/embeddings/similar/{noteId}:
* get:
* summary: Find similar notes based on a given note ID
* operationId: embeddings-similar-by-note
* parameters:
* - name: noteId
* in: path
* required: true
* schema:
* type: string
* - name: providerId
* in: query
* required: false
* schema:
* type: string
* default: openai
* description: Embedding provider ID
* - name: modelId
* in: query
* required: false
* schema:
* type: string
* default: text-embedding-3-small
* description: Embedding model ID
* - name: limit
* in: query
* required: false
* schema:
* type: integer
* default: 10
* description: Maximum number of similar notes to return
* - name: threshold
* in: query
* required: false
* schema:
* type: number
* format: float
* default: 0.7
* description: Similarity threshold (0.0-1.0)
* responses:
* '200':
* description: List of similar notes
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* similarNotes:
* type: array
* items:
* type: object
* properties:
* noteId:
* type: string
* title:
* type: string
* similarity:
* type: number
* format: float
* '400':
* description: Invalid request parameters
* '404':
* description: Note not found
* security:
* - session: []
* tags: ["llm"]
*/
async function findSimilarNotes(req: Request, res: Response) {
const noteId = req.params.noteId;
const providerId = req.query.providerId as string || 'openai';
const modelId = req.query.modelId as string || 'text-embedding-3-small';
const limit = parseInt(req.query.limit as string || '10', 10);
const threshold = parseFloat(req.query.threshold as string || '0.7');
if (!noteId) {
return [400, {
success: false,
message: "Note ID is required"
}];
}
const embedding = await vectorStore.getEmbeddingForNote(noteId, providerId, modelId);
if (!embedding) {
// If no embedding exists for this note yet, generate one
const note = becca.getNote(noteId);
if (!note) {
return [404, {
success: false,
message: "Note not found"
}];
}
const context = await vectorStore.getNoteEmbeddingContext(noteId);
const provider = providerManager.getEmbeddingProvider(providerId);
if (!provider) {
return [400, {
success: false,
message: `Embedding provider '${providerId}' not found`
}];
}
const newEmbedding = await provider.generateNoteEmbeddings(context);
await vectorStore.storeNoteEmbedding(noteId, providerId, modelId, newEmbedding);
const similarNotes = await vectorStore.findSimilarNotes(
newEmbedding, providerId, modelId, limit, threshold
);
return {
success: true,
similarNotes
};
}
const similarNotes = await vectorStore.findSimilarNotes(
embedding.embedding, providerId, modelId, limit, threshold
);
return {
success: true,
similarNotes
};
}
/**
* @swagger
* /api/llm/embeddings/search:
* post:
* summary: Search for notes similar to provided text
* operationId: embeddings-search-by-text
* parameters:
* - name: providerId
* in: query
* required: false
* schema:
* type: string
* default: openai
* description: Embedding provider ID
* - name: modelId
* in: query
* required: false
* schema:
* type: string
* default: text-embedding-3-small
* description: Embedding model ID
* - name: limit
* in: query
* required: false
* schema:
* type: integer
* default: 10
* description: Maximum number of similar notes to return
* - name: threshold
* in: query
* required: false
* schema:
* type: number
* format: float
* default: 0.7
* description: Similarity threshold (0.0-1.0)
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* text:
* type: string
* description: Text to search with
* responses:
* '200':
* description: List of similar notes
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* similarNotes:
* type: array
* items:
* type: object
* properties:
* noteId:
* type: string
* title:
* type: string
* similarity:
* type: number
* format: float
* '400':
* description: Invalid request parameters
* security:
* - session: []
* tags: ["llm"]
*/
async function searchByText(req: Request, res: Response) {
const { text } = req.body;
const providerId = req.query.providerId as string || 'openai';
const modelId = req.query.modelId as string || 'text-embedding-3-small';
const limit = parseInt(req.query.limit as string || '10', 10);
const threshold = parseFloat(req.query.threshold as string || '0.7');
if (!text) {
return [400, {
success: false,
message: "Search text is required"
}];
}
const provider = providerManager.getEmbeddingProvider(providerId);
if (!provider) {
return [400, {
success: false,
message: `Embedding provider '${providerId}' not found`
}];
}
// Generate embedding for the search text
const embedding = await provider.generateEmbeddings(text);
// Find similar notes
const similarNotes = await vectorStore.findSimilarNotes(
embedding, providerId, modelId, limit, threshold
);
return {
success: true,
similarNotes
};
}
/**
* @swagger
* /api/llm/embeddings/providers:
* get:
* summary: Get available embedding providers
* operationId: embeddings-get-providers
* responses:
* '200':
* description: List of available embedding providers
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* providers:
* type: array
* items:
* type: object
* properties:
* id:
* type: string
* name:
* type: string
* isEnabled:
* type: boolean
* priority:
* type: integer
* config:
* type: object
* security:
* - session: []
* tags: ["llm"]
*/
async function getProviders(req: Request, res: Response) {
const providerConfigs = await providerManager.getEmbeddingProviderConfigs();
return {
success: true,
providers: providerConfigs
};
}
/**
* @swagger
* /api/llm/embeddings/providers/{providerId}:
* patch:
* summary: Update embedding provider configuration
* operationId: embeddings-update-provider
* parameters:
* - name: providerId
* in: path
* required: true
* schema:
* type: string
* description: Provider ID to update
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* enabled:
* type: boolean
* description: Whether provider is enabled
* priority:
* type: integer
* description: Priority order (lower is higher priority)
* config:
* type: object
* description: Provider-specific configuration
* responses:
* '200':
* description: Provider updated successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* '400':
* description: Invalid provider ID or configuration
* security:
* - session: []
* tags: ["llm"]
*/
async function updateProvider(req: Request, res: Response) {
const { providerId } = req.params;
const { isEnabled, priority, config } = req.body;
const success = await providerManager.updateEmbeddingProviderConfig(
providerId, isEnabled, priority
);
if (!success) {
return [404, {
success: false,
message: "Provider not found"
}];
}
return {
success: true
};
}
/**
* @swagger
* /api/llm/embeddings/reprocess:
* post:
* summary: Reprocess embeddings for all notes
* operationId: embeddings-reprocess-all
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* providerId:
* type: string
* description: Provider ID to use for reprocessing
* modelId:
* type: string
* description: Model ID to use for reprocessing
* forceReprocess:
* type: boolean
* description: Whether to reprocess notes that already have embeddings
* responses:
* '200':
* description: Reprocessing started
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* jobId:
* type: string
* message:
* type: string
* '400':
* description: Invalid provider ID or configuration
* security:
* - session: []
* tags: ["llm"]
*/
async function reprocessAllNotes(req: Request, res: Response) {
// Import cls
const cls = (await import("../../services/cls.js")).default;
// Start the reprocessing operation in the background
setTimeout(async () => {
try {
// Wrap the operation in cls.init to ensure proper context
cls.init(async () => {
await indexService.reprocessAllNotes();
log.info("Embedding reprocessing completed successfully");
});
} catch (error: any) {
log.error(`Error during background embedding reprocessing: ${error.message || "Unknown error"}`);
}
}, 0);
// Return the response data
return {
success: true,
message: "Embedding reprocessing started in the background"
};
}
/**
* @swagger
* /api/llm/embeddings/queue-status:
* get:
* summary: Get status of the embedding processing queue
* operationId: embeddings-queue-status
* parameters:
* - name: jobId
* in: query
* required: false
* schema:
* type: string
* description: Optional job ID to get status for a specific processing job
* responses:
* '200':
* description: Queue status information
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* status:
* type: string
* enum: [idle, processing, paused]
* progress:
* type: number
* format: float
* description: Progress percentage (0-100)
* details:
* type: object
* security:
* - session: []
* tags: ["llm"]
*/
async function getQueueStatus(req: Request, res: Response) {
// Use the imported sql instead of requiring it
const queueCount = await sql.getValue(
"SELECT COUNT(*) FROM embedding_queue"
);
const failedCount = await sql.getValue(
"SELECT COUNT(*) FROM embedding_queue WHERE attempts > 0"
);
const totalEmbeddingsCount = await sql.getValue(
"SELECT COUNT(*) FROM note_embeddings"
);
return {
success: true,
status: {
queueCount,
failedCount,
totalEmbeddingsCount
}
};
}
/**
* @swagger
* /api/llm/embeddings/stats:
* get:
* summary: Get embedding statistics
* operationId: embeddings-stats
* responses:
* '200':
* description: Embedding statistics
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* stats:
* type: object
* properties:
* totalEmbeddings:
* type: integer
* providers:
* type: object
* modelCounts:
* type: object
* lastUpdated:
* type: string
* format: date-time
* security:
* - session: []
* tags: ["llm"]
*/
async function getEmbeddingStats(req: Request, res: Response) {
const stats = await vectorStore.getEmbeddingStats();
return {
success: true,
stats
};
}
/**
* @swagger
* /api/llm/embeddings/failed:
* get:
* summary: Get list of notes that failed embedding generation
* operationId: embeddings-failed-notes
* responses:
* '200':
* description: List of failed notes
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* failedNotes:
* type: array
* items:
* type: object
* properties:
* noteId:
* type: string
* title:
* type: string
* error:
* type: string
* failedAt:
* type: string
* format: date-time
* security:
* - session: []
* tags: ["llm"]
*/
async function getFailedNotes(req: Request, res: Response) {
const limit = parseInt(req.query.limit as string || '100', 10);
const failedNotes = await vectorStore.getFailedEmbeddingNotes(limit);
// No need to fetch note titles here anymore as they're already included in the response
return {
success: true,
failedNotes: failedNotes
};
}
/**
* @swagger
* /api/llm/embeddings/retry/{noteId}:
* post:
* summary: Retry generating embeddings for a failed note
* operationId: embeddings-retry-note
* parameters:
* - name: noteId
* in: path
* required: true
* schema:
* type: string
* description: Note ID to retry
* - name: providerId
* in: query
* required: false
* schema:
* type: string
* description: Provider ID to use (defaults to configured default)
* - name: modelId
* in: query
* required: false
* schema:
* type: string
* description: Model ID to use (defaults to provider default)
* responses:
* '200':
* description: Retry result
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* '400':
* description: Invalid request
* '404':
* description: Note not found
* security:
* - session: []
* tags: ["llm"]
*/
async function retryFailedNote(req: Request, res: Response) {
const { noteId } = req.params;
if (!noteId) {
return [400, {
success: false,
message: "Note ID is required"
}];
}
const success = await vectorStore.retryFailedEmbedding(noteId);
if (!success) {
return [404, {
success: false,
message: "Failed note not found or note is not marked as failed"
}];
}
return {
success: true,
message: "Note queued for retry"
};
}
/**
* @swagger
* /api/llm/embeddings/retry-all-failed:
* post:
* summary: Retry generating embeddings for all failed notes
* operationId: embeddings-retry-all-failed
* requestBody:
* required: false
* content:
* application/json:
* schema:
* type: object
* properties:
* providerId:
* type: string
* description: Provider ID to use (defaults to configured default)
* modelId:
* type: string
* description: Model ID to use (defaults to provider default)
* responses:
* '200':
* description: Retry started
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* jobId:
* type: string
* security:
* - session: []
* tags: ["llm"]
*/
async function retryAllFailedNotes(req: Request, res: Response) {
const count = await vectorStore.retryAllFailedEmbeddings();
return {
success: true,
message: `${count} failed notes queued for retry`
};
}
/**
* @swagger
* /api/llm/embeddings/rebuild-index:
* post:
* summary: Rebuild the vector store index
* operationId: embeddings-rebuild-index
* responses:
* '200':
* description: Rebuild started
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* jobId:
* type: string
* security:
* - session: []
* tags: ["llm"]
*/
async function rebuildIndex(req: Request, res: Response) {
// Start the index rebuilding operation in the background
setTimeout(async () => {
try {
await indexService.startFullIndexing(true);
log.info("Index rebuilding completed successfully");
} catch (error: any) {
log.error(`Error during background index rebuilding: ${error.message || "Unknown error"}`);
}
}, 0);
// Return the response data
return {
success: true,
message: "Index rebuilding started in the background"
};
}
/**
* @swagger
* /api/llm/embeddings/index-rebuild-status:
* get:
* summary: Get status of the vector index rebuild operation
* operationId: embeddings-rebuild-status
* parameters:
* - name: jobId
* in: query
* required: false
* schema:
* type: string
* description: Optional job ID to get status for a specific rebuild job
* responses:
* '200':
* description: Rebuild status information
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* status:
* type: string
* enum: [idle, in_progress, completed, failed]
* progress:
* type: number
* format: float
* description: Progress percentage (0-100)
* message:
* type: string
* details:
* type: object
* properties:
* startTime:
* type: string
* format: date-time
* processed:
* type: integer
* total:
* type: integer
* security:
* - session: []
* tags: ["llm"]
*/
async function getIndexRebuildStatus(req: Request, res: Response) {
const status = indexService.getIndexRebuildStatus();
return {
success: true,
status
};
}
/**
* Start embedding generation when AI is enabled
*/
async function startEmbeddings(req: Request, res: Response) {
try {
log.info("Starting embedding generation system");
// Initialize the index service if not already initialized
await indexService.initialize();
// Start automatic indexing
await indexService.startEmbeddingGeneration();
return {
success: true,
message: "Embedding generation started"
};
} catch (error: any) {
log.error(`Error starting embeddings: ${error.message || 'Unknown error'}`);
throw new Error(`Failed to start embeddings: ${error.message || 'Unknown error'}`);
}
}
/**
* Stop embedding generation when AI is disabled
*/
async function stopEmbeddings(req: Request, res: Response) {
try {
log.info("Stopping embedding generation system");
// Stop automatic indexing
await indexService.stopEmbeddingGeneration();
return {
success: true,
message: "Embedding generation stopped"
};
} catch (error: any) {
log.error(`Error stopping embeddings: ${error.message || 'Unknown error'}`);
throw new Error(`Failed to stop embeddings: ${error.message || 'Unknown error'}`);
}
}
export default {
findSimilarNotes,
searchByText,
getProviders,
updateProvider,
reprocessAllNotes,
getQueueStatus,
getEmbeddingStats,
getFailedNotes,
retryFailedNote,
retryAllFailedNotes,
rebuildIndex,
getIndexRebuildStatus,
startEmbeddings,
stopEmbeddings
};

View File

@@ -2,8 +2,6 @@ import type { Request, Response } from "express";
import log from "../../services/log.js";
import options from "../../services/options.js";
// Import the index service for knowledge base management
import indexService from "../../services/llm/index_service.js";
import restChatService from "../../services/llm/rest_chat_service.js";
import chatStorageService from '../../services/llm/chat_storage_service.js';
@@ -371,400 +369,13 @@ async function sendMessage(req: Request, res: Response) {
return restChatService.handleSendMessage(req, res);
}
/**
* @swagger
* /api/llm/indexes/stats:
* get:
* summary: Get stats about the LLM knowledge base indexing status
* operationId: llm-index-stats
* responses:
* '200':
* description: Index stats successfully retrieved
* security:
* - session: []
* tags: ["llm"]
*/
async function getIndexStats(req: Request, res: Response) {
try {
// Check if AI is enabled
const aiEnabled = await options.getOptionBool('aiEnabled');
if (!aiEnabled) {
return {
success: false,
message: "AI features are disabled"
};
}
// Return indexing stats
const stats = await indexService.getIndexingStats();
return {
success: true,
...stats
};
} catch (error: any) {
log.error(`Error getting index stats: ${error.message || 'Unknown error'}`);
throw new Error(`Failed to get index stats: ${error.message || 'Unknown error'}`);
}
}
/**
* @swagger
* /api/llm/indexes:
* post:
* summary: Start or continue indexing the knowledge base
* operationId: llm-start-indexing
* requestBody:
* required: false
* content:
* application/json:
* schema:
* type: object
* properties:
* force:
* type: boolean
* description: Whether to force reindexing of all notes
* responses:
* '200':
* description: Indexing started successfully
* security:
* - session: []
* tags: ["llm"]
*/
async function startIndexing(req: Request, res: Response) {
try {
// Check if AI is enabled
const aiEnabled = await options.getOptionBool('aiEnabled');
if (!aiEnabled) {
return {
success: false,
message: "AI features are disabled"
};
}
const { force = false } = req.body;
// Start indexing
await indexService.startFullIndexing(force);
return {
success: true,
message: "Indexing started"
};
} catch (error: any) {
log.error(`Error starting indexing: ${error.message || 'Unknown error'}`);
throw new Error(`Failed to start indexing: ${error.message || 'Unknown error'}`);
}
}
/**
* @swagger
* /api/llm/indexes/failed:
* get:
* summary: Get list of notes that failed to index
* operationId: llm-failed-indexes
* parameters:
* - name: limit
* in: query
* required: false
* schema:
* type: integer
* default: 100
* responses:
* '200':
* description: Failed indexes successfully retrieved
* security:
* - session: []
* tags: ["llm"]
*/
async function getFailedIndexes(req: Request, res: Response) {
try {
// Check if AI is enabled
const aiEnabled = await options.getOptionBool('aiEnabled');
if (!aiEnabled) {
return {
success: false,
message: "AI features are disabled"
};
}
const limit = parseInt(req.query.limit as string || "100", 10);
// Get failed indexes
const failed = await indexService.getFailedIndexes(limit);
return {
success: true,
failed
};
} catch (error: any) {
log.error(`Error getting failed indexes: ${error.message || 'Unknown error'}`);
throw new Error(`Failed to get failed indexes: ${error.message || 'Unknown error'}`);
}
}
/**
* @swagger
* /api/llm/indexes/notes/{noteId}:
* put:
* summary: Retry indexing a specific note that previously failed
* operationId: llm-retry-index
* parameters:
* - name: noteId
* in: path
* required: true
* schema:
* type: string
* responses:
* '200':
* description: Index retry successfully initiated
* security:
* - session: []
* tags: ["llm"]
*/
async function retryFailedIndex(req: Request, res: Response) {
try {
// Check if AI is enabled
const aiEnabled = await options.getOptionBool('aiEnabled');
if (!aiEnabled) {
return {
success: false,
message: "AI features are disabled"
};
}
const { noteId } = req.params;
// Retry indexing the note
const result = await indexService.retryFailedNote(noteId);
return {
success: true,
message: result ? "Note queued for indexing" : "Failed to queue note for indexing"
};
} catch (error: any) {
log.error(`Error retrying failed index: ${error.message || 'Unknown error'}`);
throw new Error(`Failed to retry index: ${error.message || 'Unknown error'}`);
}
}
/**
* @swagger
* /api/llm/indexes/failed:
* put:
* summary: Retry indexing all failed notes
* operationId: llm-retry-all-indexes
* responses:
* '200':
* description: Retry of all failed indexes successfully initiated
* security:
* - session: []
* tags: ["llm"]
*/
async function retryAllFailedIndexes(req: Request, res: Response) {
try {
// Check if AI is enabled
const aiEnabled = await options.getOptionBool('aiEnabled');
if (!aiEnabled) {
return {
success: false,
message: "AI features are disabled"
};
}
// Retry all failed notes
const count = await indexService.retryAllFailedNotes();
return {
success: true,
message: `${count} notes queued for reprocessing`
};
} catch (error: any) {
log.error(`Error retrying all failed indexes: ${error.message || 'Unknown error'}`);
throw new Error(`Failed to retry all indexes: ${error.message || 'Unknown error'}`);
}
}
/**
* @swagger
* /api/llm/indexes/notes/similar:
* get:
* summary: Find notes similar to a query string
* operationId: llm-find-similar-notes
* parameters:
* - name: query
* in: query
* required: true
* schema:
* type: string
* - name: contextNoteId
* in: query
* required: false
* schema:
* type: string
* - name: limit
* in: query
* required: false
* schema:
* type: integer
* default: 5
* responses:
* '200':
* description: Similar notes found successfully
* security:
* - session: []
* tags: ["llm"]
*/
async function findSimilarNotes(req: Request, res: Response) {
try {
// Check if AI is enabled
const aiEnabled = await options.getOptionBool('aiEnabled');
if (!aiEnabled) {
return {
success: false,
message: "AI features are disabled"
};
}
const query = req.query.query as string;
const contextNoteId = req.query.contextNoteId as string | undefined;
const limit = parseInt(req.query.limit as string || "5", 10);
if (!query) {
return {
success: false,
message: "Query is required"
};
}
// Find similar notes
const similar = await indexService.findSimilarNotes(query, contextNoteId, limit);
return {
success: true,
similar
};
} catch (error: any) {
log.error(`Error finding similar notes: ${error.message || 'Unknown error'}`);
throw new Error(`Failed to find similar notes: ${error.message || 'Unknown error'}`);
}
}
/**
* @swagger
* /api/llm/indexes/context:
* get:
* summary: Generate context for an LLM query based on the knowledge base
* operationId: llm-generate-context
* parameters:
* - name: query
* in: query
* required: true
* schema:
* type: string
* - name: contextNoteId
* in: query
* required: false
* schema:
* type: string
* - name: depth
* in: query
* required: false
* schema:
* type: integer
* default: 2
* responses:
* '200':
* description: Context generated successfully
* security:
* - session: []
* tags: ["llm"]
*/
async function generateQueryContext(req: Request, res: Response) {
try {
// Check if AI is enabled
const aiEnabled = await options.getOptionBool('aiEnabled');
if (!aiEnabled) {
return {
success: false,
message: "AI features are disabled"
};
}
const query = req.query.query as string;
const contextNoteId = req.query.contextNoteId as string | undefined;
const depth = parseInt(req.query.depth as string || "2", 10);
if (!query) {
return {
success: false,
message: "Query is required"
};
}
// Generate context
const context = await indexService.generateQueryContext(query, contextNoteId, depth);
return {
success: true,
context
};
} catch (error: any) {
log.error(`Error generating query context: ${error.message || 'Unknown error'}`);
throw new Error(`Failed to generate query context: ${error.message || 'Unknown error'}`);
}
}
/**
* @swagger
* /api/llm/indexes/notes/{noteId}:
* post:
* summary: Index a specific note for LLM knowledge base
* operationId: llm-index-note
* parameters:
* - name: noteId
* in: path
* required: true
* schema:
* type: string
* responses:
* '200':
* description: Note indexed successfully
* security:
* - session: []
* tags: ["llm"]
*/
async function indexNote(req: Request, res: Response) {
try {
// Check if AI is enabled
const aiEnabled = await options.getOptionBool('aiEnabled');
if (!aiEnabled) {
return {
success: false,
message: "AI features are disabled"
};
}
const { noteId } = req.params;
if (!noteId) {
return {
success: false,
message: "Note ID is required"
};
}
// Index the note
const result = await indexService.generateNoteIndex(noteId);
return {
success: true,
message: result ? "Note indexed successfully" : "Failed to index note"
};
} catch (error: any) {
log.error(`Error indexing note: ${error.message || 'Unknown error'}`);
throw new Error(`Failed to index note: ${error.message || 'Unknown error'}`);
}
}
/**
* @swagger
@@ -936,15 +547,5 @@ export default {
listSessions,
deleteSession,
sendMessage,
streamMessage,
// Knowledge base index management
getIndexStats,
startIndexing,
getFailedIndexes,
retryFailedIndex,
retryAllFailedIndexes,
findSimilarNotes,
generateQueryContext,
indexNote
streamMessage
};

View File

@@ -99,12 +99,6 @@ type MetricsData = ReturnType<typeof etapiMetrics.collectMetrics>;
* totalRecentNotes:
* type: integer
* example: 50
* totalEmbeddings:
* type: integer
* example: 123
* totalEmbeddingProviders:
* type: integer
* example: 2
* noteTypes:
* type: object
* additionalProperties:

View File

@@ -40,17 +40,6 @@ import OpenAI from "openai";
* type: string
* type:
* type: string
* embeddingModels:
* type: array
* items:
* type: object
* properties:
* id:
* type: string
* name:
* type: string
* type:
* type: string
* '500':
* description: Error listing models
* security:
@@ -82,8 +71,7 @@ async function listModels(req: Request, res: Response) {
// Filter and categorize models
const allModels = response.data || [];
// Include all models as chat models, without filtering by specific model names
// This allows models from providers like OpenRouter to be displayed
// Include all models as chat models, excluding embedding models
const chatModels = allModels
.filter((model) =>
// Exclude models that are explicitly for embeddings
@@ -96,23 +84,10 @@ async function listModels(req: Request, res: Response) {
type: 'chat'
}));
const embeddingModels = allModels
.filter((model) =>
// Only include embedding-specific models
model.id.includes('embedding') ||
model.id.includes('embed')
)
.map((model) => ({
id: model.id,
name: model.id,
type: 'embedding'
}));
// Return the models list
return {
success: true,
chatModels,
embeddingModels
chatModels
};
} catch (error: any) {
log.error(`Error listing OpenAI models: ${error.message || 'Unknown error'}`);

View File

@@ -100,30 +100,11 @@ const ALLOWED_OPTIONS = new Set<OptionNames>([
"openaiApiKey",
"openaiBaseUrl",
"openaiDefaultModel",
"openaiEmbeddingModel",
"openaiEmbeddingApiKey",
"openaiEmbeddingBaseUrl",
"anthropicApiKey",
"anthropicBaseUrl",
"anthropicDefaultModel",
"voyageApiKey",
"voyageEmbeddingModel",
"voyageEmbeddingBaseUrl",
"ollamaBaseUrl",
"ollamaDefaultModel",
"ollamaEmbeddingModel",
"ollamaEmbeddingBaseUrl",
"embeddingAutoUpdateEnabled",
"embeddingDimensionStrategy",
"embeddingSelectedProvider",
"embeddingSimilarityThreshold",
"embeddingBatchSize",
"embeddingUpdateInterval",
"enableAutomaticIndexing",
"maxNotesPerLlmQuery",
// Embedding options
"embeddingDefaultDimension",
"mfaEnabled",
"mfaMethod"
]);

View File

@@ -54,7 +54,6 @@ import relationMapApiRoute from "./api/relation-map.js";
import otherRoute from "./api/other.js";
import metricsRoute from "./api/metrics.js";
import shareRoutes from "../share/routes.js";
import embeddingsRoute from "./api/embeddings.js";
import ollamaRoute from "./api/ollama.js";
import openaiRoute from "./api/openai.js";
import anthropicRoute from "./api/anthropic.js";
@@ -377,31 +376,7 @@ function register(app: express.Application) {
asyncApiRoute(PST, "/api/llm/chat/:chatNoteId/messages", llmRoute.sendMessage);
asyncApiRoute(PST, "/api/llm/chat/:chatNoteId/messages/stream", llmRoute.streamMessage);
// LLM index management endpoints - reorganized for REST principles
asyncApiRoute(GET, "/api/llm/indexes/stats", llmRoute.getIndexStats);
asyncApiRoute(PST, "/api/llm/indexes", llmRoute.startIndexing); // Create index process
asyncApiRoute(GET, "/api/llm/indexes/failed", llmRoute.getFailedIndexes);
asyncApiRoute(PUT, "/api/llm/indexes/notes/:noteId", llmRoute.retryFailedIndex); // Update index for note
asyncApiRoute(PUT, "/api/llm/indexes/failed", llmRoute.retryAllFailedIndexes); // Update all failed indexes
asyncApiRoute(GET, "/api/llm/indexes/notes/similar", llmRoute.findSimilarNotes); // Get similar notes
asyncApiRoute(GET, "/api/llm/indexes/context", llmRoute.generateQueryContext); // Get context
asyncApiRoute(PST, "/api/llm/indexes/notes/:noteId", llmRoute.indexNote); // Create index for specific note
// LLM embeddings endpoints
asyncApiRoute(GET, "/api/llm/embeddings/similar/:noteId", embeddingsRoute.findSimilarNotes);
asyncApiRoute(PST, "/api/llm/embeddings/search", embeddingsRoute.searchByText);
asyncApiRoute(GET, "/api/llm/embeddings/providers", embeddingsRoute.getProviders);
asyncApiRoute(PATCH, "/api/llm/embeddings/providers/:providerId", embeddingsRoute.updateProvider);
asyncApiRoute(PST, "/api/llm/embeddings/reprocess", embeddingsRoute.reprocessAllNotes);
asyncApiRoute(GET, "/api/llm/embeddings/queue-status", embeddingsRoute.getQueueStatus);
asyncApiRoute(GET, "/api/llm/embeddings/stats", embeddingsRoute.getEmbeddingStats);
asyncApiRoute(GET, "/api/llm/embeddings/failed", embeddingsRoute.getFailedNotes);
asyncApiRoute(PST, "/api/llm/embeddings/retry/:noteId", embeddingsRoute.retryFailedNote);
asyncApiRoute(PST, "/api/llm/embeddings/retry-all-failed", embeddingsRoute.retryAllFailedNotes);
asyncApiRoute(PST, "/api/llm/embeddings/rebuild-index", embeddingsRoute.rebuildIndex);
asyncApiRoute(GET, "/api/llm/embeddings/index-rebuild-status", embeddingsRoute.getIndexRebuildStatus);
asyncApiRoute(PST, "/api/llm/embeddings/start", embeddingsRoute.startEmbeddings);
asyncApiRoute(PST, "/api/llm/embeddings/stop", embeddingsRoute.stopEmbeddings);
// LLM provider endpoints - moved under /api/llm/providers hierarchy
asyncApiRoute(GET, "/api/llm/providers/ollama/models", ollamaRoute.listModels);