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()
73 lines
3 KiB
Bash
Executable file
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'\"}}]}}'"
|