Checkpoint: Phase 10: LLM Provider Configuration — Ollama Cloud как дефолт, локальный Ollama закомментирован (GPU only), docker-compose/stack обновлены, .env.example с 4 провайдерами

This commit is contained in:
Manus
2026-03-20 19:16:44 -04:00
parent 02742f836c
commit dda99d7056
6 changed files with 259 additions and 71 deletions

53
docker/.env.example Normal file
View File

@@ -0,0 +1,53 @@
##############################################################################
# GoClaw Control Center — Environment Variables
# Copy this file to docker/.env and fill in your values.
#
# cp docker/.env.example docker/.env
# docker compose -f docker/docker-compose.yml up -d
##############################################################################
# ── LLM Provider ─────────────────────────────────────────────────────────────
#
# Choose ONE of the following providers by setting LLM_BASE_URL + LLM_API_KEY.
#
# Option 1: Ollama Cloud (default)
# Sign up at https://ollama.com → Settings → API Keys
# Supports all public Ollama models (qwen2.5, llama3, mistral, etc.)
LLM_BASE_URL=https://ollama.com/v1
LLM_API_KEY=your-ollama-cloud-api-key-here
# Option 2: OpenAI
# LLM_BASE_URL=https://api.openai.com/v1
# LLM_API_KEY=sk-...
# Option 3: Any OpenAI-compatible API (Groq, Together, Mistral, etc.)
# LLM_BASE_URL=https://api.groq.com/openai/v1
# LLM_API_KEY=gsk_...
# Option 4: Local Ollama on a GPU machine (no API key needed)
# Also uncomment the "ollama" service in docker-compose.yml if running locally.
# LLM_BASE_URL=http://localhost:11434
# LLM_API_KEY=
# Default model to use when agent config is not available
DEFAULT_MODEL=qwen2.5:7b
# ── Database ──────────────────────────────────────────────────────────────────
MYSQL_ROOT_PASSWORD=goClawRoot123
MYSQL_DATABASE=goclaw
MYSQL_USER=goclaw
MYSQL_PASSWORD=goClawPass123
# ── Authentication ────────────────────────────────────────────────────────────
JWT_SECRET=change-me-to-a-random-64-char-string
# ── Manus OAuth (optional, for Manus-hosted deployment) ──────────────────────
VITE_APP_ID=
OAUTH_SERVER_URL=
VITE_OAUTH_PORTAL_URL=
# ── Manus Built-in APIs (optional) ───────────────────────────────────────────
BUILT_IN_FORGE_API_URL=
BUILT_IN_FORGE_API_KEY=
VITE_FRONTEND_FORGE_API_KEY=
VITE_FRONTEND_FORGE_API_URL=

View File

@@ -6,8 +6,9 @@ RUN npm install -g pnpm@latest
WORKDIR /app
# Copy package files for layer caching
# Copy package files and patches for layer caching
COPY package.json pnpm-lock.yaml ./
COPY patches/ ./patches/
RUN pnpm install --frozen-lockfile
# Copy source code (exclude gateway/ and docker/)
@@ -40,7 +41,9 @@ RUN npm install -g pnpm@latest
# Copy package files and install production deps only
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod
COPY patches/ ./patches/
# Install all deps (vite is needed at runtime for SSR/proxy)
RUN pnpm install --frozen-lockfile --ignore-scripts
# Copy built artifacts
COPY --from=builder /app/dist ./dist

View File

@@ -4,23 +4,34 @@
# Services:
# control-center — React + Node.js tRPC frontend/backend (:3000)
# gateway — Go Orchestrator + Tool Executor (:18789)
# ollama — Local LLM server (:11434)
# db — MySQL 8 (:3306)
#
# LLM Provider (set in .env or environment):
# Cloud (default): LLM_BASE_URL=https://ollama.com/v1 + LLM_API_KEY=<key>
# OpenAI-compat: LLM_BASE_URL=https://api.openai.com/v1 + LLM_API_KEY=<key>
# Local GPU node: LLM_BASE_URL=http://<gpu-host>:11434 (no key needed)
#
# Local Ollama (GPU only):
# The ollama service below is commented out by default.
# Uncomment it only on machines with a compatible GPU.
# Then set: LLM_BASE_URL=http://ollama:11434
#
# Usage:
# cp docker/.env.example docker/.env # fill in LLM_API_KEY etc.
# docker compose -f docker/docker-compose.yml up -d
# docker compose -f docker/docker-compose.yml logs -f gateway
# docker compose -f docker/docker-compose.yml down -v
##############################################################################
version: "3.9"
name: goclaw
networks:
goclaw-net:
driver: bridge
volumes:
ollama-data:
mysql-data:
# ollama-data: # Uncomment when using local Ollama service below
services:
@@ -47,27 +58,32 @@ services:
retries: 5
start_period: 30s
# ── Ollama LLM Server ─────────────────────────────────────────────────────
ollama:
image: ollama/ollama:latest
container_name: goclaw-ollama
restart: unless-stopped
ports:
- "11434:11434"
volumes:
- ollama-data:/root/.ollama
networks:
- goclaw-net
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
# Uncomment if no GPU:
# environment:
# - OLLAMA_NUM_PARALLEL=2
# ── Local Ollama LLM Server (GPU ONLY — disabled by default) ─────────────
# Uncomment this entire block only on machines with a compatible NVIDIA or
# Apple Silicon GPU. Then set LLM_BASE_URL=http://ollama:11434 in the
# gateway service below (or in your .env file).
#
# ollama:
# image: ollama/ollama:latest
# container_name: goclaw-ollama
# restart: unless-stopped
# ports:
# - "11434:11434"
# volumes:
# - ollama-data:/root/.ollama
# networks:
# - goclaw-net
# environment:
# - OLLAMA_NUM_PARALLEL=2
# - OLLAMA_MAX_LOADED_MODELS=2
# # NVIDIA GPU support — uncomment if available:
# # deploy:
# # resources:
# # reservations:
# # devices:
# # - driver: nvidia
# # count: all
# # capabilities: [gpu]
# ── Go Gateway (Orchestrator + Tool Executor) ─────────────────────────────
gateway:
@@ -80,21 +96,34 @@ services:
- "18789:18789"
environment:
PORT: "18789"
OLLAMA_BASE_URL: "http://ollama:11434"
# ── LLM Provider ─────────────────────────────────────────────────────
# Cloud default (Ollama Cloud, OpenAI-compatible):
LLM_BASE_URL: "${LLM_BASE_URL:-https://ollama.com/v1}"
LLM_API_KEY: "${LLM_API_KEY:-${OLLAMA_API_KEY:-}}"
# Legacy alias (still supported):
OLLAMA_API_KEY: "${OLLAMA_API_KEY:-${LLM_API_KEY:-}}"
# ── To use local Ollama on GPU node, set in .env: ─────────────────────
# LLM_BASE_URL=http://ollama:11434 (if ollama service above is enabled)
# LLM_BASE_URL=http://<gpu-host-ip>:11434 (external GPU machine)
# ─────────────────────────────────────────────────────────────────────
DEFAULT_MODEL: "${DEFAULT_MODEL:-qwen2.5:7b}"
DATABASE_URL: "${MYSQL_USER:-goclaw}:${MYSQL_PASSWORD:-goClawPass123}@tcp(db:3306)/${MYSQL_DATABASE:-goclaw}?parseTime=true"
PROJECT_ROOT: "/app"
REQUEST_TIMEOUT_SECS: "120"
GATEWAY_REQUEST_TIMEOUT_SECS: "120"
GATEWAY_MAX_TOOL_ITERATIONS: "10"
LOG_LEVEL: "info"
depends_on:
db:
condition: service_healthy
ollama:
condition: service_started
# ollama: # Uncomment if using local Ollama service above
# condition: service_started
networks:
- goclaw-net
volumes:
# Mount project root for file tools (read-only in prod, rw in dev)
# Mount project root for file tools (read-only)
- ..:/app:ro
# Mount Docker socket for docker_exec tool
- /var/run/docker.sock:/var/run/docker.sock
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:18789/health"]
interval: 15s

View File

@@ -1,7 +1,23 @@
##############################################################################
# GoClaw Control Center — Docker Stack (Docker Swarm Production)
#
# LLM Provider:
# By default the gateway uses Ollama Cloud (https://ollama.com/v1).
# Set LLM_BASE_URL and LLM_API_KEY via Docker secrets or environment.
#
# To use a local Ollama instance on a GPU-equipped Swarm node:
# 1. Uncomment the "ollama" service below.
# 2. Add the label gpu=true to the GPU node:
# docker node update --label-add gpu=true <node-id>
# 3. Change LLM_BASE_URL in the gateway service to: http://ollama:11434
#
# Deploy:
# # Create required secrets first:
# echo "rootpass" | docker secret create mysql-root-password -
# echo "pass" | docker secret create mysql-password -
# echo "jwtsecret"| docker secret create jwt-secret -
# echo "ollamakey"| docker secret create llm-api-key -
#
# docker stack deploy -c docker/docker-stack.yml goclaw
#
# Remove:
@@ -19,14 +35,10 @@ networks:
attachable: true
volumes:
ollama-data:
driver: local
mysql-data:
driver: local
configs:
gateway-env:
external: true
# ollama-data: # Uncomment when using local Ollama service below
# driver: local
secrets:
mysql-root-password:
@@ -35,6 +47,8 @@ secrets:
external: true
jwt-secret:
external: true
llm-api-key:
external: true
services:
@@ -73,45 +87,59 @@ services:
retries: 5
start_period: 30s
# ── Ollama LLM Server ─────────────────────────────────────────────────────
ollama:
image: ollama/ollama:latest
volumes:
- ollama-data:/root/.ollama
networks:
- goclaw-net
deploy:
replicas: 1
placement:
constraints:
# Pin to GPU node if available
- node.labels.gpu == true
restart_policy:
condition: on-failure
delay: 15s
resources:
limits:
memory: 16G
reservations:
memory: 4G
# GPU support via nvidia-container-runtime
# Uncomment on GPU-enabled nodes:
# runtime: nvidia
# environment:
# - NVIDIA_VISIBLE_DEVICES=all
# ── Local Ollama LLM Server (GPU ONLY — disabled by default) ─────────────
# Uncomment this entire block only on Swarm nodes with a compatible GPU.
# After uncommenting, also change gateway LLM_BASE_URL to http://ollama:11434
# and remove the llm-api-key secret from gateway (not needed for local Ollama).
#
# ollama:
# image: ollama/ollama:latest
# volumes:
# - ollama-data:/root/.ollama
# networks:
# - goclaw-net
# deploy:
# replicas: 1
# placement:
# constraints:
# # Pin to GPU-labelled node: docker node update --label-add gpu=true <id>
# - node.labels.gpu == true
# restart_policy:
# condition: on-failure
# delay: 15s
# resources:
# limits:
# memory: 16G
# reservations:
# memory: 4G
# # NVIDIA GPU support — uncomment on GPU-enabled nodes:
# # runtime: nvidia
# # environment:
# # - NVIDIA_VISIBLE_DEVICES=all
# ── Go Gateway (Orchestrator + Tool Executor) ─────────────────────────────
gateway:
image: git.softuniq.eu/uniqai/goclaw/gateway:latest
environment:
PORT: "18789"
OLLAMA_BASE_URL: "http://ollama:11434"
# ── LLM Provider ─────────────────────────────────────────────────────
# Default: Ollama Cloud (requires llm-api-key secret below)
LLM_BASE_URL: "${LLM_BASE_URL:-https://ollama.com/v1}"
DEFAULT_MODEL: "${DEFAULT_MODEL:-qwen2.5:7b}"
# LLM_API_KEY is injected via /run/secrets/llm-api-key (see below)
# ── To switch to local GPU Ollama, set: ──────────────────────────────
# LLM_BASE_URL: "http://ollama:11434"
# (and uncomment the ollama service above)
# ─────────────────────────────────────────────────────────────────────
DATABASE_URL: "goclaw:{{MYSQL_PASSWORD}}@tcp(db:3306)/goclaw?parseTime=true"
PROJECT_ROOT: "/app"
REQUEST_TIMEOUT_SECS: "120"
GATEWAY_REQUEST_TIMEOUT_SECS: "120"
GATEWAY_MAX_TOOL_ITERATIONS: "10"
LOG_LEVEL: "info"
secrets:
- mysql-password
- source: llm-api-key
target: /run/secrets/llm-api-key
networks:
- goclaw-net
ports:
@@ -119,6 +147,9 @@ services:
published: 18789
protocol: tcp
mode: ingress
volumes:
# Docker socket for docker_exec tool
- /var/run/docker.sock:/var/run/docker.sock
deploy:
replicas: 2
update_config:

View File

@@ -4,15 +4,35 @@ import (
"log"
"os"
"strconv"
"strings"
"github.com/joho/godotenv"
)
// Config holds all runtime configuration for the GoClaw Gateway.
//
// LLM Provider selection (via LLM_BASE_URL env):
//
// Cloud (default): https://ollama.com/v1 — Ollama Cloud API (requires OLLAMA_API_KEY)
// OpenAI-compat: https://api.openai.com/v1 — OpenAI or any OpenAI-compatible endpoint
// Local GPU node: http://localhost:11434/v1 — Local Ollama (only on machines with GPU)
//
// To use a local Ollama instance on a GPU-equipped agent node, set:
//
// LLM_BASE_URL=http://<gpu-node-ip>:11434/v1
// LLM_API_KEY= # (empty for local Ollama)
type Config struct {
// Server
Port string
// Ollama / LLM
// LLM / Ollama Cloud
// LLM_BASE_URL — OpenAI-compatible base URL (without trailing slash, /v1 suffix included).
// Default: https://ollama.com/v1 (Ollama Cloud).
// For local GPU agents set LLM_BASE_URL=http://<host>:11434/v1
LLMBaseURL string
LLMAPIKey string
// Deprecated aliases kept for backward-compat (mapped to LLMBaseURL / LLMAPIKey)
OllamaBaseURL string
OllamaAPIKey string
@@ -29,17 +49,34 @@ type Config struct {
}
func Load() *Config {
// Try to load .env from parent directory (project root)
// Try to load .env from parent directory (project root) or current dir
_ = godotenv.Load("../.env")
_ = godotenv.Load(".env")
maxIter, _ := strconv.Atoi(getEnv("GATEWAY_MAX_TOOL_ITERATIONS", "10"))
timeout, _ := strconv.Atoi(getEnv("GATEWAY_REQUEST_TIMEOUT_SECS", "120"))
// Resolve LLM base URL — priority: LLM_BASE_URL > OLLAMA_BASE_URL > default cloud
rawLLMURL := getEnvFirst(
"LLM_BASE_URL", // preferred new name
"OLLAMA_BASE_URL", // legacy alias
)
if rawLLMURL == "" {
// Default: Ollama Cloud (OpenAI-compatible, requires OLLAMA_API_KEY)
rawLLMURL = "https://ollama.com/v1"
}
// Normalise: strip trailing slash, ensure /v1 suffix for bare Ollama hosts
llmBaseURL := normaliseLLMURL(rawLLMURL)
// Resolve API key — priority: LLM_API_KEY > OLLAMA_API_KEY
llmAPIKey := getEnvFirst("LLM_API_KEY", "OLLAMA_API_KEY")
cfg := &Config{
Port: getEnv("GATEWAY_PORT", "18789"),
OllamaBaseURL: getEnv("OLLAMA_BASE_URL", "https://ollama.com/v1"),
OllamaAPIKey: getEnv("OLLAMA_API_KEY", ""),
LLMBaseURL: llmBaseURL,
LLMAPIKey: llmAPIKey,
OllamaBaseURL: llmBaseURL, // backward-compat alias
OllamaAPIKey: llmAPIKey, // backward-compat alias
DatabaseURL: getEnv("DATABASE_URL", ""),
ProjectRoot: getEnv("PROJECT_ROOT", "/home/ubuntu/goclaw-control-center"),
DefaultModel: getEnv("DEFAULT_MODEL", "qwen2.5:7b"),
@@ -47,16 +84,43 @@ func Load() *Config {
RequestTimeoutSecs: timeout,
}
if cfg.OllamaAPIKey == "" {
log.Println("[Config] WARNING: OLLAMA_API_KEY is not set")
if cfg.LLMAPIKey == "" {
log.Println("[Config] WARNING: LLM_API_KEY / OLLAMA_API_KEY is not set — cloud API calls will fail")
}
if cfg.DatabaseURL == "" {
log.Println("[Config] WARNING: DATABASE_URL is not set — agent config will use defaults")
}
log.Printf("[Config] LLM endpoint: %s", cfg.LLMBaseURL)
return cfg
}
// normaliseLLMURL ensures the URL has a /v1 suffix for bare Ollama hosts.
// Examples:
//
// "http://localhost:11434" → "http://localhost:11434/v1"
// "http://localhost:11434/" → "http://localhost:11434/v1"
// "https://ollama.com/v1" → "https://ollama.com/v1" (unchanged)
// "https://api.openai.com/v1" → unchanged
func normaliseLLMURL(raw string) string {
u := strings.TrimRight(raw, "/")
if !strings.HasSuffix(u, "/v1") {
u += "/v1"
}
return u
}
// getEnvFirst returns the value of the first non-empty env variable from the list.
func getEnvFirst(keys ...string) string {
for _, k := range keys {
if v := os.Getenv(k); v != "" {
return v
}
}
return ""
}
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v

View File

@@ -143,3 +143,11 @@
- [ ] Write Go unit tests (gateway/internal/tools/executor_test.go)
- [ ] Write Go integration test for orchestrator chat loop
- [ ] Push to Gitea (NW)
## Phase 10: LLM Provider Configuration
- [x] config.go: default LLM_BASE_URL = https://ollama.com/v1 (Ollama Cloud)
- [x] config.go: support LLM_BASE_URL + LLM_API_KEY env vars (legacy OLLAMA_* aliases kept)
- [x] config.go: normaliseLLMURL() — auto-append /v1 for bare Ollama hosts
- [x] docker-compose.yml: ollama service commented out (GPU only), LLM_BASE_URL/LLM_API_KEY added
- [x] docker-stack.yml: ollama service commented out (GPU only), llm-api-key secret added
- [x] docker/.env.example: 4 LLM provider options documented (Ollama Cloud, OpenAI, Groq, Local GPU)