iSanzy1/klass-mobile-llm-adapter
Klass LLM Adapter Service
FastAPI-based adapter service that will become the single LLM boundary for the Klass media generation flow.
Current capabilities in this provider phase:
- Provides the new deployable
llm-adapter-service/structure. - Boots a FastAPI app from
app.main:app. - Exposes
GET /healthandGET /v1/health. - Enforces shared-secret signed request verification for interpretation and delivery routes.
- Adds structured JSON logging and per-request
X-Request-Idheaders. - Reports readiness for Postgres connectivity, active provider configuration, and inter-service auth configuration.
- Exposes
POST /v1/interpretandPOST /v1/respondwith request validation, governance preflight, semantic cache reuse, provider execution, response-contract validation, ledger writes, and fallback or structured-error behavior. - Normalizes interpretation and delivery payloads into a vendor-neutral internal provider request shape.
- Implements the Gemini provider client for
generateContentcalls, response text extraction, stable error mapping, and usage metadata capture. - Adds an OpenAI-ready provider client for
responses-style JSON generation and a route-level provider router with per-route primary/fallback policy. - Adds adapter-owned Postgres cache tables, deterministic semantic cache-key generation, route-specific TTL policies, advisory-lock stampede protection helpers, and lazy/manual cleanup utilities without Redis.
- Adds fixed-window rate-limit policy and bucket tables, request ledger tables, price catalog state, and daily cost aggregation views for future governance and observability.
- Adds a governance runtime that syncs baseline route policies into Postgres, enforces interpretation and delivery request quotas before provider calls, tracks deny counters without inflating request counts on blocked retries, and applies daily route budgets using estimated request cost.
- Exposes governance visibility in the health payload so operators can see active route limits, disable delivery temporarily, and detect when daily budget headroom is approaching exhaustion.
- Registers a shared adapter error handler so future routes can return stable structured JSON errors for quota or budget denials.
- Adds a cost tracking runtime that resolves active price-catalog entries, estimates request cost from normalized Gemini/OpenAI token usage, writes request-ledger rows for success, failure, cache-hit, and fallback paths, and preserves upstream request ids plus cost-source metadata.
- Exposes
GET /ops/summaryandGET /v1/ops/summaryfor route/provider operational metrics including latency, cache-hit ratio, deny rate, retry volume, fallback volume, and estimated cost totals. - Adds configurable Postgres pooling and optional startup auto-migration for Docker or Hugging Face deployment.
- Leaves backend cutover, deployment hardening, and end-to-end smoke verification as the primary remaining phases.
This service is prepared to be deployed as a Docker-based Hugging Face Space.
Local Run
pip install -r requirements.txt
python -m app.database migrate
uvicorn app.main:app --reloadOptional manual cache maintenance:
python -m app.cache cleanup --route all --limit 100This service should point to its own Postgres database. Do not reuse the Laravel backend database as the adapter operational state store.
Hugging Face Spaces
Deploy this folder as a Docker Space.
Suggested Space secrets and variables for the baseline:
LLM_ADAPTER_DATABASE_URLLLM_ADAPTER_DATABASE_CONNECT_TIMEOUT_SECONDS=3LLM_ADAPTER_DATABASE_POOL_MIN_SIZE=1LLM_ADAPTER_DATABASE_POOL_MAX_SIZE=5LLM_ADAPTER_DATABASE_POOL_MAX_IDLE_SECONDS=300LLM_ADAPTER_DATABASE_AUTO_MIGRATE=falseLLM_ADAPTER_UPSTREAM_TIMEOUT_SECONDS=30LLM_ADAPTER_SHARED_SECRETLLM_ADAPTER_SHARED_SECRET_PREVIOUSoptional comma-separated rotation secretsLLM_ADAPTER_REQUEST_MAX_AGE_SECONDS=300LLM_ADAPTER_CACHE_SCHEMA_VERSION=llm_adapter_cache.v1LLM_ADAPTER_INTERPRETATION_CACHE_TTL_SECONDS=86400LLM_ADAPTER_DELIVERY_CACHE_TTL_SECONDS=21600LLM_ADAPTER_CACHE_STAMPEDE_POLL_INTERVAL_MS=100LLM_ADAPTER_CACHE_STAMPEDE_WAIT_TIMEOUT_MS=1500LLM_ADAPTER_CACHE_CLEANUP_BATCH_SIZE=100LLM_ADAPTER_CACHE_LAZY_CLEANUP_INTERVAL_SECONDS=60LLM_ADAPTER_ACTIVE_INTERPRETATION_PROVIDER=geminiLLM_ADAPTER_ACTIVE_DELIVERY_PROVIDER=geminiLLM_ADAPTER_ALLOW_ROUTE_PROVIDER_DIVERGENCE=trueLLM_ADAPTER_INTERPRETATION_FALLBACK_PROVIDER=optional secondary provider for/v1/interpretLLM_ADAPTER_DELIVERY_FALLBACK_PROVIDER=optional secondary provider for/v1/respondLLM_ADAPTER_PROVIDER_FALLBACK_ERROR_CODES=provider_timeout,provider_connection_failed,provider_rate_limited,provider_unavailableLLM_ADAPTER_INTERPRETATION_REQUESTS_PER_MINUTE=30LLM_ADAPTER_INTERPRETATION_REQUESTS_PER_HOUR=600LLM_ADAPTER_INTERPRETATION_DAILY_BUDGET_USD=25.00LLM_ADAPTER_INTERPRETATION_DEFAULT_ESTIMATED_COST_USD=0.025LLM_ADAPTER_INTERPRETATION_EXHAUSTED_ACTION=denyLLM_ADAPTER_DELIVERY_ROUTE_ENABLED=trueLLM_ADAPTER_DELIVERY_REQUESTS_PER_MINUTE=60LLM_ADAPTER_DELIVERY_REQUESTS_PER_HOUR=1200LLM_ADAPTER_DELIVERY_DAILY_BUDGET_USD=10.00LLM_ADAPTER_DELIVERY_DEFAULT_ESTIMATED_COST_USD=0.010LLM_ADAPTER_DELIVERY_EXHAUSTED_ACTION=degradeLLM_ADAPTER_BUDGET_WARNING_RATIO=0.80LLM_ADAPTER_GEMINI_API_KEYLLM_ADAPTER_GEMINI_BASE_URL=https://generativelanguage.googleapis.comLLM_ADAPTER_GEMINI_API_VERSION=v1betaLLM_ADAPTER_GEMINI_INTERPRET_MODEL=gemini-2.0-flashLLM_ADAPTER_GEMINI_DELIVERY_MODEL=gemini-2.0-flashLLM_ADAPTER_OPENAI_API_KEYoptional until OpenAI is enabledLLM_ADAPTER_OPENAI_BASE_URL=https://api.openai.comLLM_ADAPTER_OPENAI_INTERPRET_MODEL=gpt-5.4LLM_ADAPTER_OPENAI_DELIVERY_MODEL=gpt-5.4LLM_ADAPTER_OPENAI_ORGANIZATION=optionalLLM_ADAPTER_OPENAI_PROJECT=optionalLLM_ADAPTER_SERVICE_NAME=klass-llm-adapterLLM_ADAPTER_SERVICE_VERSION=0.1.0LLM_ADAPTER_LOG_LEVEL=infoPORT=7860
Smoke test endpoints:
GET /healthGET /v1/healthGET /ops/summaryGET /v1/ops/summary
Environment Variables
LLM_ADAPTER_DATABASE_URL: Postgres DSN used for readiness checks and future adapter state.LLM_ADAPTER_DATABASE_CONNECT_TIMEOUT_SECONDS: Postgres connection timeout in seconds. Default3.LLM_ADAPTER_DATABASE_POOL_MIN_SIZE: minimum pooled Postgres connections kept by the adapter process. Default1.LLM_ADAPTER_DATABASE_POOL_MAX_SIZE: maximum pooled Postgres connections opened by the adapter process. Default5.LLM_ADAPTER_DATABASE_POOL_MAX_IDLE_SECONDS: idle lifetime for pooled Postgres connections. Default300.LLM_ADAPTER_DATABASE_AUTO_MIGRATE: whentrue, the adapter tries to apply pending SQL migrations during startup and logs failures without crashing the process. Defaultfalse.LLM_ADAPTER_UPSTREAM_TIMEOUT_SECONDS: timeout for Gemini or future provider calls. Default30.LLM_ADAPTER_SHARED_SECRET: shared secret placeholder for future signed request auth readiness.LLM_ADAPTER_SHARED_SECRET_PREVIOUS: optional comma-separated previous secrets accepted during rotation.LLM_ADAPTER_REQUEST_MAX_AGE_SECONDS: placeholder max age used by future signed auth. Default300.LLM_ADAPTER_CACHE_SCHEMA_VERSION: schema namespace embedded into cache keys so version bumps invalidate old semantic cache entries. Defaultllm_adapter_cache.v1.LLM_ADAPTER_INTERPRETATION_CACHE_TTL_SECONDS: TTL for interpretation cache entries. Default86400.LLM_ADAPTER_DELIVERY_CACHE_TTL_SECONDS: TTL for delivery cache entries. Default21600.LLM_ADAPTER_CACHE_STAMPEDE_POLL_INTERVAL_MS: polling interval used when waiting on an in-flight cache fill protected by advisory locks. Default100.LLM_ADAPTER_CACHE_STAMPEDE_WAIT_TIMEOUT_MS: maximum wait time before failing fast when another replica owns the in-flight cache fill lock. Default1500.LLM_ADAPTER_CACHE_CLEANUP_BATCH_SIZE: maximum expired rows deleted per cache table in one cleanup run. Default100.LLM_ADAPTER_CACHE_LAZY_CLEANUP_INTERVAL_SECONDS: minimum interval between lazy cleanup runs triggered by cache operations. Default60; set0to allow cleanup on every cache access.LLM_ADAPTER_ACTIVE_INTERPRETATION_PROVIDER: active provider alias for/v1/interpret. Defaultgemini.LLM_ADAPTER_ACTIVE_DELIVERY_PROVIDER: active provider alias for/v1/respond. Defaultgemini.LLM_ADAPTER_ALLOW_ROUTE_PROVIDER_DIVERGENCE: whentrue, interpretation and delivery may use different active providers. Defaulttrue.LLM_ADAPTER_INTERPRETATION_FALLBACK_PROVIDER: optional fallback provider alias for interpretation when the primary provider is unavailable, times out, or gets rate-limited.LLM_ADAPTER_DELIVERY_FALLBACK_PROVIDER: optional fallback provider alias for delivery when the primary provider is unavailable, times out, or gets rate-limited.LLM_ADAPTER_PROVIDER_FALLBACK_ERROR_CODES: comma-separated adapter error codes that trigger provider fallback. Defaultprovider_timeout,provider_connection_failed,provider_rate_limited,provider_unavailable.LLM_ADAPTER_INTERPRETATION_REQUESTS_PER_MINUTE: baseline fixed-window request ceiling for/v1/interpret. Default30.LLM_ADAPTER_INTERPRETATION_REQUESTS_PER_HOUR: sustained request ceiling for/v1/interpret. Default600.LLM_ADAPTER_INTERPRETATION_DAILY_BUDGET_USD: daily estimated-cost ceiling for/v1/interpret. Default25.00.LLM_ADAPTER_INTERPRETATION_DEFAULT_ESTIMATED_COST_USD: preflight estimated cost applied when checking the interpretation daily budget before an upstream call. Default0.025.LLM_ADAPTER_INTERPRETATION_EXHAUSTED_ACTION: governance action when interpretation quota or budget is exhausted. Supported values:deny,degrade. Defaultdeny.LLM_ADAPTER_DELIVERY_ROUTE_ENABLED: temporary operator switch for delivery route availability. Whenfalse, governance immediately returns a fallback-friendly block instead of attempting a provider call. Defaulttrue.LLM_ADAPTER_DELIVERY_REQUESTS_PER_MINUTE: baseline fixed-window request ceiling for/v1/respond. Default60.LLM_ADAPTER_DELIVERY_REQUESTS_PER_HOUR: sustained request ceiling for/v1/respond. Default1200.LLM_ADAPTER_DELIVERY_DAILY_BUDGET_USD: daily estimated-cost ceiling for/v1/respond. Default10.00.LLM_ADAPTER_DELIVERY_DEFAULT_ESTIMATED_COST_USD: preflight estimated cost applied when checking the delivery daily budget before an upstream call. Default0.010.LLM_ADAPTER_DELIVERY_EXHAUSTED_ACTION: governance action when delivery quota or budget is exhausted. Supported values:deny,degrade. Defaultdegrade.LLM_ADAPTER_BUDGET_WARNING_RATIO: threshold ratio used by health visibility to flag a route budget as nearing exhaustion. Default0.80.LLM_ADAPTER_GEMINI_API_KEY: Gemini provider credential.LLM_ADAPTER_GEMINI_BASE_URL: Gemini API base URL. Defaulthttps://generativelanguage.googleapis.com.LLM_ADAPTER_GEMINI_API_VERSION: Gemini REST API version segment. Defaultv1beta.LLM_ADAPTER_GEMINI_INTERPRET_MODEL: Gemini model used when the interpretation request model alias is provider-neutral. Defaultgemini-2.0-flash.LLM_ADAPTER_GEMINI_DELIVERY_MODEL: Gemini model used when the delivery request model alias is provider-neutral. Defaultgemini-2.0-flash.LLM_ADAPTER_OPENAI_API_KEY: OpenAI provider credential.LLM_ADAPTER_OPENAI_BASE_URL: OpenAI API base URL. Defaulthttps://api.openai.com.LLM_ADAPTER_OPENAI_INTERPRET_MODEL: OpenAI model used when the interpretation request model alias is provider-neutral. Defaultgpt-5.4.LLM_ADAPTER_OPENAI_DELIVERY_MODEL: OpenAI model used when the delivery request model alias is provider-neutral. Defaultgpt-5.4.LLM_ADAPTER_OPENAI_ORGANIZATION: optional OpenAI organization header value.LLM_ADAPTER_OPENAI_PROJECT: optional OpenAI project header value.LLM_ADAPTER_SERVICE_NAME: health metadata value. Defaultklass-llm-adapter.LLM_ADAPTER_SERVICE_VERSION: health metadata value. Default0.1.0.LLM_ADAPTER_LOG_LEVEL: application log level. Defaultinfo.
Inter-Service Auth
The adapter uses timestamped HMAC signatures for backend-to-adapter requests.
Expected request headers:
X-Request-IdX-Klass-Generation-IdX-Klass-Request-TimestampX-Klass-Signature-Algorithm=hmac-sha256X-Klass-Signature
The signature format matches the existing Python renderer boundary:
sha256_hmac(shared_secret, timestamp + "." + raw_request_body)
Only the adapter shared secret should live in Laravel. Provider API keys such as Gemini or OpenAI must remain exclusive to llm-adapter-service.
Credential Rotation
Use a two-step rollout for zero-downtime HMAC rotation:
- Deploy
llm-adapter-servicewith the new secret inLLM_ADAPTER_SHARED_SECRETand keep the old secret inLLM_ADAPTER_SHARED_SECRET_PREVIOUS. - Deploy Laravel with the same new value in
MEDIA_GENERATION_LLM_ADAPTER_SHARED_SECRET. - Confirm
GET /healthorGET /v1/healthreportsauth.rotation_enabled=trueand the expectedauth.accepted_secret_countduring the grace period. - Remove the previous secret from
LLM_ADAPTER_SHARED_SECRET_PREVIOUSafter in-flight jobs are drained.
Health Contract
GET /health and GET /v1/health return schema version llm_adapter_health.v1.
The payload includes:
service_nameservice_versionstatusandreadydependencies.postgresdependencies.providers.interpretationdependencies.providers.deliveryauthgovernance.readygovernance.budget_warning_ratiogovernance.routes[]with route enablement, minute/hour ceilings, daily budget, remaining headroom, and warning/exhausted status
The endpoint returns HTTP 200 when all readiness checks pass and HTTP 503 when any required dependency is not ready.
Database State
Adapter database migrations live in app/migrations/ and can be applied with:
python -m app.database migrateThe current migrations create:
schema_migrationsinterpretation_cache_entriesdelivery_cache_entriesrate_limit_policiesrate_limit_bucketsllm_request_ledgerprice_catalog_entriesllm_request_daily_aggregatesllm_request_daily_route_aggregates
The cache tables store cache_key, request_payload, response_payload, created_at, expires_at, hit_count, and last_hit_at, plus indexes for cache-key lookup and TTL cleanup.
Interpretation and delivery use separate TTL settings because delivery cache entries usually depend on more volatile artifact and publication metadata than interpretation entries. Cache keys embed schema version, provider alias, and model so version changes or provider/model swaps automatically invalidate prior semantic cache entries.
The cache service also provides Postgres advisory-lock helpers for in-flight stampede protection, bounded wait-or-fail-fast polling, lazy cleanup during cache operations, and a manual cleanup command for operators.
The governance state also stores fixed-window rate-limit policies and buckets keyed by route/provider/model, a request ledger for upstream LLM calls with retry and fallback metadata, provider/model pricing rows, and daily aggregate views for route-level cost reporting, cache effectiveness, and retry volume.
The governance runtime currently manages baseline route policies for interpret and respond, records usage and deny counters into rate_limit_buckets, and uses daily estimated-cost buckets to surface budget headroom in health checks.
The cost tracking runtime writes llm_request_ledger rows for success, failure, cache-hit, and fallback paths, looks up active price_catalog_entries to estimate cost from normalized provider usage, and falls back to route-level internal cost estimates when no catalog row is active.
Operator Summary Contract
GET /ops/summary and GET /v1/ops/summary return schema version llm_adapter_ops.v1.
The payload includes:
windowwithfrom_date,to_date, anddaysactive_routeswith the active provider, default model, and fallback provider per routerouteswith per-route request volume, cache hit ratio, deny rate, average latency, retry volume, fallback volume, error volume, token totals, and estimated cost volumeprovider_modelswith per route/provider/model cost and latency rollups for dashboard cards or tables
Next Phases
The adapter runtime is now wired for both /v1/interpret and /v1/respond. Remaining work is centered on backend cutover, deployment rollout, and end-to-end smoke verification across the Laravel backend, the adapter, and the Python renderer.
