Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lightspeed): integrate lightspeed backend #2501

Merged
43 changes: 17 additions & 26 deletions plugins/lightspeed/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ The Lightspeed plugin enables you to interact with any LLM server running a mode

## For administrators

### Prerequisites

- Follow the lightspeed backend plugin [README](https://github.com/janus-idp/backstage-plugins/tree/main/plugins/lightspeed-backend) to integrate lightspeed backend in your Backstage instance.

### Installation

1. Install the Lightspeed plugin using the following command:
Expand All @@ -14,26 +18,15 @@ The Lightspeed plugin enables you to interact with any LLM server running a mode

### Configuration

1. Set the proxy to the desired LLM server in the `app-config.yaml` file as follows:

```yaml title="app-config.yaml"
proxy:
endpoints:
'/lightspeed/api':
target: http://localhost:11434/v1/
headers:
Authorization: Bearer <token>
```

2. Add a new nav item **Lightspeed** in App `packages/app/src/App.tsx`:
1. Add a new nav item **Lightspeed** in App `packages/app/src/App.tsx`:

```tsx title="packages/app/src/components/App.tsx"
/* highlight-add-next-line */ import { LightspeedPage } from '@janus-idp/backstage-plugin-lightspeed';

<Route path="/lightspeed" element={<LightspeedPage />} />;
```

3. Enable **Lightspeed** page in `packages/app/src/components/Root/Root.tsx`:
2. Enable **Lightspeed** page in `packages/app/src/components/Root/Root.tsx`:

```tsx title="packages/app/src/components/Root/Root.tsx"
/* highlight-add-next-line */ import { LightspeedIcon } from '@janus-idp/backstage-plugin-lightspeed';
Expand Down Expand Up @@ -94,15 +87,14 @@ global:
text: Lightspeed
```

- add the proxy configuration in the `app-config.yaml`
- add the lightspeed configuration in the `app-config.yaml`

```
proxy:
endpoints:
'/lightspeed/api':
target: http://localhost:11434/v1/
headers:
Authorization: Bearer <token>
lightspeed:
servers:
- id: <server_id>
url: <server_URL>
token: <api_key>
```

---
Expand Down Expand Up @@ -136,12 +128,11 @@ mv package $(echo $archive | sed -e 's:\.tgz$::')

```

proxy:
endpoints:
'/lightspeed/api':
target: http://localhost:11434/v1/
headers:
Authorization: Bearer <token>
lightspeed:
servers:
- id: <server id>
url: <serverURL>
token: <api key> # dummy token

dynamicPlugins:
frontend:
Expand Down
27 changes: 26 additions & 1 deletion plugins/lightspeed/config.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
export interface Config {}
export interface Config {
/**
* Configuration required for using lightspeed
* @visibility frontend
*/
lightspeed: {
/**
* @visibility frontend
*/
servers: Array</**
* @visibility frontend
*/
{
/**
* The id of the server.
* @visibility frontend
*/
id: string;
/**
* The url of the server.
* @visibility frontend
*/
url: string;
}>;
};
}
3 changes: 2 additions & 1 deletion plugins/lightspeed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@mui/icons-material": "^5.15.18",
"@patternfly/react-core": "6.0.0-prerelease.21",
"@patternfly/virtual-assistant": "2.0.0-alpha.61",
"@tanstack/react-query": "^5.59.15",
"openai": "^4.52.6",
"react-markdown": "^9.0.1",
"react-use": "^17.2.4"
Expand All @@ -59,7 +60,7 @@
"@backstage/dev-utils": "1.1.2",
"@backstage/test-utils": "1.7.0",
"@testing-library/jest-dom": "6.4.8",
"@testing-library/react": "14.3.1",
"@testing-library/react": "^16.0.1",
"@testing-library/react-hooks": "8.0.1",
"@testing-library/user-event": "14.5.2",
"msw": "1.3.3",
Expand Down
137 changes: 137 additions & 0 deletions plugins/lightspeed/src/api/LightspeedApiClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { ConfigApi, FetchApi } from '@backstage/core-plugin-api';

import { LightspeedAPI } from './api';

export type Options = {
configApi: ConfigApi;
fetchApi: FetchApi;
};

export class LightspeedApiClient implements LightspeedAPI {
private readonly configApi: ConfigApi;
private readonly fetchApi: FetchApi;

constructor(options: Options) {
this.configApi = options.configApi;
this.fetchApi = options.fetchApi;
}

async getBaseUrl() {
return `${this.configApi.getString('backend.baseUrl')}/api/lightspeed`;
}

getServerUrl() {
// Currently supports a single llm server
return `${this.configApi.getConfigArray('lightspeed.servers')[0].getOptionalString('url')}`;
}

async createMessage(
prompt: string,
selectedModel: string,
conversation_id: string,
) {
const baseUrl = await this.getBaseUrl();

const response = await this.fetchApi.fetch(`${baseUrl}/v1/query`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
conversation_id,
serverURL: this.getServerUrl(),
model: selectedModel,
query: prompt,
historyLength: 10,
}),
});

if (!response.body) {
throw new Error('Readable stream is not supported or there is no body.');
}

if (!response.ok) {
throw new Error(
`failed to fetch data, status ${response.status}: ${response.statusText}`,
);
}
return response.body.getReader();
}

private async fetcher(url: string) {
const response = await this.fetchApi.fetch(url, {
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(
`failed to fetch data, status ${response.status}: ${response.statusText}`,
);
}
return response;
}

async getAllModels() {
const baseUrl = await this.getBaseUrl();
const result = await this.fetcher(`${baseUrl}/v1/models`);
const response = await result.json();
return response?.data ? response.data : [];
}

async getConversationMessages(conversation_id: string) {
const baseUrl = await this.getBaseUrl();
const result = await this.fetcher(
`${baseUrl}/conversations/${encodeURIComponent(conversation_id)}`,
);
return await result.json();
}

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) {
throw new Error(
`failed to create conversation, status ${response.status}: ${response.statusText}`,
);
}
return await response.json();
}

async deleteConversation(conversation_id: string) {
const baseUrl = await this.getBaseUrl();

const response = await this.fetchApi.fetch(
`${baseUrl}/conversations/${encodeURIComponent(conversation_id)}`,
{
method: 'DELETE',
headers: {},
},
);

if (!response.ok) {
throw new Error(
`failed to delete conversation, status ${response.status}: ${response.statusText}`,
);
}
return { success: true };
}
}
107 changes: 0 additions & 107 deletions plugins/lightspeed/src/api/LightspeedProxyClient.test.ts

This file was deleted.

Loading
Loading