#!/usr/bin/env bash
# llm-heartbeat — garde « chaud » le tuyau LiteLLM ↔ llama-server.
#
# Pourquoi : llama-server ferme les connexions keep-alive inactives, mais LiteLLM
# garde le socket mort dans son pool → la 1ʳᵉ requête après une pause part dans le
# vide → timeout → 502 (cf. admin/incidents-llm-gpu.md). Un petit appel régulier
# maintient la connexion vivante (et la re-crée AVANT la vraie demande si elle est morte).
#
# Double rôle (watchdog d'observation) : logge un échec → visible dans
# `journalctl -u llm-heartbeat`. Déployé/géré par le rôle Ansible `litellm`.
set -u

URL="http://127.0.0.1:{{ litellm_port }}/v1/chat/completions"
KEY="{{ litellm_master_key }}"
MODEL="{{ llm_heartbeat_model }}"
TIMEOUT="{{ llm_heartbeat_timeout }}"
INTERVAL="{{ llm_heartbeat_interval }}"

# `/no_think` + max_tokens:1 → génération quasi instantanée (~10 ms GPU), coût négligeable.
payload="{\"model\":\"${MODEL}\",\"messages\":[{\"role\":\"user\",\"content\":\"ping /no_think\"}],\"max_tokens\":1}"

fails=0
while true; do
  code=$(curl -s -m "$TIMEOUT" -o /dev/null -w '%{http_code}' \
    "$URL" \
    -H "Authorization: Bearer ${KEY}" \
    -H "Content-Type: application/json" \
    -d "$payload" 2>/dev/null) || code="000"

  if [ "$code" = "200" ]; then
    fails=0
  else
    fails=$((fails + 1))
    echo "heartbeat KO: HTTP ${code} (${MODEL}) — chaîne LLM non réactive (${fails} échec(s) consécutif(s))"
  fi

  sleep "$INTERVAL"
done
