Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6b7c92e
feat(lightspeed): AI Notebooks with persistent conversations
JslYoon Mar 16, 2026
510a2b4
update readme
JslYoon Mar 16, 2026
bb7fc34
Merge branch 'main' into JslYoon-ai-notebooks-dp
JslYoon Mar 16, 2026
d5f0d34
addressing lots of comments
JslYoon Mar 18, 2026
0c50003
Merge branch 'main' into JslYoon-ai-notebooks-dp
JslYoon Mar 18, 2026
91712a5
refactor & cleanup
JslYoon Mar 19, 2026
64889e8
Merge branch 'main' into JslYoon-ai-notebooks-dp
JslYoon Mar 19, 2026
f128d5d
cleanup & yarn.lock
JslYoon Mar 19, 2026
63db570
Merge branch 'main' into JslYoon-ai-notebooks-dp
JslYoon Mar 20, 2026
2359a00
Merge branch 'main' into JslYoon-ai-notebooks-dp
JslYoon Mar 22, 2026
ec13739
Merge branch 'main' into JslYoon-ai-notebooks-dp
JslYoon Mar 23, 2026
b5dde86
Merge branch 'main' into JslYoon-ai-notebooks-dp
JslYoon Mar 23, 2026
d5ede44
more test scripts
JslYoon Mar 23, 2026
f595bd3
cleanup code upsert document & addressing comments
JslYoon Mar 24, 2026
4613f58
Merge branch 'main' into JslYoon-ai-notebooks-dp
JslYoon Mar 24, 2026
e1cfea1
Merge branch 'main' into JslYoon-ai-notebooks-dp
JslYoon Mar 24, 2026
f1eca89
final upsert changes
JslYoon Mar 24, 2026
7367466
moving constant to constant.ts
JslYoon Mar 24, 2026
9b47ec1
Update workspaces/lightspeed/plugins/lightspeed-backend/src/service/n…
JslYoon Mar 24, 2026
36f0be8
Update workspaces/lightspeed/plugins/lightspeed-backend/src/service/n…
JslYoon Mar 24, 2026
90959a9
Update workspaces/lightspeed/plugins/lightspeed-backend/src/service/n…
JslYoon Mar 24, 2026
def8075
final cleanup
JslYoon Mar 24, 2026
60621fd
final changes
JslYoon Mar 24, 2026
dfb508d
Merge branch 'main' into JslYoon-ai-notebooks-dp
JslYoon Mar 25, 2026
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: false

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,226 @@
/*
* 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';

import { DEFAULT_LLAMA_STACK_PORT } from '../src/service/constant';

export const LLAMA_STACK_ADDR = `http://0.0.0.0:${DEFAULT_LLAMA_STACK_PORT}`;

// 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 76 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;
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;
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 174 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