From 9b2573ebb8cfe7aa1ac8a93a57800a04154ce45f Mon Sep 17 00:00:00 2001 From: Johannes Rest Date: Wed, 28 Jan 2026 22:27:34 +0100 Subject: [PATCH] Weitere Tools eingebaut. Damit ist die SQL AI einsatzbereit. --- README.md | 56 +++++++++--- bin/sqlai | 208 ++++++++++++++++++++++++++++-------------- scripts/bootstrap.sh | 25 ++--- scripts/selftest.sh | 212 +++++++++++++++++++++++++++++++++++++++++++ scripts/update.sh | 15 ++- 5 files changed, 414 insertions(+), 102 deletions(-) create mode 100755 scripts/selftest.sh diff --git a/README.md b/README.md index e411dbd..e5515b6 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,51 @@ -# jr-sql-ai (Terminal-first SQL Server Expert KI, lokal) +# jr-sql-ai (Terminal-first SQL Server 2022 Expert KI, lokal) -Ziel: Lokale Expert-KI für **SQL Server 2022** (T-SQL, Views, Stored Procedures, Execution Plans, UTF-8 Migration), +Lokale Expert-KI für **SQL Server 2022** (T-SQL, Views, Stored Procedures, Execution Plans, UTF-8 Migration), aufrufbar **vom Terminal**, ohne direkte DB-Verbindung (nur Copy & Paste / Dateien). +Die KI läuft lokal via **Ollama** in Docker und wird über ein kleines CLI (`sqlai`) genutzt. + +--- + ## Features -- **Einfacher Tech-Stack:** Docker + Ollama + Bash + curl + python -- **Host Networking:** nutzt den Host-Netzwerkstack (Routing/DNS wie Host; ideal wenn nur `br0` zuverlässig ist) -- **Auto-Updates:** Runtime + Models via `systemd --user` Timer -- **Viele Logs:** jede Ausführung schreibt detaillierte Logs unter `./logs/` -- **Freie SQL Server 2022 Q&A:** Modus `ask` für allgemeine Fragen, ohne Pipe via `--text` + +- **Schmaler Tech-Stack:** Docker + Ollama + Bash + curl + python +- **Terminal-first:** `sqlai ask ...` / `sqlai analyze-tsql ...` etc. +- **Ohne DB-Verbindung:** nur Analyse/Planung/Empfehlung anhand von Input +- **Auto-Updates:** Runtime + Models via `scripts/update.sh` und optional `systemd --user` Timer +- **Warmup:** nach Updates (und optional nach Bootstrap) wird ein kurzer Request gesendet (schneller “First Real Query”) +- **Selftest:** `scripts/selftest.sh` prüft End-to-End (Docker, API, Models, echte Anfrage) +- **Resilient:** Model-Fallback (wenn Expert-Model fehlt → Base-Model) +- **Viele Logs:** jedes Script schreibt nachvollziehbare Logs nach `./logs/` + +--- ## Voraussetzungen (Arch Linux) -- docker + docker compose -- curl -- python -## Quickstart +- `docker` + `docker compose` +- `curl` +- `python` +- optional (für GPU): NVIDIA Container Toolkit + Compose GPU-Konfiguration + +--- + +## Repository Struktur (Kurzüberblick) + +- `bin/sqlai` – CLI (ask + Analyse-Modi) inkl. Logging & Model-Fallback +- `scripts/bootstrap.sh` – startet Container, pullt Modelle, baut Expert-Model, Warmup +- `scripts/update.sh` – pullt Runtime+Modelle, rebuild Expert-Model, Warmup +- `scripts/selftest.sh` – End-to-End Check inkl. echter Anfrage +- `docker-compose.yml` – Ollama Service (Host networking, optional GPU) +- `Modelfile` – Expert-Systemprompt (SQL Server 2022 Fokus) +- `prompts/` – Prompt-Library (Copy & Paste Templates) +- `systemd/user/` – optionaler Daily Update Timer (User Service) + +--- + +# 1) Installation / Build (Bootstrap) + +## 1.1 Repo auspacken / klonen +Wenn du ein tar.gz erhalten hast: ```bash -cp .env.example .env -./scripts/bootstrap.sh +tar -xzf jr-sql-ai.tar.gz +cd jr-sql-ai diff --git a/bin/sqlai b/bin/sqlai index b9dcd71..3676771 100755 --- a/bin/sqlai +++ b/bin/sqlai @@ -7,6 +7,7 @@ ENV_FILE="${ROOT}/.env" : "${OLLAMA_URL:=http://127.0.0.1:11434}" : "${EXPERT_MODEL:=jr-sql-expert}" +: "${BASE_MODEL:=}" # fallback ts() { date -Is; } @@ -14,7 +15,6 @@ log_dir="${ROOT}/logs" mkdir -p "$log_dir" log_file="${log_dir}/sqlai-$(date -I).log" -# Log everything (stdout+stderr) exec > >(tee -a "$log_file") 2>&1 usage() { @@ -83,7 +83,6 @@ if [[ -z "${input//[[:space:]]/}" ]]; then exit 4 fi -# Short instruction per mode (core policy is in Modelfile SYSTEM prompt) case "$mode" in ask) instruction=$'Beantworte die Frage als SQL Server 2022 Experte.\n\nWichtig:\n- Wenn Kontext fehlt: keine Rückfragen stellen; stattdessen Annahmen offenlegen und Optionen (A/B/C) mit Vor-/Nachteilen geben.\n- Ergebnis immer strukturiert mit: Kurzfazit, Optionen, Risiken/Checks, Nächste Schritte.\n- Wenn sinnvoll: konkrete T-SQL/DDL Snippets und Checklisten liefern.' @@ -112,12 +111,50 @@ esac req_id="$(date +%Y%m%d-%H%M%S)-$$-$RANDOM" +# --- helper: model existence via /api/tags --- +model_exists() { + # args: modelname + local m="$1" + local tags + tags="$(curl -sS "${OLLAMA_URL}/api/tags" 2>/dev/null || true)" + [[ -z "$tags" ]] && return 1 + printf '%s' "$tags" | python -c ' +import json,sys +m=sys.argv[1] +try: + obj=json.load(sys.stdin) +except Exception: + sys.exit(2) +names=set() +for it in obj.get("models", []): + n=it.get("name") + if n: names.add(n) +# Accept exact match, or ":latest" variants +ok = (m in names) or (m + ":latest" in names) or (m.endswith(":latest") and m[:-7] in names) +sys.exit(0 if ok else 1) +' "$m" >/dev/null 2>&1 +} + +pick_model() { + # Prefer expert, fallback to base + if model_exists "$EXPERT_MODEL"; then + printf '%s' "$EXPERT_MODEL" + return 0 + fi + if [[ -n "${BASE_MODEL:-}" ]] && model_exists "$BASE_MODEL"; then + echo "[$(ts)] sqlai: WARN: expert model not found in /api/tags; falling back to BASE_MODEL=${BASE_MODEL}" + printf '%s' "$BASE_MODEL" + return 0 + fi + echo "[$(ts)] sqlai: ERROR: neither EXPERT_MODEL=${EXPERT_MODEL} nor BASE_MODEL=${BASE_MODEL:-} found (api/tags)." + return 1 +} + echo "================================================================================" echo "[$(ts)] sqlai: REQUEST_START id=$req_id" -echo "[$(ts)] sqlai: MODE=$mode MODEL=$EXPERT_MODEL OLLAMA_URL=$OLLAMA_URL" +echo "[$(ts)] sqlai: MODE=$mode EXPERT_MODEL=$EXPERT_MODEL BASE_MODEL=${BASE_MODEL:-} OLLAMA_URL=$OLLAMA_URL" echo "[$(ts)] sqlai: INPUT_SOURCE=$input_src INPUT_BYTES=$(printf "%s" "$input" | wc -c)" -# Delimited markup prompt=$(cat </dev/null 2>&1; then - echo "[$(ts)] sqlai: ERROR: response is not valid JSON id=$req_id" - echo "[$(ts)] sqlai: RAW_RESPONSE_BEGIN id=$req_id" - printf '%s\n' "$resp" - echo "[$(ts)] sqlai: RAW_RESPONSE_END id=$req_id" - echo "[$(ts)] sqlai: REQUEST_END id=$req_id status=error" - exit 11 -fi + resp_file="$(mktemp)" + http_code="$( + curl -sS -o "$resp_file" -w "%{http_code}" \ + -X POST "${OLLAMA_URL}/api/generate" \ + -H 'Content-Type: application/json' \ + --data-binary "$payload" \ + || true + )" -# Extract error/response/metrics in one pass -extracted="$( -printf '%s' "$resp" | python -c ' + resp="$(cat "$resp_file")" + rm -f "$resp_file" + + resp_bytes="$(printf "%s" "$resp" | wc -c)" + echo "[$(ts)] sqlai: attempt=$attempt model=$model HTTP_CODE=$http_code RESP_BYTES=$resp_bytes id=$req_id" + + # Validate JSON + if ! printf '%s' "$resp" | python -c 'import json,sys; json.load(sys.stdin)' >/dev/null 2>&1; then + echo "[$(ts)] sqlai: ERROR: invalid JSON response attempt=$attempt id=$req_id" + echo "[$(ts)] sqlai: RAW_RESPONSE_BEGIN attempt=$attempt id=$req_id" + printf '%s\n' "$resp" | head -n 200 + echo "[$(ts)] sqlai: RAW_RESPONSE_END attempt=$attempt id=$req_id" + return 90 + fi + + extracted="$( + printf '%s' "$resp" | python -c ' import json,sys obj=json.load(sys.stdin) out={ @@ -180,47 +225,72 @@ out={ } print(json.dumps(out, ensure_ascii=False)) ' -)" + )" -error_msg="$(printf '%s' "$extracted" | python -c 'import json,sys; print((json.load(sys.stdin).get("error") or "").strip())')" -response_txt="$(printf '%s' "$extracted" | python -c 'import json,sys; print(json.load(sys.stdin).get("response") or "")')" + error_msg="$(printf '%s' "$extracted" | python -c 'import json,sys; print((json.load(sys.stdin).get("error") or "").strip())')" + response_txt="$(printf '%s' "$extracted" | python -c 'import json,sys; print(json.load(sys.stdin).get("response") or "")')" -# HTTP != 200 is error -if [[ "$http_code" != "200" ]]; then - echo "[$(ts)] sqlai: ERROR: non-200 HTTP_CODE=$http_code id=$req_id" - if [[ -n "$error_msg" ]]; then - echo "[$(ts)] sqlai: OLLAMA_ERROR=$error_msg id=$req_id" - else - echo "[$(ts)] sqlai: BODY_SNIPPET_BEGIN id=$req_id" - printf '%s\n' "$resp" | head -n 120 - echo "[$(ts)] sqlai: BODY_SNIPPET_END id=$req_id" + # HTTP != 200 -> error + if [[ "$http_code" != "200" ]]; then + echo "[$(ts)] sqlai: ERROR: non-200 HTTP_CODE=$http_code attempt=$attempt id=$req_id" + [[ -n "$error_msg" ]] && echo "[$(ts)] sqlai: OLLAMA_ERROR=$error_msg attempt=$attempt id=$req_id" + return 91 + fi + + # API-level error + if [[ -n "$error_msg" ]]; then + echo "[$(ts)] sqlai: ERROR: OLLAMA_ERROR=$error_msg attempt=$attempt id=$req_id" + # special code for retry decisions + if printf '%s' "$error_msg" | grep -qiE 'model .*not found|not found'; then + return 42 + fi + return 92 + fi + + # Print answer + printf "\n%s\n\n" "$(printf "%s" "$response_txt" | sed 's/[[:space:]]*$//')" + + # Print metrics optionally + if [[ "$no_metrics" != "1" ]]; then + echo "METRICS=$(printf '%s' "$extracted" | python -c 'import json,sys; import json as j; print(j.dumps(json.load(sys.stdin)["metrics"], ensure_ascii=False))')" + fi + + # Empty answer warning + if [[ -z "${response_txt//[[:space:]]/}" ]]; then + echo "[$(ts)] sqlai: WARN: empty response attempt=$attempt model=$model id=$req_id" + fi + + return 0 +} + +model_primary="$(pick_model)" +echo "[$(ts)] sqlai: selected_model_primary=$model_primary id=$req_id" + +# Attempt 1 +set +e +do_request "$model_primary" "1" +rc=$? +set -e + +# Retry with BASE_MODEL if "model not found" and we weren't already using it +if [[ "$rc" -eq 42 ]]; then + if [[ -n "${BASE_MODEL:-}" ]] && [[ "$model_primary" != "$BASE_MODEL" ]] && model_exists "$BASE_MODEL"; then + echo "[$(ts)] sqlai: WARN: retrying with BASE_MODEL=$BASE_MODEL (expert model not found) id=$req_id" + set +e + do_request "$BASE_MODEL" "2" + rc2=$? + set -e + rc="$rc2" + else + echo "[$(ts)] sqlai: ERROR: retry requested but BASE_MODEL unavailable id=$req_id" + rc=93 fi - echo "[$(ts)] sqlai: REQUEST_END id=$req_id status=error" - exit 12 fi -# API-level error -if [[ -n "$error_msg" ]]; then - echo "[$(ts)] sqlai: ERROR: OLLAMA_ERROR=$error_msg id=$req_id" - echo "[$(ts)] sqlai: REQUEST_END id=$req_id status=error" - exit 13 -fi - -# Print answer -printf "\n%s\n\n" "$(printf "%s" "$response_txt" | sed 's/[[:space:]]*$//')" - -# If response empty, dump JSON snippet to log -if [[ -z "${response_txt//[[:space:]]/}" ]]; then - echo "[$(ts)] sqlai: WARN: empty response id=$req_id" - echo "[$(ts)] sqlai: RAW_JSON_SNIPPET_BEGIN id=$req_id" - printf '%s\n' "$resp" | head -n 200 - echo "[$(ts)] sqlai: RAW_JSON_SNIPPET_END id=$req_id" -fi - -# Print metrics optionally -if [[ "$no_metrics" != "1" ]]; then - metrics_line="$(printf '%s' "$extracted" | python -c 'import json,sys; print("METRICS="+json.dumps(json.load(sys.stdin)["metrics"], ensure_ascii=False))')" - echo "$metrics_line" +if [[ "$rc" -ne 0 ]]; then + echo "[$(ts)] sqlai: REQUEST_END id=$req_id status=error rc=$rc" + echo "================================================================================" + exit "$rc" fi echo "[$(ts)] sqlai: REQUEST_END id=$req_id status=ok" diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index d73a079..5c0aebb 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -2,11 +2,7 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" - -# Create .env if missing (do not overwrite) cp -n "${ROOT}/.env.example" "${ROOT}/.env" || true - -# shellcheck disable=SC1090 source "${ROOT}/.env" ts(){ date -Is; } @@ -14,8 +10,6 @@ ts(){ date -Is; } log_dir="${ROOT}/logs" mkdir -p "$log_dir" log_file="${log_dir}/bootstrap-$(date -Iseconds).log" - -# log everything (stdout+stderr) exec > >(tee -a "$log_file") 2>&1 echo "[$(ts)] bootstrap: starting (ROOT=$ROOT)" @@ -51,7 +45,6 @@ echo "[$(ts)] bootstrap: building expert model: ${EXPERT_MODEL}" tmp="$(mktemp)" sed "s/\${BASE_MODEL}/${BASE_MODEL}/g" "${ROOT}/Modelfile" > "$tmp" -# Copy Modelfile into container and build from explicit path (robust) docker cp "$tmp" ollama:/tmp/Modelfile.jr-sql-expert docker exec -it ollama ollama create "${EXPERT_MODEL}" -f /tmp/Modelfile.jr-sql-expert @@ -61,15 +54,15 @@ echo "[$(ts)] bootstrap: verifying model exists..." docker exec -it ollama ollama list | grep -F "${EXPERT_MODEL}" >/dev/null && \ echo "[$(ts)] bootstrap: OK: ${EXPERT_MODEL} is available." -# End-to-end test -if [[ ! -x "${ROOT}/bin/sqlai" ]]; then - echo "[$(ts)] bootstrap: ERROR: ${ROOT}/bin/sqlai not found or not executable" - ls -la "${ROOT}/bin" || true - exit 2 +# Warmup +if [[ -x "${ROOT}/bin/sqlai" ]]; then + echo "[$(ts)] bootstrap: warmup: sending a short request (no-metrics)..." + "${ROOT}/bin/sqlai" ask --text "Warmup: reply with exactly 'OK'." --no-metrics || { + echo "[$(ts)] bootstrap: WARN: warmup failed (continuing)." + } + echo "[$(ts)] bootstrap: warmup: done" +else + echo "[$(ts)] bootstrap: WARN: ${ROOT}/bin/sqlai not executable; skipping warmup." fi -echo "[$(ts)] bootstrap: test (running one request)..." -echo "SELECT 1;" | "${ROOT}/bin/sqlai" analyze-tsql -echo "[$(ts)] bootstrap: test done" - echo "[$(ts)] bootstrap: done" diff --git a/scripts/selftest.sh b/scripts/selftest.sh new file mode 100755 index 0000000..b4d22fd --- /dev/null +++ b/scripts/selftest.sh @@ -0,0 +1,212 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="${ROOT}/.env" + +ts(){ date -Is; } + +log_dir="${ROOT}/logs" +mkdir -p "$log_dir" +log_file="${log_dir}/selftest-$(date -Iseconds).log" +exec > >(tee -a "$log_file") 2>&1 + +echo "================================================================================" +echo "[$(ts)] selftest: START ROOT=$ROOT" + +# -------- helpers -------- +fail() { + local msg="$1" + local code="${2:-1}" + echo "[$(ts)] selftest: FAIL code=$code msg=$msg" + echo "[$(ts)] selftest: END status=FAIL" + echo "================================================================================" + exit "$code" +} + +warn() { + echo "[$(ts)] selftest: WARN $*" +} + +ok() { + echo "[$(ts)] selftest: OK $*" +} + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || fail "missing command: $1" 10 +} + +# robust JSON check +is_json() { + # reads stdin + python -c 'import json,sys; json.load(sys.stdin)' >/dev/null 2>&1 +} + +# Extract model names from /api/tags JSON and check membership +model_in_tags() { + # args: modelname, tags_json_string + local model="$1" + local tags="$2" + printf '%s' "$tags" | python -c ' +import json,sys +m=sys.argv[1] +obj=json.load(sys.stdin) +names=set() +for it in obj.get("models", []): + n=it.get("name") + if n: names.add(n) +ok = (m in names) or (m + ":latest" in names) or (m.endswith(":latest") and m[:-7] in names) +sys.exit(0 if ok else 1) +' "$model" >/dev/null 2>&1 +} + +# -------- preflight -------- +need_cmd docker +need_cmd curl +need_cmd python +need_cmd grep +need_cmd sed + +if [[ ! -f "$ENV_FILE" ]]; then + warn ".env not found, creating from .env.example" + cp -n "${ROOT}/.env.example" "${ROOT}/.env" || true +fi + +# shellcheck disable=SC1090 +source "${ROOT}/.env" + +: "${OLLAMA_URL:=http://127.0.0.1:11434}" +: "${EXPERT_MODEL:=jr-sql-expert}" +: "${BASE_MODEL:=}" + +ok "loaded env: OLLAMA_URL=$OLLAMA_URL EXPERT_MODEL=$EXPERT_MODEL BASE_MODEL=${BASE_MODEL:-}" + +if [[ ! -x "${ROOT}/bin/sqlai" ]]; then + fail "bin/sqlai missing or not executable at ${ROOT}/bin/sqlai" 11 +fi + +# docker compose availability (plugin or legacy) +if docker compose version >/dev/null 2>&1; then + ok "docker compose available" +else + fail "docker compose not available (install docker compose plugin)" 12 +fi + +# container state +if docker ps --format '{{.Names}}' | grep -qx 'ollama'; then + ok "container 'ollama' is running" +else + warn "container 'ollama' not running; attempting 'docker compose up -d'" + docker compose -f "${ROOT}/docker-compose.yml" up -d || fail "docker compose up failed" 13 + if ! docker ps --format '{{.Names}}' | grep -qx 'ollama'; then + fail "container 'ollama' still not running after compose up" 14 + fi + ok "container 'ollama' is running after compose up" +fi + +# API wait loop +ok "waiting for Ollama API at $OLLAMA_URL ..." +api_ok="0" +for i in {1..120}; do + if curl -sS "${OLLAMA_URL}/api/tags" >/dev/null 2>&1; then + api_ok="1" + break + fi + sleep 1 +done +[[ "$api_ok" == "1" ]] || fail "Ollama API not reachable at $OLLAMA_URL after waiting" 20 +ok "Ollama API reachable" + +# Fetch /api/tags and validate JSON +tags="$(curl -sS "${OLLAMA_URL}/api/tags" || true)" +[[ -n "$tags" ]] || fail "/api/tags returned empty body" 21 + +if ! printf '%s' "$tags" | is_json; then + echo "[$(ts)] selftest: /api/tags RAW_BEGIN" + printf '%s\n' "$tags" | head -n 200 + echo "[$(ts)] selftest: /api/tags RAW_END" + fail "/api/tags did not return JSON" 22 +fi +ok "/api/tags returned valid JSON" + +# Model availability checks +expert_present="0" +base_present="0" + +if model_in_tags "$EXPERT_MODEL" "$tags"; then + expert_present="1" + ok "EXPERT_MODEL present: $EXPERT_MODEL" +else + warn "EXPERT_MODEL not present in tags: $EXPERT_MODEL" +fi + +if [[ -n "${BASE_MODEL:-}" ]] && model_in_tags "$BASE_MODEL" "$tags"; then + base_present="1" + ok "BASE_MODEL present: $BASE_MODEL" +else + if [[ -n "${BASE_MODEL:-}" ]]; then + warn "BASE_MODEL not present in tags: $BASE_MODEL" + else + warn "BASE_MODEL empty in .env" + fi +fi + +if [[ "$expert_present" != "1" && "$base_present" != "1" ]]; then + warn "No expert/base model found. Attempting to pull BASE_MODEL if set..." + if [[ -n "${BASE_MODEL:-}" ]]; then + docker exec -it ollama ollama pull "${BASE_MODEL}" || warn "ollama pull BASE_MODEL failed" + tags2="$(curl -sS "${OLLAMA_URL}/api/tags" || true)" + if printf '%s' "$tags2" | is_json && model_in_tags "$BASE_MODEL" "$tags2"; then + ok "BASE_MODEL now present after pull: $BASE_MODEL" + base_present="1" + else + fail "Neither EXPERT_MODEL nor BASE_MODEL available after attempted pull" 23 + fi + else + fail "Neither EXPERT_MODEL nor BASE_MODEL available and BASE_MODEL is empty" 24 + fi +fi + +# Warmup request +ok "warmup request via sqlai (no-metrics)" +set +e +"${ROOT}/bin/sqlai" ask --text "Warmup: reply with exactly 'OK'." --no-metrics +rc=$? +set -e +if [[ "$rc" -ne 0 ]]; then + warn "warmup failed rc=$rc (continuing to real query)" +else + ok "warmup succeeded" +fi + +# Real query (slightly longer) +ok "real query via sqlai (ask)" +set +e +out="$("${ROOT}/bin/sqlai" ask --text "Give a concise checklist to troubleshoot parameter sniffing in SQL Server 2022. Keep it technical." --no-metrics 2>&1)" +rc=$? +set -e +if [[ "$rc" -ne 0 ]]; then + echo "[$(ts)] selftest: sqlai output BEGIN" + printf '%s\n' "$out" | tail -n 200 + echo "[$(ts)] selftest: sqlai output END" + fail "real query failed rc=$rc" 30 +fi + +# Basic sanity: must contain at least some non-empty text +if [[ -z "${out//[[:space:]]/}" ]]; then + fail "real query returned empty output" 31 +fi + +ok "real query returned non-empty output" + +# Summary +echo "--------------------------------------------------------------------------------" +echo "[$(ts)] selftest: SUMMARY" +echo "[$(ts)] selftest: API=OK" +echo "[$(ts)] selftest: EXPERT_MODEL_PRESENT=$expert_present" +echo "[$(ts)] selftest: BASE_MODEL_PRESENT=$base_present" +echo "[$(ts)] selftest: LOG_FILE=$log_file" +echo "--------------------------------------------------------------------------------" + +echo "[$(ts)] selftest: END status=OK" +echo "================================================================================" diff --git a/scripts/update.sh b/scripts/update.sh index fd8f655..a83d9af 100755 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -2,8 +2,6 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" - -# shellcheck disable=SC1090 source "${ROOT}/.env" ts(){ date -Is; } @@ -11,8 +9,6 @@ ts(){ date -Is; } log_dir="${ROOT}/logs" mkdir -p "$log_dir" log_file="${log_dir}/update-$(date -Iseconds).log" - -# log everything (stdout+stderr) exec > >(tee -a "$log_file") 2>&1 echo "[$(ts)] update: starting (ROOT=$ROOT)" @@ -60,4 +56,15 @@ echo "[$(ts)] update: verifying model exists..." docker exec -it ollama ollama list | grep -F "${EXPERT_MODEL}" >/dev/null && \ echo "[$(ts)] update: OK: ${EXPERT_MODEL} is available." +# Warmup (loads model + GPU kernels, reduces first real query latency) +if [[ -x "${ROOT}/bin/sqlai" ]]; then + echo "[$(ts)] update: warmup: sending a short request (no-metrics)..." + "${ROOT}/bin/sqlai" ask --text "Warmup: reply with exactly 'OK'." --no-metrics || { + echo "[$(ts)] update: WARN: warmup failed (continuing)." + } + echo "[$(ts)] update: warmup: done" +else + echo "[$(ts)] update: WARN: ${ROOT}/bin/sqlai not executable; skipping warmup." +fi + echo "[$(ts)] update: complete"