Перейти к содержанию

FastAPI memory service

Extra [fastapi] публикует scoped MemoryService через небольшой HTTP API. Аутентификация, получение scope и создание service задаются обязательными host-callback'ами; tenant, user и thread отсутствуют в request body.

pip install "protoprompt[fastapi]"
export PROTOPROMPT_API_KEY='replace-with-a-random-test-key'
python examples/fastapi_memory_service.py
curl -H "Authorization: Bearer $PROTOPROMPT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"memory_id":"renewal","text":"Договор продлевается в мае"}' \
  http://localhost:8000/v1/memories

curl -H "Authorization: Bearer $PROTOPROMPT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"Когда продлевается договор?"}' \
  http://localhost:8000/v1/memories/search

Пример работает офлайн на детерминированных demo embeddings. В production замените их и SQLite на provider embeddings и асинхронный production store. SQLite обёрнут через as_async, поэтому disk I/O не блокирует event loop.

Граница безопасности

У create_fastapi_memory_app(service_factory, scope_resolver, authorize) нет небезопасного default, доверяющего X-Tenant или пользовательскому вводу. В production authorize проверяет JWT/session/mTLS identity и кладёт доверенные claims в request.state; scope_resolver строит MemoryScope только из них. Адаптер дополнительно проверяет, что полученный MemoryService закреплён ровно за этим scope.

Неизвестные поля запрещены; ограничены text, identifiers, top_k, размер metadata и score threshold. Ограничьте общий размер body и request timeout ещё и на reverse proxy. /healthz не раскрывает backend/tenant и остаётся единственным маршрутом без авторизации. Explain не содержит recalled text.

API включает remember/search/forget, manifest/explain/budget report и опциональные profile endpoints. Profile signal отвечает 409, если host не настроил ProfileManager. Если он передан, его host-owned scope должен точно совпадать со scope у MemoryService; при несовпадении конструктор завершится до первого profile I/O.

Lifespan и деплой

Передайте FastAPI lifespan context: в нём открываются connection pools до начала трафика и закрываются только host-owned ресурсы. Локальный пример закрывает SQLite именно в lifespan.

Kubernetes-рецепт намеренно однорепличный из-за SQLite:

docker build -f examples/fastapi/Dockerfile -t protoprompt-fastapi:local .
minikube image load protoprompt-fastapi:local
kubectl create secret generic protoprompt-api \
  --from-literal=api-key='replace-with-a-random-test-key'
kubectl apply -f examples/fastapi/k8s.yaml
kubectl port-forward service/protoprompt-memory 8000:80

Для нескольких реплик замените SQLite на PostgreSQL/pgvector и общий profile/session backend. Не масштабируйте этот manifest горизонтально поверх ReadWriteOnce SQLite volume.

Миграция и откат

Сначала подключите HTTP app рядом с существующим процессом и направьте на него маленькую авторизованную когорту. До копирования памяти сравните mapping scope с текущими auth claims. Для отката уберите route и верните трафик in-process адаптеру; данные backend не меняются.

FastAPI остаётся optional recipe, а не обязательным core server. Расширение диапазона версий требует ASGI-тестов isolation, validation, lifespan и missing-extra. Ломающие изменения маршрутов получают новый prefix /vN.