Skip to content

Commit 5c67291

Browse files
committed
feat: RHOAIENG-51775 enable external vector stores in GenAI playground
1 parent 6a0fbf3 commit 5c67291

15 files changed

Lines changed: 448 additions & 150 deletions

packages/gen-ai/bff/internal/api/lsd_vectorstores_handler.go

Lines changed: 24 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"strings"
1212

1313
"github.com/julienschmidt/httprouter"
14-
"github.com/openai/openai-go/v2"
1514
"github.com/opendatahub-io/gen-ai/internal/constants"
1615
"github.com/opendatahub-io/gen-ai/internal/integrations"
1716
"github.com/opendatahub-io/gen-ai/internal/integrations/llamastack"
@@ -36,100 +35,71 @@ func hashUsername(username string) string {
3635
return hex.EncodeToString(hash[:16])
3736
}
3837

39-
// LlamaStackListVectorStoresHandler handles GET /gen-ai/api/v1/vectorstores
40-
// TEMPORARY: This handler implements username-based vectorstore isolation by filtering
41-
// the list to return only the vectorstore matching the hashed username.
42-
// TODO: Replace with proper multi-tenant vectorstore support when available
38+
// LlamaStackListVectorStoresHandler handles GET /gen-ai/api/v1/vectorstores.
39+
//
40+
// Returns all vector stores from LlamaStack. Callers are responsible for
41+
// distinguishing between the auto-provisioned file-upload inline store and external
42+
// registered stores using the metadata fields:
43+
// - Auto-provisioned (Milvus file-upload): metadata.created_by == "auto-provisioning"
44+
// - External (registered from llama-stack-config): metadata.created_by is absent
45+
//
46+
// The handler ensures the current user's auto-provisioned store exists, creating
47+
// it if necessary, before returning all stores.
48+
// TEMPORARY: Auto-provisioning will be replaced with proper multi-tenant support.
4349
func (app *App) LlamaStackListVectorStoresHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
4450
ctx := r.Context()
4551

46-
// TEMPORARY: Get username from context for vectorstore isolation
52+
// TEMPORARY: Ensure the current user's auto-provisioned store exists.
4753
identity, ok := ctx.Value(constants.RequestIdentityKey).(*integrations.RequestIdentity)
4854
if !ok || identity == nil {
4955
app.unauthorizedResponse(w, r, errors.New("user identity not found in context"))
5056
return
5157
}
5258

53-
// Get the Kubernetes client to fetch username (uses identity from context)
5459
client, err := app.kubernetesClientFactory.GetClient(ctx)
5560
if err != nil {
5661
app.serverErrorResponse(w, r, err)
5762
return
5863
}
5964

60-
// Fetch the username from the Kubernetes API
6165
username, err := client.GetUser(ctx, identity)
6266
if err != nil {
6367
app.serverErrorResponse(w, r, err)
6468
return
6569
}
6670

67-
hashedUsername := hashUsername(username)
68-
69-
// TEMPORARY: Ignore limit and order parameters from request
70-
// We always return exactly 1 vectorstore (user's own), so we need to fetch all
71-
// vectorstores to find the match. Empty params means fetch all with defaults.
72-
params := llamastack.ListVectorStoresParams{}
73-
74-
// // Parse limit parameter (1-100, default 20)
75-
// if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
76-
// if limit, err := strconv.ParseInt(limitStr, 10, 64); err == nil {
77-
// if limit >= 1 && limit <= 100 {
78-
// params.Limit = &limit
79-
// }
80-
// }
81-
// }
82-
83-
// // Parse order parameter ("asc" or "desc")
84-
// if order := r.URL.Query().Get("order"); order == "asc" || order == "desc" {
85-
// params.Order = order
86-
// }
87-
88-
// Get all vectorstores
89-
vectorStores, err := app.repositories.VectorStores.ListVectorStores(ctx, params)
71+
vectorStores, err := app.repositories.VectorStores.ListVectorStores(ctx, llamastack.ListVectorStoresParams{})
9072
if err != nil {
9173
app.handleLlamaStackClientError(w, r, err)
9274
return
9375
}
9476

95-
// TEMPORARY: Filter vectorstores to find the one matching hashed username
96-
var userVectorStore *openai.VectorStore
97-
foundUserVectorStore := false
98-
99-
for i := range vectorStores {
100-
if vectorStores[i].Name == hashedUsername {
101-
userVectorStore = &vectorStores[i]
102-
foundUserVectorStore = true
77+
// TEMPORARY: Create the user's auto-provisioned store if it doesn't exist yet.
78+
hashedUsername := hashUsername(username)
79+
hasUserStore := false
80+
for _, vs := range vectorStores {
81+
if vs.Name == hashedUsername {
82+
hasUserStore = true
10383
break
10484
}
10585
}
10686

107-
// TEMPORARY: If no vectorstore found for this user, create one automatically
108-
if !foundUserVectorStore {
109-
createParams := llamastack.CreateVectorStoreParams{
87+
if !hasUserStore {
88+
newStore, err := app.repositories.VectorStores.CreateVectorStore(ctx, llamastack.CreateVectorStoreParams{
11089
Name: hashedUsername,
11190
Metadata: map[string]string{
11291
"created_by": "auto-provisioning",
11392
"username": username,
11493
},
115-
}
116-
117-
newVectorStore, err := app.repositories.VectorStores.CreateVectorStore(ctx, createParams)
94+
})
11895
if err != nil {
11996
app.handleLlamaStackClientError(w, r, err)
12097
return
12198
}
122-
123-
userVectorStore = newVectorStore
124-
}
125-
126-
// TEMPORARY: Return only the user's vectorstore as a single-item array
127-
response := VectorStoresResponse{
128-
Data: []openai.VectorStore{*userVectorStore},
99+
vectorStores = append(vectorStores, *newStore)
129100
}
130101

131-
err = app.WriteJSON(w, http.StatusOK, response, nil)
132-
if err != nil {
102+
if err := app.WriteJSON(w, http.StatusOK, VectorStoresResponse{Data: vectorStores}, nil); err != nil {
133103
app.serverErrorResponse(w, r, err)
134104
}
135105
}

packages/gen-ai/frontend/src/app/Chatbot/ChatbotConfigInstance.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import * as React from 'react';
22
import { MessageBox, ChatbotWelcomePrompt } from '@patternfly/chatbot';
3-
import {
4-
ChatbotSourceSettings,
5-
GuardrailModelConfig,
6-
MCPServerFromAPI,
7-
TokenInfo,
8-
} from '~/app/types';
3+
import { GuardrailModelConfig, MCPServerFromAPI, TokenInfo } from '~/app/types';
94
import { ServerStatusInfo } from '~/app/hooks/useMCPServerStatuses';
105
import useChatbotMessages, { UseChatbotMessagesReturn } from './hooks/useChatbotMessages';
116
import {
@@ -19,14 +14,15 @@ import {
1914
selectGuardrailUserInputEnabled,
2015
selectGuardrailModelOutputEnabled,
2116
selectRagEnabled,
17+
selectKnowledgeMode,
18+
selectSelectedVectorStoreId,
2219
} from './store';
2320
import { ChatbotMessages } from './ChatbotMessagesList';
2421
import { sampleWelcomePrompts } from './const';
2522

2623
interface ChatbotConfigInstanceProps {
2724
configId: string;
2825
username?: string;
29-
selectedSourceSettings: ChatbotSourceSettings | null;
3026
currentVectorStoreId: string | null;
3127
mcpServers: MCPServerFromAPI[];
3228
mcpServerStatuses: Map<string, ServerStatusInfo>;
@@ -41,7 +37,6 @@ interface ChatbotConfigInstanceProps {
4137
export const ChatbotConfigInstance: React.FC<ChatbotConfigInstanceProps> = ({
4238
configId,
4339
username,
44-
selectedSourceSettings,
4540
currentVectorStoreId,
4641
mcpServers,
4742
mcpServerStatuses,
@@ -58,6 +53,19 @@ export const ChatbotConfigInstance: React.FC<ChatbotConfigInstanceProps> = ({
5853
const selectedModel = useChatbotConfigStore(selectSelectedModel(configId));
5954
const selectedMcpServerIds = useChatbotConfigStore(selectSelectedMcpServerIds(configId));
6055
const isRagEnabled = useChatbotConfigStore(selectRagEnabled(configId));
56+
const knowledgeMode = useChatbotConfigStore(selectKnowledgeMode(configId));
57+
const selectedVectorStoreId = useChatbotConfigStore(selectSelectedVectorStoreId(configId));
58+
const updateSelectedVectorStoreId = useChatbotConfigStore(
59+
(state) => state.updateSelectedVectorStoreId,
60+
);
61+
62+
// Sync the inline store ID into selectedVectorStoreId when in inline mode,
63+
// so selectedVectorStoreId is always the active vector store regardless of mode.
64+
React.useEffect(() => {
65+
if (knowledgeMode === 'inline') {
66+
updateSelectedVectorStoreId(configId, currentVectorStoreId);
67+
}
68+
}, [knowledgeMode, currentVectorStoreId, configId, updateSelectedVectorStoreId]);
6169

6270
// Guardrails configuration from store
6371
const guardrail = useChatbotConfigStore(selectGuardrail(configId));
@@ -87,13 +95,12 @@ export const ChatbotConfigInstance: React.FC<ChatbotConfigInstanceProps> = ({
8795

8896
const messagesHook = useChatbotMessages({
8997
modelId: selectedModel,
90-
selectedSourceSettings,
9198
systemInstruction,
9299
isRawUploaded: isRagEnabled,
93100
username,
94101
isStreamingEnabled,
95102
temperature,
96-
currentVectorStoreId,
103+
currentVectorStoreId: selectedVectorStoreId,
97104
selectedServerIds: selectedMcpServerIds,
98105
mcpServers,
99106
mcpServerStatuses,

packages/gen-ai/frontend/src/app/Chatbot/ChatbotPlayground.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,6 @@ const ChatbotPlayground: React.FC<ChatbotPlaygroundProps> = ({
413413
key={`${configId}-chatbot-instance`}
414414
configId={configId}
415415
username={username}
416-
selectedSourceSettings={sourceManagement.selectedSourceSettings}
417416
currentVectorStoreId={fileManagement.currentVectorStoreId}
418417
mcpServers={mcpServers}
419418
mcpServerStatuses={mcpServerStatuses}

0 commit comments

Comments
 (0)