From 2b7593c3ac8f03c136e82c31592b8e9f5dd142a3 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 21 Apr 2026 23:41:03 -0400 Subject: [PATCH 1/9] separating notebooks and lightspeed vector_store Signed-off-by: Lucas --- workspaces/lightspeed/app-config.yaml | 6 +- .../src/service/constant.ts | 2 +- .../src/service/notebooks/notebooksRouters.ts | 7 +- .../src/service/notebooks/openapi.yaml | 1147 ----------------- .../lightspeed-backend/src/service/router.ts | 20 +- 5 files changed, 25 insertions(+), 1157 deletions(-) delete mode 100644 workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/openapi.yaml diff --git a/workspaces/lightspeed/app-config.yaml b/workspaces/lightspeed/app-config.yaml index 31819e32c4a..47909c0e7f1 100644 --- a/workspaces/lightspeed/app-config.yaml +++ b/workspaces/lightspeed/app-config.yaml @@ -19,13 +19,13 @@ organization: # Disable AI Notebooks feature by default lightspeed: notebooks: - enabled: false + enabled: true queryDefaults: - model: redhataillama-31-8b-instruct + model: redhataillama-31-8b-instruct ## move these to run.yaml, provider id needs to match provider_id: vllm sessionDefaults: provider_id: notebooks - embedding_model: ${LLAMA_STACK_EMBEDDING_MODEL} + embedding_model: sentence-transformers/all-mpnet-base-v2 embedding_dimension: 768 backend: diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts index b08666f1231..5cc80f4a6bd 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts @@ -22,6 +22,7 @@ export const DEFAULT_CHUNKING_STRATEGY_TYPE = 'auto'; // auto chunking export const DEFAULT_MAX_CHUNK_SIZE_TOKENS = 512; // 512 tokens export const DEFAULT_CHUNK_OVERLAP_TOKENS = 50; // 50 tokens export const DEFAULT_LLAMA_STACK_PORT = 8321; // Llama Stack port +export const DEFAULT_LIGHTSPEED_SERVICE_HOST = 'localhost'; // Lightspeed core service host export const DEFAULT_LIGHTSPEED_SERVICE_PORT = 8080; // Lightspeed service port export const DEFAULT_MAX_FILE_SIZE_MB = 20 * 1024 * 1024; // 20MB export const NOTEBOOKS_SYSTEM_PROMPT = @@ -45,7 +46,6 @@ Make no mistakes. /** * HTTP and networking constants */ -export const LIGHTSPEED_SERVICE_HOST = '0.0.0.0'; // Lightspeed core service host export const URL_FETCH_TIMEOUT_MS = 30000; // 30 second timeout for URL fetching export const USER_AGENT = 'RHDH-AI-Notebooks-Bot/1.0'; // User agent for HTTP requests export const MAX_URL_CONTENT_SIZE = 10 * 1024 * 1024; // 10MB max for URL fetched content diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts index f60356a3aca..49009b650e2 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts @@ -29,10 +29,10 @@ import { lightspeedNotebooksUsePermission } from '@red-hat-developer-hub/backsta import { Readable } from 'stream'; import { + DEFAULT_LIGHTSPEED_SERVICE_HOST, DEFAULT_LIGHTSPEED_SERVICE_PORT, HTTP_STATUS_ACCEPTED, HTTP_STATUS_INTERNAL_ERROR, - LIGHTSPEED_SERVICE_HOST, MAX_QUERY_RETRIES, NOTEBOOKS_SYSTEM_PROMPT, upload, @@ -65,10 +65,7 @@ export async function createNotebooksRouter( const notebooksRouter = Router(); notebooksRouter.use(express.json()); - const lightSpeedPort = - config.getOptionalNumber('lightspeed.servicePort') ?? - DEFAULT_LIGHTSPEED_SERVICE_PORT; - const lightspeedBaseUrl = `http://${LIGHTSPEED_SERVICE_HOST}:${lightSpeedPort}`; + const lightspeedBaseUrl = `http://${DEFAULT_LIGHTSPEED_SERVICE_HOST}:${DEFAULT_LIGHTSPEED_SERVICE_PORT}`; const queryModel = config.getOptionalString( 'lightspeed.notebooks.queryDefaults.model', ); diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/openapi.yaml b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/openapi.yaml deleted file mode 100644 index 8f6e5fb59fe..00000000000 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/openapi.yaml +++ /dev/null @@ -1,1147 +0,0 @@ -openapi: 3.0.3 -info: - title: AI Notebooks API - description: | - AI Notebooks API for managing sessions (vector stores) and documents for RAG (Retrieval-Augmented Generation) queries. - - ## Features - - **Session Management**: Create, read, update, and delete notebook sessions (vector stores) - - **Document Management**: Upload and manage documents in sessions for RAG queries - - **Query**: Execute RAG queries against session documents - - **File Support**: md, txt, pdf, json, yaml, yml, log, url - - **SSRF Protection**: Built-in security against Server-Side Request Forgery attacks - - ## Authentication - All endpoints (except `/health`) require Backstage authentication and the `lightspeed.notebooks.use` permission. - version: 1.0.0 - contact: - name: Red Hat Developer Hub - license: - name: Apache 2.0 - url: https://www.apache.org/licenses/LICENSE-2.0 - -servers: - - url: /api/lightspeed/ai-notebooks - description: AI Notebooks API - -tags: - - name: Health - description: Health check endpoint - - name: Sessions - description: Session (vector store) management operations - - name: Documents - description: Document management within sessions - - name: Query - description: RAG query operations - -paths: - /health: - get: - tags: - - Health - summary: Health check - description: Check if the AI Notebooks service is running - operationId: getHealth - security: [] - responses: - '200': - description: Service is healthy - content: - application/json: - schema: - type: object - required: - - status - properties: - status: - type: string - enum: [ok] - example: - status: ok - - /v1/sessions: - post: - tags: - - Sessions - summary: Create a new session - description: Create a new notebook session (vector store) for organizing documents - operationId: createSession - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - name - properties: - name: - type: string - description: Session name (required, cannot be empty) - example: My Research Project - minLength: 1 - description: - type: string - description: Session description (optional) - example: Documents for machine learning research - metadata: - type: object - description: Additional custom metadata fields (optional) - additionalProperties: true - example: - custom_field: custom-value - status: active - responses: - '200': - description: Session created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/SessionResponse' - example: - status: success - session: - session_id: vs-abc123def456 - user_id: user:default/johndoe - name: My Research Project - description: Documents for machine learning research - created_at: '2024-01-15T10:30:00.000Z' - updated_at: '2024-01-15T10:30:00.000Z' - metadata: - custom_field: custom-value - status: active - conversation_id: null - document_ids: [] - message: Session created successfully - '400': - description: Bad request - validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - examples: - missingName: - summary: Missing required name field - value: - status: error - error: name is required - '403': - description: Forbidden - user lacks lightspeed.notebooks.use permission - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: User lacks permission - '500': - description: Internal server error during session creation - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: Failed to create vector store - - get: - tags: - - Sessions - summary: List all sessions - description: Get all notebook sessions for the authenticated user, sorted by created_at (newest first) - operationId: listSessions - responses: - '200': - description: Sessions retrieved successfully (returns empty array if user has no sessions) - content: - application/json: - schema: - $ref: '#/components/schemas/SessionListResponse' - examples: - withSessions: - summary: User has sessions - value: - status: success - sessions: - - session_id: vs-def456 - user_id: user:default/johndoe - name: Recent Project - description: Latest work - created_at: '2024-01-16T14:00:00.000Z' - updated_at: '2024-01-16T14:00:00.000Z' - metadata: - conversation_id: conv-xyz - document_ids: [doc-1, doc-2] - - session_id: vs-abc123 - user_id: user:default/johndoe - name: Old Project - description: Archived work - created_at: '2024-01-15T10:30:00.000Z' - updated_at: '2024-01-15T10:30:00.000Z' - metadata: - conversation_id: null - document_ids: [] - count: 2 - noSessions: - summary: User has no sessions - value: - status: success - sessions: [] - count: 0 - '403': - description: Forbidden - user lacks lightspeed.notebooks.use permission - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: User lacks permission - '500': - description: Internal server error during session retrieval - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: Failed to list vector stores - - /v1/sessions/{sessionId}: - put: - tags: - - Sessions - summary: Update a session - description: Update session name, description, or metadata (all fields optional) - operationId: updateSession - parameters: - - $ref: '#/components/parameters/SessionId' - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - description: New session name (optional) - example: Updated Project Name - description: - type: string - description: New session description (optional) - example: Updated description - metadata: - type: object - description: Updated metadata (optional, merged with existing) - additionalProperties: true - example: - custom_field: updated-value - status: active - responses: - '200': - description: Session updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/SessionResponse' - example: - status: success - session: - session_id: vs-abc123 - user_id: user:default/johndoe - name: Updated Project Name - description: Updated description - created_at: '2024-01-15T10:30:00.000Z' - updated_at: '2024-01-16T15:20:00.000Z' - metadata: - custom_field: updated-value - status: active - conversation_id: conv-123 - document_ids: [doc-1] - message: Session updated successfully - '403': - description: Forbidden - user does not own this session - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: User does not have access to this session - '404': - description: Not found - session does not exist - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: Vector store not found - '500': - description: Internal server error during update - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: Failed to update vector store - - delete: - tags: - - Sessions - summary: Delete a session - description: Permanently delete a session and all its documents - operationId: deleteSession - parameters: - - $ref: '#/components/parameters/SessionId' - responses: - '200': - description: Session deleted successfully - content: - application/json: - schema: - $ref: '#/components/schemas/SessionResponse' - example: - status: success - session: - session_id: vs-abc123 - user_id: user:default/johndoe - name: Deleted Session - description: This session was deleted - created_at: '2024-01-15T10:30:00.000Z' - updated_at: '2024-01-15T10:30:00.000Z' - metadata: - conversation_id: null - document_ids: [] - message: Session deleted successfully - '403': - description: Forbidden - user does not own this session - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: User does not have access to this session - '404': - description: Not found - session does not exist - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: Vector store not found - '500': - description: Internal server error during deletion - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: Failed to delete vector store - - /v1/sessions/{sessionId}/documents: - get: - tags: - - Documents - summary: List documents in a session - description: Get all documents in the specified session (optionally filtered by file type) - operationId: listDocuments - parameters: - - $ref: '#/components/parameters/SessionId' - - name: fileType - in: query - required: false - description: Filter documents by source type - schema: - type: string - enum: [text, pdf, url, md, json, yaml, log] - responses: - '200': - description: Documents retrieved successfully (returns empty array if no documents match) - content: - application/json: - schema: - $ref: '#/components/schemas/DocumentListResponse' - examples: - withDocuments: - summary: Session has documents - value: - status: success - session_id: vs-abc123 - documents: - - document_id: research-paper - title: Research Paper - session_id: vs-abc123 - user_id: user:default/johndoe - source_type: pdf - created_at: '2024-01-15T11:00:00.000Z' - metadata: - fileName: paper.pdf - fileType: pdf - pageCount: 25 - parseTimestamp: '2024-01-15T11:00:00.000Z' - - document_id: readme - title: README - session_id: vs-abc123 - user_id: user:default/johndoe - source_type: md - created_at: '2024-01-15T11:05:00.000Z' - metadata: - fileName: README.md - fileType: md - parseTimestamp: '2024-01-15T11:05:00.000Z' - count: 2 - noDocuments: - summary: Session has no documents - value: - status: success - session_id: vs-abc123 - documents: [] - count: 0 - '403': - description: Forbidden - user does not own this session - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: User does not have access to this session - '404': - description: Not found - session does not exist - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: Vector store not found - '500': - description: Internal server error during document retrieval - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: Failed to retrieve documents - - put: - tags: - - Documents - summary: Upload a document - description: | - Upload a document to the session. Supports multiple file types: - - **Text files**: md, txt, log - - **Structured data**: json, yaml, yml - - **Binary**: pdf - - **Web content**: url (fetches and parses remote content) - - **Security**: URLs are validated against SSRF attacks (blocks private IPs, localhost, cloud metadata endpoints) - - **File Size Limit**: Maximum preset limit - operationId: uploadDocument - parameters: - - $ref: '#/components/parameters/SessionId' - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: - - title - - fileType - properties: - title: - type: string - description: Document title (used to generate document_id, must be unique within session) - example: My Research Paper - fileType: - $ref: '#/components/schemas/FileType' - file: - type: string - format: binary - description: File to upload (required for all types except 'url') - encoding: - file: - contentType: application/octet-stream, text/plain, application/pdf, application/json, text/yaml, text/markdown - responses: - '202': - description: Document upload started (processing asynchronously) - content: - application/json: - schema: - type: object - required: - - status - - file_id - - document_id - - session_id - - message - properties: - status: - type: string - enum: [processing] - file_id: - type: string - description: Llama Stack file ID for tracking - document_id: - type: string - description: Document identifier (sanitized from title) - session_id: - type: string - description: Parent session ID - message: - type: string - example: - status: processing - file_id: file_xyz789abc - document_id: my-research-paper - session_id: vs-abc123 - message: Document upload started - '400': - description: Bad request - validation or parsing error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - examples: - missingTitle: - summary: Missing required title - value: - status: error - error: title is required - unsupportedFileType: - summary: Unsupported or missing file type - value: - status: error - error: 'Unsupported file type: exe. Supported types: md, txt, pdf, json, yaml, yml, log, url' - noFile: - summary: No file uploaded (for non-url types) - value: - status: error - error: No file uploaded - fileTooLarge: - summary: File exceeds preset limit - value: - status: error - error: File size exceeds preset limit - invalidJSON: - summary: Invalid JSON file - value: - status: error - error: 'Invalid JSON file: SyntaxError: Unexpected token' - invalidYAML: - summary: Invalid YAML file - value: - status: error - error: 'Invalid YAML file: YAMLException: bad indentation' - invalidURL: - summary: Invalid URL format - value: - status: error - error: 'Invalid URL format: not-a-url' - ssrfBlocked: - summary: SSRF attack blocked - value: - status: error - error: Access to private/internal IP addresses is not allowed - noURLParam: - summary: Missing URL parameter for url type - value: - status: error - error: URL is required when fileType is "url" - '403': - description: Forbidden - user lacks permission or does not own session - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: User does not have access to this session - '404': - description: Not found - session does not exist - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: Vector store not found - '409': - description: Conflict - document with this title already exists in session - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: Document with title 'My Research Paper' already exists in this session - '500': - description: Internal server error during upload or processing - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - examples: - pdfParsingError: - summary: PDF parsing failed - value: - status: error - error: 'Error parsing PDF: Invalid PDF structure' - urlFetchError: - summary: Failed to fetch URL - value: - status: error - error: 'Error fetching URL: Failed to fetch URL: 500 Internal Server Error' - serverError: - summary: Generic server error - value: - status: error - error: Failed to upload document to vector store - - /v1/sessions/{sessionId}/documents/{documentId}/status: - get: - tags: - - Documents - summary: Get document processing status - description: Check the status of a document upload/processing operation - operationId: getDocumentStatus - parameters: - - $ref: '#/components/parameters/SessionId' - - $ref: '#/components/parameters/DocumentId' - responses: - '200': - description: Document status retrieved successfully - content: - application/json: - schema: - type: object - required: - - status - - document_id - - session_id - properties: - status: - type: string - enum: [processing, completed, failed] - description: Current processing status - document_id: - type: string - session_id: - type: string - error: - type: string - description: Error message (only present if status is 'failed') - examples: - completed: - summary: Document processing completed - value: - status: completed - document_id: my-research-paper - session_id: vs-abc123 - processing: - summary: Document still processing - value: - status: processing - document_id: my-research-paper - session_id: vs-abc123 - failed: - summary: Document processing failed - value: - status: failed - document_id: my-research-paper - session_id: vs-abc123 - error: Failed to extract text from PDF - '400': - description: Bad request - missing document_id parameter - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: document_id query parameter is required - '403': - description: Forbidden - user does not own this session - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: User does not have access to this session - '404': - description: Not found - session or document does not exist - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - examples: - sessionNotFound: - summary: Session not found - value: - status: error - error: Vector store not found - documentNotFound: - summary: Document not found - value: - status: error - error: File not found - '500': - description: Internal server error during status check - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: Failed to retrieve file status - - /v1/sessions/{sessionId}/documents/{documentId}: - delete: - tags: - - Documents - summary: Delete a document - description: Remove a document from the session's vector store - operationId: deleteDocument - parameters: - - $ref: '#/components/parameters/SessionId' - - $ref: '#/components/parameters/DocumentId' - responses: - '200': - description: Document deleted successfully - content: - application/json: - schema: - $ref: '#/components/schemas/DocumentResponse' - example: - status: success - document_id: my-research-paper - session_id: vs-abc123 - message: Document deleted successfully - '403': - description: Forbidden - user does not own this session - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: User does not have access to this session - '404': - description: Not found - session or document does not exist - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - examples: - sessionNotFound: - summary: Session not found - value: - status: error - error: Vector store not found - documentNotFound: - summary: Document not found - value: - status: error - error: Document not found - '500': - description: Internal server error during deletion - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: Failed to delete document from vector store - - /v1/sessions/{sessionId}/query: - post: - tags: - - Query - summary: Execute RAG query - description: | - Execute a RAG (Retrieval-Augmented Generation) query against the session's documents. - Returns a streaming response (Server-Sent Events) with the AI-generated answer. - - **Conversation Management**: - - First query creates a new conversation_id (automatically captured) - - Subsequent queries reuse the conversation_id for context continuity - - The conversation_id is stored in session metadata - - **Streaming Response**: - - Content-Type: text/event-stream - - Each chunk sent as SSE format: `data: {JSON}\n\n` - - Response includes conversation_id and text chunks - operationId: querySession - parameters: - - $ref: '#/components/parameters/SessionId' - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - query - properties: - query: - type: string - description: Question or prompt to ask about the documents - example: What are the main findings in the research papers? - minLength: 1 - responses: - '200': - description: Streaming response with query results - content: - text/event-stream: - schema: - type: string - description: | - Server-Sent Events stream. Each event is formatted as: - ``` - data: {"data":{"conversation_id":"conv-123","chunk":"text..."}} - - ``` - example: | - data: {"data":{"conversation_id":"conv-abc123","chunk":"Based on the research papers,"}} - - data: {"data":{"conversation_id":"conv-abc123","chunk":" the main findings include:"}} - - data: {"data":{"conversation_id":"conv-abc123","chunk":" improved accuracy by 15%"}} - - data: {"data":{"conversation_id":"conv-abc123","chunk":" and reduced latency."}} - '400': - description: Bad request - missing or invalid query - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: query is required - '403': - description: Forbidden - user lacks permission or does not own session - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: User does not have access to this session - '404': - description: Not found - session does not exist - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - example: - status: error - error: Vector store not found - '500': - description: Internal server error during query processing - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - examples: - lightspeedError: - summary: Error from lightspeed-core service - value: - status: error - error: 'Error from Llama Stack server: validation error' - genericError: - summary: Generic server error - value: - status: error - error: Failed to process query - -components: - parameters: - SessionId: - name: sessionId - in: path - required: true - description: Session identifier (Llama Stack vector store ID, format 'vs-{alphanumeric}') - schema: - type: string - example: vs-abc123def456 - pattern: '^vs-[a-zA-Z0-9]+$' - - DocumentId: - name: documentId - in: path - required: true - description: Document identifier (sanitized from title, lowercase with hyphens) - schema: - type: string - example: my-research-paper - pattern: '^[a-z0-9-]+$' - - schemas: - FileType: - type: string - enum: - - md - - txt - - pdf - - json - - yaml - - yml - - log - - url - description: | - Supported file types: - - `md`: Markdown files - - `txt`: Plain text files - - `pdf`: PDF documents (text extraction) - - `json`: JSON files (validated before upload) - - `yaml`/`yml`: YAML files (validated before upload) - - `log`: Log files - - `url`: Remote web content (automatically fetched, SSRF-protected) - - NotebookSession: - type: object - required: - - session_id - - user_id - - name - - description - - created_at - - updated_at - properties: - session_id: - type: string - description: Unique session identifier (Llama Stack vector store ID) - example: vs-abc123def456 - user_id: - type: string - description: User entity reference (Backstage user) - example: user:default/johndoe - name: - type: string - description: Session name - example: My Research Project - description: - type: string - description: Session description - example: Documents for machine learning research - created_at: - type: string - format: date-time - description: Session creation timestamp (ISO 8601) - example: '2024-01-15T10:30:00.000Z' - updated_at: - type: string - format: date-time - description: Last update timestamp (ISO 8601) - example: '2024-01-16T14:20:00.000Z' - metadata: - type: object - description: Session metadata (all fields optional) - properties: - document_ids: - type: array - items: - type: string - description: List of document IDs in this session - example: [doc-1, doc-2] - conversation_id: - type: string - nullable: true - description: Active conversation ID for RAG queries (null until first query) - example: conv-abc123 - embedding_model: - type: string - description: Embedding model used - example: text-embedding-ada-002 - embedding_dimension: - type: integer - description: Embedding vector dimension - example: 1536 - provider_id: - type: string - description: AI provider identifier - example: openai - additionalProperties: true - example: - document_ids: [doc-1, doc-2] - conversation_id: conv-abc123 - embedding_model: text-embedding-ada-002 - embedding_dimension: 1536 - provider_id: openai - custom_field: custom-value - - SessionDocument: - type: object - required: - - document_id - - title - - session_id - - user_id - - source_type - - created_at - properties: - document_id: - type: string - description: Document identifier (sanitized from title) - example: my-research-paper - title: - type: string - description: Original document title - example: My Research Paper - session_id: - type: string - description: Parent session ID - example: vs-abc123 - user_id: - type: string - description: Document owner (same as session owner) - example: user:default/johndoe - source_type: - type: string - enum: [text, pdf, url, md, json, yaml, log] - description: Document source/file type - example: pdf - created_at: - type: string - format: date-time - description: Document creation timestamp (ISO 8601) - example: '2024-01-15T10:30:00.000Z' - metadata: - type: object - description: Document metadata from parsing - additionalProperties: true - example: - fileName: research.pdf - fileType: pdf - pageCount: 25 - parseTimestamp: '2024-01-15T10:30:00.000Z' - - SessionResponse: - type: object - required: - - status - properties: - status: - type: string - enum: [success, error] - example: success - session: - $ref: '#/components/schemas/NotebookSession' - message: - type: string - description: Success message - example: Session created successfully - error: - type: string - description: Error message (only present when status is 'error') - - SessionListResponse: - type: object - required: - - status - properties: - status: - type: string - enum: [success, error] - example: success - sessions: - type: array - items: - $ref: '#/components/schemas/NotebookSession' - description: Array of sessions (empty array if user has no sessions) - count: - type: integer - description: Number of sessions returned - example: 5 - error: - type: string - description: Error message (only present when status is 'error') - - DocumentResponse: - type: object - required: - - status - properties: - status: - type: string - enum: [success, error] - example: success - document_id: - type: string - example: my-research-paper - title: - type: string - example: My Research Paper - session_id: - type: string - example: vs-abc123 - replaced: - type: boolean - description: Whether this operation replaced an existing document - example: false - message: - type: string - description: Success message - example: Document deleted successfully - error: - type: string - description: Error message (only present when status is 'error') - - DocumentListResponse: - type: object - required: - - status - properties: - status: - type: string - enum: [success, error] - example: success - session_id: - type: string - example: vs-abc123 - documents: - type: array - items: - $ref: '#/components/schemas/SessionDocument' - description: Array of documents (empty array if session has no documents) - count: - type: integer - description: Number of documents returned - example: 3 - error: - type: string - description: Error message (only present when status is 'error') - - ErrorResponse: - type: object - required: - - status - - error - properties: - status: - type: string - enum: [error] - example: error - error: - type: string - description: Human-readable error message - example: Session not found - - securitySchemes: - BackstageAuth: - type: http - scheme: bearer - description: Backstage authentication token. User must have `lightspeed.notebooks.use` permission. - -security: - - BackstageAuth: [] diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts index bba1caa65c5..a5439ef0440 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts @@ -40,6 +40,7 @@ import { McpValidationResult, } from './mcp-server-types'; import { McpServerValidator } from './mcp-server-validator'; +import { VectorStoresOperator } from './notebooks/VectorStoresOperator'; import { userPermissionAuthorization } from './permission'; import { createTokenEncryptor } from './token-encryption'; import { @@ -107,6 +108,12 @@ export async function createRouter( DEFAULT_LIGHTSPEED_SERVICE_PORT; const system_prompt = config.getOptionalString('lightspeed.systemPrompt'); + const vectorStoresOperator = new VectorStoresOperator( + `http://0.0.0.0:${port}`, + logger, + ); + let lightspeed_vector_store_id: string = ''; + // Parse admin-configured MCP servers from app-config. // Only name is required; token is optional (users can provide their own via the UI). // URLs come from LCS (GET /v1/mcp-servers), not from app-config. @@ -589,6 +596,17 @@ export async function createRouter( lightspeedChatCreatePermission, credentials, ); + + // get the vector store id for the rhdh-product-docs vector store + if (lightspeed_vector_store_id === '') { + const vectorStores = await vectorStoresOperator.vectorStores.list(); + lightspeed_vector_store_id = + vectorStores.data.find((v: any) => + v.name.startsWith('rhdh-product-docs'), + )?.id || ''; + } + request.body.vector_store_ids = ['asdf']; + const userQueryParam = `user_id=${encodeURIComponent(user_id)}`; request.body.media_type = 'application/json'; // set media_type to receive start and end event // if system_prompt is defined in lightspeed config @@ -605,7 +623,7 @@ export async function createRouter( settingsStore, user_id, ); - + console.log('requestBodyasdf', requestBody); const fetchResponse = await fetch( `http://0.0.0.0:${port}/v1/streaming_query?${userQueryParam}`, { From 45dc3b73acbdbba15785ed5037d0750f2e8bda21 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 22 Apr 2026 16:42:36 -0400 Subject: [PATCH 2/9] notebooks only requires queryDefaults, adding changeset Signed-off-by: Lucas --- .../.changeset/chilled-guests-search.md | 5 +++ workspaces/lightspeed/app-config.yaml | 8 ++--- .../plugins/lightspeed-backend/README.md | 7 ----- .../__fixtures__/lcsHandlers.ts | 14 +++++++++ .../lightspeed-backend/app-config.yaml | 7 ----- .../src/service/constant.ts | 2 +- .../service/notebooks/VectorStoresOperator.ts | 31 ++++++++++++++++++- .../documents/documentService.test.ts | 3 +- .../service/notebooks/notebooksRouter.test.ts | 2 ++ .../src/service/notebooks/notebooksRouters.ts | 7 +++-- .../notebooks/sessions/sessionService.test.ts | 3 +- .../notebooks/sessions/sessionService.ts | 30 +++--------------- .../src/service/router.test.ts | 5 +++ .../lightspeed-backend/src/service/router.ts | 6 ++-- 14 files changed, 76 insertions(+), 54 deletions(-) create mode 100644 workspaces/lightspeed/.changeset/chilled-guests-search.md diff --git a/workspaces/lightspeed/.changeset/chilled-guests-search.md b/workspaces/lightspeed/.changeset/chilled-guests-search.md new file mode 100644 index 00000000000..bb46387364c --- /dev/null +++ b/workspaces/lightspeed/.changeset/chilled-guests-search.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/backstage-plugin-lightspeed-backend': minor +--- + +All lightspeed query is now called with rhdh-docs vector_store. Notebooks app-config only now requires queryDefaults model and provider diff --git a/workspaces/lightspeed/app-config.yaml b/workspaces/lightspeed/app-config.yaml index 47909c0e7f1..a9538702f9b 100644 --- a/workspaces/lightspeed/app-config.yaml +++ b/workspaces/lightspeed/app-config.yaml @@ -19,14 +19,10 @@ organization: # Disable AI Notebooks feature by default lightspeed: notebooks: - enabled: true + enabled: false queryDefaults: - model: redhataillama-31-8b-instruct ## move these to run.yaml, provider id needs to match + model: redhataillama-31-8b-instruct provider_id: vllm - sessionDefaults: - provider_id: notebooks - embedding_model: sentence-transformers/all-mpnet-base-v2 - embedding_dimension: 768 backend: # Used for enabling authentication, secret is shared by all backend plugins diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/README.md b/workspaces/lightspeed/plugins/lightspeed-backend/README.md index f46cbd3eb39..f15785fc0d5 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/README.md +++ b/workspaces/lightspeed/plugins/lightspeed-backend/README.md @@ -113,13 +113,6 @@ lightspeed: model: llama3.1-8b-instruct # Model to use for answering queries provider_id: ollama # AI provider for the query model - # Required: Session defaults for creating vector stores - # All three fields are required when Notebooks is enabled - sessionDefaults: - provider_id: notebooks # Vector store provider ID (must match Llama Stack config) - embedding_model: sentence-transformers/all-mpnet-base-v2 # Model for generating embeddings - embedding_dimension: 768 # Embedding vector dimension (must match model output) - # Optional: Chunking strategy for document processing chunkingStrategy: type: auto # 'auto' or 'static' (default: auto) diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/__fixtures__/lcsHandlers.ts b/workspaces/lightspeed/plugins/lightspeed-backend/__fixtures__/lcsHandlers.ts index 2bec5a88a31..c731fc463b6 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/__fixtures__/lcsHandlers.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/__fixtures__/lcsHandlers.ts @@ -268,6 +268,20 @@ export const lcsHandlers: HttpHandler[] = [ }); }), + // Vector stores list endpoint - returns mock RHDH product docs vector store + http.get(`${LOCAL_LCS_ADDR}/v1/vector-stores`, () => { + return HttpResponse.json({ + data: [ + { + id: 'vs-rhdh-product-docs', + name: 'rhdh-product-docs', + provider_id: 'notebooks', + metadata: {}, + }, + ], + }); + }), + // Catch-all handler for unknown paths http.all(`${LOCAL_LCS_ADDR}/*`, ({ request }) => { console.log(`Caught request to unknown path: ${request.url}`); diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/app-config.yaml b/workspaces/lightspeed/plugins/lightspeed-backend/app-config.yaml index 144f61822de..760aaf519ea 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/app-config.yaml +++ b/workspaces/lightspeed/plugins/lightspeed-backend/app-config.yaml @@ -13,13 +13,6 @@ # provider_id: ollama # AI provider for query model (e.g., ollama, vllm) # model: llama3.1-8b-instruct # Model to use for answering queries # -# # REQUIRED when enabled: Session defaults for vector stores -# # All three fields are required -# sessionDefaults: -# provider_id: notebooks # Vector store provider ID (must match Llama Stack config) -# embedding_model: sentence-transformers/all-mpnet-base-v2 # Embedding model for documents -# embedding_dimension: 768 # Vector dimension (must match embedding model output) -# # # OPTIONAL: Chunking strategy for document processing # chunkingStrategy: # type: auto # 'auto' (default) or 'static' diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts index 5cc80f4a6bd..14cacd44b6f 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts @@ -22,7 +22,7 @@ export const DEFAULT_CHUNKING_STRATEGY_TYPE = 'auto'; // auto chunking export const DEFAULT_MAX_CHUNK_SIZE_TOKENS = 512; // 512 tokens export const DEFAULT_CHUNK_OVERLAP_TOKENS = 50; // 50 tokens export const DEFAULT_LLAMA_STACK_PORT = 8321; // Llama Stack port -export const DEFAULT_LIGHTSPEED_SERVICE_HOST = 'localhost'; // Lightspeed core service host +export const DEFAULT_LIGHTSPEED_SERVICE_HOST = '0.0.0.0'; // Lightspeed core service host export const DEFAULT_LIGHTSPEED_SERVICE_PORT = 8080; // Lightspeed service port export const DEFAULT_MAX_FILE_SIZE_MB = 20 * 1024 * 1024; // 20MB export const NOTEBOOKS_SYSTEM_PROMPT = diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/VectorStoresOperator.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/VectorStoresOperator.ts index a69fdcb8c93..a20a412801d 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/VectorStoresOperator.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/VectorStoresOperator.ts @@ -82,16 +82,45 @@ async function handleHttpError( * * This class provides the same interface as LlamaStackClient but proxies calls through * lightspeed-core REST API instead of calling llama stack directly. + * + * Implemented as a singleton to ensure single instance across the application. */ export class VectorStoresOperator { + private static instance: VectorStoresOperator | null = null; private baseURL: string; private logger: LoggerService; - constructor(lightspeedCoreUrl: string, logger: LoggerService) { + private constructor(lightspeedCoreUrl: string, logger: LoggerService) { this.baseURL = lightspeedCoreUrl; this.logger = logger; } + /** + * Get the singleton instance of VectorStoresOperator + * @param lightspeedCoreUrl - Lightspeed core URL (required on first call) + * @param logger - Logger service (required on first call) + * @returns The singleton instance + */ + static getInstance( + lightspeedCoreUrl: string, + logger: LoggerService, + ): VectorStoresOperator { + if (!VectorStoresOperator.instance) { + VectorStoresOperator.instance = new VectorStoresOperator( + lightspeedCoreUrl, + logger, + ); + } + return VectorStoresOperator.instance; + } + + /** + * Reset the singleton instance (primarily for testing) + */ + static resetInstance(): void { + VectorStoresOperator.instance = null; + } + /** * Vector Stores API - mirrors LlamaStackClient.vectorStores structure */ diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.test.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.test.ts index 740defde6e5..356b9a53f46 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.test.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.test.ts @@ -62,7 +62,8 @@ describe('DocumentService', () => { }, }, }); - operator = new VectorStoresOperator(LIGHTSPEED_CORE_ADDR, logger); + VectorStoresOperator.resetInstance(); // Reset singleton before each test + operator = VectorStoresOperator.getInstance(LIGHTSPEED_CORE_ADDR, logger); documentService = new DocumentService(operator, logger, config); sessionService = new SessionService(operator, logger, config); diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouter.test.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouter.test.ts index cb43d27b99b..1407111a334 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouter.test.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouter.test.ts @@ -26,6 +26,7 @@ import { resetMockStorage, } from '../../../__fixtures__/lightspeedCoreHandlers'; import { createNotebooksRouter } from './notebooksRouters'; +import { VectorStoresOperator } from './VectorStoresOperator'; const mockUserId = 'user:default/guest'; @@ -53,6 +54,7 @@ describe('Notebooks Router', () => { beforeEach(async () => { resetMockStorage(); + VectorStoresOperator.resetInstance(); // Reset singleton before each test const logger = mockServices.logger.mock(); const config = mockServices.rootConfig({ data: { diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts index 49009b650e2..baefe33a1ed 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts @@ -65,7 +65,10 @@ export async function createNotebooksRouter( const notebooksRouter = Router(); notebooksRouter.use(express.json()); - const lightspeedBaseUrl = `http://${DEFAULT_LIGHTSPEED_SERVICE_HOST}:${DEFAULT_LIGHTSPEED_SERVICE_PORT}`; + const lightSpeedPort = + config.getOptionalNumber('lightspeed.servicePort') ?? + DEFAULT_LIGHTSPEED_SERVICE_PORT; + const lightspeedBaseUrl = `http://${DEFAULT_LIGHTSPEED_SERVICE_HOST}:${lightSpeedPort}`; const queryModel = config.getOptionalString( 'lightspeed.notebooks.queryDefaults.model', ); @@ -82,7 +85,7 @@ export async function createNotebooksRouter( `AI Notebooks connecting to Lightspeed-Core at ${lightspeedBaseUrl}`, ); - const vectorStoresOperator = new VectorStoresOperator( + const vectorStoresOperator = VectorStoresOperator.getInstance( lightspeedBaseUrl, logger, ); diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.test.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.test.ts index 2848bb07490..bdd2cc00107 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.test.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.test.ts @@ -60,7 +60,8 @@ describe('SessionService', () => { }, }, }); - operator = new VectorStoresOperator(LIGHTSPEED_CORE_ADDR, logger); + VectorStoresOperator.resetInstance(); // Reset singleton before each test + operator = VectorStoresOperator.getInstance(LIGHTSPEED_CORE_ADDR, logger); service = new SessionService(operator, logger, config); }); diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts index 7aeb891c2bb..14dcbbb7baa 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts @@ -32,8 +32,6 @@ export class SessionService { private logger: LoggerService; private client: VectorStoresOperator; private providerId: string; - private embeddingModel: string; - private embeddingDimension: number; constructor( client: VectorStoresOperator, @@ -43,25 +41,11 @@ export class SessionService { this.client = client; this.logger = logger; - const requireConfig = (value: T | undefined, key: string): T => { - if (value === undefined) throw new Error(`${key} is required in config`); - return value; - }; - - this.providerId = requireConfig( - config?.getString('lightspeed.notebooks.sessionDefaults.provider_id'), - 'lightspeed.notebooks.sessionDefaults.provider_id', - ); - this.embeddingModel = requireConfig( - config?.getString('lightspeed.notebooks.sessionDefaults.embedding_model'), - 'lightspeed.notebooks.sessionDefaults.embedding_model', - ); - this.embeddingDimension = requireConfig( - config?.getNumber( - 'lightspeed.notebooks.sessionDefaults.embedding_dimension', - ), - 'lightspeed.notebooks.sessionDefaults.embedding_dimension', - ); + // Use optional config with default fallback + this.providerId = + config?.getOptionalString( + 'lightspeed.notebooks.sessionDefaults.provider_id', + ) ?? 'notebooks'; } /** @@ -94,16 +78,12 @@ export class SessionService { ...metadata, conversation_id: null, provider_id: this.providerId, - embedding_model: this.embeddingModel, - embedding_dimension: this.embeddingDimension, }, }; const vectorStore = await this.client.vectorStores.create({ name: name || `Session for ${userId}`, provider_id: this.providerId, - embedding_model: this.embeddingModel, - embedding_dimension: this.embeddingDimension, metadata: buildVectorStoreMetadata(tempSession), }); diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.test.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.test.ts index 6df5eff2a3c..c857df309dc 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.test.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.test.ts @@ -29,6 +29,7 @@ import request from 'supertest'; import { handlers, LOCAL_AI_ADDR } from '../../__fixtures__/handlers'; import { lcsHandlers, LOCAL_LCS_ADDR } from '../../__fixtures__/lcsHandlers'; import { lightspeedPlugin } from '../plugin'; +import { VectorStoresOperator } from './notebooks/VectorStoresOperator'; const mockUserId = `user: default/user1`; const mockConversationId = 'conversation-id-1'; @@ -108,6 +109,10 @@ describe('lightspeed router tests', () => { rcs.close(); }); + beforeEach(() => { + VectorStoresOperator.resetInstance(); // Reset singleton before each test + }); + afterEach(() => { jest.clearAllMocks(); server.resetHandlers(); diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts index a5439ef0440..9401789d4c1 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts @@ -108,7 +108,7 @@ export async function createRouter( DEFAULT_LIGHTSPEED_SERVICE_PORT; const system_prompt = config.getOptionalString('lightspeed.systemPrompt'); - const vectorStoresOperator = new VectorStoresOperator( + const vectorStoresOperator = VectorStoresOperator.getInstance( `http://0.0.0.0:${port}`, logger, ); @@ -605,7 +605,7 @@ export async function createRouter( v.name.startsWith('rhdh-product-docs'), )?.id || ''; } - request.body.vector_store_ids = ['asdf']; + request.body.vector_store_ids = [lightspeed_vector_store_id]; const userQueryParam = `user_id=${encodeURIComponent(user_id)}`; request.body.media_type = 'application/json'; // set media_type to receive start and end event @@ -623,7 +623,7 @@ export async function createRouter( settingsStore, user_id, ); - console.log('requestBodyasdf', requestBody); + const fetchResponse = await fetch( `http://0.0.0.0:${port}/v1/streaming_query?${userQueryParam}`, { From ff8bcfb791921acc01f7b7552715e523e2d059f0 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Apr 2026 12:52:44 -0400 Subject: [PATCH 3/9] all files uploaded to lightspeed stack is now .txt Signed-off-by: Lucas --- .../.changeset/chilled-guests-search.md | 1 + .../plugins/lightspeed-backend/README.md | 6 ------ .../lightspeed-backend/src/service/constant.ts | 3 ++- .../notebooks/documents/documentService.ts | 15 ++++----------- .../src/service/notebooks/notebooksRouters.ts | 1 - 5 files changed, 7 insertions(+), 19 deletions(-) diff --git a/workspaces/lightspeed/.changeset/chilled-guests-search.md b/workspaces/lightspeed/.changeset/chilled-guests-search.md index bb46387364c..79b28823f10 100644 --- a/workspaces/lightspeed/.changeset/chilled-guests-search.md +++ b/workspaces/lightspeed/.changeset/chilled-guests-search.md @@ -3,3 +3,4 @@ --- All lightspeed query is now called with rhdh-docs vector_store. Notebooks app-config only now requires queryDefaults model and provider +All files uploaded to lightspeed-stack will be converted to .txt diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/README.md b/workspaces/lightspeed/plugins/lightspeed-backend/README.md index f15785fc0d5..8d3e8c7c6a0 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/README.md +++ b/workspaces/lightspeed/plugins/lightspeed-backend/README.md @@ -136,12 +136,6 @@ lightspeed: - **`queryDefaults.model`** _(required)_: The LLM model to use for answering RAG queries. Must be available in the configured provider. - **`queryDefaults.provider_id`** _(required)_: The AI provider identifier for the query model (e.g., `ollama`, `vllm`). Both `model` and `provider_id` must be configured together. -**Session Defaults** _(required when enabled)_: - -- **`sessionDefaults.provider_id`** _(required)_: Vector store provider identifier. Must match a provider configured in your Llama Stack instance (e.g., `notebooks`, `chromadb`). This determines where document embeddings are stored. -- **`sessionDefaults.embedding_model`** _(required)_: The embedding model to use for converting documents to vectors (e.g., `sentence-transformers/all-mpnet-base-v2`). Must be available in Llama Stack. -- **`sessionDefaults.embedding_dimension`** _(required)_: Dimension of the embedding vectors produced by the embedding model. Must match the model's output dimension (commonly `768`, `384`, or `1536`). - **Chunking Strategy** _(optional)_: - **`chunkingStrategy.type`** _(optional)_: Document chunking strategy - `auto` (automatic, default) or `static` (fixed size) diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts index 14cacd44b6f..2c74b9d4d45 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts @@ -37,9 +37,10 @@ Constraints: Output Format: 1. Summary: A 1-2 sentence high-level answer. 2. Detailed Analysis: A structured breakdown using bullet points. -3. References: A list of sources used. +3. References: A list of sources used. References should be in the format of [Document Title] in a new line for each reference. Disclaimer: Your answers **MUST** be grounded in the provided documents. If the answer isn't present, state: "I don't know based on the provided documents." +Remember, **ALL** references must be from the provided documents and provided documents only. Make no mistakes. `.trim(); diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.ts index b98b0a02e5b..2e589656685 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.ts @@ -102,23 +102,16 @@ export class DocumentService { * Upload a file to the Files API * @param content - File content as string * @param title - File title/name - * @param fileType - Optional file type for MIME type detection * @returns File ID from the Files API * @throws Error if upload fails */ - async uploadFile( - content: string, - title: string, - fileType?: string, - ): Promise { + async uploadFile(content: string, title: string): Promise { try { // Determine MIME type from file type or default to text/plain - const mimeType = fileType - ? FILE_TYPE_TO_MIME[fileType] || 'text/plain' - : 'text/plain'; - + const mimeType = 'text/plain'; + const txtFilename = `${title.replace(/\.[^.]+$/, '')}.txt`; const file = await this.client.files.create({ - file: await toFile(Buffer.from(content, 'utf-8'), title, { + file: await toFile(Buffer.from(content, 'utf-8'), txtFilename, { type: mimeType, }), purpose: 'assistants', diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts index baefe33a1ed..5a0e05c9e2c 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts @@ -314,7 +314,6 @@ export async function createNotebooksRouter( const fileId = await documentService.uploadFile( parsedDocument.content, title, - fileType, ); res.status(HTTP_STATUS_ACCEPTED).json({ From f4d3c301e370e2890da425a822c6205916b64d8b Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Apr 2026 14:36:46 -0400 Subject: [PATCH 4/9] passing tests Signed-off-by: Lucas --- .../documents/documentService.test.ts | 48 ++++--------------- .../notebooks/documents/documentService.ts | 1 - .../src/service/notebooks/notebooksRouters.ts | 6 +-- .../notebooks/sessions/sessionService.test.ts | 2 +- .../notebooks/sessions/sessionService.ts | 14 +----- 5 files changed, 12 insertions(+), 59 deletions(-) diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.test.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.test.ts index 356b9a53f46..84692639433 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.test.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.test.ts @@ -65,7 +65,7 @@ describe('DocumentService', () => { VectorStoresOperator.resetInstance(); // Reset singleton before each test operator = VectorStoresOperator.getInstance(LIGHTSPEED_CORE_ADDR, logger); documentService = new DocumentService(operator, logger, config); - sessionService = new SessionService(operator, logger, config); + sessionService = new SessionService(operator, logger); // Create a test session for document operations const session = await sessionService.createSession( @@ -85,7 +85,6 @@ describe('DocumentService', () => { const fileId = await documentService.uploadFile( 'Test content', 'test-file.txt', - 'txt', ); expect(fileId).toBeDefined(); @@ -94,23 +93,13 @@ describe('DocumentService', () => { it('should handle upload errors', async () => { // Mock a failure by passing invalid content - await expect( - documentService.uploadFile('', '', 'txt'), - ).resolves.toBeDefined(); + await expect(documentService.uploadFile('', '')).resolves.toBeDefined(); }); it('should use correct MIME type based on file type', async () => { - const fileId1 = await documentService.uploadFile( - '{}', - 'test.json', - 'json', - ); - const fileId2 = await documentService.uploadFile( - 'text', - 'test.txt', - 'txt', - ); - const fileId3 = await documentService.uploadFile('# MD', 'test.md', 'md'); + const fileId1 = await documentService.uploadFile('{}', 'test.json'); + const fileId2 = await documentService.uploadFile('text', 'test.txt'); + const fileId3 = await documentService.uploadFile('# MD', 'test.md'); expect(fileId1).toBeDefined(); expect(fileId2).toBeDefined(); @@ -120,11 +109,7 @@ describe('DocumentService', () => { describe('getFileStatus', () => { it('should get file status for existing document', async () => { - const fileId = await documentService.uploadFile( - 'Content', - 'Test Doc', - 'text', - ); + const fileId = await documentService.uploadFile('Content', 'Test Doc'); await documentService.upsertDocument( sessionId, 'Test Doc', @@ -150,7 +135,6 @@ describe('DocumentService', () => { const fileId = await documentService.uploadFile( 'This is test content', 'Test Document', - 'text', ); const result = await documentService.upsertDocument( @@ -170,7 +154,6 @@ describe('DocumentService', () => { const fileId1 = await documentService.uploadFile( 'Original content', 'Original Title', - 'text', ); await documentService.upsertDocument( sessionId, @@ -182,7 +165,6 @@ describe('DocumentService', () => { const fileId2 = await documentService.uploadFile( 'Updated content', 'Original Title', - 'text', ); const result = await documentService.upsertDocument( sessionId, @@ -200,7 +182,6 @@ describe('DocumentService', () => { const fileId1 = await documentService.uploadFile( 'Content', 'Original Title', - 'text', ); await documentService.upsertDocument( sessionId, @@ -212,7 +193,6 @@ describe('DocumentService', () => { const fileId2 = await documentService.uploadFile( 'Updated content', 'New Title', - 'text', ); const result = await documentService.upsertDocument( sessionId, @@ -231,7 +211,6 @@ describe('DocumentService', () => { const fileId1 = await documentService.uploadFile( 'Content 1', 'Document 1', - 'text', ); await documentService.upsertDocument( sessionId, @@ -243,7 +222,6 @@ describe('DocumentService', () => { const fileId2 = await documentService.uploadFile( 'Content 2', 'Document 2', - 'text', ); await documentService.upsertDocument( sessionId, @@ -266,11 +244,7 @@ describe('DocumentService', () => { }); it('should filter documents by file type', async () => { - const fileId1 = await documentService.uploadFile( - 'Content', - 'Text Doc', - 'text', - ); + const fileId1 = await documentService.uploadFile('Content', 'Text Doc'); await documentService.upsertDocument( sessionId, 'Text Doc', @@ -278,11 +252,7 @@ describe('DocumentService', () => { fileId1, ); - const fileId2 = await documentService.uploadFile( - 'Content', - 'PDF Doc', - 'pdf', - ); + const fileId2 = await documentService.uploadFile('Content', 'PDF Doc'); await documentService.upsertDocument( sessionId, 'PDF Doc', @@ -300,7 +270,6 @@ describe('DocumentService', () => { const fileId = await documentService.uploadFile( 'Content', 'Test Document', - 'text', ); await documentService.upsertDocument( sessionId, @@ -324,7 +293,6 @@ describe('DocumentService', () => { const fileId = await documentService.uploadFile( 'Content', 'Test Document', - 'text', ); await documentService.upsertDocument( sessionId, diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.ts index 2e589656685..4c4febb48de 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.ts @@ -22,7 +22,6 @@ import { DEFAULT_CHUNK_OVERLAP_TOKENS, DEFAULT_CHUNKING_STRATEGY_TYPE, DEFAULT_MAX_CHUNK_SIZE_TOKENS, - FILE_TYPE_TO_MIME, } from '../../constant'; import { SessionDocument, UpsertResult } from '../types/notebooksTypes'; import { VectorStoresOperator } from '../VectorStoresOperator'; diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts index 5a0e05c9e2c..182269659a1 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts @@ -89,11 +89,7 @@ export async function createNotebooksRouter( lightspeedBaseUrl, logger, ); - const sessionService = new SessionService( - vectorStoresOperator, - logger, - config, - ); + const sessionService = new SessionService(vectorStoresOperator, logger); const documentService = new DocumentService( vectorStoresOperator, logger, diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.test.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.test.ts index b0b7bd4eddf..b726a4031be 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.test.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.test.ts @@ -62,7 +62,7 @@ describe('SessionService', () => { }); VectorStoresOperator.resetInstance(); // Reset singleton before each test operator = VectorStoresOperator.getInstance(LIGHTSPEED_CORE_ADDR, logger); - service = new SessionService(operator, logger, config); + service = new SessionService(operator, logger); }); afterEach(() => { diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts index 14dcbbb7baa..b0a10f8645f 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts @@ -15,7 +15,6 @@ */ import { LoggerService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; import { NotAllowedError, NotFoundError } from '@backstage/errors'; import { NotebookSession, SessionMetadata } from '../types/notebooksTypes'; @@ -33,19 +32,10 @@ export class SessionService { private client: VectorStoresOperator; private providerId: string; - constructor( - client: VectorStoresOperator, - logger: LoggerService, - config?: Config, - ) { + constructor(client: VectorStoresOperator, logger: LoggerService) { this.client = client; this.logger = logger; - - // Use optional config with default fallback - this.providerId = - config?.getOptionalString( - 'lightspeed.notebooks.sessionDefaults.provider_id', - ) ?? 'notebooks'; + this.providerId = 'notebooks'; } /** From f81481ececfca1b5f68ae4ec6b57e43846486fcb Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Apr 2026 14:42:18 -0400 Subject: [PATCH 5/9] clean code Signed-off-by: Lucas --- .../notebooks/sessions/sessionService.test.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.test.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.test.ts index b726a4031be..e103a239e6a 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.test.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.test.ts @@ -47,19 +47,6 @@ describe('SessionService', () => { beforeEach(() => { resetMockStorage(); - const config = mockServices.rootConfig({ - data: { - lightspeed: { - notebooks: { - sessionDefaults: { - provider_id: 'test-notebooks', - embedding_model: 'test-embedding-model', - embedding_dimension: 768, - }, - }, - }, - }, - }); VectorStoresOperator.resetInstance(); // Reset singleton before each test operator = VectorStoresOperator.getInstance(LIGHTSPEED_CORE_ADDR, logger); service = new SessionService(operator, logger); From 623c49eecb74446eb0cdf388fb08f279a3c18d61 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Apr 2026 15:49:05 -0400 Subject: [PATCH 6/9] addressing comments Signed-off-by: Lucas --- .../src/service/notebooks/documents/documentService.ts | 7 ------- .../plugins/lightspeed-backend/src/service/router.ts | 5 ++++- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.ts index 4c4febb48de..37865739614 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/documents/documentService.ts @@ -90,13 +90,6 @@ export class DocumentService { ); } - /** - * Upload a file to the Files API - * @param content - File content as string - * @param title - File title/name - * @returns File ID from the Files API - * @throws Error if upload fails - */ /** * Upload a file to the Files API * @param content - File content as string diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts index 9401789d4c1..7625cbe087d 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts @@ -605,7 +605,10 @@ export async function createRouter( v.name.startsWith('rhdh-product-docs'), )?.id || ''; } - request.body.vector_store_ids = [lightspeed_vector_store_id]; + + if (lightspeed_vector_store_id !== '') { + request.body.vector_store_ids = [lightspeed_vector_store_id]; + } const userQueryParam = `user_id=${encodeURIComponent(user_id)}`; request.body.media_type = 'application/json'; // set media_type to receive start and end event From 67354a24616a189a91f7da6bcf427b093a4a4af8 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Apr 2026 15:59:16 -0400 Subject: [PATCH 7/9] fix comments Signed-off-by: Lucas --- workspaces/lightspeed/app-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/lightspeed/app-config.yaml b/workspaces/lightspeed/app-config.yaml index a9538702f9b..2f65ace298a 100644 --- a/workspaces/lightspeed/app-config.yaml +++ b/workspaces/lightspeed/app-config.yaml @@ -21,8 +21,8 @@ lightspeed: notebooks: enabled: false queryDefaults: - model: redhataillama-31-8b-instruct - provider_id: vllm + model: ${NOTEBOOKS_QUERY_MODEL} + provider_id: ${NOTEBOOKS_QUERY_PROVIDER_ID} backend: # Used for enabling authentication, secret is shared by all backend plugins From 73286a6461e8a955715fb7605d0780b2fc5c16f0 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Apr 2026 16:12:48 -0400 Subject: [PATCH 8/9] adding readme Signed-off-by: Lucas --- workspaces/lightspeed/plugins/lightspeed-backend/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/README.md b/workspaces/lightspeed/plugins/lightspeed-backend/README.md index 8d3e8c7c6a0..eff574017fc 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/README.md +++ b/workspaces/lightspeed/plugins/lightspeed-backend/README.md @@ -110,8 +110,8 @@ lightspeed: # Required: Query defaults for RAG queries # Both model and provider_id must be configured together queryDefaults: - model: llama3.1-8b-instruct # Model to use for answering queries - provider_id: ollama # AI provider for the query model + model: ${NOTEBOOKS_QUERY_MODEL} # Model to use for answering queries. Must map to a model inabled in your Lightspeed Stack run.yaml + provider_id: ${NOTEBOOKS_QUERY_PROVIDER_ID} # AI provider for the query model. Must map to a provier inabled in your Lightspeed Stack run.yaml # Optional: Chunking strategy for document processing chunkingStrategy: @@ -136,6 +136,8 @@ lightspeed: - **`queryDefaults.model`** _(required)_: The LLM model to use for answering RAG queries. Must be available in the configured provider. - **`queryDefaults.provider_id`** _(required)_: The AI provider identifier for the query model (e.g., `ollama`, `vllm`). Both `model` and `provider_id` must be configured together. +> **Important**: The `model` and `provider_id` values must map to a provider and model that are actually enabled in your Lightspeed Stack run.yaml configuration. If the provider or model is not available in Lightspeed Stack, queries will fail. For example, if `openai` enabled in Lightspeed via ENABLE_OPENAI, then model must be available (model=gpt-4o-mini). + **Chunking Strategy** _(optional)_: - **`chunkingStrategy.type`** _(optional)_: Document chunking strategy - `auto` (automatic, default) or `static` (fixed size) From e5227ae3a7ce7262e365e622a24a41a9a8773267 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Apr 2026 16:23:40 -0400 Subject: [PATCH 9/9] fixed spelling errors & grammar on readme Signed-off-by: Lucas --- workspaces/lightspeed/plugins/lightspeed-backend/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/README.md b/workspaces/lightspeed/plugins/lightspeed-backend/README.md index eff574017fc..a05d27622a5 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/README.md +++ b/workspaces/lightspeed/plugins/lightspeed-backend/README.md @@ -110,8 +110,8 @@ lightspeed: # Required: Query defaults for RAG queries # Both model and provider_id must be configured together queryDefaults: - model: ${NOTEBOOKS_QUERY_MODEL} # Model to use for answering queries. Must map to a model inabled in your Lightspeed Stack run.yaml - provider_id: ${NOTEBOOKS_QUERY_PROVIDER_ID} # AI provider for the query model. Must map to a provier inabled in your Lightspeed Stack run.yaml + model: ${NOTEBOOKS_QUERY_MODEL} # Model to use for answering queries. Must map to a model available through the provider set in $NOTEBOOKS_QUERY_PROVIDER_ID + provider_id: ${NOTEBOOKS_QUERY_PROVIDER_ID} # AI provider for the query model. Must map to a provider enabled in your Lightspeed config.yaml # Optional: Chunking strategy for document processing chunkingStrategy: @@ -136,7 +136,7 @@ lightspeed: - **`queryDefaults.model`** _(required)_: The LLM model to use for answering RAG queries. Must be available in the configured provider. - **`queryDefaults.provider_id`** _(required)_: The AI provider identifier for the query model (e.g., `ollama`, `vllm`). Both `model` and `provider_id` must be configured together. -> **Important**: The `model` and `provider_id` values must map to a provider and model that are actually enabled in your Lightspeed Stack run.yaml configuration. If the provider or model is not available in Lightspeed Stack, queries will fail. For example, if `openai` enabled in Lightspeed via ENABLE_OPENAI, then model must be available (model=gpt-4o-mini). +> **Important**: The `model` and `provider_id` values must map to a provider and model that are actually enabled in your Lightspeed config.yaml configuration. If the provider or model is not available in Lightspeed, queries will fail. For example, if `openai` enabled in Lightspeed via ENABLE_OPENAI, then model must be available, e.g (model=gpt-4o-mini). **Chunking Strategy** _(optional)_: