diff --git a/docs/diagrams/private-chat-ui.excalidraw.png b/docs/diagrams/private-chat-ui.excalidraw.png deleted file mode 100644 index 7e6f1e8b..00000000 Binary files a/docs/diagrams/private-chat-ui.excalidraw.png and /dev/null differ diff --git a/docs/diagrams/private-deep-chat.excalidraw.png b/docs/diagrams/private-deep-chat.excalidraw.png new file mode 100644 index 00000000..f3b9d4a6 Binary files /dev/null and b/docs/diagrams/private-deep-chat.excalidraw.png differ diff --git a/docs/screenshots/private-deep-chat.png b/docs/screenshots/private-deep-chat.png new file mode 100644 index 00000000..f9f346f8 Binary files /dev/null and b/docs/screenshots/private-deep-chat.png differ diff --git a/docs/tutorials/private-chat-app.md b/docs/tutorials/private-chat-app.md deleted file mode 100644 index 33e911b7..00000000 --- a/docs/tutorials/private-chat-app.md +++ /dev/null @@ -1,45 +0,0 @@ -# Private Chat App - -In this tutorial you are going to deploy a custom, multitenant private chat application. The Chat UI is powered by [Deep Chat](https://deepchat.dev/) - an open source web component that is easy to embed into any frontend web app framework or simple HTML page. KubeAI will be used to ensure that all chat interactions are kept private within the cluster. - -![Private Chat UI](../diagrams/private-chat-ui.excalidraw.png) - -## Guide - -Follow the local installation guide in the [quickstart section](/). Avoid installing any models. - -```bash -git clone https://github.com/substratusai/kubeai -cd ./kubeai/examples/private-deep-chat -``` - -Build the private chat application and load the image into the local kind cluster. - -```bash -docker build -t private-deep-chat:latest . -kind load docker-image private-deep-chat:latest -``` - -Deploy the private chat application along with some KubeAI Models. - -```bash -kubectl apply -f ./manifests -``` - -Start a port-forward. - -```bash -kubectl port-forward svc/private-deep-chat 8000:80 -``` - -In your browser, navigate to localhost:8000. - -Login as any of the following users: - -|User|Password | -|----|---------| -|nick|nickspass| -|sam |samspass | -|joe |joespass | - -These users each have access to different KubeAI Models. You can see this assignment by looking at the user mapping in `./main.go` and the associated `tenancy` label on the Models in `./manifests/models.yaml`. \ No newline at end of file diff --git a/docs/tutorials/private-deep-chat.md b/docs/tutorials/private-deep-chat.md new file mode 100644 index 00000000..fceaf51a --- /dev/null +++ b/docs/tutorials/private-deep-chat.md @@ -0,0 +1,74 @@ +# Private Deep Chat + +In this tutorial you are going to deploy a custom, multitenant, private chat application. The Chat UI is powered by Deep Chat - an open source web component that is easy to embed into any frontend web app framework or simple HTML page. KubeAI will be used to ensure that all chat interactions are kept private within the cluster. + +![Screenshot](../screenshots/private-deep-chat.png) + +In this example, we will deploy a custom Go server that will authenticate users using Basic Authentication. When a webpage is requested, a simple HTML page with the `` web component will be served. We will configure Deep Chat and KubeAI to communicate using the OpenAI API format: + +```html + + +``` + +When the HTML page loads we will use Javascript to make an initial request to fetch available models. The Go server will proxy this request to KubeAI: + +```go +proxyHandler := httputil.NewSingleHostReverseProxy(kubeAIURL) + +http.Handle("/openai/", authUserToKubeAI(proxyHandler)) +``` + +The server will translate the username and password provided in the basic auth header into a label selector that will tell KubeAI to filter the models it returns. The same approach will also be used to enforce access at inference-time. + +```go +r.Header.Set("X-Label-Selector", fmt.Sprintf("tenancy in (%s)", + strings.Join(tenancy, ","), +)) +``` + +While this is a simple example application, this overall architecture can be used when incorporating chat into a production application. + +![Architecture](../diagrams/private-deep-chat.excalidraw.png) + +## Guide + +Follow the local installation guide in the [quickstart section](/). Avoid installing any models. + +```bash +git clone https://github.com/substratusai/kubeai +cd ./kubeai/examples/private-deep-chat +``` + +Build the private chat application and load the image into the local kind cluster. + +```bash +docker build -t private-deep-chat:latest . +kind load docker-image private-deep-chat:latest +``` + +Deploy the private chat application along with some KubeAI Models. + +```bash +kubectl apply -f ./manifests +``` + +Start a port-forward. + +```bash +kubectl port-forward svc/private-deep-chat 8000:80 +``` + +In your browser, navigate to localhost:8000. + +Login as any of the following users: + +|User|Password | +|----|---------| +|nick|nickspass| +|sam |samspass | +|joe |joespass | + +These users each have access to different KubeAI Models. You can see this assignment by looking at the user mapping in `./main.go` and the associated `tenancy` label on the Models in `./manifests/models.yaml`. \ No newline at end of file diff --git a/examples/private-deep-chat/main.go b/examples/private-deep-chat/main.go index 776fb8b9..60f615e9 100644 --- a/examples/private-deep-chat/main.go +++ b/examples/private-deep-chat/main.go @@ -48,13 +48,6 @@ func authUserToKubeAI(h http.Handler) http.Handler { tenancy, authenticated := authenticate(user, pass) - log.Printf("%s: %s - authenticating: basicAuthProvided=%t, user=%q, pass=%q, tenancy=%q, authenticated=%t", - r.Method, r.URL.Path, - basicAuthProvided, - user, pass, strings.Join(tenancy, ","), - authenticated, - ) - if !basicAuthProvided || !authenticated || len(tenancy) == 0 { w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) http.Error(w, "Unauthorized", http.StatusUnauthorized) diff --git a/examples/private-deep-chat/manifests/models.yaml b/examples/private-deep-chat/manifests/models.yaml index 1e61983b..109f46a6 100644 --- a/examples/private-deep-chat/manifests/models.yaml +++ b/examples/private-deep-chat/manifests/models.yaml @@ -1,7 +1,7 @@ apiVersion: kubeai.org/v1 kind: Model metadata: - name: gemma2-2b-cpu + name: gemma2-a labels: tenancy: group-a spec: @@ -14,12 +14,12 @@ spec: apiVersion: kubeai.org/v1 kind: Model metadata: - name: opt-125m-cpu + name: gemma2-b labels: tenancy: group-b spec: features: [TextGeneration] - owner: facebook - url: hf://facebook/opt-125m - engine: VLLM - resourceProfile: cpu:1 + owner: google + url: ollama://gemma2:2b + engine: OLlama + resourceProfile: cpu:2 \ No newline at end of file diff --git a/examples/private-deep-chat/static/index.html b/examples/private-deep-chat/static/index.html index b44e9067..441da6a6 100644 --- a/examples/private-deep-chat/static/index.html +++ b/examples/private-deep-chat/static/index.html @@ -13,8 +13,7 @@ - { const selectedValue = event.target.value; const selectedText = event.target.options[event.target.selectedIndex].text;