Flama Framework

website·Indexed 32 days ago

https://flama.dev

An open-source Python framework for building Machine Learning APIs and LLM services. Flama features a Rust-powered core for performance-critical paths, native support for the Model Context Protocol (MCP), and built-in capabilities for serving predictive ML models (sklearn, TensorFlow, PyTorch) and generative LLMs. It includes integrated support for Pydantic schemas, dependency injection, JWT authentication, and Domain-Driven Design (DDD) patterns.

Tokens
16.2K
Snippets
94
Records
101
Agent score
65%

What's inside Flama

  1. Overview of the Flama Python Framework

    Flama is an open-source Python framework designed for building production-ready Machine Learning APIs, LLM serving endpoints, and Model Context Protocol (MCP) servers. It features a Rust-powered core for high-performance routing and JSON processing, and automatically generates OpenAPI 3.x documentation accessible at /docs/.

    Key capabilities include:

    • Unified Model Serving: A consistent workflow for deploying predictive models (sklearn, TensorFlow, PyTorch) and generative LLMs.
    • Native LLM Serving: Support for OpenAI, Anthropic, Ollama, and Native-compatible APIs using vLLM (Linux/CUDA) or MLX (macOS/Metal) backends.
    • MCP Support: First-class stateless Model Context Protocol support to expose tools, resources, and prompts to AI agents using type-safe schemas.
    • HTTP & API Features: Streaming-first responses (SSE, NDJSON), REST resources with built-in pagination, and JWT authentication with permission-based route protection.
    • Architecture: Component-based dependency injection and Domain-Driven Design (repositories, workers, resources via the Unit of Work pattern).
    • Schema Flexibility: Support for Pydantic, Typesystem, or Marshmallow.
    • Built-in Middleware: Includes CORS, compression (brotli/gzip), correlation IDs, HTTPS redirects, trusted hosts, and sessions.
  2. Understand Flama's Rust-powered performance core

    Flama uses PyO3 to compile performance-critical paths into native Rust code. This core handles high-efficiency operations including:

    • Route table: Compiled routing with typed path converters.
    • JSON encoder: Native encoding supporting datetime, date, uuid, Enum, set, dataclass, and Decimal.
    • HTTP parsing: URL decoding, cookie parsing, and multipart/URL-encoded form parsing.
    • Compression: Support for gzip, brotli, bzip2, lzma, and zstd codecs.
    • FLM serialisation: Tar archiving for model bundles.
  3. Implement WebSocket endpoints in Flama

    Use the WebSocketEndpoint class to handle real-time bidirectional communication. You can specify the encoding (e.g., "json") and override lifecycle methods: on_connect to accept the connection, on_receive to handle incoming data, and on_disconnect for cleanup. Register the endpoint using app.add_websocket_route(path, EndpointClass).
    from flama.endpoints import WebSocketEndpoint
    
    class ChatEndpoint(WebSocketEndpoint):
        encoding = "json"
    
        async def on_connect(self, websocket):
            await websocket.accept()
    
        async def on_receive(self, websocket, data):
            await websocket.send_json({"echo": data})
    
        async def on_disconnect(self, websocket, websocket_code):
            pass
    
    app.add_websocket_route("/ws/chat/", ChatEndpoint)
  4. Create and configure an MCP server in Flama

    Flama supports the Model Context Protocol (MCP), allowing AI agents to discover and invoke tools, read resources, and use prompts via a stateless JSON-RPC interface. You can create an MCP server by instantiating the MCPServer class, specifying a name, version, and optional instructions.
    from flama import Flama
    from flama.mcp.server import MCPServer
    
    server = MCPServer("tools", version="1.0.0", instructions="My MCP tools server")
  5. Implement the Worker transaction pattern

    To ensure data integrity, always wrap repository calls within an async with worker: block. This pattern ensures that changes are automatically committed on success and rolled back if an exception occurs.
    # ALWAYS wrap repository calls in `async with worker:`
    async with worker:
        result = await worker.repository.create(data)
        # Auto-commits on success, auto-rollbacks on exception
  6. Stream data using NDJSON (Newline-Delimited JSON)

    Use NDJSONResponse to stream a sequence of JSON objects. This response type sets the content type to application/x-ndjson. Each item yielded by the generator is JSON-encoded and followed by a newline (\n) character.
    from flama.http.responses.ndjson import NDJSONResponse
    
    @app.route("/ndjson/", name="ndjson")
    def ndjson():
        async def gen():
            for i in range(50):
                yield {"i": i, "square": i * i}
    
        return NDJSONResponse(gen())
  7. Handle application startup and shutdown events

    Use the @app.on_event decorator to register asynchronous handlers that trigger during the application's startup and shutdown phases.
    @app.on_event("startup")
    async def on_startup():
        print("Application starting")
    
    @app.on_event("shutdown")
    async def on_shutdown():
        print("Application shutting down")
  8. Define MCP tools in Flama

    MCP tools are defined using the @server.tool decorator. Input schemas are automatically derived from function type hints, and output schemas are inferred from the return type annotation. Flama supports three types of tools:

    1. Simple sync tools: Standard synchronous functions.
    2. Async tools: Asynchronous functions using async def.
    3. Background task tools: Asynchronous functions marked with task=True (requires client Extensions.TASKS support).
    # Simple sync tool
    @server.tool("add", description="Add two integers")
    def add(a: int, b: int) -> int:
        return a + b
    
    # Async tool
    @server.tool(description="Greet someone by name")
    async def greet(name: str) -> str:
        return f"Hello, {name}!"
    
    # Background task tool (requires client Extensions.TASKS support)
    @server.tool("square", task=True, description="Square a number as a background task")
    async def square(x: int) -> int:
        return x * x
  9. Define MCP resources and prompts

    MCP servers can expose resources (static or dynamic data) and prompts (templates for AI interaction).

    • Resources: Defined with @server.resource, requiring a URI, name, description, and mime type.
    • Prompts: Defined with @server.prompt, where arguments are derived from the handler's parameters.
    import json
    
    # Defining a resource
    @server.resource(
        "config://app",
        name="config",
        description="Application configuration",
        mime_type="application/json",
    )
    def config():
        return json.dumps({"debug": True, "name": "my-app"})
    
    # Defining a prompt
    @server.prompt("summarise", description="Summarise the given text")
    def summarise(text: str):
        return f"Summarise the following:\n\n{text}"
  10. Flama system requirements and dependencies

    Flama supports Python versions 3.10 through 3.14. The core installation always includes the following dependencies: click (CLI), uvicorn[standard] (ASGI server), Jinja2 (templating), pyyaml (YAML parsing), rich (terminal formatting), and httpx[http2] (HTTP client).
  11. Implement a full-stack API with ML, LLM, MCP, and DDD using Flama

    This example demonstrates how to build a production-ready API using Flama. It integrates SQLAlchemy for database persistence via DDD (Domain-Driven Design) patterns, defines ML model resources, sets up an MCP (Model Context Protocol) server for tool-calling, and configures an LLM endpoint.

    Key architectural components include:

    • SQLAlchemy Integration: Uses SQLAlchemyModule for database connectivity and SQLAlchemyTableRepository for data access.
    • DDD Layers: Implements a Worker pattern (SQLAlchemyWorker) to manage repository lifecycles and a Resource layer to handle HTTP routing.
    • ML/LLM Resources: Deploys a custom ML model via MLResource and a native LLM model.
    • MCP Server: Exposes tools, resources, and prompts to an LLM via MCPServer.
    import hashlib
    import json
    import typing
    
    import sqlalchemy
    from pydantic import BaseModel as PydanticModel
    
    import flama
    from flama import Flama, types
    from flama.ddd.components import WorkerComponent
    from flama.ddd.exceptions import NotFoundError
    from flama.ddd.repositories.sqlalchemy import SQLAlchemyTableRepository
    from flama.ddd.workers.sqlalchemy import SQLAlchemyWorker
    from flama.exceptions import HTTPException
    from flama.http import APIResponse
    from flama.mcp.server import MCPServer
    from flama.models import MLResource
    from flama.resources import Resource
    from flama.resources.routing import ResourceRoute
    from flama.sqlalchemy import SQLAlchemyModule, metadata
    
    # --- Database ---
    user_table = sqlalchemy.Table(
        "user", metadata,
        sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True, autoincrement=True),
        sqlalchemy.Column("name", sqlalchemy.String, nullable=False),
        sqlalchemy.Column("email", sqlalchemy.String, unique=True, nullable=False),
        sqlalchemy.Column("password", sqlalchemy.String, nullable=False),
    )
    
    # --- Schemas ---
    class UserCreate(PydanticModel):
        name: str
        email: str
        password: str
    
    class UserOutput(PydanticModel):
        id: int
        name: str
        email: str
    
    # --- DDD layers ---
    class UserRepository(SQLAlchemyTableRepository):
        _table = user_table
    
    class UserWorker(SQLAlchemyWorker):
        user: UserRepository
    
    class UserResource(Resource):
        name = "user"
        verbose_name = "User"
    
        @ResourceRoute.method("/", methods=["POST"], name="create")
        async def create(
            self,
            worker: UserWorker,
            data: typing.Annotated[types.Schema, types.SchemaMetadata(UserCreate)],
        ):
            """
            tags:
                - User
            summary:
                Register a new user.
            responses:
                201:
                    description: User created.
            """
            async with worker:
                hashed = hashlib.sha256(data["password"].encode()).hexdigest()
                result = await worker.user.create({**data, "password": hashed})
            return APIResponse(result[0], status_code=201)
    
        @ResourceRoute.method("/{user_id:int}/", methods=["GET"], name="retrieve")
        async def retrieve(
            self, worker: UserWorker, user_id: int,
        ) -> typing.Annotated[types.Schema, types.SchemaMetadata(UserOutput)]:
            """
            tags:
                - User
            summary:
                Get user by ID.
            """
            async with worker:
                try:
                    return await worker.user.retrieve(id=user_id)
                except NotFoundError:
                    raise HTTPException(status_code=404, detail="User not found")
    
    # --- ML Model ---
    class PredictionModel(MLResource):
        name = "predictor"
        verbose_name = "ML Predictor"
        model_path = "sklearn_model.flm"
    
    # --- MCP Server ---
    mcp_server = MCPServer("tools", version="1.0.0", instructions="API tools for user management")
    
    @mcp_server.tool("user_count", description="Get the total number of users")
    async def user_count() -> int:
        return 42
    
    @mcp_server.resource("config://app", name="config", description="App config", mime_type="application/json")
    def config():
        return json.dumps({"version": "1.0.0"})
    
    @mcp_server.prompt("welcome", description="Generate a welcome message")
    def welcome(name: str):
        return f"Write a friendly welcome message for {name}."
    
    # --- App assembly ---
    app = Flama(
        openapi={
            "info": {
                "title": "Full-Stack ML + LLM + MCP + DDD API",
                "version": "1.0.0",
                "description": "Flama production API 🔥",
            }
        },
        docs="/docs/",
        components=[
            WorkerComponent(worker=UserWorker()),
        ],
        modules=[
            SQLAlchemyModule(url="sqlite+aiosqlite:///app.db"),
        ],
    )
    
    app.resources.add_resource("/users/", UserResource)
    app.models.add_model_resource(path="/predict", resource=PredictionModel)
    app.models.add_model(path="/llm/", model="gemma.flm", name="gemma", serving=("native", "openai"))
    app.mcp.add_server("/mcp/", "tools", server=mcp_server)
    
    if __name__ == "__main__":
        flama.run(flama_app="__main__:app", server_host="0.0.0.0", server_port=8080, server_reload=True)