Skip to content

Commit 75ca5cf

Browse files
authored
feat(lightspeed): Adopt road-core service APIs as backend upstream (#516)
* add RHDH system prompt Signed-off-by: Stephanie <yangcao@redhat.com> * adopt road-core first commit Signed-off-by: Stephanie <yangcao@redhat.com> * add rcs handlers Signed-off-by: Stephanie <yangcao@redhat.com> * fix tests Signed-off-by: Stephanie <yangcao@redhat.com> * add media_type to receive start and end event Signed-off-by: Stephanie <yangcao@redhat.com> * do not set default history leangth for conversation list Signed-off-by: Stephanie <yangcao@redhat.com> * commit yarn lock Signed-off-by: Stephanie <yangcao@redhat.com> * update yarn.lock Signed-off-by: Stephanie <yangcao@redhat.com> * fix ci failure Signed-off-by: Stephanie <yangcao@redhat.com> * cleanup Signed-off-by: Stephanie <yangcao@redhat.com> * fix review comments Signed-off-by: Stephanie <yangcao@redhat.com> --------- Signed-off-by: Stephanie <yangcao@redhat.com>
1 parent 0dbd3bf commit 75ca5cf

14 files changed

Lines changed: 446 additions & 1217 deletions

File tree

workspaces/lightspeed/plugins/lightspeed-backend/__fixtures__/handlers.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ import { http, HttpResponse } from 'msw';
1818

1919
const localHostAndPort = 'localhost:443';
2020
export const LOCAL_AI_ADDR = `http://${localHostAndPort}/v1`;
21+
export const mockModelRes = {
22+
object: 'list',
23+
data: [
24+
{
25+
id: 'ibm-granite-8b-code-instruct',
26+
object: 'model',
27+
},
28+
],
29+
};
2130

2231
function loadTestFixture(filePathFromFixturesDir: string) {
2332
return require(`${__dirname}/${filePathFromFixturesDir}`);
@@ -47,15 +56,23 @@ export const handlers = [
4756
}),
4857

4958
http.get(`${LOCAL_AI_ADDR}/models`, () => {
50-
const mockModelRes = {
51-
object: 'list',
52-
data: [
53-
{
54-
id: 'ibm-granite-8b-code-instruct',
55-
object: 'model',
56-
},
57-
],
58-
};
5959
return HttpResponse.json(mockModelRes);
6060
}),
61+
62+
// Catch-all handler for unknown paths
63+
http.all(`${LOCAL_AI_ADDR}/*`, ({ request }) => {
64+
console.log(`Caught request to unknown path: ${request.url}`);
65+
66+
// Return a 404 response
67+
return new Response(
68+
JSON.stringify({
69+
error: 'Not found',
70+
message: `The requested resource at ${request.url} was not found`,
71+
}),
72+
{
73+
status: 404,
74+
headers: { 'Content-Type': 'application/json' },
75+
},
76+
);
77+
}),
6178
];
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { http, HttpResponse } from 'msw';
18+
19+
export const LOCAL_RCS_ADDR = 'http://0.0.0.0:8080';
20+
21+
function loadTestFixture(filePathFromFixturesDir: string) {
22+
return require(`${__dirname}/${filePathFromFixturesDir}`);
23+
}
24+
25+
const mockConversationId = 'conversation-id-1';
26+
const mockConversationId2 = `conversation-id-2`;
27+
const conversation1History = [
28+
{
29+
content: 'what is openshit lightspeed',
30+
response_metadata: {
31+
created_at: 1741287754.162095,
32+
},
33+
type: 'human',
34+
},
35+
{
36+
content:
37+
"OpenShift Lightspeed is a generative AI assistant integrated into the Red Hat Developer Hub (RHDH), an internal developer portal built on CNCF Backstage. It enhances developer productivity by streamlining workflows, providing instant access to technical knowledge, and supporting developers in their day-to-day tasks.\n\nOpenShift Lightspeed offers various features such as code assistance, knowledge retrieval, system navigation, troubleshooting, and integration support. It can generate, debug, and optimize code snippets, provide instant access to internal and external documentation, offer step-by-step instructions for Red Hat Developer Hub features, diagnose issues in services, pipelines, and configurations with actionable recommendations, and assist with Backstage plugins and integrations, including Kubernetes, CI/CD, and GitOps pipelines.\n\nOpenShift Lightspeed is designed to help developers work smarter, solve problems faster, and ensure they can focus on building and deploying software efficiently. It adapts its communication style to match the user's technical proficiency, providing concise, technical language for experts and clear explanations with examples for beginners.\n\nOpenShift Lightspeed is well-versed in various domains, including programming languages, DevOps, cloud platforms, Backstage, infrastructure as code, security, and documentation and standards. It leverages Markdown to format code snippets, tables, and lists for readability in its responses.",
38+
response_metadata: {
39+
created_at: 1741287761.8619468,
40+
provider: 'my_ollama',
41+
model: 'granite3-dense:8b',
42+
},
43+
type: 'ai',
44+
},
45+
];
46+
47+
const conversation2History = [
48+
{
49+
content: 'Hello',
50+
response_metadata: {
51+
created_at: 1741287754.162095,
52+
},
53+
type: 'human',
54+
},
55+
{
56+
content: 'ai dummy response for test purpose',
57+
response_metadata: {
58+
created_at: 1741287761.8619468,
59+
provider: 'my_ollama',
60+
model: 'granite3-dense:8b',
61+
},
62+
type: 'ai',
63+
},
64+
];
65+
66+
export const chatHistory: any = {};
67+
chatHistory[mockConversationId] = conversation1History;
68+
chatHistory[mockConversationId2] = conversation2History;
69+
70+
export const rcsHandlers = [
71+
http.post(`${LOCAL_RCS_ADDR}/v1/streaming_query`, () => {
72+
const textEncoder = new TextEncoder();
73+
const mockData = loadTestFixture('chatResponse.json');
74+
75+
const stream = new ReadableStream({
76+
start(controller) {
77+
mockData.forEach((chunk: any) => {
78+
controller.enqueue(
79+
textEncoder.encode(`data: ${JSON.stringify(chunk)}\n\n`),
80+
);
81+
});
82+
controller.close();
83+
},
84+
});
85+
86+
return new HttpResponse(stream, {
87+
headers: {
88+
'Content-Type': 'text/plain',
89+
},
90+
});
91+
}),
92+
93+
http.get(`${LOCAL_RCS_ADDR}/conversations/:conversation_id`, ({ params }) => {
94+
const conversation_id = params.conversation_id as string;
95+
if (conversation_id in chatHistory) {
96+
const mockHistoryRes = {
97+
chat_history: chatHistory[conversation_id],
98+
};
99+
return HttpResponse.json(mockHistoryRes);
100+
}
101+
return new HttpResponse(
102+
JSON.stringify({
103+
error: `Conversation ${conversation_id} not found`,
104+
}),
105+
{
106+
status: 500,
107+
headers: { 'Content-Type': 'application/json' },
108+
},
109+
);
110+
}),
111+
112+
http.get(`${LOCAL_RCS_ADDR}/conversations`, () => {
113+
const conversations = [];
114+
const ids = Object.keys(chatHistory);
115+
for (const id of ids) {
116+
const conversation = {
117+
conversation_id: id,
118+
topic_summary: 'dummy summary',
119+
last_message_timestamp: 1742237500.516723,
120+
};
121+
conversations.push(conversation);
122+
}
123+
const mockConversations = {
124+
conversations: conversations,
125+
};
126+
127+
return HttpResponse.json(mockConversations);
128+
}),
129+
130+
http.delete(
131+
`${LOCAL_RCS_ADDR}/conversations/:conversation_id`,
132+
({ params }) => {
133+
const conversation_id = params.conversation_id as string;
134+
if (conversation_id in chatHistory) {
135+
delete chatHistory[conversation_id];
136+
return HttpResponse.json('ok', { status: 200 });
137+
}
138+
return new HttpResponse(
139+
JSON.stringify({
140+
error: `Conversation ${conversation_id} not found`,
141+
}),
142+
{
143+
status: 500,
144+
headers: { 'Content-Type': 'application/json' },
145+
},
146+
);
147+
},
148+
),
149+
150+
http.get(`${LOCAL_RCS_ADDR}/conversations`, () => {
151+
const mockModelRes = {
152+
conversations: [
153+
{
154+
conversation_id: 'c0a3bc27-77cc-46da-822f-93a9c0e0de5b',
155+
topic_summary: 'LIGHTSPEED CONCEPT',
156+
last_message_timestamp: 1741289312.209488,
157+
},
158+
{
159+
conversation_id: 'e6df4804-5ada-4138-9dfb-4ba515beb4c5',
160+
topic_summary: 'AI Topic Summarizer Capability',
161+
last_message_timestamp: 1741289568.360167,
162+
},
163+
{
164+
conversation_id: '933bb6e3-35d3-453a-88fc-b387175f2979',
165+
topic_summary: 'OpenShift Overview',
166+
last_message_timestamp: 1741289606.5125692,
167+
},
168+
],
169+
};
170+
return HttpResponse.json(mockModelRes);
171+
}),
172+
173+
// Catch-all handler for unknown paths
174+
http.all(`${LOCAL_RCS_ADDR}/*`, ({ request }) => {
175+
console.log(`Caught request to unknown path: ${request.url}`);
176+
177+
// Return a 404 response
178+
return new HttpResponse(
179+
JSON.stringify({
180+
error: 'Not found',
181+
message: `The requested resource at ${request.url} was not found`,
182+
}),
183+
{
184+
status: 404,
185+
headers: { 'Content-Type': 'application/json' },
186+
},
187+
);
188+
}),
189+
];

workspaces/lightspeed/plugins/lightspeed-backend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@
5252
"@langchain/openai": "^0.3.0",
5353
"@red-hat-developer-hub/backstage-plugin-lightspeed-common": "workspace:^",
5454
"express": "^4.21.1",
55-
"http-proxy-middleware": "^3.0.2"
55+
"http-proxy-middleware": "^3.0.2",
56+
"node-fetch": "2.6.9"
5657
},
5758
"devDependencies": {
5859
"@backstage/backend-test-utils": "^1.3.0",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
go.sum database tree
2+
35349559
3+
cwARJnSNg8Z/MAAzFAd/fv3b+Q8rBz8/xuTov0QLF8k=
4+
5+
— sum.golang.org Az3gri8APOYqbbuJR+TCHB2v45wZWgBcFmWeqEKxwAfMJBYqV8NtIF3hc01t7TKgo2YdmhPWOuPio7XG3GzWUC5wkA8=

0 commit comments

Comments
 (0)