Minimal code, maximum automation for AI workflows.
Write regular Python functions. Add two decorators. Get FastAPI endpoints, execution history, and time travel debugging automatically.
Ombra solves the biggest pain point in AI engineering: debugging complex chains. Instead of re-running expensive/slow LLM calls from scratch every time you tweak a prompt, Ombra lets you "time travel" back to any step, edit its inputs, and replay from there using cached results for everything upstream.
- ⚡ Instant API: Your Python functions become production-ready FastAPI endpoints.
- 🔄 Time Travel: Resume execution from any step. Tweaked a prompt in step 3? Replay from step 3, reusing results from steps 1 & 2.
- 📊 Visual Dashboard: See exactly what happened, when, and why. Inspect inputs/outputs for every step.
- 💾 Automatic Persistence: All execution history, inputs, and outputs are saved to SQLite.
- 🤖 LLM Integration: Built-in
call_llmhelper that handles multimodal inputs (like PDFs) seamlessly.
pip install ombra
# or
uv add ombraRequires Python ≥3.12.
Let's build a real-world example: A workflow that takes a PDF, analyzes it using Gemini, and drafts a response email.
Define structured inputs/outputs using Pydantic.
from pydantic import BaseModel
class DocumentAnalysis(BaseModel):
"""Structured analysis of a document."""
summary: str
key_entities: list[str]
sentiment: str
requires_action: bool
class EmailDraft(BaseModel):
"""Proposed email draft based on analysis."""
subject: str
body: str
recipient_role: strWrite async functions decorated with @step(). Use call_llm for AI tasks.
from ombra import step, File, call_llm, Model, Message, Role
from models import DocumentAnalysis, EmailDraft
@step()
async def analyze_document(file: File) -> DocumentAnalysis:
"""Sends PDF to Gemini for analysis."""
prompt = """
Analyze the attached PDF. Provide a summary, key entities, sentiment,
and if action is required.
"""
response = await call_llm(
model=Model.GEMINI_2_5_FLASH_LITE,
messages=[
Message(role=Role.USER, content=[prompt, file]) # Native file support!
],
response_format=DocumentAnalysis # Structured Output!
)
# Ombra + LiteLLM guarantees the response matches your Pydantic model
return DocumentAnalysis.model_validate_json(response.content)
@step()
async def draft_response_email(analysis: DocumentAnalysis) -> EmailDraft:
"""Drafts an email based on the analysis."""
prompt = f"""
Draft a response based on this analysis:
Summary: {analysis.summary}
Sentiment: {analysis.sentiment}
"""
response = await call_llm(
model=Model.GEMINI_2_5_FLASH_LITE,
messages=[Message(role=Role.USER, content=prompt)],
response_format=EmailDraft # Structured Output!
)
return EmailDraft.model_validate_json(response.content)Decorate your main function with @workflow.
from ombra import workflow, File
from models import EmailDraft
from steps import analyze_document, draft_response_email
@workflow(name="process_document", description="Analyze PDF and draft email")
async def process_document_workflow(file: File) -> EmailDraft:
# Step 1: Analyze the PDF
analysis = await analyze_document(file)
# Step 2: Draft an email based on the analysis
email_draft = await draft_response_email(analysis)
return email_draftStart the development server pointing to your code:
uv run ombra dev --workflows-dir .You'll see:
INFO: 🚀 Starting Ombra Dev Server at http://127.0.0.1:8000
...
INFO: ✨ Registered workflow: process_document
INFO:
INFO: 📖 Run Workflows: http://localhost:8000/docs
INFO: 👀 View Executions: http://localhost:8000/workflows
INFO:
- Execute: Go to
http://localhost:8000/docs, findPOST /workflows/process_document/execute, upload a PDF, and run it. - Visualize: Go to
http://localhost:8000/workflowsto see the execution live. You'll see theanalyze_documentstep completing, followed bydraft_response_email. - Time Travel:
- Let's say you don't like the email tone in Step 2.
- Instead of re-uploading the PDF and re-running the expensive Step 1, just click "Resume" on the
draft_response_emailstep in the UI. - You can even edit the inputs (e.g., change the prompt logic in your code) and replay just that step!
- Wraps any
asyncfunction. - Automatically serializes and persists inputs/outputs to SQLite.
- Handles nesting (steps calling steps).
- Supports file handling natively.
- Wraps the entry point function.
- Automatically generates a FastAPI route based on type hints.
- Manages the execution context.
- Built-in wrapper around standard LLM APIs.
- Multimodal: Pass text and
Fileobjects directly in thecontentlist. Ombra handles the base64 conversion and formatting for you. - Structured Outputs: Pass a Pydantic model to
response_formatto guarantee valid JSON outputs matching your schema. - Visualized: Shows token usage and model details directly in the execution dashboard.
ombra dev: Starts the dev server with hot reloading.--workflows-dir: Specify where your workflow files are (defaults to current dir).
ombra serve: Starts a production-ready server.
MIT