Skip to main content

A batteries-included framework for machine learning, analytics and LLM agents, built on Django's philosophy.

Project description

mlango

A batteries-included framework for machine learning, analytics and LLM agents — built on Django's philosophy.

Read this in Русский.

CI Python License

ML projects tend to become a pile of scripts: one to load data, one to train, a notebook that produced the number in the slide deck, a checkpoints/ directory nobody can map back to a commit. Web development had exactly this problem, and Django solved it — not with a better library, but with a framework: a project layout, a settings module, declarative classes, migrations, an auto-generated admin, and a manage.py that ties it together.

mlango applies that answer to ML. You declare datasets, models, agents and evaluations; the framework runs them, versions them, records them and shows them to you.

# reviews/datasets.py
from mlango.core import fields
from mlango.data import Dataset, JSONLSource

class Reviews(Dataset):
    """Customer product reviews."""

    id = fields.IntegerField()
    text = fields.TextField()
    label = fields.LabelField(["negative", "positive"])

    class Meta:
        source = JSONLSource("data/reviews.jsonl")
        primary_key = "id"
# reviews/models.py
from mlango.core import fields
from mlango.training import Model
from reviews.datasets import Reviews

class Sentiment(Model):
    """TF-IDF into logistic regression."""

    max_features = fields.IntegerField(default=20_000, tunable=True)
    C = fields.FloatField(default=1.0, min_value=0.0, tunable=True)

    class Meta:
        dataset = Reviews
        trainer = "sklearn"
        task = "classification"
        features = ["text"]

    def build(self):
        from sklearn.feature_extraction.text import TfidfVectorizer
        from sklearn.linear_model import LogisticRegression
        from sklearn.pipeline import make_pipeline
        return make_pipeline(
            TfidfVectorizer(max_features=self.max_features),
            LogisticRegression(C=self.C),
        )
python manage.py train reviews.Sentiment -p C=2.0

That one command resolves your class, opens a tracked run, seeds every RNG, splits the data deterministically, calls your build(), drives the training loop, records metrics, captures the git commit, saves the artifact and registers a promotable model version. You wrote build() and four field declarations.


Install

pip install "mlango[sklearn]"

Extras: sklearn, torch, anthropic, dev, or all.

Five minutes from nothing

mlango startproject myproject
cd myproject
python manage.py migrate
python manage.py train demo.Sentiment
python manage.py runserver

Open http://127.0.0.1:8000/admin/. Unlike a bare scaffold, a fresh mlango project already contains a working example — a dataset, a trained model with real metrics, an agent with a tool, and an eval suite — so the admin has something in it the first time you look.

No configuration is required to get there: the metastore is SQLite, artifacts go to a local directory, and agents run on an offline provider that needs no API key.


What you get

Declarative classes with a _meta

Four families, one system. Everything generic in the framework — the admin, migrations, the CLI, the API — is written against _meta, which is why one admin renders all four.

You declare You get
Dataset A lazy queryset, schema validation, deterministic splits, content-addressed versioning
Model Hyperparameters as validated fields, tracked runs, callbacks, a model registry with stages
Agent A tool-use loop, tools with schemas derived from type hints, memory, full step-by-step tracing
Eval Per-case scoring persisted to the metastore, so a regression is a diff between two runs

A queryset for data

Lazy, composable, and recorded alongside the run that used it:

train, val = Reviews.objects.filter(label="positive").shuffle(seed=0).split(train=0.8, val=0.2).values()

for batch in train.batch(32):
    ...

Lookups follow Django's spelling — filter(stars__gte=4), exclude(text__icontains="spam"), filter(language__in=["en", "de"]). Splits are assigned by hashing each record's key, so adding rows never moves existing ones between train and test — the property that makes a held-out set trustworthy six months later.

Migrations for schemas

python manage.py makemigrations
python manage.py migrate

Changing a dataset's fields generates a real, reviewable migration file. Data migrations use RunPython, exactly as you would expect.

An admin you did not build

Every declared object appears automatically — no registration required. Register only to change how it looks:

@admin.register(Reviews)
class ReviewsAdmin(admin.ObjectAdmin):
    list_display = ("id", "text", "label")
    list_filter = ("label",)
    search_fields = ("text",)

The admin shows data previews with filters and search, run history with metric charts, side-by-side run comparison, dataset and model versions with one-click promotion, and a step-by-step trace viewer for every agent call. It is server-rendered with no build step and no CDN.

Agents as declarations

from mlango.agents import Agent, BufferMemory, tool

@tool
def search_docs(query: str, limit: int = 5) -> list[str]:
    """Search the product documentation.

    Args:
        query: What to search for.
        limit: Maximum number of results.
    """
    return retrieve(query, limit)

class Support(Agent):
    """Answers product questions from the docs."""

    class Meta:
        model = "claude-opus-5"
        system = "You are a support engineer. Cite the docs you used."
        tools = [search_docs]
        memory = BufferMemory(k=20)

The JSON schema comes from your type hints and docstring, so a tool is described in exactly one place. The framework owns the loop, retries, tool dispatch, usage accounting and tracing.

Serving from the same declaration

# myproject/routes.py
from mlango.serve import path

urlpatterns = [
    path("predict/", Sentiment.as_endpoint(stage="production")),
    path("chat/", Support.as_endpoint()),
]

manage.py runserver serves the admin and a documented API together; OpenAPI schemas are derived from the declarations, so /api/docs describes your model's inputs without you writing a schema.


The command line

python manage.py check                          # validate the whole project
python manage.py inspectdata data/reviews.csv    # declare a Dataset from a file
python manage.py dataset head reviews.Reviews   # peek at the data
python manage.py dataset materialize reviews.Reviews
python manage.py makemigrations && python manage.py migrate
python manage.py train reviews.Sentiment -p C=2.0 --tag baseline
python manage.py predict reviews.Sentiment "loved every minute"
python manage.py runs list
python manage.py runs compare 7c8f1020 c089b7e6
python manage.py evaluate reviews.Accuracy --min-pass-rate 0.9
python manage.py agent support.Support           # interactive session
python manage.py traces show a1b2c3d4            # replay an agent call
python manage.py shell                           # everything pre-imported
python manage.py test                            # against a throwaway metastore
python manage.py runserver

inspectdata is Django's inspectdb for data files: it samples a CSV, JSONL or Parquet file and prints a Dataset with the field types, ranges, label classes and primary key already filled in, so your first declaration is an edit rather than a blank page.

Apps can ship their own commands in <app>/management/commands/, and they appear in manage.py help automatically — including overriding a built-in.


Configuration

One settings module, every default documented in mlango.conf.global_settings. Backends are swapped by setting, not by rewriting code:

METASTORE = {"URL": "postgresql://user@host/mlango"}   # SQLite by default
STORAGE = {"BACKEND": "myproject.storage.S3Storage"}
TRAINERS = {"lightgbm": "myproject.trainers.LightGBMTrainer"}
PROVIDERS = {"vllm": "myproject.providers.VLLMProvider"}
SERVE_MIDDLEWARE = ["mlango.serve.middleware.ApiKeyMiddleware", ...]

Why a framework and not a library

A library is something you call. A framework calls you. That inversion is the whole point, and it is what buys the conveniences above:

  • Project layout and settingsmanage.py, MLANGO_SETTINGS_MODULE
  • An app registry — autodiscovers datasets.py, models.py, agents.py, evals.py, admin.py
  • Migrations — generated, reviewable files for declared schemas
  • An admin generated from declarations
  • A management command system apps can extend and override
  • Signalsrun_finished, epoch_finished, tool_called, and more
  • Pluggable backends behind settings

If mlango were a library you would still be writing the run loop, the tracking schema, the admin and the CLI. Being a framework is what removes them.


Documentation

Full docs, including a tutorial that builds a project end to end: https://denisdrobyshev.github.io/mlango/

Contributing

Contributions are welcome — see CONTRIBUTING.md for the development setup, and CODE_OF_CONDUCT.md for community expectations. Good first issues are labelled good first issue.

License

MIT — see LICENSE.

mlango is not affiliated with or endorsed by the Django Software Foundation. It borrows Django's design philosophy, gratefully.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mlango-0.1.0.tar.gz (303.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mlango-0.1.0-py3-none-any.whl (213.0 kB view details)

Uploaded Python 3

File details

Details for the file mlango-0.1.0.tar.gz.

File metadata

  • Download URL: mlango-0.1.0.tar.gz
  • Upload date:
  • Size: 303.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for mlango-0.1.0.tar.gz
Algorithm Hash digest
SHA256 91a7b4180276853bdf361769b0bbac9937d9fe50d693ec9db935c0e3ad165bc6
MD5 733acd44fd321faa4fe0c1dc792e60d9
BLAKE2b-256 53a6a807a175c1f2412af8feaddbb480d6405d8459b392eb61269446c1395492

See more details on using hashes here.

Provenance

The following attestation bundles were made for mlango-0.1.0.tar.gz:

Publisher: release.yml on DenisDrobyshev/mlango

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mlango-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: mlango-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 213.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for mlango-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cf54ead01cce7e2421216aa351b9d76e1a3cc81be5100e489438eaecda2cfb78
MD5 7f6e872eb9314b259c561e9bb5046df6
BLAKE2b-256 f72d93f5d0115a926863d8e81e3f356926d4c4f6c2121429fec5278660d17ca8

See more details on using hashes here.

Provenance

The following attestation bundles were made for mlango-0.1.0-py3-none-any.whl:

Publisher: release.yml on DenisDrobyshev/mlango

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page