Weitere Tools eingebaut. Damit ist die SQL AI einsatzbereit.
This commit is contained in:
208
bin/sqlai
208
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:-<empty>} 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:-<empty>} OLLAMA_URL=$OLLAMA_URL"
|
||||
echo "[$(ts)] sqlai: INPUT_SOURCE=$input_src INPUT_BYTES=$(printf "%s" "$input" | wc -c)"
|
||||
|
||||
# Delimited markup
|
||||
prompt=$(cat <<EOF
|
||||
${instruction}
|
||||
|
||||
@@ -127,43 +164,51 @@ ${input}
|
||||
EOF
|
||||
)
|
||||
|
||||
# Build JSON safely (no heredoc+herestring combos)
|
||||
payload="$(
|
||||
printf '%s' "$prompt" | python -c '
|
||||
payload_for_model() {
|
||||
local model="$1"
|
||||
printf '%s' "$prompt" | python -c '
|
||||
import json, os, sys
|
||||
model=os.environ.get("EXPERT_MODEL","jr-sql-expert")
|
||||
model=sys.argv[1]
|
||||
prompt=sys.stdin.read()
|
||||
print(json.dumps({"model": model, "prompt": prompt, "stream": False}, ensure_ascii=False))
|
||||
'
|
||||
)"
|
||||
' "$model"
|
||||
}
|
||||
|
||||
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
|
||||
)"
|
||||
do_request() {
|
||||
# args: model, attempt
|
||||
local model="$1"
|
||||
local attempt="$2"
|
||||
|
||||
resp="$(cat "$resp_file")"
|
||||
rm -f "$resp_file"
|
||||
local payload resp_file http_code resp resp_bytes extracted error_msg response_txt
|
||||
|
||||
echo "[$(ts)] sqlai: HTTP_CODE=$http_code RESP_BYTES=$(printf "%s" "$resp" | wc -c) id=$req_id"
|
||||
payload="$(payload_for_model "$model")"
|
||||
|
||||
# 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
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user