Search, RAG и API¶
API — тонкая HTTP-оболочка над authentication, rate limiting и SearchService. Search возвращает проверяемые fragments; Ask добавляет generation только поверх релевантного результата.
- Вход
- HTTP JSON + X-API-Key
- Выход
- SearchResponse, AskResponse, TenderDetails или ErrorResponse
- Состояние
- PostgreSQL + AIProvider; request_id живёт в одном HTTP request
- Точка запуска
uvicorn tender_lens.api.main:app
Карта файлов¶
| Файл | Назначение |
|---|---|
api/main.py |
app factory, lifespan, middleware, exception handlers, static mount |
api/routes.py |
health, detail, search, ask endpoints |
api/auth.py |
API-key generation/hash/lookup |
api/rate_limit.py |
atomic fixed UTC-minute counter |
search.py |
pgvector retrieval и grounded ask |
ai.py |
provider protocol, fake/Ollama, prompt |
App factory и lifespan¶
create_app() принимает optional dependencies, поэтому HTTP tests подставляют in-memory service без PostgreSQL. В production lifespan создаёт engine/session factory и AI provider, сохраняет их в application.state, а при shutdown закрывает собственные ресурсы.
Middleware принимает клиентский X-Request-ID или генерирует UUID и возвращает его в каждом ответе. Exception handlers преобразуют Pydantic validation и внутренние exceptions в одинаковую envelope.
Authentication¶
Dependency authenticate_api_key():
- требует header;
- считает SHA-256;
- ищет
ApiKey.key_hash; - проверяет
enabled; - делает constant-time
compare_digest; - detach-ит model от session и возвращает identity следующей dependency.
Health endpoints открыты; tender/search/ask защищены.
Rate limiter¶
consume_rate_limit() блокирует строку ключа FOR UPDATE. При новой UTC-минуте count обнуляется. Успех увеличивает count и возвращает только X-RateLimit-Limit, Remaining, Reset. Превышение rollback-ит и добавляет Retry-After только к 429.
Search и Ask делят один counter, потому что обе операции потребляют AI/DB ресурсы.
Exact cosine retrieval¶
Query превращается в embedding, далее PostgreSQL вычисляет:
<=> — cosine distance, поэтому 1 - distance — cosine similarity. SQL ограничивает значение диапазоном [-1, 1], фильтрует MIN_RELEVANCE_SCORE, сортирует по близости и UUID, затем применяет limit.
MVP использует exact scan: просто и детерминированно для небольшого индекса. При большом числе chunks потребуются HNSW/IVFFlat и измеренный recall/latency trade-off.
Fake и live provider¶
AIProvider задаёт embed, generate, health.
FakeAIProvider— hashing trick: token получает детерминированный индекс и знак по SHA-256, vector нормализуется. Это не нейросеть и не production semantic model; он делает CI быстрым и повторяемым.OllamaAIProviderвызывает/api/embed,/api/generate,/api/tags, строго проверяет количество и размерность vectors и переводит HTTP/JSON ошибки в typed dependency error.
Grounded Ask¶
ask() сначала вызывает тот же Search. Пустой результат возвращает «Недостаточно данных в базе знаний» без LLM. Иначе build_rag_prompt() передаёт вопрос, названия, source URLs и fragments.
«Grounded» означает, что ответ должен опираться на переданный контекст. Sources в ответе позволяют человеку проверить основание, но модель всё равно может ошибиться — поэтому интерфейс не скрывает fragments.
Endpoints¶
| Method/path | Auth/rate | Назначение |
|---|---|---|
GET /health/live |
нет | процесс отвечает |
GET /health/ready |
нет | PostgreSQL и AI доступны |
GET /api/v1/tenders/{id} |
auth | карточка и attachments |
POST /api/v1/search |
auth + rate | top 1..10 chunks |
POST /api/v1/ask |
auth + rate | answer по top 1..5 chunks |
Полные payloads — в HTTP API.