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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions workspaces/lightspeed/.changeset/poor-ads-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@red-hat-developer-hub/backstage-plugin-lightspeed-backend': major
'@red-hat-developer-hub/backstage-plugin-lightspeed-common': minor
---

Implement AI Notebooks session, document, and query service
5 changes: 5 additions & 0 deletions workspaces/lightspeed/app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ app:
organization:
name: Red Hat

# Disable AI Notebooks feature by default
lightspeed:
aiNotebooks:
enabled: true

backend:
# Used for enabling authentication, secret is shared by all backend plugins
# See https://backstage.io/docs/auth/service-to-service-auth for
Expand Down
89 changes: 89 additions & 0 deletions workspaces/lightspeed/plugins/lightspeed-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,92 @@ permission:
policies-csv-file: /some/path/rbac-policy.csv
policyFileReload: true
```

### AI Notebooks (Developer Preview)

AI Notebooks is an experimental feature that enables document-based conversations with Retrieval-Augmented Generation (RAG).

For user-facing feature documentation, see the [Lightspeed Frontend README](../lightspeed/README.md#ai-notebooks-developer-preview).

#### Prerequisites

AI Notebooks requires a **Llama Stack service** to be running. Llama Stack provides the vector database, embeddings, and RAG capabilities.

For Llama Stack setup and configuration, refer to the [Llama Stack documentation](https://github.com/llamastack/llama-stack).

#### Configuration

To enable AI Notebooks, add the following configuration to your `app-config.yaml`:

```yaml
lightspeed:
aiNotebooks:
enabled: true # Enable AI Notebooks feature (default: false)

# Required when enabled: Llama Stack service configuration
llamaStack:
port: 8321 # Llama Stack API endpoint (required, commonly 8321)

# Optional embedding configuration
embeddingModel: sentence-transformers/nomic-ai/nomic-embed-text-v1.5 # (default shown)
embeddingDimension: 768 # Embedding vector dimension (default: 768)
vectorIo:
providerId: rhdh-docs # Vector store provider ID (default: rhdh-docs)

# Optional: File processing timeout (default: 30000ms = 30 seconds)
fileProcessingTimeoutMs: 30000

# Optional: Chunking strategy for document processing
chunkingStrategy:
type: auto # 'auto' or 'static' (default: auto)
# For 'static' chunking:
maxChunkSizeTokens: 512 # Maximum tokens per chunk (default: 512)
chunkOverlapTokens: 50 # Overlap between chunks (default: 50)
```

**Configuration Options**:

- **`enabled`**: Enable or disable the AI Notebooks feature (default: `false`)
- **`llamaStack.port`**: Port of the Llama Stack service (default: `8321`)
- **`llamaStack.embeddingModel`**: Model used for generating embeddings (default: `sentence-transformers/nomic-ai/nomic-embed-text-v1.5`)
- **`llamaStack.embeddingDimension`**: Dimension of embedding vectors (default: `768`)
- **`llamaStack.vectorIo.providerId`**: Vector store provider in Llama Stack config (default: `rhdh-docs`)
- **`fileProcessingTimeoutMs`**: Timeout for file processing in milliseconds (default: `30000`)
- **`chunkingStrategy.type`**: Document chunking strategy - `auto` (automatic) or `static` (fixed size) (default: `auto`)
- **`chunkingStrategy.maxChunkSizeTokens`**: Maximum chunk size in tokens for static chunking (default: `512`)
- **`chunkingStrategy.chunkOverlapTokens`**: Token overlap between chunks for static chunking (default: `50`)

#### API Endpoints

When enabled, AI Notebooks exposes the following REST API endpoints:

- **Health Check**:
- `GET /lightspeed/ai-notebooks/health` - Health check endpoint

- **Sessions**:
- `POST /lightspeed/ai-notebooks/v1/sessions` - Create a new session
- `GET /lightspeed/ai-notebooks/v1/sessions` - List all sessions
- `GET /lightspeed/ai-notebooks/v1/sessions/:sessionId` - Get session details
- `PUT /lightspeed/ai-notebooks/v1/sessions/:sessionId` - Update session
- `DELETE /lightspeed/ai-notebooks/v1/sessions/:sessionId` - Delete session

- **Documents**:
- `POST /lightspeed/ai-notebooks/v1/sessions/:sessionId/documents/upload` - Upload document
- `GET /lightspeed/ai-notebooks/v1/sessions/:sessionId/documents` - List documents
- `PUT /lightspeed/ai-notebooks/v1/sessions/:sessionId/documents/:documentId` - Update document
- `DELETE /lightspeed/ai-notebooks/v1/sessions/:sessionId/documents/:documentId` - Delete document

- **Queries**:
- `POST /lightspeed/ai-notebooks/v1/sessions/:sessionId/query` - Query documents with RAG

#### Permission Framework Support for AI Notebooks

When RBAC is enabled, users need the following permission to use AI Notebooks:

```CSV
p, role:default/team_a, lightspeed.notebooks.use, update, allow

g, user:default/<your-user-name>, role:default/team_a
```

Add this to your `rbac-policy.csv` file along with the existing lightspeed permissions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/*
* Copyright Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { http, HttpResponse, type HttpHandler } from 'msw';

export const LLAMA_STACK_ADDR = 'http://0.0.0.0:8321';

// Mock session data
export const mockSession1 = {
id: 'session-1',
name: 'Test Session 1',
embedding_model: 'sentence-transformers/nomic-ai/nomic-embed-text-v1.5',
embedding_dimension: 768,
provider_id: 'rhdh-docs',
metadata: {
user_id: 'user:default/guest',
name: 'Test Session 1',
description: 'Test description',
created_at: '2024-01-01T00:00:00.000Z',
updated_at: '2024-01-01T00:00:00.000Z',
category: 'test',
project: 'test-project',
document_ids: [],
conversation_id: null,
},
};

export const mockSession2 = {
id: 'session-2',
name: 'Test Session 2',
embedding_model: 'sentence-transformers/nomic-ai/nomic-embed-text-v1.5',
embedding_dimension: 768,
provider_id: 'rhdh-docs',
metadata: {
user_id: 'user:default/guest',
name: 'Test Session 2',
description: 'Another test',
created_at: '2024-01-02T00:00:00.000Z',
updated_at: '2024-01-02T00:00:00.000Z',
document_ids: ['doc-1'],
conversation_id: 'conv-1',
},
};

export const mockFile1 = {
id: 'file-1',
created_at: 1704067200,
status: 'completed' as const,
attributes: {
document_id: 'test-document',
user_id: 'user:default/guest',
title: 'Test Document',
session_id: 'session-1',
source_type: 'text',
created_at: '2024-01-01T00:00:00.000Z',
},
};

// In-memory storage for tests
const vectorStores = new Map<string, any>();
const files = new Map<string, any>();

Check warning on line 74 in workspaces/lightspeed/plugins/lightspeed-backend/__fixtures__/llamaStackHandlers.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Either use this collection's contents or remove the collection.

See more on https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&issues=AZz-ThvNtAxTmA_RPonE&open=AZz-ThvNtAxTmA_RPonE&pullRequest=2499
const vectorStoreFiles = new Map<string, any[]>();

export function resetMockStorage() {
vectorStores.clear();
files.clear();
vectorStoreFiles.clear();
}

export const llamaStackHandlers: HttpHandler[] = [
// Create vector store
http.post(`${LLAMA_STACK_ADDR}/v1/vector_stores`, async ({ request }) => {
const body = (await request.json()) as any;

Check warning on line 86 in workspaces/lightspeed/plugins/lightspeed-backend/__fixtures__/llamaStackHandlers.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&issues=AZz-ThvNtAxTmA_RPonF&open=AZz-ThvNtAxTmA_RPonF&pullRequest=2499
const id = `vs-${Date.now()}`;
const vectorStore = {
id,
name: body.name,
embedding_model: body.embedding_model,
embedding_dimension: body.embedding_dimension,
provider_id: body.provider_id,
metadata: body.metadata || {},
};
vectorStores.set(id, vectorStore);
vectorStoreFiles.set(id, []);
return HttpResponse.json(vectorStore);
}),

// Get vector store
http.get(`${LLAMA_STACK_ADDR}/v1/vector_stores/:id`, ({ params }) => {
const { id } = params;
const vectorStore = vectorStores.get(id as string);
if (!vectorStore) {
return HttpResponse.json(
{ error: 'Vector store not found' },
{ status: 404 },
);
}
return HttpResponse.json(vectorStore);
}),

// Update vector store
http.post(
`${LLAMA_STACK_ADDR}/v1/vector_stores/:id`,
async ({ params, request }) => {
const { id } = params;
const body = (await request.json()) as any;

Check warning on line 119 in workspaces/lightspeed/plugins/lightspeed-backend/__fixtures__/llamaStackHandlers.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&issues=AZz-ThvNtAxTmA_RPonG&open=AZz-ThvNtAxTmA_RPonG&pullRequest=2499
const vectorStore = vectorStores.get(id as string);
if (!vectorStore) {
return HttpResponse.json(
{ error: 'Vector store not found' },
{ status: 404 },
);
}
const updated = { ...vectorStore, ...body };
vectorStores.set(id as string, updated);
return HttpResponse.json(updated);
},
),

// Delete vector store
http.delete(`${LLAMA_STACK_ADDR}/v1/vector_stores/:id`, ({ params }) => {
const { id } = params;
vectorStores.delete(id as string);
vectorStoreFiles.delete(id as string);
return HttpResponse.json({ success: true });
}),

// List vector stores
http.get(`${LLAMA_STACK_ADDR}/v1/vector_stores`, () => {
return HttpResponse.json({
data: Array.from(vectorStores.values()),
});
}),

// Create file
http.post(`${LLAMA_STACK_ADDR}/v1/files`, async () => {
const id = `file-${Date.now()}`;
const file = {
id,
created_at: Math.floor(Date.now() / 1000),
purpose: 'assistants',
};
files.set(id, file);
return HttpResponse.json(file);
}),

// Delete file
http.delete(`${LLAMA_STACK_ADDR}/v1/files/:id`, ({ params }) => {
const { id } = params;
files.delete(id as string);
return HttpResponse.json({ success: true });
}),

// Create vector store file
http.post(
`${LLAMA_STACK_ADDR}/v1/vector_stores/:vectorStoreId/files`,
async ({ params, request }) => {
const { vectorStoreId } = params;
const body = (await request.json()) as any;

Check warning on line 172 in workspaces/lightspeed/plugins/lightspeed-backend/__fixtures__/llamaStackHandlers.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&issues=AZz-ThvNtAxTmA_RPonH&open=AZz-ThvNtAxTmA_RPonH&pullRequest=2499

const vectorStoreFile = {
id: body.file_id,
created_at: Math.floor(Date.now() / 1000),
status: 'completed' as 'in_progress' | 'completed',
attributes: body.attributes || {},
};

const storeFiles = vectorStoreFiles.get(vectorStoreId as string) || [];
storeFiles.push(vectorStoreFile);
vectorStoreFiles.set(vectorStoreId as string, storeFiles);

return HttpResponse.json(vectorStoreFile);
},
),

// List vector store files
http.get(
`${LLAMA_STACK_ADDR}/v1/vector_stores/:vectorStoreId/files`,
({ params }) => {
const { vectorStoreId } = params;
const storeFiles = vectorStoreFiles.get(vectorStoreId as string) || [];
return HttpResponse.json({ data: storeFiles });
},
),

// Get vector store file
http.get(
`${LLAMA_STACK_ADDR}/v1/vector_stores/:vectorStoreId/files/:fileId`,
({ params }) => {
const { vectorStoreId, fileId } = params;
const storeFiles = vectorStoreFiles.get(vectorStoreId as string) || [];
const file = storeFiles.find(f => f.id === fileId);
if (!file) {
return HttpResponse.json({ error: 'File not found' }, { status: 404 });
}
return HttpResponse.json(file);
},
),

// Delete vector store file
http.delete(
`${LLAMA_STACK_ADDR}/v1/vector_stores/:vectorStoreId/files/:fileId`,
({ params }) => {
const { vectorStoreId, fileId } = params;
const storeFiles = vectorStoreFiles.get(vectorStoreId as string) || [];
const filtered = storeFiles.filter(f => f.id !== fileId);
vectorStoreFiles.set(vectorStoreId as string, filtered);
return HttpResponse.json({ success: true });
},
),
];
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
#lightspeed:
# servicePort: 8080 # OPTIONAL: Port for lightspeed service (default: 8080)
# systemPrompt: <custom_system_prompt> # OPTIONAL: Override default RHDH system prompt
#
# # AI Notebooks (Developer Preview) - Disabled by default
# aiNotebooks:
# enabled: false # Set to true to enable AI Notebooks feature
Loading
Loading