1 Commits

Author SHA1 Message Date
6ff95287fa chore(llm): refresh defaults to match deployed Jetson backend (#2)
Some checks failed
CD — ci-ai-bot / deploy (push) Has been cancelled
2026-06-14 06:22:04 +00:00
6 changed files with 70 additions and 20 deletions

46
.gitea/workflows/cd.yml Normal file
View File

@@ -0,0 +1,46 @@
# CD — rebuild and restart ci-ai-bot on the Pi whenever bot/ or scripts/ change.
#
# How it works (Docker-out-of-Docker):
# 1. Job runs in docker:cli on the Pi runner (eulernest label).
# 2. Mounts the HOST docker socket → builds the new image directly in the host
# Docker daemon (no registry push needed — image is already local).
# 3. Mounts /mnt/external:ro → runs `docker compose up --force-recreate`
# which picks up the freshly built image and recreates the container.
#
# Prerequisite (one-time):
# - Runner config must list /var/run/docker.sock and /mnt/external in valid_volumes
# (set in ansible-eulernest roles/gitea_runner/templates/config.yaml.j2).
# - Set org variable JETSON_OLLAMA_URL in conformallab → Actions → Variables:
# value = http://192.168.178.103:11434/v1
name: CD — ci-ai-bot
on:
push:
branches: [main]
paths:
- "bot/**"
- "scripts/**"
jobs:
deploy:
runs-on: eulernest
container:
image: docker:27-cli
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /mnt/external:/mnt/external:ro
steps:
- uses: actions/checkout@v4
- name: Build ci-ai-bot image
# Build from repo root so bot/Dockerfile can COPY scripts/
run: docker build -t ci-ai-bot:latest -f bot/Dockerfile .
- name: Recreate ci-ai-bot container
# --force-recreate: always replace the container even if config is unchanged,
# so the freshly built image is picked up.
run: |
docker compose \
-f /mnt/external/docker-compose.yml \
up -d --no-deps --force-recreate ci-ai-bot

View File

@@ -30,7 +30,8 @@ jobs:
env: env:
AI_PROVIDER: ollama AI_PROVIDER: ollama
AI_MODEL: coder AI_MODEL: coder
AI_BASE_URL: http://192.168.178.103:11434/v1 # JETSON_OLLAMA_URL: org variable in conformallab → Actions → Variables
AI_BASE_URL: ${{ vars.JETSON_OLLAMA_URL || 'http://192.168.178.103:11434/v1' }}
GITEA_TOKEN: ${{ github.token }} GITEA_TOKEN: ${{ github.token }}
GITEA_API: https://git.eulernest.eu/api/v1 GITEA_API: https://git.eulernest.eu/api/v1
run: python /opt/ci-ai/scripts/ai_review.py run: python /opt/ci-ai/scripts/ai_review.py

View File

@@ -22,7 +22,8 @@ jobs:
env: env:
AI_PROVIDER: ollama AI_PROVIDER: ollama
AI_MODEL: coder AI_MODEL: coder
AI_BASE_URL: http://192.168.178.103:11434/v1 # JETSON_OLLAMA_URL: org variable in conformallab → Actions → Variables
AI_BASE_URL: ${{ vars.JETSON_OLLAMA_URL || 'http://192.168.178.103:11434/v1' }}
GITEA_TOKEN: ${{ github.token }} GITEA_TOKEN: ${{ github.token }}
GITEA_API: https://git.eulernest.eu/api/v1 GITEA_API: https://git.eulernest.eu/api/v1
steps: steps:

View File

@@ -73,7 +73,8 @@ jobs:
env: env:
AI_PROVIDER: ollama AI_PROVIDER: ollama
AI_MODEL: coder AI_MODEL: coder
AI_BASE_URL: http://192.168.178.103:11434/v1 # JETSON_OLLAMA_URL: org variable in conformallab → Actions → Variables
AI_BASE_URL: ${{ vars.JETSON_OLLAMA_URL || 'http://192.168.178.103:11434/v1' }}
GITEA_TOKEN: ${{ github.token }} GITEA_TOKEN: ${{ github.token }}
GITEA_API: https://git.eulernest.eu/api/v1 GITEA_API: https://git.eulernest.eu/api/v1
run: | run: |

View File

@@ -125,15 +125,16 @@ Provider-Wechsel = **nur Env**, kein Code-Change (`scripts/llm.py`). Anthropic-V
## Konfiguration (Env-Vars) ## Konfiguration (Env-Vars)
> Hinweis: Die **Code-Defaults in `llm.py` sind noch der alte Stand** (Anthropic, Jetson-IP > Hinweis: Die **Code-Defaults in `llm.py` entsprechen jetzt dem Jetson-Betrieb**
> `.20`, `qwen-light`). Im Betrieb überschreiben Workflows + Bot diese per Env mit den > (`ollama` / `coder` / `.103`). Workflows + Bot setzen die Werte trotzdem explizit
> Jetson-Werten (`ollama` / `coder` / `.103`). Ein Refresh der Defaults ist vorgemerkt. > per Env — ein Provider-Wechsel (z.B. zurück auf `anthropic`) bleibt damit ein
> reiner Env-Change, keine Code-Änderung.
| Var | Default (`llm.py`) | Im Betrieb | | Var | Default (`llm.py`) | Im Betrieb |
|---|---|---| |---|---|---|
| `AI_PROVIDER` | `anthropic` | **`ollama`** | | `AI_PROVIDER` | `ollama` | `ollama` |
| `AI_MODEL` | `claude-3-5-haiku-latest` / `qwen-light` | **`coder`** | | `AI_MODEL` | `coder` (ollama) / `claude-3-5-haiku-latest` (anthropic) | `coder` |
| `AI_BASE_URL` | `http://192.168.178.20:11434/v1` *(veraltet)* | **`…178.103…`** | | `AI_BASE_URL` | `http://192.168.178.103:11434/v1` | `…178.103…` |
| `JETSON_API_KEY` | — | Bearer für Ollama (optional) | | `JETSON_API_KEY` | — | Bearer für Ollama (optional) |
| `ANTHROPIC_API_KEY` | — | nur bei provider=anthropic | | `ANTHROPIC_API_KEY` | — | nur bei provider=anthropic |
| `GITEA_API` | `https://git.eulernest.eu/api/v1` | Bot: intern `http://gitea:3000/api/v1` | | `GITEA_API` | `https://git.eulernest.eu/api/v1` | Bot: intern `http://gitea:3000/api/v1` |

View File

@@ -4,18 +4,18 @@ Bewusst NUR stdlib (urllib) — so braucht der CI-Job kein `pip install` und
läuft in jedem schlanken Python-Image sofort. läuft in jedem schlanken Python-Image sofort.
Umschalten zwischen Backends über Env-Vars: Umschalten zwischen Backends über Env-Vars:
AI_PROVIDER=anthropic -> api.anthropic.com/v1/messages (Claude, jetzt) AI_PROVIDER=ollama -> <AI_BASE_URL>/chat/completions (Jetson, self-hosted, Default)
AI_PROVIDER=ollama -> <AI_BASE_URL>/chat/completions (Jetson, später) AI_PROVIDER=anthropic -> api.anthropic.com/v1/messages (Claude, optional)
Weitere Env-Vars: Weitere Env-Vars:
AI_MODEL Modellname (Default je nach Provider) AI_MODEL Modellname (Default je nach Provider; ollama: "coder" = qwen2.5-coder:7b)
ANTHROPIC_API_KEY (für anthropic)
AI_BASE_URL OpenAI-kompatible Basis-URL (für ollama), AI_BASE_URL OpenAI-kompatible Basis-URL (für ollama),
Default http://192.168.178.20:11434/v1 Default http://192.168.178.103:11434/v1
JETSON_API_KEY Bearer-Token (für ollama, optional) JETSON_API_KEY Bearer-Token (für ollama, optional)
ANTHROPIC_API_KEY (für anthropic)
Der Wechsel auf den Jetson ist damit ein reiner Env-Change in der Workflow- Provider-Wechsel ist damit ein reiner Env-Change in der Workflow-Datei
Datei — KEINE Code-Änderung. — KEINE Code-Änderung.
""" """
import json import json
import os import os
@@ -29,7 +29,7 @@ def configured():
Erlaubt den Workflows, den KI-Schritt sauber zu ÜBERSPRINGEN (exit 0) statt Erlaubt den Workflows, den KI-Schritt sauber zu ÜBERSPRINGEN (exit 0) statt
zu failen, solange z.B. noch kein ANTHROPIC_API_KEY hinterlegt ist. zu failen, solange z.B. noch kein ANTHROPIC_API_KEY hinterlegt ist.
""" """
provider = os.environ.get("AI_PROVIDER", "anthropic").lower() provider = os.environ.get("AI_PROVIDER", "ollama").lower()
if provider == "anthropic": if provider == "anthropic":
if not os.environ.get("ANTHROPIC_API_KEY"): if not os.environ.get("ANTHROPIC_API_KEY"):
return False, "ANTHROPIC_API_KEY ist nicht gesetzt" return False, "ANTHROPIC_API_KEY ist nicht gesetzt"
@@ -41,12 +41,12 @@ def configured():
def complete(system: str, user: str, *, max_tokens: int = 1500) -> str: def complete(system: str, user: str, *, max_tokens: int = 1500) -> str:
"""Schickt (system, user) ans konfigurierte Backend, gibt reinen Text zurück.""" """Schickt (system, user) ans konfigurierte Backend, gibt reinen Text zurück."""
provider = os.environ.get("AI_PROVIDER", "anthropic").lower() provider = os.environ.get("AI_PROVIDER", "ollama").lower()
if provider == "anthropic": if provider == "anthropic":
model = os.environ.get("AI_MODEL", "claude-3-5-haiku-latest") model = os.environ.get("AI_MODEL", "claude-3-5-haiku-latest")
return _anthropic(system, user, model, max_tokens) return _anthropic(system, user, model, max_tokens)
if provider == "ollama": if provider == "ollama":
model = os.environ.get("AI_MODEL", "qwen-light") model = os.environ.get("AI_MODEL", "coder")
return _ollama_openai(system, user, model, max_tokens) return _ollama_openai(system, user, model, max_tokens)
raise SystemExit(f"[llm] Unbekannter AI_PROVIDER: {provider!r}") raise SystemExit(f"[llm] Unbekannter AI_PROVIDER: {provider!r}")
@@ -88,7 +88,7 @@ def _anthropic(system: str, user: str, model: str, max_tokens: int) -> str:
def _ollama_openai(system: str, user: str, model: str, max_tokens: int) -> str: def _ollama_openai(system: str, user: str, model: str, max_tokens: int) -> str:
base = os.environ.get("AI_BASE_URL", "http://192.168.178.20:11434/v1").rstrip("/") base = os.environ.get("AI_BASE_URL", "http://192.168.178.103:11434/v1").rstrip("/")
headers = {"content-type": "application/json"} headers = {"content-type": "application/json"}
key = os.environ.get("JETSON_API_KEY", "") key = os.environ.get("JETSON_API_KEY", "")
if key: if key: