artenschutz-router/scripts/smoke.sh
m2 (AI Agent) 04d2f5b9e4 fix: align router with memory-api's actual payload shape (smoke findings)
End-to-end smoke against the live coolify memory stack surfaced three
gaps between the spec and what memory-api actually writes:

1. memory-api wraps the /ingest payload under `metadata.*` in Qdrant —
   our domain fields (fact_type, scope.states, …) don't live at the top
   of the payload, they're nested. `build_qdrant_filter` now auto-prefixes
   any non-top-level key with `metadata.`. Callers can write
   `{"fact_type": "legal"}` and it just works; top-level fields
   (`agent_id`, `memory_type`, `source`, …) pass through unprefixed.

2. The deployed BGE-M3 TEI image doesn't serve /embed_sparse (returns
   424 Failed Dependency). Stored points have `has_sparse=false`.
   `BGEClient.embed_sparse` now returns `{}` on 4xx and logs a warning;
   `QdrantReader.hybrid_query` falls back to a dense-only query when
   sparse is empty. RRF fusion is skipped in that path.

3. `scripts/smoke.sh` had a missing `shift` after reading the limit
   positional arg — caused arg leakage onto the CLI (`--limit 5 5`).
   The cleanup hint also referenced a stale agent_id; now reads from
   $ARTENSCHUTZ_AGENT_ID.

Smoke is now green end-to-end: 18 records ingested across bnatschg /
mhbasp / biotopwertliste(BY) / state-berlin under
agent_id=gruenstifter_smoketest, hybrid + payload-filter searches all
return hits. 24/24 unit tests pass.

Companion fixes pushed to github.com/machine-machine/agent.memory.system:
  d0e9529  fix: /memory/store passed wrong kwarg name to AgentMemory
  ebc896c  fix: don't close caller-supplied clients in AgentMemory.close()
2026-05-14 23:30:57 +02:00

73 lines
3 KiB
Bash
Executable file

#!/usr/bin/env bash
# End-to-end smoke for artenschutz-router against the live memory stack.
# Runs from inside the container; ROUTER_URL defaults to localhost:8080.
set -euo pipefail
ROUTER_URL="${ROUTER_URL:-http://localhost:8080}"
step() { printf '\n\033[1;36m== %s ==\033[0m\n' "$*"; }
pass() { printf '\033[1;32m ✓ %s\033[0m\n' "$*"; }
fail() { printf '\033[1;31m ✗ %s\033[0m\n' "$*"; exit 1; }
step "1/5 health"
health=$(curl -sS "$ROUTER_URL/health")
echo " $health"
echo "$health" | grep -q '"status":"ok"' || fail "health is not ok — fix services first"
pass "router can reach Qdrant + BGE-M3 + memory-api"
ingest_source() {
local source="$1" ; shift
local limit="${1:-5}" ; shift || true
step "ingest --source $source --limit $limit ${*:-}"
python -m artenschutz_router.ingest.cli \
--source "$source" \
--router-url "$ROUTER_URL" \
--limit "$limit" "$@" \
2>&1 | tail -5
}
step "2/5 fetch BNatSchG if missing"
if [ ! -s sources/bnatschg/bnatschg.txt ]; then
python scripts/fetch_bnatschg.py
fi
step "3/5 ingest small batches"
ingest_source bnatschg 5
ingest_source mhbasp 5
ingest_source biotopwertliste 3 --states BY
ingest_source state-berlin 5
step "4/5 search — hybrid, fact_type=method"
result=$(curl -sS -X POST "$ROUTER_URL/search/generic" \
-H 'Content-Type: application/json' \
-d '{"query":"Fledermäuse Kartierung","metadata_filter":{"fact_type":"method"},"limit":3}')
hits=$(echo "$result" | python -c "import sys,json; print(len(json.load(sys.stdin)['hits']))")
echo " hits: $hits"
[ "$hits" -ge 1 ] || fail "hybrid search returned 0 hits — check BGE-M3"
pass "hybrid retrieval works"
step "4b/5 search — hybrid, fact_type=legal"
result=$(curl -sS -X POST "$ROUTER_URL/search/generic" \
-H 'Content-Type: application/json' \
-d '{"query":"besonders geschützte Arten","metadata_filter":{"fact_type":"legal"},"limit":3}')
hits=$(echo "$result" | python -c "import sys,json; print(len(json.load(sys.stdin)['hits']))")
echo " hits: $hits"
[ "$hits" -ge 1 ] || fail "legal hybrid search returned 0 hits"
pass "BNatSchG indexed and queryable"
step "5/5 scroll — scope.states contains BE"
result=$(curl -sS -X POST "$ROUTER_URL/search/generic" \
-H 'Content-Type: application/json' \
-d '{"metadata_filter":{"scope.states":["BE"]},"limit":3}')
hits=$(echo "$result" | python -c "import sys,json; print(len(json.load(sys.stdin)['hits']))")
echo " hits: $hits"
[ "$hits" -ge 1 ] || fail "scope.states=BE scroll returned 0 hits — Berlin ingest missed?"
pass "payload filtering on nested keys works"
printf '\n\033[1;32mALL SMOKE STEPS PASSED\033[0m\n'
echo
agent_id_used="${ARTENSCHUTZ_AGENT_ID:-gruenstifter}"
echo "To clean up the smoke data later (agent_id=$agent_id_used):"
echo " curl -X POST http://memory-qdrant:6333/collections/agent_memory/points/delete \\"
echo " -H 'Content-Type: application/json' \\"
echo " -d '{\"filter\":{\"must\":[{\"key\":\"agent_id\",\"match\":{\"value\":\"'$agent_id_used'\"}}]}}'"