Repository: redhat-developer/rhdh-plugins
Pull request #666: feat(lightspeed): align with road-core API endpoints
Author: login: karthikjeeyar
Created: 2025-04-15T10:07:42Z | Merged: 2025-04-22T09:44:46Z

## Align Lightspeed with Road core service endpoints.

This PR contains following changes:

- Removes the `POST /conversations` endpoint.
- use `v1/query` without conversation_id to create a new conversation_id for the first conversation.
- Add inline error messages 

## Screen recording


https://github.com/user-attachments/assets/6a59bd0b-89a9-4b33-abfa-0554b9da04d0

---
Inline error messages:

![image](https://github.com/user-attachments/assets/26710ae0-1ae7-41f1-813a-6c7290ab0c00)

How to test:

1. clone [road-core service repo](https://github.com/road-core/service) & install python dependencies
2. save token to the team cluster in a file called `team_cluster_key.txt`
3. save following to a file called `rcsconfig.yaml`
```
llm_providers:
  - name: team_cluster
    type: openai
    url: "https://ibm-granite-8b-code-instruct-3scale-apicast-production.apps.rosa.redhat-ai-dev.m6no.p3.openshiftapps.com:443/v1"
    credentials_path: team_cluster_key.txt
    disable_model_check: true
ols_config:
  reference_content:
  conversation_cache:
    type: memory
    memory:
      max_entries: 1000
  logging_config:
    app_log_level: info
    lib_log_level: warning
    uvicorn_log_level: info
    suppress_metrics_in_log: false
    suppress_auth_checks_warning_in_log: false
  authentication_config:
    module: "noop"
  default_provider: team_cluster
  default_model: ibm-granite-31-8b-instruct
  query_validation_method: disabled
dev_config:
  enable_dev_ui: true
  disable_auth: false
  disable_tls: true
  enable_system_prompt_override: true

```
4. run `pdm start`

At this point, road-core service should be already start and running on `http://0.0.0.0:8080`, should see Swagger UI `http://0.0.0.0:8080/docs`

5. in lightspeed,  set the same llm server in app-config.yaml. the id should match the provider name set in rcs-config
```
lightspeed:
  servers:
    - id: 'team_cluster'
      url: 'https://ibm-granite-8b-code-instruct-3scale-apicast-production.apps.rosa.redhat-ai-dev.m6no.p3.openshiftapps.com:443/v1'
      token: <token>
```
6. run `yarn dev`

## E2E test report:

![image](https://github.com/user-attachments/assets/8cd357c2-bfb6-43d0-9ccc-8482d348bc19)

<!-- Please describe what you added, and add a screenshot if possible.
     That makes it easier to understand the change so we can :shipit: faster. -->

#### :heavy_check_mark: Checklist

<!--- Please include the following in your Pull Request when applicable: -->

- [x] A changeset describing the change and affected packages. ([more info](https://github.com/redhat-developer/rhdh-plugins/blob/main/CONTRIBUTING.md#creating-changesets))
- [ ] Added or Updated documentation
- [x] Tests for new functionality and regression tests for bug fixes
- [x] Screenshots attached (for UI changes)



Diff:
diff --git a/workspaces/lightspeed/.changeset/shy-carrots-jog.md b/workspaces/lightspeed/.changeset/shy-carrots-jog.md
new file mode 100644
index 0000000000..d29cb6d128
--- /dev/null
+++ b/workspaces/lightspeed/.changeset/shy-carrots-jog.md
@@ -0,0 +1,5 @@
+---
+'@red-hat-developer-hub/backstage-plugin-lightspeed': minor
+---
+
+Align with road-core service API response
diff --git a/workspaces/lightspeed/packages/app/e2e-tests/fixtures/responses.ts b/workspaces/lightspeed/packages/app/e2e-tests/fixtures/responses.ts
index 4c6472e2d9..c28fcbb0c3 100644
--- a/workspaces/lightspeed/packages/app/e2e-tests/fixtures/responses.ts
+++ b/workspaces/lightspeed/packages/app/e2e-tests/fixtures/responses.ts
@@ -39,21 +39,26 @@ export const defaultConversation = {
 export const conversations = [
   {
     conversation_id: 'user:development/guest+Av8Fax73D4XPx5Ls',
-    summary: 'Conversation 1',
-    lastMessageTimestamp: createdAt,
+    topic_summary: 'Conversation 1',
+    last_message_timestamp: createdAt,
+  },
+  {
+    conversation_id: 'temp-conversation-id',
+    topic_summary: 'Temporary conversation',
+    last_message_timestamp: createdAt,
   },
 ];
 
 export const moreConversations = [
   {
     conversation_id: 'user:development/guest+Av8Fax73D4XPx5Ls',
-    summary: 'Conversation 1',
-    lastMessageTimestamp: createdAt,
+    topic_summary: 'Conversation 1',
+    last_message_timestamp: createdAt,
   },
   {
     conversation_id: 'user:development/guest+Av8Fax73D4XPx5La',
-    summary: 'New Conversation',
-    lastMessageTimestamp: createdAt,
+    topic_summary: 'New Conversation',
+    last_message_timestamp: createdAt,
   },
 ];
 
@@ -62,24 +67,20 @@ export const contents = [
     lc: 1,
     type: 'constructor',
     id: ['langchain_core', 'messages', 'HumanMessage'],
-    kwargs: {
-      content: 'New conversation',
-      response_metadata: {
-        created_at: createdAt,
-      },
-      additional_kwargs: {},
+    content: 'New conversation',
+    response_metadata: {
+      created_at: createdAt,
     },
+    additional_kwargs: {},
   },
   {
     lc: 1,
     type: 'constructor',
     id: ['langchain_core', 'messages', 'AIMessage'],
-    kwargs: {
-      content: 'Still a placeholder message',
-      response_metadata: {
-        created_at: createdAt,
-        model: models[1].id,
-      },
+    content: 'Still a placeholder message',
+    response_metadata: {
+      created_at: createdAt,
+      model: models[1].id,
       tool_calls: [],
       invalid_tool_calls: [],
       additional_kwargs: {},
@@ -90,12 +91,32 @@ export const contents = [
 export const botResponse = `This is a placeholder message`;
 
 export const generateQueryResponse = (conversationId: string) => {
-  let body = '';
+  const tokens = botResponse.match(/(\s+|[^\s]+)/g) || [];
+
+  const events: {
+    event: string;
+    data?: Record<string, any>;
+    done?: boolean;
+  }[] = [];
+
+  events.push({
+    event: 'start',
+    data: { conversation_id: conversationId },
+  });
+
+  tokens.forEach((token, index) => {
+    events.push({
+      event: 'token',
+      data: { id: index, token },
+    });
+  });
 
-  for (const token of botResponse.split(' ')) {
-    body += `{"conversation_id":"${conversationId}","response":{"lc":1,"type":"constructor","id":["langchain_core","messages","AIMessageChunk"],"kwargs":{"content":" ${token}","tool_call_chunks":[],"additional_kwargs":{},"id":"chatcmpl-890","tool_calls":[],"invalid_tool_calls":[],"response_metadata":{"prompt":0,"completion":0,"created_at":1736332476031,"model":"${models[1].id}"}}}}`;
-  }
-  body += `{"conversation_id":"${conversationId}","response":{"lc":1,"type":"constructor","id":["langchain_core","messages","AIMessageChunk"],"kwargs":{"content":"","tool_call_chunks":[],"additional_kwargs":{},"id":"chatcmpl-890","tool_calls":[],"invalid_tool_calls":[],"response_metadata":{"prompt":0,"completion":0,"finish_reason":"stop","system_fingerprint":"fp_ollama","created_at":1736332476031,"model":"${models[1].id}"}}}}`;
+  events.push({
+    event: 'end',
+    done: true,
+  });
 
-  return body;
+  return `${events
+    .map(({ event, data }) => `data: ${JSON.stringify({ event, data })}\n\n`)
+    .join('')}\n`;
 };
diff --git a/workspaces/lightspeed/packages/app/e2e-tests/lightspeed.test.ts b/workspaces/lightspeed/packages/app/e2e-tests/lightspeed.test.ts
index abdf8b9093..792bc27a4d 100644
--- a/workspaces/lightspeed/packages/app/e2e-tests/lightspeed.test.ts
+++ b/workspaces/lightspeed/packages/app/e2e-tests/lightspeed.test.ts
@@ -27,6 +27,8 @@ import {
 } from './fixtures/responses';
 import { openLightspeed, sendMessage } from './utils/testHelper';
 
+const botQuery = 'Please respond';
+
 test.beforeEach(async ({ page }) => {
   await page.route(`${modelBaseUrl}/v1/models`, async route => {
     const json = { object: 'list', data: models };
@@ -37,10 +39,6 @@ test.beforeEach(async ({ page }) => {
       const json = [];
       await route.fulfill({ json });
     }
-    if (route.request().method() === 'POST') {
-      const json = defaultConversation;
-      await route.fulfill({ json });
-    }
   });
   await page.route(`${modelBaseUrl}/conversations/user*`, async route => {
     const json = [];
@@ -48,9 +46,12 @@ test.beforeEach(async ({ page }) => {
   });
   await page.route(`${modelBaseUrl}/v1/query`, async route => {
     const payload = route.request().postDataJSON();
-    conversations[0].conversation_id = payload.conversation_id;
-    contents[0].kwargs.content = payload.query;
-    const body = generateQueryResponse(payload.conversation_id);
+
+    const body = generateQueryResponse(
+      payload.query === botQuery
+        ? (conversations[1].conversation_id = payload.conversation_id)
+        : conversations[0].conversation_id,
+    );
     await route.fulfill({ body });
   });
 
@@ -82,27 +83,26 @@ test.describe('Conversation', () => {
   test.beforeEach(async ({ page }) => {
     await page.route(`${modelBaseUrl}/conversations`, async route => {
       if (route.request().method() === 'GET') {
-        const json = conversations;
+        const json = { conversations };
         await route.fulfill({ json });
       } else {
         await route.fulfill();
       }
     });
     await page.route(`${modelBaseUrl}/conversations/user*`, async route => {
-      const json = contents;
+      const json = { chat_history: contents };
       await route.fulfill({ json });
     });
   });
 
   test('Bot responds', async ({ page }) => {
-    const inputText = 'Please respond';
-    await sendMessage(inputText, page);
+    await sendMessage(botQuery, page);
 
     const userMessage = page.locator('.pf-chatbot__message--user');
     const botMessage = page.locator('.pf-chatbot__message--bot');
 
     await expect(userMessage).toBeVisible();
-    await expect(userMessage).toContainText(inputText);
+    await expect(userMessage).toContainText(botQuery);
     await expect(botMessage).toBeVisible();
     await expect(botMessage).toContainText(botResponse);
   });
@@ -123,7 +123,7 @@ test.describe('Conversation', () => {
   test('Filter and switch conversations', async ({ page }) => {
     await page.route(`${modelBaseUrl}/conversations`, async route => {
       if (route.request().method() === 'GET') {
-        const json = moreConversations;
+        const json = { conversations: moreConversations };
         await route.fulfill({ json });
       } else {
         await route.fulfill();
@@ -133,7 +133,7 @@ test.describe('Conversation', () => {
     const sidePanel = page.locator('.pf-v6-c-drawer__panel');
 
     const currentChat = sidePanel.locator('li.pf-chatbot__menu-item--active');
-    await expect(currentChat).toHaveText(moreConversations[0].summary);
+    await expect(currentChat).toHaveText(moreConversations[0].topic_summary);
 
     const chats = sidePanel.locator('li.pf-chatbot__menu-item');
     await expect(chats).toHaveCount(2);
@@ -143,14 +143,14 @@ test.describe('Conversation', () => {
     );
     await searchBox.fill('new');
     await expect(chats).toHaveCount(1);
-    await expect(chats).toHaveText(moreConversations[1].summary);
+    await expect(chats).toHaveText(moreConversations[1].topic_summary);
 
     await chats.click();
 
     const userMessage = page.locator('.pf-chatbot__message--user');
     const botMessage = page.locator('.pf-chatbot__message--bot');
 
-    await expect(userMessage).toContainText(contents[0].kwargs.content);
-    await expect(botMessage).toContainText(contents[1].kwargs.content);
+    await expect(userMessage).toContainText(contents[0].content);
+    await expect(botMessage).toContainText(contents[1].content);
   });
 });
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/api/LightspeedApiClient.ts b/workspaces/lightspeed/plugins/lightspeed/src/api/LightspeedApiClient.ts
index 7f4ec58e5c..8adcfd6635 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/api/LightspeedApiClient.ts
+++ b/workspaces/lightspeed/plugins/lightspeed/src/api/LightspeedApiClient.ts
@@ -16,6 +16,7 @@
 
 import { ConfigApi, FetchApi } from '@backstage/core-plugin-api';
 
+import { TEMP_CONVERSATION_ID } from '../const';
 import { LightspeedAPI } from './api';
 
 export type Options = {
@@ -56,11 +57,15 @@ export class LightspeedApiClient implements LightspeedAPI {
         'Content-Type': 'application/json',
       },
       body: JSON.stringify({
-        conversation_id,
-        serverURL: this.getServerUrl(),
+        conversation_id:
+          conversation_id === TEMP_CONVERSATION_ID
+            ? undefined
+            : conversation_id,
         model: selectedModel,
+        provider: this.configApi
+          .getConfigArray('lightspeed.servers')[0]
+          .getOptionalString('id'), // Currently supports a single llm server
         query: prompt,
-        historyLength: 10,
       }),
     });
 
@@ -69,9 +74,19 @@ export class LightspeedApiClient implements LightspeedAPI {
     }
 
     if (!response.ok) {
-      throw new Error(
-        `failed to fetch data, status ${response.status}: ${response.statusText}`,
-      );
+      const body = await response.body.getReader();
+      const reader = body.read();
+      const decoder = new TextDecoder('utf-8');
+      const text = await reader.then(({ done, value }) => {
+        if (done) {
+          return '';
+        }
+        return decoder.decode(value);
+      });
+      const errorMessage = JSON.parse(text);
+      if (errorMessage?.error) {
+        throw new Error(`failed to create message: ${errorMessage.error}`);
+      }
     }
     return response.body.getReader();
   }
@@ -93,45 +108,46 @@ export class LightspeedApiClient implements LightspeedAPI {
   async getAllModels() {
     const baseUrl = await this.getBaseUrl();
     const result = await this.fetcher(`${baseUrl}/v1/models`);
+
+    if (!result.ok) {
+      throw new Error(
+        `failed to get models, status ${result.status}: ${result.statusText}`,
+      );
+    }
+
     const response = await result.json();
     return response?.data ? response.data : [];
   }
 
   async getConversationMessages(conversation_id: string) {
+    if (conversation_id === TEMP_CONVERSATION_ID) {
+      return [];
+    }
     const baseUrl = await this.getBaseUrl();
     const result = await this.fetcher(
       `${baseUrl}/conversations/${encodeURIComponent(conversation_id)}`,
     );
-    return await result.json();
+    if (!result.ok) {
+      throw new Error(
+        `failed to get conversation messages, status ${result.status}: ${result.statusText}`,
+      );
+    }
+    const response = await result.json();
+    return response.chat_history ?? [];
   }
 
   async getConversations() {
     const baseUrl = await this.getBaseUrl();
     const result = await this.fetcher(`${baseUrl}/conversations`);
-    return await result.json();
-  }
 
-  async createConversation() {
-    const baseUrl = await this.getBaseUrl();
-
-    const response = await this.fetchApi.fetch(`${baseUrl}/conversations`, {
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/json',
-      },
-      body: JSON.stringify({}),
-    });
-
-    if (!response.body) {
-      throw new Error('Something went wrong.');
-    }
-
-    if (!response.ok) {
+    if (!result.ok) {
       throw new Error(
-        `failed to create conversation, status ${response.status}: ${response.statusText}`,
+        `failed to get conversation, status ${result.status}: ${result.statusText}`,
       );
     }
-    return await response.json();
+
+    const response = await result.json();
+    return response.conversations ?? [];
   }
 
   async deleteConversation(conversation_id: string) {
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/api/api.ts b/workspaces/lightspeed/plugins/lightspeed/src/api/api.ts
index 5db9cced81..660fc82a30 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/api/api.ts
+++ b/workspaces/lightspeed/plugins/lightspeed/src/api/api.ts
@@ -23,7 +23,6 @@ import { BaseMessage, ConversationList } from '../types';
 export type LightspeedAPI = {
   getAllModels: () => Promise<OpenAI.Models.Model[]>;
   getConversationMessages: (conversation_id: string) => Promise<BaseMessage[]>;
-  createConversation: () => Promise<{ conversation_id: string }>;
   createMessage: (
     prompt: string,
     selectedModel: string,
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx
index cf7b472690..d52e089fdc 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx
@@ -36,11 +36,11 @@ import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/Chat
 import { DropdownItem, Title } from '@patternfly/react-core';
 import { useQueryClient } from '@tanstack/react-query';
 
+import { TEMP_CONVERSATION_ID } from '../const';
 import {
   useBackstageUserIdentity,
   useConversationMessages,
   useConversations,
-  useCreateConversation,
   useDeleteConversation,
   useIsMobile,
   useLastOpenedConversation,
@@ -129,24 +129,15 @@ export const LightspeedChat = ({
   const queryClient = useQueryClient();
 
   const { data: conversations = [] } = useConversations();
-  const { mutateAsync: createConversation } = useCreateConversation();
   const { mutateAsync: deleteConversation } = useDeleteConversation();
   const { allowed: hasDeleteAccess } = useLightspeedDeletePermission();
 
   React.useEffect(() => {
     if (user && lastOpenedId === null && isReady) {
-      createConversation()
-        .then(({ conversation_id }) => {
-          setConversationId(conversation_id);
-          setNewChatCreated(true);
-        })
-        .catch(e => {
-          // eslint-disable-next-line
-          console.warn(e);
-          setError(e);
-        });
+      setConversationId(TEMP_CONVERSATION_ID);
+      setNewChatCreated(true);
     }
-  }, [user, isReady, lastOpenedId, setConversationId, createConversation]);
+  }, [user, isReady, lastOpenedId, setConversationId]);
 
   React.useEffect(() => {
     // Update last opened conversation whenever `conversationId` changes
@@ -155,12 +146,17 @@ export const LightspeedChat = ({
     }
   }, [conversationId, setLastOpenedId]);
 
+  const onStart = (conv_id: string) => {
+    setConversationId(conv_id);
+  };
+
   const onComplete = (message: string) => {
     setIsSendButtonDisabled(false);
     setAnnouncement(`Message from Bot: ${message}`);
     queryClient.invalidateQueries({
       queryKey: ['conversations'],
     });
+    setNewChatCreated(false);
   };
 
   const { conversationMessages, handleInputPrompt, scrollToBottomRef } =
@@ -170,6 +166,7 @@ export const LightspeedChat = ({
       selectedModel,
       avatar,
       onComplete,
+      onStart,
     );
 
   const [messages, setMessages] =
@@ -184,7 +181,9 @@ export const LightspeedChat = ({
   }, [messages, scrollToBottomRef.current]);
 
   const sendMessage = (message: string | number) => {
-    setNewChatCreated(false);
+    if (conversationId !== TEMP_CONVERSATION_ID) {
+      setNewChatCreated(false);
+    }
     setAnnouncement(
       `Message from User: ${prompt}. Message from Bot is loading.`,
     );
@@ -195,11 +194,10 @@ export const LightspeedChat = ({
   const onNewChat = React.useCallback(() => {
     (async () => {
       setMessages([]);
-      const { conversation_id } = await createConversation();
-      setConversationId(conversation_id);
+      setConversationId(TEMP_CONVERSATION_ID);
       setNewChatCreated(true);
     })();
-  }, [createConversation, setConversationId, setMessages]);
+  }, [setConversationId, setMessages]);
 
   const openDeleteModal = (conversation_id: string) => {
     setTargetConversationId(conversation_id);
@@ -221,6 +219,7 @@ export const LightspeedChat = ({
       } catch (e) {
         // eslint-disable-next-line no-console
         console.warn(e);
+        setError(e);
       }
     })();
   }, [
@@ -295,7 +294,8 @@ export const LightspeedChat = ({
   );
 
   const welcomePrompts =
-    newChatCreated || (!conversationFound && conversationMessages.length === 0)
+    (newChatCreated && conversationMessages.length === 0) ||
+    (!conversationFound && conversationMessages.length === 0)
       ? [
           {
             title: 'Topic 1',
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/hooks/useCreateConversation.ts b/workspaces/lightspeed/plugins/lightspeed/src/const.ts
similarity index 53%
rename from workspaces/lightspeed/plugins/lightspeed/src/hooks/useCreateConversation.ts
rename to workspaces/lightspeed/plugins/lightspeed/src/const.ts
index 0c5899327a..00d6ec9989 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/hooks/useCreateConversation.ts
+++ b/workspaces/lightspeed/plugins/lightspeed/src/const.ts
@@ -13,23 +13,4 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-import { useApi } from '@backstage/core-plugin-api';
-
-import { useMutation, useQueryClient } from '@tanstack/react-query';
-
-import { lightspeedApiRef } from '../api/api';
-
-export const useCreateConversation = () => {
-  const lightspeedApi = useApi(lightspeedApiRef);
-  const queryClient = useQueryClient();
-
-  return useMutation({
-    mutationFn: () => {
-      return lightspeedApi.createConversation();
-    },
-    onSuccess: () => {
-      queryClient.invalidateQueries({ queryKey: ['conversations'] });
-    },
-  });
-};
+export const TEMP_CONVERSATION_ID = 'temp-conversation-id';
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/hooks/__tests__/useConversationMessages.test.tsx b/workspaces/lightspeed/plugins/lightspeed/src/hooks/__tests__/useConversationMessages.test.tsx
index 983016988e..8c7cdaba27 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/hooks/__tests__/useConversationMessages.test.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/hooks/__tests__/useConversationMessages.test.tsx
@@ -54,6 +54,40 @@ const queryClient = new QueryClient({
   },
 });
 
+type SSEEvent = {
+  event: string;
+  data: Record<string, any>;
+};
+
+const createSSEStream = (events: SSEEvent[]): string => {
+  return `${events
+    .map(({ event, data }) => `data: ${JSON.stringify({ event, data })}\n\n`)
+    .join('')}\n`;
+};
+
+const generateSSEFromText = (
+  text: string,
+  conversationId: string = '5f8c430b-b006-4567-a89d-fdf0ab34f800',
+): string => {
+  const events: SSEEvent[] = [];
+
+  events.push({
+    event: 'start',
+    data: { conversation_id: conversationId },
+  });
+
+  const tokens = text.match(/(\s+|[^\s]+)/g) || [];
+
+  // Add token events
+  tokens.forEach((token, index) => {
+    events.push({
+      event: 'token',
+      data: { id: index, token },
+    });
+  });
+
+  return createSSEStream(events);
+};
 const wrapper = ({
   children,
 }: {
@@ -163,9 +197,11 @@ describe('useConversationMesages', () => {
 
     rerender({ conversationId: 'updatedConversationId' });
 
-    expect(result.current.conversations).toEqual({
-      updatedConversationId: [],
-    });
+    expect(result.current.conversations).toEqual(
+      expect.objectContaining({
+        updatedConversationId: [],
+      }),
+    );
   });
 
   it('should call onComplete when streaming is done', async () => {
@@ -178,7 +214,7 @@ describe('useConversationMesages', () => {
           .mockResolvedValueOnce({
             done: false,
             value: new TextEncoder().encode(
-              '{"response":{"kwargs":{"content":"Hi test-user!"}}}',
+              generateSSEFromText('Hi test-user!'),
             ),
           })
           .mockResolvedValueOnce({ done: true, value: null }),
@@ -217,10 +253,16 @@ describe('useConversationMesages', () => {
           .fn()
           .mockResolvedValueOnce({
             done: false,
+
             value: new TextEncoder().encode(
-              '{"response":{"kwargs":{"content":"{{"key": "value"} }}}',
+              `data: {"event": "start", "data": {"conversation_id": "5f8c430b-b006-4567-a89d-fdf0ab34f800"}}\n
+data: {"event": "token", "data": {"id": 0, "token": "{{"key"\n
+data: {"event": "token", "data": {"id": 1, "token": ": "value"}}\n
+data: {"event": "token", "data": {"id": 2, "token": ""}}\n
+`,
             ),
           })
+
           .mockResolvedValueOnce({ done: true, value: null }),
       }),
     };
@@ -315,7 +357,7 @@ describe('useConversationMesages', () => {
           .mockResolvedValueOnce({
             done: false,
             value: new TextEncoder().encode(
-              '{"response":{"kwargs":{"content":"Hi test-user!"}}}',
+              generateSSEFromText('Hi test-user!'),
             ),
           })
           .mockResolvedValueOnce({ done: true, value: null }),
@@ -454,7 +496,7 @@ describe('useConversationMesages', () => {
           .mockResolvedValueOnce({
             done: false,
             value: new TextEncoder().encode(
-              '{"response":{"kwargs":{"content":"Hi from conversation 1!"}}}',
+              generateSSEFromText('Hi from conversation 1!'),
             ),
           })
           .mockResolvedValueOnce({ done: true, value: null }),
@@ -494,7 +536,7 @@ describe('useConversationMesages', () => {
         .mockResolvedValueOnce({
           done: false,
           value: new TextEncoder().encode(
-            '{"response":{"kwargs":{"content":"Hi from conversation 2!"}}}',
+            generateSSEFromText('Hi from conversation 2!'),
           ),
         })
         .mockResolvedValueOnce({ done: true, value: null }),
@@ -526,13 +568,13 @@ describe('useConversationMesages', () => {
           .mockResolvedValueOnce({
             done: false,
             value: new TextEncoder().encode(
-              '{"response":{"kwargs":{"content":"Hi from conversation 1 (part 1)!"}}}',
+              generateSSEFromText('Hi from conversation 1 (part 1)!'),
             ),
           })
           .mockResolvedValueOnce({
             done: false,
             value: new TextEncoder().encode(
-              '{"response":{"kwargs":{"content":"Hi from conversation 1 (part 2)!"}}}',
+              generateSSEFromText('Hi from conversation 1 (part 2)!'),
             ),
           })
           .mockResolvedValueOnce({ done: true, value: null }),
@@ -568,7 +610,7 @@ describe('useConversationMesages', () => {
         .mockResolvedValueOnce({
           done: false,
           value: new TextEncoder().encode(
-            '{"response":{"kwargs":{"content":"Hi from conversation 2!"}}}',
+            generateSSEFromText('Hi from conversation 2!'),
           ),
         })
         .mockResolvedValueOnce({ done: true, value: null }),
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/hooks/index.ts b/workspaces/lightspeed/plugins/lightspeed/src/hooks/index.ts
index c5a5d06812..2eb8b50012 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/hooks/index.ts
+++ b/workspaces/lightspeed/plugins/lightspeed/src/hooks/index.ts
@@ -17,7 +17,6 @@ export * from './useAllModels';
 export * from './useBackstageUserIdentity';
 export * from './useConversationMessages';
 export * from './useConversations';
-export * from './useCreateConversation';
 export * from './useCreateCoversationMessage';
 export * from './useDeleteConversation';
 export * from './useIsMobile';
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/hooks/useConversationMessages.ts b/workspaces/lightspeed/plugins/lightspeed/src/hooks/useConversationMessages.ts
index 6fa0489e0f..639009dfb3 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/hooks/useConversationMessages.ts
+++ b/workspaces/lightspeed/plugins/lightspeed/src/hooks/useConversationMessages.ts
@@ -22,13 +22,13 @@ import { MessageProps } from '@patternfly/chatbot';
 import { useQuery } from '@tanstack/react-query';
 
 import { lightspeedApiRef } from '../api/api';
+import { TEMP_CONVERSATION_ID } from '../const';
 import logo from '../images/logo.svg';
 import {
   createBotMessage,
   createUserMessage,
   getMessageData,
   getTimestamp,
-  splitJsonStrings,
 } from '../utils/lightspeed-chatbox-utils';
 import { useCreateConversationMessage } from './useCreateCoversationMessage';
 
@@ -67,6 +67,7 @@ export const useConversationMessages = (
   selectedModel: string,
   avatar: string = defaultAvatar,
   onComplete?: (message: string) => void,
+  onStart?: (conversation_id: string) => void,
 ) => {
   const { mutateAsync: createMessage } = useCreateConversationMessage();
   const scrollToBottomRef = React.useRef<HTMLDivElement>(null);
@@ -83,8 +84,13 @@ export const useConversationMessages = (
   React.useEffect(() => {
     if (currentConversation !== conversationId) {
       setCurrentConversation(conversationId);
-      setConversations({
-        [conversationId]: [],
+      setConversations(prev => {
+        if (prev[conversationId]) return prev;
+
+        return {
+          ...prev,
+          [conversationId]: [],
+        };
       });
     }
   }, [currentConversation, conversationId]);
@@ -158,6 +164,8 @@ export const useConversationMessages = (
 
   const handleInputPrompt = React.useCallback(
     async (prompt: string) => {
+      let newConversationId = '';
+
       const conversationTuple = [
         createUserMessage({
           avatar,
@@ -193,6 +201,7 @@ export const useConversationMessages = (
         scrollToBottomRef.current?.scrollIntoView({ behavior: 'auto' });
       }, 0);
       const finalMessages: string[] = [];
+      let buffer = '';
 
       try {
         const reader = await createMessage({
@@ -203,62 +212,79 @@ export const useConversationMessages = (
 
         const decoder = new TextDecoder('utf-8');
         const keepGoing = true;
+
         while (keepGoing) {
-          const { done, value } = await reader.read();
+          const { value, done } = await reader.read();
           if (done) break;
 
-          const chunk = decoder.decode(value, { stream: true });
+          buffer += decoder.decode(value, { stream: true });
+
+          // Process all complete messages separated by double newlines
+          const parts = buffer.split('\n\n');
+          buffer = parts.pop()!;
 
-          const data = splitJsonStrings(chunk) ?? [];
-          data?.forEach(line => {
-            const trimmedLine = line.trim();
-            // Ignore empty lines
-            if (!trimmedLine) return;
+          for (const part of parts) {
+            const lines = part
+              .split('\n')
+              .filter(line => line.startsWith('data:'));
+
+            const jsonString = lines
+              .map(line => line.trim().slice(5).trim())
+              .join('');
             try {
-              const jsonData = JSON.parse(line);
-              const content = jsonData?.response?.kwargs?.content || '';
-              finalMessages.push(content);
-
-              // Store streaming message
-              const [humanMessage, aiMessage] =
-                streamingConversations.current[currentConversation];
-              streamingConversations.current[currentConversation] = [
-                humanMessage,
-                { ...aiMessage, content: aiMessage.content + content },
-              ];
-
-              setConversations(prevConversations => {
-                const conversation =
-                  prevConversations[currentConversation] ?? [];
-
-                const lastMessageIndex = conversation.length - 1;
-                const lastMessage =
-                  conversation.length === 0
-                    ? createBotMessage({
-                        content: '',
-                        timestamp: getTimestamp(Date.now()),
-                      })
-                    : { ...conversation[lastMessageIndex] };
-
-                lastMessage.isLoading = false;
-                lastMessage.content += content;
-                lastMessage.name =
-                  jsonData?.response?.kwargs?.response_metadata?.model;
-                lastMessage.timestamp = getTimestamp(
-                  jsonData?.response?.kwargs?.response_metadata?.created_at ||
-                    Date.now(),
-                );
-
-                const updatedConversation = [
-                  ...conversation.slice(0, lastMessageIndex),
-                  lastMessage,
+              const { event, data } = JSON.parse(jsonString);
+              if (event === 'start') {
+                if (currentConversation === TEMP_CONVERSATION_ID) {
+                  // If the conversation is temp, we need to set the new conversation id
+                  newConversationId = data?.conversation_id;
+                }
+              }
+
+              if (event === 'token') {
+                const content = data?.token || '';
+
+                finalMessages.push(content);
+
+                // Store streaming message
+                const [humanMessage, aiMessage] =
+                  streamingConversations.current[currentConversation];
+                streamingConversations.current[currentConversation] = [
+                  humanMessage,
+                  { ...aiMessage, content: aiMessage.content + content },
                 ];
 
-                return {
-                  ...prevConversations,
-                  [currentConversation]: updatedConversation,
-                };
-              });
+                setConversations(prevConversations => {
+                  const conversation =
+                    prevConversations[currentConversation] ?? [];
+
+                  const lastMessageIndex = conversation.length - 1;
+                  const lastMessage =
+                    conversation.length === 0
+                      ? createBotMessage({
+                          content: '',
+                          timestamp: getTimestamp(Date.now()),
+                        })
+                      : { ...conversation[lastMessageIndex] };
+
+                  lastMessage.isLoading = false;
+                  lastMessage.content += content;
+                  lastMessage.name =
+                    data?.response_metadata?.model || selectedModel;
+                  lastMessage.timestamp = getTimestamp(
+                    data?.response_metadata?.created_at || Date.now(),
+                  );
+
+                  const updatedConversation = [
+                    ...conversation.slice(0, lastMessageIndex),
+                    lastMessage,
+                  ];
+
+                  return {
+                    ...prevConversations,
+                    [currentConversation]: updatedConversation,
+                  };
+                });
+              }
             } catch (error) {
               // eslint-disable-next-line no-console
               console.warn('Error parsing JSON:', error);
@@ -266,7 +292,7 @@ export const useConversationMessages = (
                 onComplete('Invalid JSON received');
               }
             }
-          });
+          }
         }
       } catch (e) {
         setConversations(prevConversations => {
@@ -283,6 +309,9 @@ export const useConversationMessages = (
 
           lastMessage.isLoading = false;
           lastMessage.content += e;
+          lastMessage.error = {
+            title: e.message,
+          };
           lastMessage.timestamp = getTimestamp(Date.now());
 
           const updatedConversation = [
@@ -294,7 +323,9 @@ export const useConversationMessages = (
 
           return {
             ...prevConversations,
-            [currentConversation]: updatedConversation,
+            [newConversationId.length > 0
+              ? newConversationId
+              : currentConversation]: updatedConversation,
           };
         });
       }
@@ -303,12 +334,30 @@ export const useConversationMessages = (
       if (typeof onComplete === 'function') {
         onComplete(finalMessages.join(''));
       }
+      // Swap temp conversation messages with new conversation
+
+      if (currentConversation === TEMP_CONVERSATION_ID && newConversationId) {
+        setConversations(prevConversations => {
+          return {
+            ...prevConversations,
+            [newConversationId]: prevConversations[TEMP_CONVERSATION_ID],
+          };
+        });
+
+        onStart?.(newConversationId);
+
+        setConversations(prev => {
+          const { temp, ...rest } = prev;
+          return rest;
+        });
+      }
     },
 
     [
       avatar,
       userName,
       onComplete,
+      onStart,
       selectedModel,
       createMessage,
       currentConversation,
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/types.ts b/workspaces/lightspeed/plugins/lightspeed/src/types.ts
index 12d94dd090..b8fd74d2c4 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/types.ts
+++ b/workspaces/lightspeed/plugins/lightspeed/src/types.ts
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+import { AlertProps } from '@patternfly/react-core';
+
 export type Conversations = {
   [key: string]: {
     user: string;
@@ -22,29 +24,29 @@ export type Conversations = {
     loading: boolean;
     timestamp: string;
     botTimestamp: string;
+    error?: AlertProps;
   };
 };
 
 export interface BaseMessage {
-  lc: number;
+  name: string;
   type: string;
-  id: string[];
-  kwargs: {
-    content: string;
-    response_metadata: {
-      model?: string;
-      created_at: number;
-      role: string;
-    };
-    additional_kwargs: {
-      [_key: string]: any;
-    };
+  id: number;
+  content: string;
+  response_metadata: {
+    model?: string;
+    created_at: number;
+    role?: string;
+  };
+  additional_kwargs: {
+    [_key: string]: any;
   };
+  error?: AlertProps;
 }
 export type ConversationSummary = {
   conversation_id: string;
-  lastMessageTimestamp: number;
-  summary: string;
+  last_message_timestamp: number;
+  topic_summary: string;
 };
 
 export type ConversationList = ConversationSummary[];
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/utils/__tests__/lightspeed-chatbot-utils.test.ts b/workspaces/lightspeed/plugins/lightspeed/src/utils/__tests__/lightspeed-chatbot-utils.test.ts
index 79ef898cc6..2a38bc46bf 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/utils/__tests__/lightspeed-chatbot-utils.test.ts
+++ b/workspaces/lightspeed/plugins/lightspeed/src/utils/__tests__/lightspeed-chatbot-utils.test.ts
@@ -208,17 +208,21 @@ describe('createBotMessage', () => {
 describe('getMessageData', () => {
   it('should return content and timestamp from message.kwargs', () => {
     const message = {
-      kwargs: {
-        content: 'This is the message content',
-        response_metadata: {
-          created_at: 1730121598867,
-        },
+      content: 'This is the message content',
+      response_metadata: {
+        created_at: 1744695548.101305,
+        model: 'granite3-dense:8b',
       },
+      additional_kwargs: {},
+      id: 1,
+      type: 'human',
+      name: '',
     };
-    const result = getMessageData(message as any);
+    const result = getMessageData(message);
     expect(result).toEqual({
       content: 'This is the message content',
-      timestamp: '28/10/2024, 13:19:58',
+      model: 'granite3-dense:8b',
+      timestamp: '15/04/2025, 05:39:08',
     });
   });
 
@@ -240,36 +244,28 @@ describe('getCategorizeMessages', () => {
   const messages: ConversationList = [
     {
       conversation_id: '1',
-      lastMessageTimestamp: new Date().toISOString(),
-      summary: 'Today message',
+      last_message_timestamp: Date.now() / 1000,
+      topic_summary: 'Today message',
     },
     {
       conversation_id: '2',
-      lastMessageTimestamp: new Date(
-        Date.now() - 24 * 60 * 60 * 1000,
-      ).toISOString() as any,
-      summary: 'Yesterday message',
+      last_message_timestamp: (Date.now() - 24 * 60 * 60 * 1000) / 1000,
+      topic_summary: 'Yesterday message',
     },
     {
       conversation_id: '3',
-      lastMessageTimestamp: new Date(
-        Date.now() - 5 * 24 * 60 * 60 * 1000,
-      ).toISOString(),
-      summary: '5 days ago',
+      last_message_timestamp: (Date.now() - 5 * 24 * 60 * 60 * 1000) / 1000,
+      topic_summary: '5 days ago',
     },
     {
       conversation_id: '4',
-      lastMessageTimestamp: new Date(
-        Date.now() - 15 * 24 * 60 * 60 * 1000,
-      ).toISOString(),
-      summary: '15 days ago',
+      last_message_timestamp: (Date.now() - 15 * 24 * 60 * 60 * 1000) / 1000,
+      topic_summary: '15 days ago',
     },
     {
       conversation_id: '5',
-      lastMessageTimestamp: new Date(
-        Date.now() - 45 * 24 * 60 * 60 * 1000,
-      ).toISOString(),
-      summary: '45 days ago',
+      last_message_timestamp: (Date.now() - 45 * 24 * 60 * 60 * 1000) / 1000,
+      topic_summary: '45 days ago',
     },
   ];
 
@@ -289,7 +285,7 @@ describe('getCategorizeMessages', () => {
     expect(result['Previous 30 Days'][0].text).toBe('15 days ago');
 
     const monthYearKey = new Date(
-      messages[4].lastMessageTimestamp,
+      messages[4].last_message_timestamp * 1000,
     ).toLocaleString('default', {
       month: 'long',
       year: 'numeric',
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/utils/lightspeed-chatbox-utils.ts b/workspaces/lightspeed/plugins/lightspeed/src/utils/lightspeed-chatbox-utils.ts
index 8ef8bbfb4d..68caa92491 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/utils/lightspeed-chatbox-utils.ts
+++ b/workspaces/lightspeed/plugins/lightspeed/src/utils/lightspeed-chatbox-utils.ts
@@ -85,6 +85,9 @@ type MessageProps = {
   name?: string;
   avatar?: string | any;
   isLoading?: boolean;
+  error?: {
+    title: string;
+  };
 };
 
 export const createMessage = ({
@@ -94,6 +97,7 @@ export const createMessage = ({
   isLoading = false,
   content,
   timestamp,
+  error,
 }: MessageProps & { role: 'user' | 'bot' }) => ({
   role,
   name,
@@ -101,6 +105,7 @@ export const createMessage = ({
   isLoading,
   content,
   timestamp,
+  error,
 });
 
 export const createUserMessage = (props: MessageProps) =>
@@ -118,9 +123,9 @@ export const createBotMessage = (props: MessageProps) =>
 
 export const getMessageData = (message: BaseMessage) => {
   return {
-    model: message?.kwargs?.response_metadata?.model,
-    content: message?.kwargs?.content || '',
-    timestamp: getTimestamp(message?.kwargs?.response_metadata?.created_at),
+    model: message?.response_metadata?.model,
+    content: message?.content || '',
+    timestamp: getTimestamp(message?.response_metadata?.created_at * 1000),
   };
 };
 
@@ -149,39 +154,42 @@ export const getCategorizeMessages = (
     'Previous 7 Days': [],
     'Previous 30 Days': [],
   };
-
-  messages.forEach(c => {
-    const messageDate = new Date(c.lastMessageTimestamp);
-    const messageDayString = messageDate.toDateString();
-    const dayDifference = getDayDifference(now, c.lastMessageTimestamp);
-
-    const message: Conversation = {
-      id: c.conversation_id,
-      text: c.summary,
-      label: 'Options',
-      ...addProps(c),
-    };
-
-    if (messageDayString === today) {
-      categorizedMessages.Today.push(message);
-    } else if (dayDifference === 1) {
-      categorizedMessages.Yesterday.push(message);
-    } else if (dayDifference <= 7) {
-      categorizedMessages['Previous 7 Days'].push(message);
-    } else if (dayDifference <= 30) {
-      categorizedMessages['Previous 30 Days'].push(message);
-    } else {
-      // handle month-wise grouping
-      const monthYear = messageDate.toLocaleString('default', {
-        month: 'long',
-        year: 'numeric',
-      });
-      if (!categorizedMessages[monthYear]) {
-        categorizedMessages[monthYear] = [];
+  messages
+    .sort((a, b) => b.last_message_timestamp - a.last_message_timestamp)
+    .forEach(c => {
+      const messageDate = new Date(c.last_message_timestamp * 1000);
+      const messageDayString = messageDate.toDateString();
+      const dayDifference = getDayDifference(
+        now,
+        c.last_message_timestamp * 1000,
+      );
+      const message: Conversation = {
+        id: c.conversation_id,
+        text: c.topic_summary,
+        label: 'Options',
+        ...addProps(c),
+      };
+
+      if (messageDayString === today) {
+        categorizedMessages.Today.push(message);
+      } else if (dayDifference === 1) {
+        categorizedMessages.Yesterday.push(message);
+      } else if (dayDifference <= 7) {
+        categorizedMessages['Previous 7 Days'].push(message);
+      } else if (dayDifference <= 30) {
+        categorizedMessages['Previous 30 Days'].push(message);
+      } else {
+        // handle month-wise grouping
+        const monthYear = messageDate.toLocaleString('default', {
+          month: 'long',
+          year: 'numeric',
+        });
+        if (!categorizedMessages[monthYear]) {
+          categorizedMessages[monthYear] = [];
+        }
+        categorizedMessages[monthYear].push(message);
       }
-      categorizedMessages[monthYear].push(message);
-    }
-  });
+    });
 
   const filteredCategories = Object.keys(categorizedMessages).reduce(
     (result, category) => {


Reviews:
user: login: karthikjeeyar
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T12:52:35Z
user: login: yangcao77
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T14:09:20Z
user: login: karthikjeeyar
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T15:17:20Z
user: login: yangcao77
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T03:20:39Z
user: login: yangcao77
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T03:27:48Z
user: login: karthikjeeyar
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-17T04:53:23Z
user: login: karthikjeeyar
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-17T04:55:45Z
user: login: karthikjeeyar
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-17T05:21:59Z
user: login: yangcao77
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T14:19:05Z
user: login: yangcao77
body: Thanks @karthikjeeyar . The change looks good to me, verified working as expected on my local env. 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T14:28:53Z
user: login: karthikjeeyar
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-17T16:00:34Z

Review comments:
diff_hunk: @@ -177,6 +177,10 @@ export async function createRouter(
         if (!system_prompt) {
           request.body.system_prompt = QUERY_SYSTEM_INSTRUCTION;
         }
+        request.body.provider = config
+          .getConfigArray('lightspeed.servers')[0]
+          .getString('id'); // currently only single llm server is supported
path: workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts
user: login: karthikjeeyar
body: @yangcao77 we dont have endpoints to list all the providers, models configured in rcs at the moment (Probably needs a RFE on RCS), so I am using lightspeed backend to attach the provider Id (first instance of `servers[0].id` from the config) instead of expecting it from the Client. wdyt?
created_at: 2025-04-16T12:52:35Z
author_association: MEMBER
diff_hunk: @@ -177,6 +177,10 @@ export async function createRouter(
         if (!system_prompt) {
           request.body.system_prompt = QUERY_SYSTEM_INSTRUCTION;
         }
+        request.body.provider = config
+          .getConfigArray('lightspeed.servers')[0]
+          .getString('id'); // currently only single llm server is supported
path: workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts
user: login: yangcao77
body: Yes. This is the correct way to do it. lightspeed backend should be used to attach the provider id from plugin config. Our sidecar setup script & instruction will convert the plugin config to rcs config. 

since currently only single llm server provider is supported, `servers[0].id ` should be fine. 

 to support multiple llm server providers , backend should have everything supported. does UI have the design on how to display providers and models to allow user to select? should we open an issue to track the multiple provider feature?
created_at: 2025-04-16T14:09:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,6 +177,10 @@ export async function createRouter(
         if (!system_prompt) {
           request.body.system_prompt = QUERY_SYSTEM_INSTRUCTION;
         }
+        request.body.provider = config
+          .getConfigArray('lightspeed.servers')[0]
+          .getString('id'); // currently only single llm server is supported
path: workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts
user: login: karthikjeeyar
body: Model selector is already available, but we dont have any designs for choosing different providers yet. Definitely having endpoints to list all the configured llm providers and its models in RCS is useful because we can leverage those endpoints to only show the RCS configured models rather than directly pulling all the models from the model service directly.
created_at: 2025-04-16T15:17:20Z
author_association: MEMBER
diff_hunk: @@ -177,6 +177,10 @@ export async function createRouter(
         if (!system_prompt) {
           request.body.system_prompt = QUERY_SYSTEM_INSTRUCTION;
         }
+        request.body.provider = config
+          .getConfigArray('lightspeed.servers')[0]
+          .getString('id'); // currently only single llm server is supported
path: workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts
user: login: yangcao77
body: we are not going to have models defined in rcs config for RHDH Lightspeed use case. we want to still use llm `/v1/models` to dynamically list available models on providers instead of hard coded in rcs configuration. 
created_at: 2025-04-17T03:20:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,11 +31,12 @@ export const validateCompletionsRequest = (
       .json({ error: 'model is required and must be a non-empty string' });
   }
 
-  if (typeof reqData.provider !== 'string' || reqData.provider.trim() === '') {
-    return res
-      .status(400)
-      .json({ error: 'provider is required and must be a non-empty string' });
-  }
+  // Todo: // removing this validation for now, as we do not have provider information from the client.
path: workspaces/lightspeed/plugins/lightspeed-backend/src/service/validation.ts
user: login: yangcao77
body: frontend should be able to read the list of available providers configured in RHDH plugin config.
as the visibility is set to frontend `@visibility frontend`
created_at: 2025-04-17T03:27:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,11 +31,12 @@ export const validateCompletionsRequest = (
       .json({ error: 'model is required and must be a non-empty string' });
   }
 
-  if (typeof reqData.provider !== 'string' || reqData.provider.trim() === '') {
-    return res
-      .status(400)
-      .json({ error: 'provider is required and must be a non-empty string' });
-  }
+  // Todo: // removing this validation for now, as we do not have provider information from the client.
path: workspaces/lightspeed/plugins/lightspeed-backend/src/service/validation.ts
user: login: karthikjeeyar
body: Right, we can read the config from frontend too. Updated the code to send provider from Client.
created_at: 2025-04-17T04:53:23Z
author_association: MEMBER
diff_hunk: @@ -177,6 +177,10 @@ export async function createRouter(
         if (!system_prompt) {
           request.body.system_prompt = QUERY_SYSTEM_INSTRUCTION;
         }
+        request.body.provider = config
+          .getConfigArray('lightspeed.servers')[0]
+          .getString('id'); // currently only single llm server is supported
path: workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts
user: login: karthikjeeyar
body: Only downside I see is, we may show 6 models listed from the llm but out of that if only 2 models are registered in RCS config then we will still show those 4 extra models which will never work for the users.
created_at: 2025-04-17T04:55:44Z
author_association: MEMBER
diff_hunk: @@ -177,6 +177,10 @@ export async function createRouter(
         if (!system_prompt) {
           request.body.system_prompt = QUERY_SYSTEM_INSTRUCTION;
         }
+        request.body.provider = config
+          .getConfigArray('lightspeed.servers')[0]
+          .getString('id'); // currently only single llm server is supported
path: workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts
user: login: karthikjeeyar
body: I am surfacing an inline error message, if the user chooses a model that is not registered with RCS.


![image](https://github.com/user-attachments/assets/42e64e8e-efb4-48e1-af3a-12e91484d8e7)

created_at: 2025-04-17T05:21:59Z
author_association: MEMBER
diff_hunk: @@ -177,6 +177,10 @@ export async function createRouter(
         if (!system_prompt) {
           request.body.system_prompt = QUERY_SYSTEM_INSTRUCTION;
         }
+        request.body.provider = config
+          .getConfigArray('lightspeed.servers')[0]
+          .getString('id'); // currently only single llm server is supported
path: workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts
user: login: yangcao77
body: You will need to set `disable_model_check: true` in the provider config in rcs, and provide empty model list.

```
llm_providers:
  - name: team_cluster
    type: openai
    url: "https://ibm-granite-8b-code-instruct-3scale-apicast-production.apps.rosa.redhat-ai-dev.m6no.p3.openshiftapps.com:443/v1"
    credentials_path: team_cluster_key.txt
    disable_model_check: true
```
created_at: 2025-04-17T14:19:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,6 +177,10 @@ export async function createRouter(
         if (!system_prompt) {
           request.body.system_prompt = QUERY_SYSTEM_INSTRUCTION;
         }
+        request.body.provider = config
+          .getConfigArray('lightspeed.servers')[0]
+          .getString('id'); // currently only single llm server is supported
path: workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts
user: login: karthikjeeyar
body: Ah okay. I dint know about this prop,Thanks.
created_at: 2025-04-17T16:00:34Z
author_association: MEMBER

PR comments:
user: login: rhdh-gh-app[bot]
created_at: 2025-04-15T10:08:02Z
body: > [!IMPORTANT]
> This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior.

## Changed Packages

| Package Name | Package Path | Changeset Bump | Current Version |
|:-------------|:-------------|:--------------:|:----------------|
| app | workspaces/lightspeed/packages/app | **none** | `v0.0.0` |
| @red-hat-developer-hub/backstage-plugin-lightspeed | workspaces/lightspeed/plugins/lightspeed | **minor** | `v0.4.3` |


<!-- changeset-feedback -->
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-04-17T05:21:24Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=redhat-developer_rhdh-plugins&pullRequest=666) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [7 New issues](https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&pullRequest=666&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&pullRequest=666&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=redhat-developer_rhdh-plugins&pullRequest=666&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=redhat-developer_rhdh-plugins&pullRequest=666&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=redhat-developer_rhdh-plugins&pullRequest=666&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=redhat-developer_rhdh-plugins&pullRequest=666)


author_association: NONE