Files
meida_backend/scripts/smoke.sh
T
xsl 8a4720472b build: local stack, container images and the end-to-end smoke script
docker-compose brings up PostgreSQL 16 with pgvector and Redis 7 on
non-standard host ports so it never collides with an existing local database.
scripts/smoke.sh is the proof: compose, migrate, boot the API, issue a token,
call the real DeepSeek API and print the resulting ob_ai_call and bl_ledger
rows. services/vision and services/matting are placeholders noting that the
local RTX 5090 makes BiRefNet and RTMPose developable without cloud GPU.
2026-08-14 00:26:39 +08:00

127 lines
5.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# End-to-end proof that the scaffold works against the REAL DeepSeek API.
#
# compose up → migrate → start API → dev token → POST /v1/agent/chat
# → print the ob_ai_call row and the bl_ledger rows
#
# This is the only script that spends real money. Everything in `make test` is
# hermetic.
set -Eeuo pipefail
cd "$(dirname "$0")/.."
COMPOSE="docker compose -f deploy/docker-compose.dev.yml"
API_PORT="${API_PORT:-8010}"
BASE="http://127.0.0.1:${API_PORT}"
LOG_DIR=".smoke"
API_LOG="${LOG_DIR}/api.log"
API_PID=""
bold() { printf '\n\033[1m== %s\033[0m\n' "$1"; }
fail() { printf '\033[31mFAIL: %s\033[0m\n' "$1" >&2; exit 1; }
cleanup() {
if [[ -n "${API_PID}" ]] && kill -0 "${API_PID}" 2>/dev/null; then
kill "${API_PID}" 2>/dev/null || true
wait "${API_PID}" 2>/dev/null || true
fi
}
trap cleanup EXIT
mkdir -p "${LOG_DIR}"
[[ -f .env ]] || fail ".env not found. Copy .env.example to .env and set DEEPSEEK_API_KEY."
set -a; source .env; set +a
[[ -n "${DEEPSEEK_API_KEY:-}" ]] || fail "DEEPSEEK_API_KEY is empty in .env"
bold "1/7 Starting PostgreSQL (pgvector) and Redis"
${COMPOSE} up -d postgres redis
for _ in $(seq 1 60); do
if ${COMPOSE} exec -T postgres pg_isready -U meida -d meida >/dev/null 2>&1; then break; fi
sleep 1
done
${COMPOSE} exec -T postgres pg_isready -U meida -d meida >/dev/null || fail "PostgreSQL never became ready"
${COMPOSE} exec -T redis redis-cli ping >/dev/null || fail "Redis never became ready"
echo "postgres + redis ready"
bold "2/7 Applying migrations"
uv run alembic upgrade head
${COMPOSE} exec -T postgres psql -U meida -d meida -c "\dt"
${COMPOSE} exec -T postgres psql -U meida -d meida -tAc \
"SELECT extname FROM pg_extension WHERE extname='vector'" | grep -q vector \
|| fail "pgvector extension missing"
echo "pgvector extension present"
bold "3/7 Starting the API"
uv run uvicorn apps.api.main:app --host 127.0.0.1 --port "${API_PORT}" >"${API_LOG}" 2>&1 &
API_PID=$!
for _ in $(seq 1 60); do
if curl -fsS "${BASE}/healthz" >/dev/null 2>&1; then break; fi
sleep 0.5
done
curl -fsS "${BASE}/healthz" >/dev/null || { cat "${API_LOG}"; fail "API did not start"; }
curl -fsS "${BASE}/readyz" | tee /dev/null >/dev/null || { cat "${API_LOG}"; fail "API not ready"; }
echo "api up on ${BASE} (logs: ${API_LOG})"
bold "4/7 Issuing a dev token"
DEVICE_KEY="smoke-$(date +%s)"
TOKEN_JSON=$(curl -fsS -X POST "${BASE}/v1/auth/dev-token" \
-H 'Content-Type: application/json' \
-d "{\"device_key\":\"${DEVICE_KEY}\"}")
TOKEN=$(printf '%s' "${TOKEN_JSON}" | python3 -c 'import json,sys;print(json.load(sys.stdin)["data"]["access_token"])')
USER_ID=$(printf '%s' "${TOKEN_JSON}" | python3 -c 'import json,sys;print(json.load(sys.stdin)["data"]["user_id"])')
[[ -n "${TOKEN}" ]] || fail "no access token issued"
echo "user_id=${USER_ID}"
bold "5/7 Calling POST /v1/agent/chat against the REAL DeepSeek API"
CHAT_JSON=$(curl -fsS -X POST "${BASE}/v1/agent/chat" \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer ${TOKEN}" \
-d '{"message":"三十岁男生,第一次去互联网公司面试,穿什么比较稳妥?"}')
printf '%s' "${CHAT_JSON}" >"${LOG_DIR}/chat.json"
python3 -m json.tool --no-ensure-ascii "${LOG_DIR}/chat.json"
python3 - "${LOG_DIR}/chat.json" <<'PYEOF'
import json, sys
with open(sys.argv[1], encoding="utf-8") as fh:
body = json.load(fh)
assert body["code"] == 0, body
data = body["data"]
assert data["reply"].strip(), "empty model reply"
print("\n--- MODEL REPLY -------------------------------------------------")
print(data["reply"])
print("-----------------------------------------------------------------")
print("provider={provider} model={model} prompt={prompt_id}@{prompt_version}".format(**data))
print("usage={}".format(data["usage"]))
print("quota_remaining={}".format(data["quota_remaining"]))
PYEOF
bold "6/7 ob_ai_call row (成本归因的唯一数据源, 架构文档 §13)"
${COMPOSE} exec -T postgres psql -U meida -d meida -x -c \
"SELECT call_id, user_id, scene, provider, model, prompt_id, prompt_version,
tokens_in, tokens_out, tokens_cached, latency_ms, cost_cents,
success, error_code, ref_type, created_at
FROM ob_ai_call
WHERE user_id = ${USER_ID}
ORDER BY created_at DESC"
ROWS=$(${COMPOSE} exec -T postgres psql -U meida -d meida -tAc \
"SELECT count(*) FROM ob_ai_call WHERE user_id = ${USER_ID} AND success")
[[ "${ROWS// /}" -ge 1 ]] || fail "no successful ob_ai_call row was written"
bold "7/7 bl_ledger rows (预扣三段式, 架构文档 §7.4)"
${COMPOSE} exec -T postgres psql -U meida -d meida -c \
"SELECT tx_id, delta, tx_type, status, quota_key, period_key, balance_after,
idempotency_key, created_at
FROM bl_ledger
WHERE user_id = ${USER_ID}
ORDER BY created_at"
COMMITTED=$(${COMPOSE} exec -T postgres psql -U meida -d meida -tAc \
"SELECT count(*) FROM bl_ledger
WHERE user_id = ${USER_ID} AND tx_type='consume' AND status='committed'")
[[ "${COMMITTED// /}" -ge 1 ]] || fail "quota was not committed after a successful call"
${COMPOSE} exec -T postgres psql -U meida -d meida -c \
"SELECT quota_key, period_key, used, limit_value FROM bl_quota WHERE user_id = ${USER_ID}"
printf '\n\033[32mSMOKE PASSED\033[0m — real DeepSeek reply received, ob_ai_call and bl_ledger populated.\n'