agent-market/tests/llm_proxy_check.sh

37 lines
3.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# LLM proxy path against a local fake OpenAI-compatible server (no real key needed):
# tool call -> hydrated cards, key stays server-side, 429 fallback chain, rule-based last resort, per-IP rate limit.
set -euo pipefail
cd "$(dirname "$0")/.."
D=$(mktemp -d); KEYF=$D/key; printf 'test-dummy-key' > $KEYF; chmod 600 $KEYF
python3 tests/mock_llm.py 18798 & M=$!
start() { # port models rpm
AGENT_MARKET_PORT=$1 AGENT_MARKET_DATA=$D/d$1 AGENT_MARKET_LLM_API_KEY_FILE=$KEYF AGENT_MARKET_CHAT_RPM=$3 AGENT_MARKET_CHAT_BURST=3 \
AGENT_MARKET_LLM_BASE_URL=http://127.0.0.1:18798/v1 AGENT_MARKET_LLM_MODELS=$2 python3 server.py >/dev/null 2>&1 & echo $!; }
S1=$(start 18799 busy-model,mock-model 0); S2=$(start 18800 busy-model,busy-model-2 0); S3=$(start 18801 mock-model 2)
trap 'kill $M $S1 $S2 $S3 2>/dev/null; rm -rf $D' EXIT
sleep 1.5
chat() { curl -s localhost:$1/api/chat -H 'Content-Type: application/json' -d '{"message":"show me bikes"}'; }
OUT=$(chat 18799)
echo "$OUT" | python3 -c '
import json,sys; d=json.load(sys.stdin)
assert d["agent"]=="llm" and d["model"]=="mock-model", d
assert d["fallbacks"][0]=={"model":"busy-model","error":"rate_limited","ms":d["fallbacks"][0]["ms"]}, d["fallbacks"]
types=[a["type"] for a in d["actions"]]
assert types==["show_cards"], types
assert all("bike" in l["title"].lower() for l in d["actions"][0]["payload"]["listings"])
print("PASS 429 on first model -> second model answered; cards hydrated; disallowed component dropped")'
if echo "$OUT" | grep -q test-dummy-key; then echo "FAIL key leaked to browser"; exit 1; else echo "PASS key never sent to browser"; fi
test "$(curl -s localhost:18798/ | python3 -c 'import json,sys;print(json.load(sys.stdin)["auth_ok"])')" -ge 2 && echo "PASS key read from file and sent upstream only"
chat 18800 | python3 -c '
import json,sys; d=json.load(sys.stdin)
assert d["agent"]=="fallback" and d["llm_error"]=="all_models_failed" and d["actions"], d
print("PASS all models rate-limited -> rule-based agent still renders", [a["type"] for a in d["actions"]])'
curl -s localhost:18800/api/status | python3 -c '
import json,sys; a=json.load(sys.stdin)["agent"]; assert set(a["cooling_down"])=={"busy-model","busy-model-2"}, a; print("PASS 429ed models put on cooldown")'
CODES=$(for i in 1 2 3 4 5; do curl -s -o /dev/null -w '%{http_code} ' localhost:18801/api/chat -H 'Content-Type: application/json' -H 'CF-Connecting-IP: 203.0.113.9' -d '{"message":"show me bikes"}'; done)
OTHER=$(curl -s -o /dev/null -w '%{http_code}' localhost:18801/api/chat -H 'Content-Type: application/json' -H 'CF-Connecting-IP: 203.0.113.10' -d '{"message":"show me bikes"}')
EV=$(curl -s -o /dev/null -w '%{http_code}' localhost:18801/api/chat -H 'Content-Type: application/json' -H 'CF-Connecting-IP: 203.0.113.9' -d '{"event":{"type":"landing"}}')
[[ "$EV" == "200" ]] && echo "PASS UI events (no LLM call) not rate-limited" || { echo "FAIL event limited: $EV"; exit 1; }
[[ "$CODES" == "200 200 200 429 429 " && "$OTHER" == "200" ]] && echo "PASS per-IP rate limit (burst 3: $CODES; other IP $OTHER)" || { echo "FAIL rate limit: $CODES / $OTHER"; exit 1; }