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 Русский.
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 settings —
manage.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
- Signals —
run_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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91a7b4180276853bdf361769b0bbac9937d9fe50d693ec9db935c0e3ad165bc6
|
|
| MD5 |
733acd44fd321faa4fe0c1dc792e60d9
|
|
| BLAKE2b-256 |
53a6a807a175c1f2412af8feaddbb480d6405d8459b392eb61269446c1395492
|
Provenance
The following attestation bundles were made for mlango-0.1.0.tar.gz:
Publisher:
release.yml on DenisDrobyshev/mlango
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mlango-0.1.0.tar.gz -
Subject digest:
91a7b4180276853bdf361769b0bbac9937d9fe50d693ec9db935c0e3ad165bc6 - Sigstore transparency entry: 2291665232
- Sigstore integration time:
-
Permalink:
DenisDrobyshev/mlango@5cbe08fe2cc63d95cfb7eb4c3fb3c8c8d4bfa2a1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/DenisDrobyshev
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5cbe08fe2cc63d95cfb7eb4c3fb3c8c8d4bfa2a1 -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf54ead01cce7e2421216aa351b9d76e1a3cc81be5100e489438eaecda2cfb78
|
|
| MD5 |
7f6e872eb9314b259c561e9bb5046df6
|
|
| BLAKE2b-256 |
f72d93f5d0115a926863d8e81e3f356926d4c4f6c2121429fec5278660d17ca8
|
Provenance
The following attestation bundles were made for mlango-0.1.0-py3-none-any.whl:
Publisher:
release.yml on DenisDrobyshev/mlango
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mlango-0.1.0-py3-none-any.whl -
Subject digest:
cf54ead01cce7e2421216aa351b9d76e1a3cc81be5100e489438eaecda2cfb78 - Sigstore transparency entry: 2291665281
- Sigstore integration time:
-
Permalink:
DenisDrobyshev/mlango@5cbe08fe2cc63d95cfb7eb4c3fb3c8c8d4bfa2a1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/DenisDrobyshev
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5cbe08fe2cc63d95cfb7eb4c3fb3c8c8d4bfa2a1 -
Trigger Event:
push
-
Statement type: