fastrag.serve#
Serving: a FastAPI app that persists chats and streams grounded answers.
The public surface is exactly create_app(), ServeConfig,
cleanup_expired(), CleanupReport, and CLI. Everything
else is private.
Importing this package does not require FastAPI or Typer: only
create_app() needs FastAPI and only CLI needs Typer, and a
missing serving extra surfaces as a
ConfigurationError naming the install command —
never a bare ImportError.
- class fastrag.serve.CLI(backend_url, *, token_file=None, token=None, transport=None)[source]#
Bases:
objectAn interactive terminal client for the serving API, built on Typer.
CLI(backend_url=...)returns an object; calling it (cli()) runs the interactive loop.backend_urlis the base URL of a serving app started fromcreate_app().token_filepoints at where the session token is read from and written to (default~/.fastrag/session);tokenshort-circuits all token discovery by supplying the credential directly.Typer is imported lazily inside the constructor, so merely importing this class needs no
servingextra. Only constructing aCLIdoes.- Parameters:
- class fastrag.serve.CleanupReport(sessions, chats)[source]#
Bases:
objectHow many expired rows were deleted.
- class fastrag.serve.ServeConfig(*, allowed_origins, cookie_secure=True, cookie_domain=None, session_ttl_days=30, expiry_bump_min_days=1, max_request_bytes=32768, max_content_chars=4000, max_messages_per_chat=200, max_chats_per_session=100, generation_timeout_s=120.0, keepalive_interval_s=15.0, log_level='INFO', bearer_tokens=False, metrics_enabled=True, metrics_token=None, trust_proxy_headers=False, rate_limiting_enabled=False, rate_limit_generation='10/minute', rate_limit_read='60/minute', rate_limit_new_session='20/hour', geoip_database_path=None)[source]#
Bases:
objectConfiguration for the serving app.
Frozen and keyword-only. Nothing under
src/fastrag/readsos.environ— the caller reads the environment and passes the result in, exactly as08_sources.pyalready does for the chat model. Fail fast rather than start in a degraded state: every field is validated in__post_init__, raisingConfigurationErrorearlier than startup, at the line that got it wrong.- Parameters:
allowed_origins (list[str]) – Origins allowed by CORS. The one required field, with no default. A
"*"wildcard is rejected: credentialed requests cannot useAccess-Control-Allow-Origin: *. The app configures CORS from this list itself, socreate_app()callers never add their ownCORSMiddleware.cookie_secure (bool) – Whether the session cookie carries the
Secureflag.cookie_domain (str | None) – Optional
Domainfor the session cookie.session_ttl_days (int) – Days a session (and its chats) live without activity.
expiry_bump_min_days (int) – Minimum headroom before an expiry bump is written.
max_request_bytes (int) – Reject a request body larger than this with
413.max_content_chars (int) – Reject a
contentlonger than this with422.max_messages_per_chat (int) – Cap on messages per chat (
422beyond).max_chats_per_session (int) – Cap on chats per session (
422beyond).generation_timeout_s (float) – End-to-end budget (retrieval + generation) per generation; a timeout lands as
TIMEOUT.keepalive_interval_s (float) – Seconds between SSE keep-alive comments, and between pings on the chat’s lock connection while it answers. Keep it below the idle timeout of any proxy in front of the app or of Postgres.
log_level (str) – Root logger level for the JSON log output.
bearer_tokens (bool) – Enable the bearer-token path and
POST /v1/sessions. Off by default — see “Bearer tokens, in full” in the serving design notes.metrics_enabled (bool) – Whether
GET /metricsis mounted at all. On by default; every series is still collected when this isFalse, only the route that exposes them is withheld.metrics_token (str | None) – Optional bearer token guarding
GET /metrics.None(the default) leaves the endpoint unguarded — reasonable for a scrape target reachable only from inside a private network. When set, a request without a matchingAuthorization: Bearerheader gets401and no metric text.trust_proxy_headers (bool) – Whether the leftmost entry of an incoming
X-Forwarded-Forheader is trusted as the real client IP, instead ofrequest.client.host. Off by default: this stack usually runs behind a reverse proxy, but trusting a client-settable header unconditionally is a trivial rate-limit and audit-log bypass. Turn this on only when a proxy in front of this app is known to set (and not merely pass through) the header.rate_limiting_enabled (bool) – Whether the three limits below are enforced at all. Off by default, so an existing caller or the test suite is unaffected until it opts in.
rate_limit_generation (str) – Limit on
POST /v1/chats,POST /v1/chats/{id}/messages, andPOST /v1/chats/{id}/messages/{mid}, as a"10/minute"-style string (<count>/<second|minute|hour|day>), per client IP.rate_limit_read (str) – Limit on
GET /v1/chatsandGET /v1/chats/{id}, same format, per client IP.rate_limit_new_session (str) – Limit on the branch of a request that mints a new session (
POST /v1/chatsandPOST /v1/sessionswhen neither presents a live session), same format, per client IP — not a limit on those endpoints as a whole, whichrate_limit_generationalready covers forPOST /v1/chats.geoip_database_path (Path | None) – Path to a MaxMind GeoLite2-Country
.mmdbfile, for resolving a session’scountry.None(the default) disables country resolution entirely; nothing is downloaded or vendored by this package — see the serving guide for where to get one. Requires thegeoipextra (geoip2); without it, country resolution is silently unavailable even when a path is set.
- async fastrag.serve.cleanup_expired(engine, *, target='all', batch_size=1000)[source]#
Delete expired sessions and chats.
Deleting a session cascades to its chats and messages; chats are swept separately because a chat can go idle while its session stays active.
Deletes in bounded batches — one transaction per batch — for the same reason a single unbounded
DELETEwould be wrong: cascading through chats and messages in one very large transaction would hold locks and bloat WAL for as long as it ran. Sessions are swept before chats, mirroring the cascade.This function carries the deletion logic but never runs it itself — nothing in the request path calls it. It is meant to be invoked on a daily cadence by an external scheduler (cron, a Kubernetes
CronJob, …); a short script under cron calls it.- Parameters:
engine (AsyncEngine) – The async engine to run the deletes on. The caller typically passes a
PostgresChatStore’sengine. The SQL targets the plain table names (sessions/chats) on the engine’s search path — a store pointed at a non-default schema requires a schema-qualified variant (see this task’s result file).target (str) –
"sessions","chats", or"all".batch_size (int) – Rows deleted per transaction.
- Returns:
A
CleanupReportof rows deleted per table.- Return type:
- fastrag.serve.create_app(*, engine, chat_store, config=None)[source]#
Build the serving app.
A thin adapter over its two collaborators, laid out in the example the SDK ships: a
QueryEngineand aChatStore. The app itself owns no database engine and no ORM session — chatting goes through the store’s methods, generation throughengine.stream, and the advisory lock and/healthzconnection check borrow the store’s engine via itsengineproperty. Both collaborators are required: there is no half-configured mode.- Parameters:
engine (QueryEngine) – The
QueryEnginethe API exposes — the single place retrieval and generation happen.chat_store (ChatStore) – The
ChatStoreall session/chat/message persistence goes through. Already entered by the caller (async with ... as chat_store).config (ServeConfig | None) –
ServeConfig. Defaults to a config with an emptyallowed_originslist.
- Returns:
A FastAPI app serving
/healthzand the full/v1chats API.- Return type:
FastAPI
Modules
The FastAPI app: |
|
Expired-row cleanup, callable from an external scheduler. |
|
An interactive terminal client for the serving API, built on Typer. |
|
Frozen configuration for |
|
Optional GeoIP country resolution for the serving app. |