Skip to content

Commit c346ec1

Browse files
authored
[Notebooks] cleanup (#2947)
* update readme & error 500 Signed-off-by: Lucas <lyoon@redhat.com> * update prompting, fixing documentation Signed-off-by: Lucas <lyoon@redhat.com> --------- Signed-off-by: Lucas <lyoon@redhat.com>
1 parent fd2032c commit c346ec1

8 files changed

Lines changed: 170 additions & 60 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-lightspeed-backend': minor
3+
---
4+
5+
updated config.d.ts to reflect app-config.yaml notebooks settings. Update notebooks system prompting

workspaces/lightspeed/plugins/lightspeed-backend/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Add the following lightspeed configurations into your `app-config.yaml` file:
3131

3232
```yaml
3333
lightspeed:
34-
servicePort: <portNumber> # Optional - Change the LS service port nubmer. Defaults to 8080.
34+
servicePort: <portNumber> # Optional - Change the LS service port number. Defaults to 8080.
3535
systemPrompt: <system prompt> # Optional - Override the default system prompt.
3636
mcpServers: # Optional - one or more MCP servers
3737
- name: <mcp server name> # must match the name configured in LCS
@@ -129,7 +129,7 @@ lightspeed:
129129

130130
**Notebooks Settings**:
131131

132-
- **`Notebooks.enabled`** _(optional)_: Enable or disable the Notebooks feature (default: `false`)
132+
- **`notebooks.enabled`** _(optional)_: Enable or disable the Notebooks feature (default: `false`)
133133

134134
**Query Defaults** _(required when enabled)_:
135135

workspaces/lightspeed/plugins/lightspeed-backend/config.d.ts

Lines changed: 14 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -52,69 +52,48 @@ export interface Config {
5252
/**
5353
* Configuration for AI Notebooks (Developer Preview)
5454
*/
55-
aiNotebooks?: {
55+
notebooks?: {
5656
/**
5757
* Enable/disable AI Notebooks feature
5858
* When enabled, exposes AI Notebooks REST API endpoints for document-based conversations with RAG.
59-
* Requires Llama Stack service to be running (default: http://0.0.0.0:8321).
59+
* Requires Lightspeed service to be running (default: http://0.0.0.0:8080).
6060
* @default false
6161
* @visibility frontend
6262
*/
63-
enabled?: boolean;
63+
enabled: boolean;
6464
/**
65-
* Llama Stack configuration
65+
* Lightspeed configuration
6666
* @visibility backend
6767
*/
68-
llamaStack?: {
68+
queryDefaults: {
6969
/**
70-
* Llama Stack API port
70+
* Model to use for answering queries. Must map to a model available through the provider set in provider_id.
7171
* @visibility backend
7272
*/
73-
port?: number;
73+
model: string;
7474
/**
75-
* Embedding model for vector database
75+
* AI provider for the query model. Must map to a provider enabled in your Lightspeed config.
7676
* @visibility backend
7777
*/
78-
embeddingModel?: string;
79-
/**
80-
* Embedding dimension
81-
* @visibility backend
82-
*/
83-
embeddingDimension?: number;
84-
/**
85-
* Vector IO configuration
86-
* @visibility backend
87-
*/
88-
vectorIo?: {
89-
/**
90-
* Vector store provider ID
91-
* @visibility backend
92-
*/
93-
providerId?: string;
94-
};
78+
provider_id: string;
9579
};
9680
/**
97-
* File processing timeout in milliseconds
98-
* @visibility backend
99-
*/
100-
fileProcessingTimeoutMs?: number;
101-
/**
102-
* Chunking strategy configuration
81+
* Chunking strategy for document processing
10382
* @visibility backend
10483
*/
10584
chunkingStrategy?: {
10685
/**
107-
* Type of chunking strategy ('auto' or 'static')
86+
* Document chunking strategy - 'auto' (automatic, default) or 'static' (fixed size)
10887
* @visibility backend
10988
*/
110-
type?: string;
89+
type?: 'auto' | 'static';
11190
/**
112-
* Maximum chunk size in tokens (for static strategy)
91+
* Maximum chunk size in tokens for static chunking (default: 512)
11392
* @visibility backend
11493
*/
11594
maxChunkSizeTokens?: number;
11695
/**
117-
* Chunk overlap in tokens (for static strategy)
96+
* Token overlap between chunks for static chunking (default: 50)
11897
* @visibility backend
11998
*/
12099
chunkOverlapTokens?: number;

workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,47 @@ export const DEFAULT_LLAMA_STACK_PORT = 8321; // Llama Stack port
2525
export const DEFAULT_LIGHTSPEED_SERVICE_HOST = '0.0.0.0'; // Lightspeed core service host
2626
export const DEFAULT_LIGHTSPEED_SERVICE_PORT = 8080; // Lightspeed service port
2727
export const DEFAULT_MAX_FILE_SIZE_MB = 20 * 1024 * 1024; // 20MB
28-
export const NOTEBOOKS_SYSTEM_PROMPT =
29-
`You are an expert Research Analyst. Your goal is to synthesize information across provided documents to answer user queries with high precision.
30-
31-
Constraints:
32-
- Groundedness: Only use information explicitly stated in or directly inferred from the documents. If the answer isn't present, state: "I don't know based on the provided documents."
33-
- Citations: Every claim must be followed by an inline citation (e.g., [Document Title/Id]).
34-
- Tone: Maintain a professional, objective, and analytical tone.
35-
- Conflicting Info: If documents contradict each other, highlight the discrepancy rather than choosing one.
36-
37-
Output Format:
38-
1. Summary: A 1-2 sentence high-level answer.
39-
2. Detailed Analysis: A structured breakdown using bullet points.
40-
3. References: A list of sources used. References should be in the format of [Document Title] in a new line for each reference.
41-
42-
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."
43-
Remember, **ALL** references must be from the provided documents and provided documents only.
44-
Make no mistakes.
28+
export const NOTEBOOKS_SYSTEM_PROMPT = `
29+
You are a helpful, analytical Senior Research Analyst assistant. Your primary objective is to synthesize cross-document information to answer user queries with 100% fidelity to the provided documents.
30+
31+
### QUERY TYPES - IMPORTANT
32+
* **Meta Queries ONLY:** ONLY when the user asks specifically about YOU as an assistant (e.g., "who are you", "what can you do", "hello"), respond naturally without requiring documents.
33+
* **ALL OTHER QUERIES:** For ANY other question, you MUST use the strict document-grounding rules below. This includes general knowledge questions, trivia, explanations, etc. If it's not about you as an assistant, it requires document evidence.
34+
35+
### STRICT OPERATIONAL CONSTRAINTS
36+
* **Zero Outside Knowledge:** Do NOT use prior training data, general knowledge, or unsupported logical leaps to answer queries.
37+
* **Absolute Grounding:** If the provided documents do not contain explicit, direct evidence to answer the query, you MUST output exactly: "I cannot answer this based on the provided documents."
38+
* **Precision Citations:** Every single factual claim, metric, or conclusion must have an inline citation [Document Title].
39+
* **Contradictions:** Do not resolve discrepancies. If sources conflict, explicitly document the friction (e.g., "Source A states X, whereas Source B states Y").
40+
41+
### ANALYTICAL GUIDELINES
42+
* **Comprehensive Responses:** Provide thorough, detailed analysis. Don't be overly brief - expand on the evidence with full context and explanation.
43+
* **Quantitative Focus:** Prioritize extracting specific metrics, dates, and figures.
44+
* **Objective Tone:** Use neutral, professional language. Do not use subjective adjectives (e.g., "impressive," "concerning") unless quoting the text directly.
45+
46+
### REQUIRED ANALYSIS PROCESS
47+
Before generating your response, you must internally perform evidence extraction (DO NOT show this in your output):
48+
1. Identify the core entities and requirements of the user's query.
49+
2. Extract exact, verbatim quotes from the provided documents that directly address the query.
50+
3. If no explicit quotes exist to answer the prompt, output ONLY: "I cannot answer this based on the provided documents."
51+
52+
### CRITICAL: NEVER output <evidence_extraction> tags or any internal reasoning in your visible response. These are for your internal analysis only.
53+
54+
### REQUIRED OUTPUT STRUCTURE
55+
When you have evidence from documents, structure your response as:
56+
57+
**Executive Summary:**
58+
[A comprehensive 2-4 sentence synthesis of the primary findings based strictly on the extracted evidence. Provide full context and detail.]
59+
60+
**Detailed Analysis:**
61+
* **[Key Entity/Theme]:** [Thorough explanation of the fact or data point derived from text, with full context and supporting details] [Document Title].
62+
* **[Key Entity/Theme]:** [Thorough explanation of the fact or data point derived from text, with full context and supporting details] [Document Title].
63+
64+
**Referenced Documents:**
65+
* [Document Title 1]
66+
* [Document Title 2]
67+
68+
When you lack evidence, output ONLY: "I cannot answer this based on the provided documents."
4569
`.trim();
4670

4771
/**

workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/VectorStoresOperator.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,16 @@ async function handleHttpError(
7474
error = { detail: await response.text() };
7575
}
7676
logger.error(`Failed to ${operation}:`, error);
77-
throw mapHttpStatusToError(response.status, `Failed to ${operation}`, error);
77+
78+
let status = response.status;
79+
if (status === 500 && operation === 'retrieve vector store') {
80+
logger.warn(
81+
`Treating 500 error as 404 for ${operation} (lightspeed-core limitation)`,
82+
);
83+
status = 404;
84+
}
85+
86+
throw mapHttpStatusToError(status, `Failed to ${operation}`, error);
7887
}
7988

8089
/**
@@ -171,7 +180,6 @@ export class VectorStoresOperator {
171180
},
172181
},
173182
);
174-
175183
if (!response.ok) {
176184
await handleHttpError(response, this.logger, 'retrieve vector store');
177185
}

workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouter.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,43 @@ describe('Notebooks Router', () => {
155155
});
156156
});
157157

158+
describe('GET /v1/sessions/:sessionId', () => {
159+
it('should return 404 for non-existing session', async () => {
160+
const response = await request(app).get(
161+
'/notebooks/v1/sessions/non-existing-session-id',
162+
);
163+
164+
expect(response.status).toBe(404);
165+
expect(response.body.status).toBe('error');
166+
});
167+
168+
it('should retrieve an existing session', async () => {
169+
const createResponse = await request(app)
170+
.post('/notebooks/v1/sessions')
171+
.send({ name: 'Test Session' });
172+
173+
const sessionId = createResponse.body.session.session_id;
174+
const response = await request(app).get(
175+
`/notebooks/v1/sessions/${sessionId}`,
176+
);
177+
178+
expect(response.status).toBe(200);
179+
expect(response.body.status).toBe('success');
180+
expect(response.body.session.session_id).toBe(sessionId);
181+
expect(response.body.session.name).toBe('Test Session');
182+
});
183+
});
184+
158185
describe('PUT /v1/sessions/:sessionId', () => {
186+
it('should return 404 for non-existing session', async () => {
187+
const response = await request(app)
188+
.put('/notebooks/v1/sessions/non-existing-session-id')
189+
.send({ name: 'Updated Name' });
190+
191+
expect(response.status).toBe(404);
192+
expect(response.body.status).toBe('error');
193+
});
194+
159195
it('should update session', async () => {
160196
const createResponse = await request(app)
161197
.post('/notebooks/v1/sessions')
@@ -172,6 +208,15 @@ describe('Notebooks Router', () => {
172208
});
173209

174210
describe('DELETE /v1/sessions/:sessionId', () => {
211+
it('should return 404 for non-existing session', async () => {
212+
const response = await request(app).delete(
213+
'/notebooks/v1/sessions/non-existing-session-id',
214+
);
215+
216+
expect(response.status).toBe(404);
217+
expect(response.body.status).toBe('error');
218+
});
219+
175220
it('should delete session', async () => {
176221
const createResponse = await request(app)
177222
.post('/notebooks/v1/sessions')
@@ -299,6 +344,15 @@ describe('Notebooks Router', () => {
299344
sessionId = response.body.session.session_id;
300345
});
301346

347+
it('should return 404 for non-existing session', async () => {
348+
const response = await request(app)
349+
.post('/notebooks/v1/sessions/non-existing-session/query')
350+
.send({ query: 'What is this about?' });
351+
352+
expect(response.status).toBe(404);
353+
expect(response.body.status).toBe('error');
354+
});
355+
302356
it('should return 400 if query missing', async () => {
303357
const response = await request(app)
304358
.post(`/notebooks/v1/sessions/${sessionId}/query`)

workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ export async function createNotebooksRouter(
7777
);
7878
const systemPrompt = NOTEBOOKS_SYSTEM_PROMPT;
7979

80-
if ((queryModel && !queryProvider) || (!queryModel && queryProvider)) {
81-
throw new Error('Query model and provider must be configured together');
80+
if (!queryModel || !queryProvider) {
81+
throw new Error(
82+
'Query model and provider are required. Please configure lightspeed.notebooks.queryDefaults.model and lightspeed.notebooks.queryDefaults.provider_id',
83+
);
8284
}
8385

8486
logger.info(
@@ -231,15 +233,51 @@ export async function createNotebooksRouter(
231233
this.push(`data: ${JSON.stringify(legacy)}\n\n`);
232234
} else if (eventType === 'response.completed') {
233235
const usage = parsed?.response?.usage;
236+
237+
// Log the full response to see what we're getting
238+
logger.info(
239+
`Full response.completed event: ${JSON.stringify(parsed?.response, null, 2)}`,
240+
);
241+
242+
// Extract citations/sources from tool calls (file_search results)
243+
const toolCalls = parsed?.response?.tool_calls || [];
244+
logger.info(
245+
`Tool calls received: ${JSON.stringify(toolCalls, null, 2)}`,
246+
);
247+
248+
const referencedDocuments: any[] = [];
249+
250+
for (const toolCall of toolCalls) {
251+
if (toolCall.tool_name === 'file_search') {
252+
logger.info(
253+
`Found file_search tool call: ${JSON.stringify(toolCall, null, 2)}`,
254+
);
255+
const citations = toolCall.content?.citations || [];
256+
for (const citation of citations) {
257+
referencedDocuments.push({
258+
document_id: citation.document_id || citation.file_id,
259+
content: citation.text || citation.content,
260+
});
261+
}
262+
}
263+
}
264+
265+
logger.info(
266+
`Referenced documents: ${JSON.stringify(referencedDocuments)}`,
267+
);
268+
234269
const legacy = {
235270
event: 'end',
236271
data: {
237-
referenced_documents: [],
272+
referenced_documents: referencedDocuments,
238273
input_tokens: usage?.input_tokens,
239274
output_tokens: usage?.output_tokens,
240275
},
241276
};
242277
this.push(`data: ${JSON.stringify(legacy)}\n\n`);
278+
} else {
279+
// Log unhandled event types to help identify what we're missing
280+
logger.debug(`Unhandled SSE event type: ${eventType}`, parsed);
243281
}
244282
}
245283

@@ -458,6 +496,8 @@ export async function createNotebooksRouter(
458496
tools: [{ type: 'file_search', vector_store_ids: [sessionId] }],
459497
model: `${queryProvider}/${queryModel}`,
460498
stream: true,
499+
temperature: 0.05,
500+
shield_ids: [],
461501
max_tool_calls: 10,
462502
...(conversationId && { conversation: conversationId }),
463503
};

workspaces/lightspeed/plugins/lightspeed/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ Notebooks is an experimental feature that enables **document-based conversations
133133

134134
#### Prerequisites for Notebooks
135135

136-
- Notebooks requires a **Llama Stack service** to be running
136+
- Notebooks requires a **Lightspeed Stack service** to be running
137137
- The backend administrator must enable the feature (see [Backend Configuration](../lightspeed-backend/README.md#notebooks-developer-preview))
138138
- Users need the appropriate RBAC permissions (if enabled)
139139

0 commit comments

Comments
 (0)