Skip to content

Commit f208392

Browse files
authored
docs: surface PassthroughModel and make examples runnable and drift-proof (#138)
* docs: embed passthrough example in architecture page via examples/ (single source) Replaces the inline near-duplicate PassthroughModel YAML on the architecture page with a Vite ?raw import of examples/passthrough-openrouter.yaml, rendered via Starlight's <Code> component, so the docs page and the runnable manifest cannot drift. - architecture.md renamed to architecture.mdx to allow the import/JSX syntax. - astro.config.mjs: added vite.server.fs.allow (searchForWorkspaceRoot) so the cross-root ?raw import resolves during `astro dev`. - docs/test/docs-examples.test.mjs: new harness asserting the embed import and Code usage are present and that no inline duplicate of the manifest remains. Used the primary embed path (no prebuild-copy fallback needed): `npm run build` succeeded with the cross-directory ?raw import resolving, and the built docs/dist/architecture/index.html contains the embedded manifest content. * examples: add minimal and advanced-scheduling LLMModel manifests * docs: add external-provider quickstart walkthrough and HF-token command Convert quickstart.md to .mdx, embed the PassthroughModel example via the Task 1 pattern, and add a Route to an external provider section so the anchor referenced from architecture.mdx resolves. Also add an explicit kubectl command for creating the hf-token secret inline where authSecretName is introduced. * examples: align openrouter secret command flag order with quickstart prose The header comment showed 'kubectl -n <ns> create secret generic <name>' while the quickstart page renders 'kubectl create secret generic <name> -n <ns>' a few lines above the embedded manifest. Same command, consistent ordering now. * docs: add PassthroughModel reference, advanced example, fix minimal LLMModel namespace * docs: embed complete OCI and gated-HF model manifests in shared storage * docs: add external-provider troubleshooting section * docs: fix troubleshooting diagnostic commands (valid jsonpath, catch-all check) * docs: reconcile example + install manifest versions to NIC/tag reality Reconciles examples/*.yaml and installation.md pack version references against verified nebari-infrastructure-core source and the confirmed v0.1.1 pack tag: pack targetRevision v0.1.1, gpu-operator v25.10.1, AI Gateway repoURL without the oci:// prefix, clusterIssuer letsencrypt-issuer, and default storageClassName longhorn. Adds a link from installation.md's pack-app manifest to the standalone examples/argocd-application.yaml template. * examples: reference the Installation guide by name, not a repo path The passthrough example's header comment is embedded verbatim into three rendered docs pages; a repo-relative filesystem path reads oddly there. * docs: update image paths to llm-serving-pack after repo rename The configuration reference table and the shared-storage init-image reference pointed at the old ghcr path; the images now publish under llm-serving-pack. ghcr paths only; Go module path unaffected. * docs: install the pack from the published OCI chart (0.1.2) The pack chart is now published to quay.io/nebari/charts (release 0.1.2). Switch the ArgoCD Application examples in installation.md, examples/argocd-application.yaml, and README.md from a git-sourced chart (repoURL + path: charts/nebari-llm-serving) to the OCI Helm source form (repoURL + chart + targetRevision, no oci:// prefix). Cluster-config (LLMModel) sources are untouched. Update the two docs tests that hard-asserted the old git-tag values.
1 parent 9fb01eb commit f208392

16 files changed

Lines changed: 396 additions & 77 deletions

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ spec:
4545

4646
sources:
4747
# Source 1: LLM serving pack Helm chart
48-
- repoURL: https://github.com/nebari-dev/nebari-llm-serving-pack.git
49-
targetRevision: v0.1.0-alpha.7
50-
path: charts/nebari-llm-serving
48+
- repoURL: quay.io/nebari/charts
49+
chart: nebari-llm-serving
50+
targetRevision: "0.1.2"
5151
helm:
5252
releaseName: nebari-llm-serving
5353
values: |

docs/astro.config.mjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// docs/astro.config.mjs
22
import { defineConfig } from 'astro/config';
3+
import { fileURLToPath } from 'node:url';
34
import starlight from '@astrojs/starlight';
45
import { nebari } from '@nebari/starlight';
56
import rehypeMermaid from 'rehype-mermaid';
@@ -47,4 +48,16 @@ export default defineConfig({
4748
remarkPlugins: [[remarkBaseLinks, { base: process.env.BASE || '/' }]],
4849
rehypePlugins: [[rehypeMermaid, { strategy: 'inline-svg' }]],
4950
},
51+
vite: {
52+
server: {
53+
fs: {
54+
// Defense-in-depth: allow the repo root (parent of docs/) so Vite's
55+
// dev static-file middleware can serve examples/*.yaml if a future
56+
// pattern ever needs it. The current embed resolves examples/*.yaml
57+
// via the Astro SSR module graph (`?raw` import), which is NOT gated
58+
// by fs.allow, so this is precautionary, not load-bearing today.
59+
allow: [fileURLToPath(new URL('..', import.meta.url))],
60+
},
61+
},
62+
},
5063
});
Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
---
22
title: Architecture
33
---
4+
import { Code } from '@astrojs/starlight/components';
5+
import passthroughExample from '../../../../examples/passthrough-openrouter.yaml?raw';
6+
47
This page explains how the Nebari LLM serving pack is designed: what components it deploys, how the operator reconciles `LLMModel` resources, how the two authentication paths work, how the key manager operates, and what security guarantees the design provides.
58

69
For configuration knobs (Helm values, CRD fields), see [Configuration](/configuration/).
@@ -239,29 +242,9 @@ The operator combines these pack-level values with each `LLMModel`'s `access.gro
239242

240243
Issue [#95](https://github.com/nebari-dev/llm-serving-pack/issues/95). A PassthroughModel routes the shared `llm.<baseDomain>` / `llm-internal.<baseDomain>` endpoints to an external OpenAI-compatible provider (OpenRouter, api.openai.com, a remote vLLM) instead of a locally served model. The operator provisions gateway plumbing and the same two auth layers served models get; the key-manager lists PassthroughModels next to LLMModels so users mint API keys for external providers through the same UI.
241244

242-
```yaml
243-
apiVersion: llm.nebari.dev/v1alpha1
244-
kind: PassthroughModel
245-
metadata:
246-
name: openrouter
247-
namespace: nebari-llm-serving-system
248-
spec:
249-
provider:
250-
hostname: openrouter.ai # bare hostname, TLS assumed
251-
port: 443 # default
252-
schemaVersion: api/v1 # upstream path prefix (default v1)
253-
credentialSecretName: openrouter-api-key # same-namespace Secret, key "apiKey"
254-
models:
255-
catchAll: true # any model id not claimed elsewhere
256-
declared: # explicit routes, advertised by /v1/models
257-
- openai/gpt-5.2
258-
- anthropic/claude-opus-4.6
259-
access:
260-
groups: [llm] # same semantics as LLMModel access
261-
endpoints:
262-
external: {enabled: true}
263-
internal: {enabled: true}
264-
```
245+
<Code code={passthroughExample} lang="yaml" title="examples/passthrough-openrouter.yaml" />
246+
247+
For a step-by-step walkthrough see [Route to an external provider](/quickstart/#route-to-an-external-provider); for the full field reference see the [PassthroughModel CRD reference](/configuration/#passthroughmodel-crd-reference).
265248

266249
Generated resources (all in the CR's namespace, no serving stack):
267250

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
---
22
title: Configuration
33
---
4+
import { Code } from '@astrojs/starlight/components';
5+
import minimalModel from '../../../../examples/models/minimal.yaml?raw';
6+
import advancedModel from '../../../../examples/models/advanced-scheduling.yaml?raw';
7+
import passthroughExample from '../../../../examples/passthrough-openrouter.yaml?raw';
8+
49
This page is the authoritative reference for every knob the pack exposes.
510
It covers the Helm chart values you set at install time, the `LLMModel` custom resource you create per model, and the `NebariApp` fields the chart uses to wire the key-manager UI into the Nebari platform.
611

@@ -50,7 +55,7 @@ Pass these with `--set key=value` or in a `values.yaml` override file.
5055
| Key | Default | Description |
5156
|-----|---------|-------------|
5257
| `keyManager.enabled` | `true` | Deploy the key-manager service. |
53-
| `keyManager.image.repository` | `ghcr.io/nebari-dev/nebari-llm-serving-pack/key-manager` | Container image repository. |
58+
| `keyManager.image.repository` | `ghcr.io/nebari-dev/llm-serving-pack/key-manager` | Container image repository. |
5459
| `keyManager.image.tag` | `""` | Image tag. Defaults to `.Chart.AppVersion` when empty, keeping chart and image versions in sync. Override only when testing a specific build. |
5560
| `keyManager.image.pullPolicy` | `Always` | Image pull policy. |
5661
| `keyManager.auditInterval` | `5m` | How often the scaffolded audit loop runs (only when `oidcUserinfoURL` is set). Group-change revocation is not yet implemented (see below), so this loop performs no revocation today. |
@@ -83,7 +88,7 @@ The React SPA served by nginx. It serves the SPA and a runtime `/config.json`, a
8388
| Key | Default | Description |
8489
|-----|---------|-------------|
8590
| `frontend.enabled` | `true` | Deploy the frontend (nginx) Deployment, Service, and config ConfigMap. |
86-
| `frontend.image.repository` | `ghcr.io/nebari-dev/nebari-llm-serving-pack/frontend` | Frontend container image repository. |
91+
| `frontend.image.repository` | `ghcr.io/nebari-dev/llm-serving-pack/frontend` | Frontend container image repository. |
8792
| `frontend.image.tag` | `""` | Image tag. Defaults to `.Chart.AppVersion` when empty. |
8893
| `frontend.image.pullPolicy` | `Always` | Image pull policy. |
8994
| `frontend.port` | `8080` | Container port nginx listens on. |
@@ -97,7 +102,7 @@ The React SPA served by nginx. It serves the SPA and a runtime `/config.json`, a
97102

98103
| Key | Default | Description |
99104
|-----|---------|-------------|
100-
| `operator.image.repository` | `ghcr.io/nebari-dev/nebari-llm-serving-pack/operator` | Operator container image repository. |
105+
| `operator.image.repository` | `ghcr.io/nebari-dev/llm-serving-pack/operator` | Operator container image repository. |
101106
| `operator.image.tag` | `""` | Image tag. Defaults to `.Chart.AppVersion` when empty. Override only when testing a specific build. |
102107
| `operator.image.pullPolicy` | `Always` | Image pull policy. |
103108

@@ -197,6 +202,10 @@ Escape hatches for power users. All fields optional.
197202
| `spec.advanced.vllm.extraEnv` | []EnvVar | Additional environment variables on the vLLM container (supports `value`, `valueFrom`, `secretKeyRef`, etc.). |
198203
| `spec.advanced.inferencePool.schedulerConfig` | object | EPP scheduler plugin configuration (free-form, `x-kubernetes-preserve-unknown-fields`). |
199204

205+
A complete manifest exercising these fields:
206+
207+
<Code code={advancedModel} lang="yaml" title="examples/models/advanced-scheduling.yaml" />
208+
200209
### Status fields
201210

202211
The operator writes these fields to `status`; they are read-only.
@@ -213,6 +222,45 @@ The operator writes these fields to `status`; they are read-only.
213222

214223
---
215224

225+
## PassthroughModel CRD reference
226+
227+
`PassthroughModel` (`passthroughmodels.llm.nebari.dev`, group `llm.nebari.dev/v1alpha1`) routes the shared endpoints to an external OpenAI-compatible provider instead of a locally served model. It provisions gateway routing and the same two auth layers as `LLMModel`, but no storage, serving, or scheduling. Required fields: `spec.provider`, `spec.access`.
228+
229+
### spec.provider
230+
231+
| Field | Type | Required | Description |
232+
|-------|------|----------|-------------|
233+
| `spec.provider.hostname` | string | Yes | Provider hostname, e.g. `openrouter.ai` (bare host, TLS assumed). |
234+
| `spec.provider.port` | integer | No (default `443`) | Provider port. |
235+
| `spec.provider.schemaVersion` | string | No (default `v1`) | Upstream API path prefix, e.g. `api/v1` for OpenRouter, `v1` for api.openai.com. |
236+
| `spec.provider.credentialSecretName` | string | Yes | Secret in the PassthroughModel's namespace whose `apiKey` key holds the provider API key. Injected upstream; end users never see it. |
237+
238+
### spec.models
239+
240+
| Field | Type | Required | Description |
241+
|-------|------|----------|-------------|
242+
| `spec.models.catchAll` | boolean | No | Route any model id not claimed by a more specific route. Served LLMModels always win (they match Host and `x-ai-eg-model`). |
243+
| `spec.models.declared` | []string | No | Explicit model ids; also advertised by the gateway's `/v1/models` (external route only) so UIs can build a picker. |
244+
245+
### spec.access
246+
247+
Same semantics as `LLMModel`: set `public: true` or list `groups` (see [spec.access](#specaccess) above).
248+
249+
### spec.endpoints
250+
251+
| Field | Type | Default | Description |
252+
|-------|------|---------|-------------|
253+
| `spec.endpoints.external.enabled` | boolean | `true` | Create the external (API-key) endpoint. |
254+
| `spec.endpoints.internal.enabled` | boolean | `true` | Create the internal (JWT) endpoint. |
255+
256+
> **Naming constraint:** an LLMModel and a PassthroughModel cannot share a name in the same namespace (both derive the `<name>-api-keys` Secret). Only one catch-all PassthroughModel per gateway is supported.
257+
258+
### Minimal PassthroughModel example
259+
260+
<Code code={passthroughExample} lang="yaml" title="examples/passthrough-openrouter.yaml" />
261+
262+
---
263+
216264
## NebariApp fields (key-manager)
217265

218266
The chart renders a `NebariApp` CR (API group `reconcilers.nebari.dev/v1`) when `keyManager.nebariApp.enabled=true`. The `nebari-operator` consumes this CR to provision an HTTPRoute, a cert-manager Certificate, a Keycloak OIDC client, and a landing page tile.
@@ -251,27 +299,4 @@ For the full NebariApp CRD schema, see the [nebari-operator](https://github.com/
251299

252300
The example below is the smallest valid manifest. See [Quickstart](/quickstart/) for an annotated walkthrough and [Shared Storage](/shared-storage/) for PVC options.
253301

254-
```yaml
255-
apiVersion: llm.nebari.dev/v1alpha1
256-
kind: LLMModel
257-
metadata:
258-
name: my-model
259-
namespace: llm-serving
260-
spec:
261-
access:
262-
public: true
263-
model:
264-
name: mistralai/Devstral-Small-2505
265-
source: huggingface
266-
authSecretName: hf-token
267-
preload: true
268-
storage:
269-
type: pvc
270-
size: 200Gi
271-
resources:
272-
gpu:
273-
count: 1
274-
type: nvidia
275-
serving:
276-
replicas: 1
277-
```
302+
<Code code={minimalModel} lang="yaml" title="examples/models/minimal.yaml" />

docs/src/content/docs/installation.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -837,7 +837,8 @@ non-empty `id`.
837837

838838
## 8. Install the nebari-llm-serving pack
839839

840-
The pack itself ships as a single Helm chart that reconciles three things
840+
The pack itself ships as a single Helm chart, published to the
841+
`quay.io/nebari/charts` OCI registry, that reconciles three things
841842
into the cluster:
842843

843844
- The **pack operator** (`nebari-llm-serving-operator`) which watches
@@ -875,9 +876,9 @@ metadata:
875876
spec:
876877
project: foundational
877878
source:
878-
repoURL: https://github.com/nebari-dev/llm-serving-pack.git
879-
targetRevision: v0.1.0-alpha.9
880-
path: charts/nebari-llm-serving
879+
repoURL: quay.io/nebari/charts
880+
chart: nebari-llm-serving
881+
targetRevision: "0.1.2"
881882
helm:
882883
releaseName: nebari-llm-serving
883884
values: |
@@ -925,6 +926,10 @@ spec:
925926
backoff: { duration: 5s, factor: 2, maxDuration: 3m }
926927
```
927928

929+
A generic multi-source template (chart + a cluster-config repo for
930+
LLMModel CRs) is at
931+
[`examples/argocd-application.yaml`](https://github.com/nebari-dev/llm-serving-pack/blob/main/examples/argocd-application.yaml).
932+
928933
The `platform.gateway.external` and `platform.gateway.internal` blocks
929934
both point at `nebari-gateway` because this runbook uses a single
930935
shared Gateway. With `manageSharedListeners: true` the operator adds
Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
---
22
title: Quickstart
33
---
4+
import { Code } from '@astrojs/starlight/components';
5+
import passthroughExample from '../../../../examples/passthrough-openrouter.yaml?raw';
6+
47
Get a model serving in five minutes. This page covers the fast path: deploy the pack, apply one `LLMModel`, and call the API.
58

69
For a full production-grade setup (air-gapped clusters, HuggingFace token secrets, monitoring, and all values), see [Installation](/installation/) and [Configuration](/configuration/).
@@ -144,7 +147,12 @@ spec:
144147
enabled: true
145148
```
146149

147-
For gated HuggingFace models, create a Secret with your HuggingFace token and reference it:
150+
For gated HuggingFace models, create a Secret holding your token, then reference it from the model spec:
151+
152+
```bash
153+
kubectl create secret generic hf-token -n nebari-llm-serving-system \
154+
--from-literal=HF_TOKEN=hf_your_token_here
155+
```
148156

149157
```yaml
150158
spec:
@@ -190,3 +198,29 @@ response = client.chat.completions.create(
190198
messages=[{"role": "user", "content": "Hello"}],
191199
)
192200
```
201+
202+
## Route to an external provider
203+
204+
Instead of serving a model locally, a `PassthroughModel` points the shared endpoints at an external OpenAI-compatible provider (OpenRouter, api.openai.com, a remote vLLM). Users get the same two auth layers and mint keys in the same UI; the platform's provider key is injected upstream and never exposed.
205+
206+
First create a Secret holding the platform's provider key (the `apiKey` field), in the operator namespace:
207+
208+
```bash
209+
kubectl create secret generic openrouter-api-key -n nebari-llm-serving-system \
210+
--from-literal=apiKey='sk-or-v1-...'
211+
```
212+
213+
Then apply the PassthroughModel:
214+
215+
<Code code={passthroughExample} lang="yaml" title="examples/passthrough-openrouter.yaml" />
216+
217+
Once it reconciles, any provider model id works against the shared external endpoint with a minted key (or against `llm-internal.<baseDomain>` with a Keycloak JWT):
218+
219+
```bash
220+
curl https://llm.your-cluster.example.com/v1/chat/completions \
221+
-H "Authorization: Bearer sk-your-api-key" \
222+
-H "Content-Type: application/json" \
223+
-d '{"model": "openai/gpt-5.2", "messages": [{"role": "user", "content": "Hello"}]}'
224+
```
225+
226+
See the [PassthroughModel CRD reference](/configuration/#passthroughmodel-crd-reference) for every field.
Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
---
22
title: Shared Storage
33
---
4+
import { Code } from '@astrojs/starlight/components';
5+
import ociModel from '../../../../examples/models/oci-model.yaml?raw';
6+
import devstralModel from '../../../../examples/models/devstral-small.yaml?raw';
7+
48
Model files are large. Downloading a 30-80 GB model on every pod start is slow, wastes bandwidth, and delays requests. The LLM Serving Pack solves this with persistent model storage managed by the operator: models are downloaded once, cached on a PVC, and shared across replicas.
59

610
## How model storage works
@@ -18,7 +22,7 @@ All model loading paths - HuggingFace and OCI - mount the model at `/model-cache
1822
When `model.source` is `huggingface` (or any value other than `oci`, including an empty string when the field is left unset - the operator treats any non-`oci` source as HuggingFace), the operator:
1923

2024
1. Creates a PVC named `<llmmodel-name>-model-storage` in the same namespace
21-
2. Adds an init container running `ghcr.io/nebari-dev/nebari-llm-serving-pack/model-downloader:latest`
25+
2. Adds an init container running `ghcr.io/nebari-dev/llm-serving-pack/model-downloader:latest`
2226
3. The init container runs `hf download <model-name> --local-dir /model-cache`
2327
4. Mounts the PVC at `/model-cache` in both the init container and the vLLM container
2428

@@ -57,6 +61,10 @@ kubectl create secret generic hf-token -n <operator-namespace> \
5761

5862
The operator injects `HF_TOKEN` as an environment variable into the init container.
5963

64+
A complete gated-model manifest (create the `hf-token` Secret first, as shown above):
65+
66+
<Code code={devstralModel} lang="yaml" title="examples/models/devstral-small.yaml" />
67+
6068
### PVC access mode
6169

6270
The PVC is created with `accessMode: ReadWriteOnce`. Most cloud block storage classes only support `ReadWriteOnce`, which means all replicas must be scheduled on the same node when sharing the PVC. If you need replicas on different nodes, use a storage class backed by a network filesystem (NFS, EFS, Filestore) that supports `ReadWriteMany`.
@@ -91,6 +99,10 @@ Tags are mutable; a digest pins the exact image layer set.
9199

92100
Note: Kubernetes image volumes (alpha in 1.31, beta in 1.35) would eliminate the copy step, but many clusters do not have the `ImageVolume` feature gate enabled. The init-container copy path is the only supported OCI mechanism in v0.1.
93101

102+
A complete OCI manifest:
103+
104+
<Code code={ociModel} lang="yaml" title="examples/models/oci-model.yaml" />
105+
94106
## Storage type: PVC vs emptyDir for HuggingFace models
95107

96108
For HuggingFace models you can choose between `pvc` (default) and `emptyDir`:

docs/src/content/docs/troubleshooting.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ Check the LLMModel status conditions for a machine-readable message:
239239

240240
```bash
241241
kubectl get llmmodel -n nebari-llm-serving-system <model-name> \
242-
-o jsonpath='{.status.conditions}' | python3 -m json.tool
242+
-o jsonpath='{range .status.conditions[*]}{.type}={.status} {.reason}: {.message}{"\n"}{end}'
243243
```
244244

245245
See the [configuration reference](/configuration/) for valid LLMModel field values.
@@ -274,3 +274,35 @@ If either flag is missing, update the ConfigMap (or the ArgoCD Application value
274274
```bash
275275
kubectl rollout restart deploy -n envoy-gateway-system envoy-gateway
276276
```
277+
278+
---
279+
280+
## External provider (PassthroughModel) not working
281+
282+
**Upstream 401 / auth errors from the provider.** The gateway injects the key from `spec.provider.credentialSecretName`. Confirm the Secret exists in the operator namespace and has an `apiKey` key:
283+
284+
```bash
285+
kubectl -n nebari-llm-serving-system get secret <credentialSecretName> \
286+
-o jsonpath='{.data.apiKey}' | base64 -d | head -c 8; echo
287+
```
288+
289+
If empty or the wrong key name, recreate it: the key MUST be named `apiKey`.
290+
291+
**PassthroughModel stuck with `ApplyFailed`.** This condition usually means the Envoy AI Gateway CRDs are not installed; the operator requeues every minute rather than failing outright. Check the CR and the CRDs:
292+
293+
```bash
294+
kubectl -n nebari-llm-serving-system get passthroughmodel <name> \
295+
-o jsonpath='{range .status.conditions[*]}{.type}={.status} {.reason}: {.message}{"\n"}{end}'
296+
kubectl get crd | grep aigateway
297+
```
298+
299+
Install the AI Gateway (see [Installation](/installation/)) and the next reconcile clears the condition.
300+
301+
**A model id you expected to hit the provider is served locally instead.** Per-LLMModel routes match Host and `x-ai-eg-model`, so they always win over a catch-all PassthroughModel. If a declared provider model id collides with a served model name, the served model wins. Rename or remove the conflicting LLMModel, or declare the provider model under a distinct id.
302+
303+
```bash
304+
# If the model id you expected to reach the provider appears here, that
305+
# LLMModel's route wins over the catch-all. Rename or remove the LLMModel,
306+
# or declare the provider model under a different id.
307+
kubectl get llmmodels -n nebari-llm-serving-system
308+
```

0 commit comments

Comments
 (0)