GenAI examples — complete flows¶
The generative-AI recipe documents each piece; here
they combine into real flows, self-hosted end to end. Install the
extras per example:
uv add "tempest-fastapi-sdk[genai,genai-rag,genai-audio]".
1. Load the model only if it fits¶
Before downloading gigabytes, check; let the SDK pick the precision that fits:
import asyncio
from tempest_fastapi_sdk.genai import TextGenerator, recommend
MODEL = "Qwen/Qwen2.5-7B-Instruct"
rec = recommend(model_id=MODEL) # tries bf16 -> int8 -> int4
if not rec.fits:
raise RuntimeError(rec.reason + " " + (rec.suggestion or ""))
gen = TextGenerator(
MODEL,
device=rec.device,
quantization=rec.dtype.value if rec.dtype.value in ("int8", "int4") else None,
idle_unload_seconds=300, # free VRAM between bursts
)
async def main() -> None:
"""Run this example."""
answer = await gen.generate("Explain PIX in one sentence.")
print(answer)
asyncio.run(main())
2. RAG over a PDF knowledge base¶
Index the PDFs once, answer grounded questions afterwards. In-memory here
(dev); swap for PgVectorStore(db, dim=384) in production.
import asyncio
from tempest_fastapi_sdk.genai import Embedder, TextGenerator
from tempest_fastapi_sdk.genai.rag import InMemoryVectorStore, PdfReader, Retriever
rag = Retriever(
Embedder("sentence-transformers/all-MiniLM-L6-v2", normalize=True),
InMemoryVectorStore(),
)
gen = TextGenerator("Qwen/Qwen2.5-7B-Instruct", quantization="int4")
async def main() -> None:
"""Run this example."""
# indexing (once, at startup or in a job)
for pdf in ("manual.pdf", "faq.pdf", "policies.pdf"):
await rag.index(PdfReader().chunks(f"/kb/{pdf}", max_chars=1500, overlap=150))
# querying (cheap, per request)
async def answer(question: str) -> str:
context = await rag.retrieve(question, top_k=5)
prompt = f"{context}\n\nAnswer only from the sources above.\n{question}"
return await gen.generate(prompt, max_new_tokens=400)
asyncio.run(main())
3. Web-grounded answer (SearXNG)¶
No own corpus — search the web, extract, answer:
import httpx
from tempest_fastapi_sdk import HTTPClient
from tempest_fastapi_sdk.genai import TextGenerator
from tempest_fastapi_sdk.genai.rag import ContentExtractor, SearxngBackend, WebSearch
search = WebSearch(SearxngBackend("http://localhost:8080", http_client=HTTPClient()))
extractor = ContentExtractor(http_client=httpx.AsyncClient())
gen = TextGenerator("Qwen/Qwen2.5-7B-Instruct", quantization="int4")
async def answer_from_web(question: str) -> str:
context = await search.retrieve(question, extractor=extractor, max_results=5)
return await gen.generate(f"{context}\n\n{question}", max_new_tokens=400)
4. Fully local voice assistant¶
The full loop: audio → text → RAG/LLM → text → audio. Nothing leaves the box.
from tempest_fastapi_sdk.genai import TextGenerator
from tempest_fastapi_sdk.genai.audio import Language, SpeechToText, TextToSpeech
stt = SpeechToText("base")
gen = TextGenerator("Qwen/Qwen2.5-7B-Instruct", quantization="int4")
tts = TextToSpeech.for_language(Language.EN_US)
async def voice_turn(audio_wav_path: str) -> bytes:
"""Take speech, return speech."""
heard = await stt.transcribe(audio_wav_path, language=Language.EN_US)
reply = await gen.chat([
{"role": "system", "content": "You are a concise assistant."},
{"role": "user", "content": heard.text},
])
return await tts.synthesize(reply) # WAV bytes
Combine with RAG: swap gen.chat(...)
for gen.generate(await rag.retrieve(heard.text)) to get a voice
assistant that answers about your PDFs.
5. FastAPI transcription endpoint¶
from fastapi import APIRouter, UploadFile
from tempest_fastapi_sdk.genai.audio import Language, SpeechToText
router = APIRouter()
stt = SpeechToText("base") # load in the lifespan in prod
@router.post("/transcribe")
async def transcribe(file: UploadFile) -> dict[str, object]:
result = await stt.transcribe(await file.read(), language=Language.EN_US)
return {"text": result.text, "duration": result.duration}
Scale and economy¶
- Batch: wrap
embedder._embed_manyin aBatchSchedulerto coalesce concurrent embeddings into one forward pass. - Share models:
ModelRegistry(max_models=2)reuses loaded models across call sites and unloads the LRU. - Free VRAM:
idle_unload_seconds+gen.unload_if_idle()in a@tq.interval(60)TaskQueue task. - Embedding cache:
Embedder(cache=...)skips the model for text already seen.
Full reference for each piece: Self-hosted generative AI.