Fixed several bugs. First working solution. But uses CPUs rather than NVIDIA GPUs
This commit is contained in:
67
README.md
67
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)
|
- **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
|
- **Auto-Updates:** Runtime + Models via `systemd --user` Timer
|
||||||
- **Viele Logs:** jede Ausführung schreibt detaillierte Logs unter `./logs/`
|
- **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)
|
## Voraussetzungen (Arch Linux)
|
||||||
- docker + docker compose
|
- docker + docker compose
|
||||||
- curl
|
- curl
|
||||||
- python (für JSON-Quoting/Parsing)
|
- python
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
./scripts/bootstrap.sh
|
./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`
|
|
||||||
|
|||||||
181
bin/sqlai
181
bin/sqlai
@@ -14,22 +14,33 @@ log_dir="${ROOT}/logs"
|
|||||||
mkdir -p "$log_dir"
|
mkdir -p "$log_dir"
|
||||||
log_file="${log_dir}/sqlai-$(date -I).log"
|
log_file="${log_dir}/sqlai-$(date -I).log"
|
||||||
|
|
||||||
# log everything (stdout+stderr)
|
# Log everything (stdout+stderr)
|
||||||
exec > >(tee -a "$log_file") 2>&1
|
exec > >(tee -a "$log_file") 2>&1
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<'EOF'
|
||||||
Usage:
|
Usage:
|
||||||
sqlai <mode> [--file path]
|
sqlai <mode> [--file path] [--text "free text"] [--no-metrics]
|
||||||
|
|
||||||
Modes:
|
Modes:
|
||||||
analyze-tsql
|
ask General SQL Server 2022 Q&A (free text)
|
||||||
analyze-proc
|
analyze-tsql Analyze a T-SQL query
|
||||||
analyze-view
|
analyze-proc Analyze a stored procedure
|
||||||
analyze-plan
|
analyze-view Analyze a view
|
||||||
utf8-migration
|
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:
|
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
|
sqlai analyze-plan --file showplan.xml
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -39,44 +50,58 @@ mode="${1:-}"
|
|||||||
shift || true
|
shift || true
|
||||||
|
|
||||||
file=""
|
file=""
|
||||||
|
text=""
|
||||||
|
no_metrics="0"
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
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;;
|
*) echo "[$(ts)] ERROR: Unknown arg: $1"; usage; exit 2;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Read input (priority: --text > --file > stdin)
|
||||||
input=""
|
input=""
|
||||||
if [[ -n "$file" ]]; then
|
input_src=""
|
||||||
if [[ ! -f "$file" ]]; then
|
if [[ -n "$text" ]]; then
|
||||||
echo "[$(ts)] ERROR: file not found: $file"
|
input="$text"
|
||||||
exit 3
|
input_src="text"
|
||||||
fi
|
elif [[ -n "$file" ]]; then
|
||||||
|
[[ -f "$file" ]] || { echo "[$(ts)] ERROR: file not found: $file"; exit 3; }
|
||||||
input="$(cat "$file")"
|
input="$(cat "$file")"
|
||||||
|
input_src="file:${file}"
|
||||||
else
|
else
|
||||||
input="$(cat)"
|
input="$(cat)"
|
||||||
|
input_src="stdin"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "${input//[[:space:]]/}" ]]; then
|
if [[ -z "${input//[[:space:]]/}" ]]; then
|
||||||
echo "[$(ts)] ERROR: empty input"
|
echo "[$(ts)] ERROR: empty input (source=$input_src)"
|
||||||
exit 4
|
exit 4
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Short instruction per mode (core policy is in Modelfile SYSTEM prompt)
|
||||||
case "$mode" in
|
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)
|
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)
|
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)
|
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)
|
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)
|
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"
|
echo "[$(ts)] ERROR: unknown mode: $mode"
|
||||||
@@ -85,38 +110,66 @@ case "$mode" in
|
|||||||
;;
|
;;
|
||||||
esac
|
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 <<EOF
|
prompt=$(cat <<EOF
|
||||||
${instruction}
|
${instruction}
|
||||||
|
|
||||||
---BEGIN INPUT---
|
---BEGIN INPUT (${mode})---
|
||||||
${input}
|
${input}
|
||||||
---END INPUT---
|
---END INPUT (${mode})---
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
echo "[$(ts)] sqlai: MODE=$mode MODEL=$EXPERT_MODEL OLLAMA_URL=$OLLAMA_URL"
|
# Build JSON safely (no heredoc+herestring combos)
|
||||||
echo "[$(ts)] sqlai: INPUT_BYTES=$(printf "%s" "$input" | wc -c)"
|
payload="$(
|
||||||
|
printf '%s' "$prompt" | python -c '
|
||||||
payload="$(python - <<'PY'
|
|
||||||
import json, os, sys
|
import json, os, sys
|
||||||
model=os.environ.get("EXPERT_MODEL","jr-sql-expert")
|
model=os.environ.get("EXPERT_MODEL","jr-sql-expert")
|
||||||
prompt=sys.stdin.read()
|
prompt=sys.stdin.read()
|
||||||
print(json.dumps({"model": model, "prompt": prompt, "stream": False}, ensure_ascii=False))
|
print(json.dumps({"model": model, "prompt": prompt, "stream": False}, ensure_ascii=False))
|
||||||
PY
|
'
|
||||||
<<<"$prompt")"
|
)"
|
||||||
|
|
||||||
echo "[$(ts)] sqlai: sending request..."
|
resp_file="$(mktemp)"
|
||||||
resp="$(curl -sS -X POST "${OLLAMA_URL}/api/generate" -H 'Content-Type: application/json' --data-binary "$payload")" || {
|
http_code="$(
|
||||||
rc=$?
|
curl -sS -o "$resp_file" -w "%{http_code}" \
|
||||||
echo "[$(ts)] sqlai: ERROR: curl failed rc=$rc"
|
-X POST "${OLLAMA_URL}/api/generate" \
|
||||||
exit 10
|
-H 'Content-Type: application/json' \
|
||||||
}
|
--data-binary "$payload" \
|
||||||
|
|| true
|
||||||
|
)"
|
||||||
|
|
||||||
python - <<'PY'
|
resp="$(cat "$resp_file")"
|
||||||
|
rm -f "$resp_file"
|
||||||
|
|
||||||
|
echo "[$(ts)] sqlai: HTTP_CODE=$http_code RESP_BYTES=$(printf "%s" "$resp" | wc -c) 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: 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
|
import json,sys
|
||||||
obj=json.loads(sys.stdin.read())
|
obj=json.load(sys.stdin)
|
||||||
print("\n" + (obj.get("response","").rstrip()) + "\n")
|
out={
|
||||||
md = {
|
"error": obj.get("error"),
|
||||||
|
"response": obj.get("response",""),
|
||||||
|
"metrics": {
|
||||||
"total_duration": obj.get("total_duration"),
|
"total_duration": obj.get("total_duration"),
|
||||||
"load_duration": obj.get("load_duration"),
|
"load_duration": obj.get("load_duration"),
|
||||||
"prompt_eval_count": obj.get("prompt_eval_count"),
|
"prompt_eval_count": obj.get("prompt_eval_count"),
|
||||||
@@ -124,7 +177,51 @@ md = {
|
|||||||
"eval_count": obj.get("eval_count"),
|
"eval_count": obj.get("eval_count"),
|
||||||
"eval_duration": obj.get("eval_duration"),
|
"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 "================================================================================"
|
||||||
|
|||||||
27
prompts/sqlserver_qna.md
Normal file
27
prompts/sqlserver_qna.md
Normal file
@@ -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
|
||||||
|
<PASTE QUESTION HERE>
|
||||||
|
|
||||||
|
## 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---
|
||||||
|
<PASTE HERE>
|
||||||
|
---END QUESTION---
|
||||||
@@ -2,26 +2,34 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
# Create .env if missing (do not overwrite)
|
||||||
cp -n "${ROOT}/.env.example" "${ROOT}/.env" || true
|
cp -n "${ROOT}/.env.example" "${ROOT}/.env" || true
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
source "${ROOT}/.env"
|
source "${ROOT}/.env"
|
||||||
|
|
||||||
ts(){ date -Is; }
|
ts(){ date -Is; }
|
||||||
|
|
||||||
log_dir="${ROOT}/logs"
|
log_dir="${ROOT}/logs"
|
||||||
mkdir -p "$log_dir"
|
mkdir -p "$log_dir"
|
||||||
log_file="${log_dir}/bootstrap-$(date -Iseconds).log"
|
log_file="${log_dir}/bootstrap-$(date -Iseconds).log"
|
||||||
|
|
||||||
|
# log everything (stdout+stderr)
|
||||||
exec > >(tee -a "$log_file") 2>&1
|
exec > >(tee -a "$log_file") 2>&1
|
||||||
|
|
||||||
echo "[$(ts)] bootstrap: starting (ROOT=$ROOT)"
|
echo "[$(ts)] bootstrap: starting (ROOT=$ROOT)"
|
||||||
|
|
||||||
echo "[$(ts)] bootstrap: docker compose up -d"
|
echo "[$(ts)] bootstrap: docker compose up -d"
|
||||||
docker compose -f "${ROOT}/docker-compose.yml" up -d
|
docker compose -f "${ROOT}/docker-compose.yml" up -d
|
||||||
|
|
||||||
echo "[$(ts)] bootstrap: waiting for Ollama API at ${OLLAMA_URL} ..."
|
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
|
if curl -sS "${OLLAMA_URL}/api/tags" >/dev/null 2>&1; then
|
||||||
echo "[$(ts)] bootstrap: Ollama API is up."
|
echo "[$(ts)] bootstrap: Ollama API is up."
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
if [[ $i -eq 90 ]]; then
|
if [[ $i -eq 120 ]]; then
|
||||||
echo "[$(ts)] bootstrap: ERROR: API did not come up in time."
|
echo "[$(ts)] bootstrap: ERROR: API did not come up in time."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -39,11 +47,29 @@ if [[ -n "${EXTRA_MODELS:-}" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[$(ts)] bootstrap: building expert model: ${EXPERT_MODEL}"
|
echo "[$(ts)] bootstrap: building expert model: ${EXPERT_MODEL}"
|
||||||
|
|
||||||
tmp="$(mktemp)"
|
tmp="$(mktemp)"
|
||||||
sed "s/\${BASE_MODEL}/${BASE_MODEL}/g" "${ROOT}/Modelfile" > "$tmp"
|
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"
|
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: done"
|
||||||
echo "[$(ts)] bootstrap: test:"
|
|
||||||
echo " echo "SELECT 1;" | ${ROOT}/bin/sqlai analyze-tsql"
|
|
||||||
|
|||||||
@@ -2,27 +2,34 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
source "${ROOT}/.env"
|
source "${ROOT}/.env"
|
||||||
|
|
||||||
ts(){ date -Is; }
|
ts(){ date -Is; }
|
||||||
|
|
||||||
log_dir="${ROOT}/logs"
|
log_dir="${ROOT}/logs"
|
||||||
mkdir -p "$log_dir"
|
mkdir -p "$log_dir"
|
||||||
log_file="${log_dir}/update-$(date -Iseconds).log"
|
log_file="${log_dir}/update-$(date -Iseconds).log"
|
||||||
|
|
||||||
|
# log everything (stdout+stderr)
|
||||||
exec > >(tee -a "$log_file") 2>&1
|
exec > >(tee -a "$log_file") 2>&1
|
||||||
|
|
||||||
echo "[$(ts)] update: starting (ROOT=$ROOT)"
|
echo "[$(ts)] update: starting (ROOT=$ROOT)"
|
||||||
|
|
||||||
echo "[$(ts)] update: pulling docker image(s)"
|
echo "[$(ts)] update: pulling docker image(s)"
|
||||||
docker compose -f "${ROOT}/docker-compose.yml" pull
|
docker compose -f "${ROOT}/docker-compose.yml" pull
|
||||||
|
|
||||||
echo "[$(ts)] update: restarting services"
|
echo "[$(ts)] update: restarting services"
|
||||||
docker compose -f "${ROOT}/docker-compose.yml" up -d
|
docker compose -f "${ROOT}/docker-compose.yml" up -d
|
||||||
|
|
||||||
echo "[$(ts)] update: waiting for Ollama API at ${OLLAMA_URL} ..."
|
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
|
if curl -sS "${OLLAMA_URL}/api/tags" >/dev/null 2>&1; then
|
||||||
echo "[$(ts)] update: Ollama API is up."
|
echo "[$(ts)] update: Ollama API is up."
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
if [[ $i -eq 90 ]]; then
|
if [[ $i -eq 120 ]]; then
|
||||||
echo "[$(ts)] update: ERROR: API did not come up in time."
|
echo "[$(ts)] update: ERROR: API did not come up in time."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -40,9 +47,17 @@ if [[ -n "${EXTRA_MODELS:-}" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[$(ts)] update: rebuilding expert model: ${EXPERT_MODEL}"
|
echo "[$(ts)] update: rebuilding expert model: ${EXPERT_MODEL}"
|
||||||
|
|
||||||
tmp="$(mktemp)"
|
tmp="$(mktemp)"
|
||||||
sed "s/\${BASE_MODEL}/${BASE_MODEL}/g" "${ROOT}/Modelfile" > "$tmp"
|
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"
|
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"
|
echo "[$(ts)] update: complete"
|
||||||
|
|||||||
Reference in New Issue
Block a user