Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 229 additions & 0 deletions internal/embed/infrastructure/base/templates/llm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
---
# LLM foundation services (OKR-1)
#
# This deploys:
# - An ExternalName Service "ollama" that resolves to the host's Ollama server
# - llms.py (LLMSpy) as an OpenAI-compatible gateway / router over providers
#
# Design notes:
# - No in-cluster Ollama is deployed; the host is expected to run Ollama
# (or another OpenAI-compatible server) on port 11434.
# - The ollama Service abstracts host resolution:
# k3d → host.k3d.internal
# k3s → resolved at stack init via node IP
# - LLMSpy and all consumers reference ollama.llm.svc.cluster.local:11434,
# which the ExternalName Service routes to the host.
apiVersion: v1
kind: Namespace
metadata:
name: llm

---
# ExternalName Service: routes ollama.llm.svc.cluster.local → host Ollama.
# The externalName is resolved during `obol stack init` via the {{OLLAMA_HOST}} placeholder.
apiVersion: v1
kind: Service
metadata:
name: ollama
namespace: llm
labels:
app: ollama
spec:
type: ExternalName
externalName: {{OLLAMA_HOST}}
ports:
- name: http
port: 11434
protocol: TCP

---
# llms.py v3 configuration for Obol Stack:
# - Ollama provider enabled by default (host machine via ollama Service)
# - Anthropic and OpenAI providers available (disabled by default; enabled via `obol llm configure`)
# - Default model is glm-4.7-flash
apiVersion: v1
kind: ConfigMap
metadata:
name: llmspy-config
namespace: llm
data:
llms.json: |
{
"version": 3,
"defaults": {
"headers": {
"Content-Type": "application/json",
"User-Agent": "llmspy.org/3.0"
},
"text": {
"model": "glm-4.7-flash",
"messages": [
{ "role": "user", "content": [{ "type": "text", "text": "" }] }
]
}
},
"providers": {
"ollama": {
"enabled": true
},
"anthropic": {
"enabled": false
},
"openai": {
"enabled": false
}
}
}
providers.json: |
{
"ollama": {
"id": "ollama",
"npm": "ollama",
"api": "http://ollama.llm.svc.cluster.local:11434",
"models": {},
"all_models": true
},
"anthropic": {
"id": "anthropic",
"api_key": "$ANTHROPIC_API_KEY",
"models": {},
"all_models": true
},
"openai": {
"id": "openai",
"api_key": "$OPENAI_API_KEY",
"models": {},
"all_models": true
}
}

---
# Secret for cloud provider API keys. Empty by default; patched imperatively
# via `obol llm configure` or `obol openclaw setup`.
apiVersion: v1
kind: Secret
metadata:
name: llms-secrets
namespace: llm
type: Opaque
stringData:
ANTHROPIC_API_KEY: ""
OPENAI_API_KEY: ""

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: llmspy
namespace: llm
labels:
app: llmspy
spec:
replicas: 1
selector:
matchLabels:
app: llmspy
template:
metadata:
labels:
app: llmspy
spec:
initContainers:
# Seed ~/.llms/llms.json from the ConfigMap. llms.py also writes runtime
# state (e.g. analytics) under ~/.llms, so we keep the directory writable.
- name: seed-config
image: busybox:1.36.1
imagePullPolicy: IfNotPresent
command:
- sh
- -c
- |
set -eu
mkdir -p /data
cp /config/llms.json /data/llms.json
cp /config/providers.json /data/providers.json
chmod 666 /data/llms.json /data/providers.json
volumeMounts:
- name: llmspy-config
mountPath: /config
readOnly: true
- name: llmspy-home
mountPath: /data
containers:
- name: llmspy
# Obol fork of LLMSpy with smart routing extension.
# Pin a specific version for reproducibility.
image: ghcr.io/obolnetwork/llms:3.0.32-obol.1-rc.4
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8000
protocol: TCP
command:
- llms
args:
- --config
- /home/llms/.llms/llms.json
- --serve
- "8000"
envFrom:
- secretRef:
name: llms-secrets
optional: true
env:
# Avoid surprises if the image changes its default HOME.
- name: HOME
value: /home/llms
volumeMounts:
- name: llmspy-home
mountPath: /home/llms/.llms
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 2
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 1000m
memory: 1Gi
volumes:
- name: llmspy-config
configMap:
name: llmspy-config
items:
- key: llms.json
path: llms.json
- key: providers.json
path: providers.json
- name: llmspy-home
emptyDir: {}

---
apiVersion: v1
kind: Service
metadata:
name: llmspy
namespace: llm
labels:
app: llmspy
spec:
type: ClusterIP
selector:
app: llmspy
ports:
- name: http
port: 8000
targetPort: http
protocol: TCP