#!/usr/bin/env bash # La configuracion de Hermes, PARSEADA. # # Esta prueba existe por un fallo concreto que casi cuela. El config.yaml que # genera el instalador declara `provider` y `base_url` DOS VECES dentro del # bloque model:, una arriba y otra ~45 lineas mas abajo. En YAML gana la ultima. # Al configurar Ollama en las de arriba el fichero PARECIA correcto leyendolo, y # al parsearlo salia provider=auto, base_url=https://openrouter.ai/api/v1: el # carril habria salido por OpenRouter creyendo nosotros que iba a localhost. # # Con 1890 lineas y 121 activas, leer el fichero no demuestra nada. Aqui todo se # comprueba con yaml.safe_load, que es lo mismo que hace Hermes al arrancar. cd "$(dirname "$0")" && . ./comun.sh titulo "El cerebro" afirma "existe $CONFIG" test -f "$CONFIG" comprueba "el modelo por defecto es el verde" "$(config_model default)" "$MODELO" # Sin esto Hermes se cree lo que dice /api/show, que son 262144 —el maximo del # modelo— y no los 24576 configurados. El sintoma serian respuestas truncadas y # llamadas rotas, y la conclusion natural ("el 4B no vale") seria falsa. CTX_YAML=$(config_model context_length) CTX_REAL=$(parametro "$MODELO" num_ctx) comprueba "Hermes sabe el contexto REAL, no el que reporta /api/show" \ "$CTX_YAML" "$CTX_REAL" # Los dos numeros tienen que ser iguales Y llegar al suelo de 64k. Poner aqui # 65536 con el modelo a 24576 pasaria el arranque y romperia en marcha: Hermes # no comprimiria hasta acercarse a 64k y ollama iria tirando los tokens mas # viejos, que son el prompt de sistema y los esquemas de las herramientas. El # agente dejaria de llamar herramientas a mitad de sesion, sin un solo error. mayor_que "y ese contexto llega al suelo de Hermes" "${CTX_YAML:-0}" 63999 BASE=$(config_model base_url) case "$BASE" in http://127.0.0.1:*|http://localhost:*) echo " $(verde OK) el cerebro apunta a esta maquina ($BASE)"; _ok=$((_ok + 1)) ;; *) echo " $(rojo FALLO) base_url NO es local: $BASE"; _fallo=$((_fallo + 1)) ;; esac titulo "Cero nube" # Se recorre el YAML entero, no solo el bloque model:. Cualquier http(s) que no # apunte al propio equipo es una salida potencial y tiene que estar justificada. FUERA=$(python3 - "$CONFIG" <<'PY' import re, sys, yaml LOCAL = re.compile(r'^https?://(127\.0\.0\.1|localhost|0\.0\.0\.0|\[::1\])') URL = re.compile(r'https?://[^\s"\']+') fuera = [] def anda(nodo, ruta=""): if isinstance(nodo, dict): for k, v in nodo.items(): anda(v, f"{ruta}.{k}" if ruta else str(k)) elif isinstance(nodo, list): for i, v in enumerate(nodo): anda(v, f"{ruta}[{i}]") elif isinstance(nodo, str): for u in URL.findall(nodo): if not LOCAL.match(u): fuera.append(f"{ruta} = {u}") anda(yaml.safe_load(open(sys.argv[1]))) for f in fuera: print(f) PY ) if [ -z "$FUERA" ]; then echo " $(verde OK) ninguna URL de nube activa en el config"; _ok=$((_ok + 1)) else echo " $(rojo FALLO) hay URLs que salen de esta maquina:" printf '%s\n' "$FUERA" | sed 's/^/ /' _fallo=$((_fallo + 1)) fi titulo "Desde donde mira el agente" # `cwd: "."` de fabrica NO es "el directorio desde el que lanzas hermes", por # mucho que lo diga su documentacion: el agente arranca en $HOME. Con la raiz # ahi, "lee verde/README.md" acaba leyendo $HOME/README.md —el de # Gitleaks— y resumiendolo perfectamente. Ningun error, respuesta inutil. CWD_AGENTE=$(python3 -c " import yaml print(yaml.safe_load(open('$CONFIG')).get('terminal', {}).get('cwd', ''))" 2>/dev/null) comprueba "el agente arranca en la raiz del proyecto" "$CWD_AGENTE" "$RAIZ" titulo "El lanzador entra donde debe" # Un `cd` antes de llamar a hermes NO basta: Hermes restaura el directorio que # tenga apuntado en la sesion. El sintoma no parece de rutas — se le pide "lee # verde/README.md y resumelo" y devuelve un resumen impecable de OTRO fichero, # sin un solo error — asi que se comprueba estaticamente. if grep -q -- '--in "\$RAIZ"' "$VERDE/verde-launcher.sh"; then echo " $(verde OK) verde-launcher.sh pasa --in y no confia en el cd"; _ok=$((_ok + 1)) else echo " $(rojo FALLO) verde-launcher.sh no pasa --in: Hermes abrira en otro sitio" _fallo=$((_fallo + 1)) fi titulo "La voz apunta a la del naranja" # Sin esto la comparacion de voz entre carriles no diria nada: Hermes se # descargaria su propia voz de Piper (la GPL de OHF-Voice) en vez de usar el # binario MIT y el .onnx que ya tiene el naranja. ORDEN=$(python3 -c " import yaml t = yaml.safe_load(open('$CONFIG')).get('tts', {}) p = t.get('providers', {}).get(t.get('provider', ''), {}) print(p.get('command', '')) " 2>/dev/null) case "$ORDEN" in */verde/voz-verde.sh*) echo " $(verde OK) TTS por comando -> voz-verde.sh"; _ok=$((_ok + 1)) ;; *) echo " $(rojo FALLO) el TTS no apunta al adaptador: $ORDEN"; _fallo=$((_fallo + 1)) ;; esac stt_local() { # $1 = clave dentro de stt.local python3 -c " import yaml print(yaml.safe_load(open('$CONFIG')).get('stt', {}).get('local', {}).get('$1', '')) " 2>/dev/null | tr -d '\n' } comprueba "la escucha es la local de Hermes" "$(stt_local model)" "small" # 839 MiB de pico contra una tarjeta con 3301 MiB de modelo dentro: whisper # tiene que soltar la VRAM cuando calla, o la siguiente frase da OOM. menor_que "whisper suelta la VRAM tras callar (s)" "$(stt_local unload_after_idle_seconds)" 601 resumen