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

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():

  1. требует header;
  2. считает SHA-256;
  3. ищет ApiKey.key_hash;
  4. проверяет enabled;
  5. делает constant-time compare_digest;
  6. 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 вычисляет:

1 - (chunks.embedding <=> CAST(:embedding AS vector))

<=> — 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.