Files
club-3090/scripts/verify.sh
T
572b1c8ac0 scripts: auto-detect served model from /v1/models in verify/bench (#372) (#388)
report.sh autodetects the running container + URL + engine but not the
served model name, so verify.sh / verify-full.sh / verify-stress.sh /
bench.sh fell back to a hardcoded MODEL=qwen3.6-27b-autoround. Against a
non-qwen vLLM endpoint (e.g. vllm/gemma-26ba4b-single serving
gemma-4-26b-a4b-awq) every request 404'd with "The model
`qwen3.6-27b-autoround` does not exist", failing every check (#372).
llama.cpp ignores the request's model field, so the same wrong default
silently "worked" there (#371) — which masked the bug.

Add a shared preflight_autodetect_model helper that resolves the served
name from the endpoint's /v1/models (first id) when MODEL is unset, and
call it in the four affected scripts before their qwen fallback. This
mirrors what soak-test.sh / bench-agentic.sh / quality-test.sh already
do. An explicit MODEL= still wins (important for llama-swap/multi-model
endpoints); the qwen literal stays as a last resort if detection no-ops.

Validated: unit (resolve / respect-explicit / unreachable-fallback),
end-to-end against a mock /v1/models, and the full scripts/tests suite
stays green (the one pre-existing test-compose-registry-disk failure is
unrelated — untracked nex-n2-mini WIP composes).

Co-authored-by: noonghunna <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>
2026-06-12 14:54:51 +05:00

175 lines
7.8 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Post-setup smoke test — confirms the stack is healthy before you start using it.
#
# Runs four checks, each short-circuits on failure with an actionable hint:
# 1. Server responds on /v1/models
# 2. Genesis patches applied cleanly (tool_call fix is the fragile one)
# 3. Basic text completion works (Paris sanity)
# 4. Tool calling works end-to-end (request includes tools → response has tool_calls[])
#
# If check 4 fails but checks 1-3 pass, your Genesis tool_call patch didn't apply
# and you're on a vLLM nightly that drifted past our pinned digest. See the README
# troubleshooting section.
#
# Env vars (optional):
# URL Override endpoint. Default: http://localhost:8020
# MODEL Served model name. Default: qwen3.6-27b-autoround
# CONTAINER Docker container name for log scraping. Default: vllm-qwen36-27b
set -euo pipefail
# Auto-detect running container + port + served model (env vars still win).
# See scripts/preflight.sh::preflight_autodetect_endpoint / _model.
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
if [[ -f "${ROOT_DIR}/scripts/preflight.sh" ]]; then
# shellcheck source=preflight.sh
source "${ROOT_DIR}/scripts/preflight.sh"
preflight_autodetect_endpoint
fi
URL="${URL:-http://localhost:8020}"
# Resolve the served model from /v1/models when MODEL is unset (#372). The qwen
# literal below is only a last resort if detection no-ops (endpoint unreachable).
declare -F preflight_autodetect_model >/dev/null && preflight_autodetect_model
MODEL="${MODEL:-qwen3.6-27b-autoround}"
CONTAINER="${CONTAINER:-vllm-qwen36-27b}"
pass() { printf " \033[32m✓\033[0m %s\n" "$1"; }
fail() { printf " \033[31m✗\033[0m %s\n" "$1"; printf " \033[33m→\033[0m %s\n" "$2"; exit 1; }
echo "Running smoke test against ${URL} (model=${MODEL}, container=${CONTAINER})"
echo ""
# --------------------------------------------------------------------
# 1. Server reachable
# --------------------------------------------------------------------
echo "[1/4] Server reachable on /v1/models ..."
if curl -sf -m 5 "${URL}/v1/models" >/dev/null 2>&1; then
pass "server is serving"
else
fail "no response from ${URL}/v1/models" \
"Start the stack: cd compose && docker compose up -d ; docker logs -f ${CONTAINER}"
fi
# --------------------------------------------------------------------
# 2. Genesis patches applied cleanly
# --------------------------------------------------------------------
# Anchors updated 2026-05-02 for Genesis v7.14+ logging conventions. The
# pre-v7.14 "[OK] Qwen3 tool_call fix" marker is no longer emitted; v7.14+
# logs "[Genesis] applied:" per patch and "apply_all elapsed:" once at the
# end. We don't tail the grep output because v7.14+ prints 50+ apply lines
# and the canonical "apply_all elapsed:" anchor fires LAST. Reported by
# @troymroberts in club-3090#25 and refined per @JusefPol in club-3090#29.
echo "[2/4] Genesis patches applied ..."
if ! command -v docker >/dev/null 2>&1; then
echo " (skipped — docker not in PATH, cannot read container logs)"
elif ! docker inspect "${CONTAINER}" >/dev/null 2>&1; then
echo " (skipped — container '${CONTAINER}' not found; if your container has a different name, set CONTAINER=...)"
else
logs="$(docker logs "${CONTAINER}" 2>&1)"
if echo "$logs" | grep -q "\[Genesis\] FAILED"; then
fail "Genesis apply_all reported FAILED patch(es)" \
"Inspect: docker logs ${CONTAINER} 2>&1 | grep -E 'Genesis.*FAILED' | head"
elif echo "$logs" | grep -q "apply_all elapsed"; then
pass "Genesis patches applied (apply_all completed clean)"
elif echo "$logs" | grep -q "\[Genesis\] applied:"; then
pass "Genesis patches applied (apply_all may still be running)"
else
echo " (warn — no Genesis marker in logs; container may have been restarted. Continuing.)"
fi
fi
# --------------------------------------------------------------------
# 3. Basic completion — Paris sanity
# --------------------------------------------------------------------
echo "[3/4] Basic completion — capital of France ..."
resp="$(curl -sf -m 30 "${URL}/v1/chat/completions" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"${MODEL}\",
\"messages\": [{\"role\": \"user\", \"content\": \"What is the capital of France? Reply in one short sentence.\"}],
\"max_tokens\": 30,
\"temperature\": 0.6,
\"chat_template_kwargs\": {\"enable_thinking\": false}
}")" || fail "completion request failed" "Check docker logs ${CONTAINER}"
content="$(echo "$resp" | python3 -c "import sys,json; print(json.load(sys.stdin)['choices'][0]['message']['content'])" 2>/dev/null || true)"
if echo "$content" | grep -qi "Paris"; then
pass "reply contains 'Paris': $(echo "$content" | head -c 70)..."
else
fail "reply didn't mention Paris: $(echo "$content" | head -c 80)" \
"Model may be loading badly or using wrong chat template. Check docker logs ${CONTAINER}."
fi
# --------------------------------------------------------------------
# 4. Tool calling end-to-end — request with tools[] → response with tool_calls[]
# --------------------------------------------------------------------
echo "[4/4] Tool calling — model should populate tool_calls[] ..."
tool_resp="$(curl -sf -m 60 "${URL}/v1/chat/completions" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"${MODEL}\",
\"messages\": [
{\"role\": \"user\", \"content\": \"What is the weather in San Francisco right now? Use the get_weather tool.\"}
],
\"tools\": [
{
\"type\": \"function\",
\"function\": {
\"name\": \"get_weather\",
\"description\": \"Get the current weather for a given city.\",
\"parameters\": {
\"type\": \"object\",
\"properties\": {
\"city\": {\"type\": \"string\", \"description\": \"City name\"},
\"units\": {\"type\": \"string\", \"enum\": [\"celsius\", \"fahrenheit\"]}
},
\"required\": [\"city\"]
}
}
}
],
\"tool_choice\": \"auto\",
\"max_tokens\": 200,
\"temperature\": 0.3,
\"chat_template_kwargs\": {\"enable_thinking\": false}
}")" || fail "tool-call request failed" "Check docker logs ${CONTAINER}"
tool_calls="$(echo "$tool_resp" | python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
tc = d['choices'][0]['message'].get('tool_calls')
if tc:
print(json.dumps(tc, indent=2))
else:
# Check if model inlined <tool_call> as plain text — the symptom of a broken patch
content = d['choices'][0]['message'].get('content') or ''
if '<tool_call>' in content:
print('__INLINED__', content[:200], sep='\n')
else:
print('__NONE__', content[:200], sep='\n')
except Exception as e:
print(f'__PARSE_ERROR__: {e}')
" 2>&1)"
if echo "$tool_calls" | grep -q "__INLINED__"; then
fail "model emitted <tool_call> as inline text (tool_calls[] is empty)" \
"Genesis Patch 12 (Qwen3 tool_call fix) did not apply. Re-check the container logs and pin the image digest. README § Troubleshooting has the full chain."
elif echo "$tool_calls" | grep -q "__NONE__"; then
fail "model answered without invoking the tool" \
"May be a model-behavior issue (it chose not to call) rather than a patch issue. Try rephrasing the prompt or lowering temperature. Raw content: $(echo "$tool_calls" | tail -1)"
elif echo "$tool_calls" | grep -q "__PARSE_ERROR__"; then
fail "couldn't parse the response JSON" \
"Response was: $(echo "$tool_resp" | head -c 400)"
elif echo "$tool_calls" | grep -qi "get_weather"; then
pass "tool_calls[] populated, includes get_weather:"
echo "$tool_calls" | head -20 | sed 's/^/ /'
else
fail "unexpected tool_calls structure" \
"Raw: $(echo "$tool_calls" | head -c 300)"
fi
echo ""
echo "All checks passed. Stack is ready for use."