From c3ab22a9bc795a080baf1c91f74ecd9e81921988 Mon Sep 17 00:00:00 2001 From: Johannes Rest Date: Wed, 28 Jan 2026 22:02:12 +0100 Subject: [PATCH] Fixed several bugs. First working solution. But uses CPUs rather than NVIDIA GPUs --- README.md | 67 +------------- bin/sqlai | 193 +++++++++++++++++++++++++++++---------- prompts/sqlserver_qna.md | 27 ++++++ scripts/bootstrap.sh | 36 +++++++- scripts/update.sh | 21 ++++- 5 files changed, 223 insertions(+), 121 deletions(-) create mode 100644 prompts/sqlserver_qna.md diff --git a/README.md b/README.md index 18d23e9..e411dbd 100644 --- a/README.md +++ b/README.md @@ -8,77 +8,14 @@ aufrufbar **vom Terminal**, ohne direkte DB-Verbindung (nur Copy & Paste / Datei - **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` ## Voraussetzungen (Arch Linux) - docker + docker compose - curl -- python (für JSON-Quoting/Parsing) +- python ## Quickstart ```bash cp .env.example .env ./scripts/bootstrap.sh -echo "SELECT 1;" | ./bin/sqlai analyze-tsql -``` - -## Verwendung (Copy & Paste) -### T-SQL -```bash -cat query.sql | ./bin/sqlai analyze-tsql -``` - -### Stored Procedure -```bash -cat dbo.usp_Something.sql | ./bin/sqlai analyze-proc -``` - -### View -```bash -cat dbo.vw_Something.sql | ./bin/sqlai analyze-view -``` - -### Execution Plan -- Unterstützt Showplan XML oder Text. -```bash -./bin/sqlai analyze-plan --file showplan.xml -``` - -### UTF-8 Migration Plan -```bash -cat schema_snippet.sql | ./bin/sqlai utf8-migration -``` - -## Prompt Library (Templates) -Unter `./prompts/` findest du Copy&Paste-Templates: -- `prompts/tsql_review.md` -- `prompts/plan_review.md` -- `prompts/utf8_migration_runbook.md` -- `prompts/indexing_checklist.md` -- `prompts/sniffing_stats_playbook.md` -- `prompts/proc_refactor_template.md` -- `prompts/view_analysis_template.md` - -## Logs -- CLI Logs: `./logs/sqlai-YYYY-MM-DD.log` -- Bootstrap Logs: `./logs/bootstrap-YYYY-MM-DDTHH:MM:SS.log` -- Update Logs: `./logs/update-YYYY-MM-DDTHH:MM:SS.log` - -Die Logs enthalten: -- Input-Bytes, Mode, Model -- Roh-Metriken aus Ollama (Token Counts, Durations) -- Fehler inkl. curl exit codes - -## Auto-Updates aktivieren (systemd --user) -```bash -mkdir -p ~/.config/systemd/user -cp systemd/user/* ~/.config/systemd/user/ -systemctl --user daemon-reload -systemctl --user enable --now jr-sql-ai-update.timer -systemctl --user list-timers | grep jr-sql-ai -``` - -## Troubleshooting -- **API nicht erreichbar**: Prüfe `docker ps` und ob Port 11434 lokal erreichbar ist (Host-Netz). -- **Langsam/OOM**: setze `BASE_MODEL` kleiner (z.B. 7b) und re-run `./scripts/update.sh`. -- **Model fehlt**: `./scripts/update.sh` ausführen und prüfen ob `ollama list` im Container das Model zeigt: - `docker exec -it ollama ollama list` diff --git a/bin/sqlai b/bin/sqlai index 03d2def..b9dcd71 100755 --- a/bin/sqlai +++ b/bin/sqlai @@ -14,22 +14,33 @@ log_dir="${ROOT}/logs" mkdir -p "$log_dir" log_file="${log_dir}/sqlai-$(date -I).log" -# log everything (stdout+stderr) +# Log everything (stdout+stderr) exec > >(tee -a "$log_file") 2>&1 usage() { - cat < [--file path] + sqlai [--file path] [--text "free text"] [--no-metrics] + Modes: - analyze-tsql - analyze-proc - analyze-view - analyze-plan - utf8-migration + ask General SQL Server 2022 Q&A (free text) + analyze-tsql Analyze a T-SQL query + analyze-proc Analyze a stored procedure + analyze-view Analyze a view + analyze-plan Analyze an execution plan (Showplan XML or text) + utf8-migration Create a UTF-8 migration plan (SQL Server 2022) + +Input options: + --text "..." Provide input text directly (no pipe needed) + --file path Read input from file + (no args) Reads from STDIN + +Other options: + --no-metrics Do not print metrics line Examples: - cat query.sql | sqlai analyze-tsql + sqlai ask --text "How do I troubleshoot parameter sniffing in SQL Server 2022?" + echo "SELECT 1;" | sqlai analyze-tsql sqlai analyze-plan --file showplan.xml EOF } @@ -39,44 +50,58 @@ mode="${1:-}" shift || true file="" +text="" +no_metrics="0" + while [[ $# -gt 0 ]]; do case "$1" in - --file) file="$2"; shift 2;; + --file) file="${2:-}"; shift 2;; + --text) text="${2:-}"; shift 2;; + --no-metrics) no_metrics="1"; shift 1;; + -h|--help) usage; exit 0;; *) echo "[$(ts)] ERROR: Unknown arg: $1"; usage; exit 2;; esac done +# Read input (priority: --text > --file > stdin) input="" -if [[ -n "$file" ]]; then - if [[ ! -f "$file" ]]; then - echo "[$(ts)] ERROR: file not found: $file" - exit 3 - fi +input_src="" +if [[ -n "$text" ]]; then + input="$text" + input_src="text" +elif [[ -n "$file" ]]; then + [[ -f "$file" ]] || { echo "[$(ts)] ERROR: file not found: $file"; exit 3; } input="$(cat "$file")" + input_src="file:${file}" else input="$(cat)" + input_src="stdin" fi if [[ -z "${input//[[:space:]]/}" ]]; then - echo "[$(ts)] ERROR: empty input" + echo "[$(ts)] ERROR: empty input (source=$input_src)" 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.' + ;; analyze-tsql) - instruction="Analysiere das folgende T-SQL (SQL Server 2022). Finde Performance-Probleme, SARGability, Indizes, Statistiken, Parameter Sniffing Risiken und gib konkrete Verbesserungen." + instruction=$'Analysiere das folgende T-SQL (SQL Server 2022): Performance, SARGability, Datentypen, Joins/Predicates, Indizes/Stats, Parameter Sniffing. Gib konkrete Rewrite- und Index-Ideen.' ;; analyze-proc) - instruction="Analysiere die folgende Stored Procedure (SQL Server 2022). Finde Performance-/Correctness-Risiken, Transaktions-/Locking-Themen, Parameter Sniffing, fehlende Indizes. Gib konkrete Refactorings." + instruction=$'Analysiere die folgende Stored Procedure (SQL Server 2022): Performance/Correctness, Transaktionen/Locking, Sniffing, Temp tables vs table variables, RBAR. Gib konkrete Refactorings.' ;; analyze-view) - instruction="Analysiere die folgende View (SQL Server 2022). Prüfe SARGability, Expand/Inlining-Effekte, mögliche Indexing-Optionen (z.B. indexed view falls sinnvoll) und Plan-Auswirkungen." + instruction=$'Analysiere die folgende View (SQL Server 2022): SARGability, Predicate Pushdown, Expand/Inlining, Aggregationen/Distinct/Union, UDF-Risiken. Gib konkrete Verbesserungen.' ;; analyze-plan) - instruction="Analysiere den folgenden SQL Server Execution Plan (XML Showplan oder Text). Identifiziere teure Operatoren, Spills, Warnungen, Kardinalitätsfehler, fehlende Indizes und gib konkrete Fixes." + instruction=$'Analysiere den folgenden SQL Server Execution Plan (Showplan XML oder Text): Hotspots, Spills/Warnings, Memory Grants, Kardinalität, Fixes (Rewrite/Stats/Indexing vorsichtig/Sniffing Mitigation).' ;; utf8-migration) - instruction="Erstelle einen Migrationsplan, um Tabellen/Spalten auf UTF-8 umzustellen (UTF-8 enabled collations mit _UTF8). Berücksichtige Abhängigkeiten (FK/PK/Indexes/Computed/Triggers), Risiken und gib eine Schritt-für-Schritt Checkliste." + instruction=$'Erstelle einen Migrationsplan (SQL Server 2022) zur Umstellung auf UTF-8 (UTF-8 enabled collations _UTF8): Abhängigkeiten (PK/FK/Indexes/Computed/Triggers), Cutover, Rollback, Tests, konkrete DDL/Checklisten.' ;; *) echo "[$(ts)] ERROR: unknown mode: $mode" @@ -85,46 +110,118 @@ case "$mode" in ;; esac +req_id="$(date +%Y%m%d-%H%M%S)-$$-$RANDOM" + +echo "================================================================================" +echo "[$(ts)] sqlai: REQUEST_START id=$req_id" +echo "[$(ts)] sqlai: MODE=$mode MODEL=$EXPERT_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 + +# Extract error/response/metrics in one pass +extracted="$( +printf '%s' "$resp" | python -c ' import json,sys -obj=json.loads(sys.stdin.read()) -print("\n" + (obj.get("response","").rstrip()) + "\n") -md = { - "total_duration": obj.get("total_duration"), - "load_duration": obj.get("load_duration"), - "prompt_eval_count": obj.get("prompt_eval_count"), - "prompt_eval_duration": obj.get("prompt_eval_duration"), - "eval_count": obj.get("eval_count"), - "eval_duration": obj.get("eval_duration"), +obj=json.load(sys.stdin) +out={ + "error": obj.get("error"), + "response": obj.get("response",""), + "metrics": { + "total_duration": obj.get("total_duration"), + "load_duration": obj.get("load_duration"), + "prompt_eval_count": obj.get("prompt_eval_count"), + "prompt_eval_duration": obj.get("prompt_eval_duration"), + "eval_count": obj.get("eval_count"), + "eval_duration": obj.get("eval_duration"), + } } -print("METRICS=" + json.dumps(md, ensure_ascii=False)) -PY <<<"$resp" +print(json.dumps(out, ensure_ascii=False)) +' +)" -echo "[$(ts)] sqlai: done" +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" + 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" +fi + +echo "[$(ts)] sqlai: REQUEST_END id=$req_id status=ok" +echo "================================================================================" diff --git a/prompts/sqlserver_qna.md b/prompts/sqlserver_qna.md new file mode 100644 index 0000000..2e84bee --- /dev/null +++ b/prompts/sqlserver_qna.md @@ -0,0 +1,27 @@ +# SQL Server 2022 Q&A Template (Copy & Paste) + +Zweck: Freie technische Fragestellungen zu SQL Server 2022 strukturiert beantworten lassen. +Hinweis: Keine DB-Verbindung, nur Analyse/Planung/Empfehlung. + +## Frage + + +## Kontext (optional, aber hilfreich) +- SQL Server: 2022 (ja/nein) +- DB Kompatibilitätslevel: +- Collation aktuell (DB + betroffene Spalten): +- Umfang: Anzahl Tabellen/Spalten grob, Rowcounts grob: +- Data profile: nur west-europäisch / international / Emojis / CJK / gemischt: +- Clients/Apps: .NET / JDBC / ODBC / ETL Tool: +- Schnittstellen: CSV/JSON/XML, Data Warehouse, Replication, Linked Servers: +- Non-functional: Downtime-Fenster, Rollback-Anforderung, Performance-SLA: + +## Erwartete Ausgabe (bitte strikt) +1) Kurzfazit (Empfehlung in 3–6 Bulletpoints) +2) Optionen (A/B/C) mit Vor- & Nachteilen +3) Risiken & Checks (Checkliste) +4) Konkreter Plan / Next Steps (inkl. DDL/T-SQL Snippets wenn sinnvoll) + +---BEGIN QUESTION--- + +---END QUESTION--- diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 83c39b3..d73a079 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -2,26 +2,34 @@ 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; } + 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)" + echo "[$(ts)] bootstrap: docker compose up -d" docker compose -f "${ROOT}/docker-compose.yml" up -d echo "[$(ts)] bootstrap: waiting for Ollama API at ${OLLAMA_URL} ..." -for i in {1..90}; do +for i in {1..120}; do if curl -sS "${OLLAMA_URL}/api/tags" >/dev/null 2>&1; then echo "[$(ts)] bootstrap: Ollama API is up." break fi - if [[ $i -eq 90 ]]; then + if [[ $i -eq 120 ]]; then echo "[$(ts)] bootstrap: ERROR: API did not come up in time." exit 1 fi @@ -39,11 +47,29 @@ if [[ -n "${EXTRA_MODELS:-}" ]]; then fi echo "[$(ts)] bootstrap: building expert model: ${EXPERT_MODEL}" + tmp="$(mktemp)" sed "s/\${BASE_MODEL}/${BASE_MODEL}/g" "${ROOT}/Modelfile" > "$tmp" -docker exec -i ollama ollama create "${EXPERT_MODEL}" -f - < "$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 + rm -f "$tmp" +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 +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" -echo "[$(ts)] bootstrap: test:" -echo " echo "SELECT 1;" | ${ROOT}/bin/sqlai analyze-tsql" diff --git a/scripts/update.sh b/scripts/update.sh index 501ac70..fd8f655 100755 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -2,27 +2,34 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +# shellcheck disable=SC1090 source "${ROOT}/.env" 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)" + echo "[$(ts)] update: pulling docker image(s)" docker compose -f "${ROOT}/docker-compose.yml" pull + echo "[$(ts)] update: restarting services" docker compose -f "${ROOT}/docker-compose.yml" up -d echo "[$(ts)] update: waiting for Ollama API at ${OLLAMA_URL} ..." -for i in {1..90}; do +for i in {1..120}; do if curl -sS "${OLLAMA_URL}/api/tags" >/dev/null 2>&1; then echo "[$(ts)] update: Ollama API is up." break fi - if [[ $i -eq 90 ]]; then + if [[ $i -eq 120 ]]; then echo "[$(ts)] update: ERROR: API did not come up in time." exit 1 fi @@ -40,9 +47,17 @@ if [[ -n "${EXTRA_MODELS:-}" ]]; then fi echo "[$(ts)] update: rebuilding expert model: ${EXPERT_MODEL}" + tmp="$(mktemp)" sed "s/\${BASE_MODEL}/${BASE_MODEL}/g" "${ROOT}/Modelfile" > "$tmp" -docker exec -i ollama ollama create "${EXPERT_MODEL}" -f - < "$tmp" + +docker cp "$tmp" ollama:/tmp/Modelfile.jr-sql-expert +docker exec -it ollama ollama create "${EXPERT_MODEL}" -f /tmp/Modelfile.jr-sql-expert + rm -f "$tmp" +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." + echo "[$(ts)] update: complete"