Metadata-Version: 2.4
Name: mlango
Version: 0.1.0
Summary: A batteries-included framework for machine learning, analytics and LLM agents, built on Django's philosophy.
Project-URL: Homepage, https://github.com/DenisDrobyshev/mlango
Project-URL: Documentation, https://denisdrobyshev.github.io/mlango/
Project-URL: Repository, https://github.com/DenisDrobyshev/mlango
Project-URL: Changelog, https://github.com/DenisDrobyshev/mlango/blob/master/CHANGELOG.md
Project-URL: Issues, https://github.com/DenisDrobyshev/mlango/issues
Author-email: Denis Drobyshev <drobishev.denis@icloud.com>
Maintainer-email: Denis Drobyshev <drobishev.denis@icloud.com>
License-Expression: MIT
License-File: LICENSE
Keywords: agents,analytics,data-versioning,deep-learning,django,evaluation,experiment-tracking,framework,llm,machine-learning,mlops,model-registry
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Environment :: Web Environment
Classifier: Framework :: Pydantic
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Natural Language :: English
Classifier: Natural Language :: Russian
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database :: Front-Ends
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: fastapi>=0.110
Requires-Dist: jinja2>=3.1
Requires-Dist: numpy>=1.24
Requires-Dist: pydantic>=2.6
Requires-Dist: python-multipart>=0.0.9
Requires-Dist: sqlalchemy>=2.0
Requires-Dist: uvicorn>=0.27
Provides-Extra: all
Requires-Dist: accelerate>=0.30; extra == 'all'
Requires-Dist: anthropic>=0.40; extra == 'all'
Requires-Dist: datasets>=2.19; extra == 'all'
Requires-Dist: httpx>=0.27; extra == 'all'
Requires-Dist: joblib>=1.3; extra == 'all'
Requires-Dist: mkdocs-material>=9.5; extra == 'all'
Requires-Dist: mkdocs-static-i18n>=1.2; extra == 'all'
Requires-Dist: mypy>=1.11; extra == 'all'
Requires-Dist: pre-commit>=3.7; extra == 'all'
Requires-Dist: pyarrow>=14.0; extra == 'all'
Requires-Dist: pytest-cov>=5.0; extra == 'all'
Requires-Dist: pytest>=8.0; extra == 'all'
Requires-Dist: pyyaml>=6.0; extra == 'all'
Requires-Dist: ruff>=0.6; extra == 'all'
Requires-Dist: scikit-learn>=1.3; extra == 'all'
Requires-Dist: torch>=2.0; extra == 'all'
Requires-Dist: transformers>=4.40; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.40; extra == 'anthropic'
Provides-Extra: dev
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pre-commit>=3.7; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: pyyaml>=6.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocs-static-i18n>=1.2; extra == 'docs'
Provides-Extra: huggingface
Requires-Dist: datasets>=2.19; extra == 'huggingface'
Provides-Extra: parquet
Requires-Dist: pyarrow>=14.0; extra == 'parquet'
Provides-Extra: postgres
Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
Provides-Extra: sklearn
Requires-Dist: joblib>=1.3; extra == 'sklearn'
Requires-Dist: scikit-learn>=1.3; extra == 'sklearn'
Provides-Extra: torch
Requires-Dist: torch>=2.0; extra == 'torch'
Provides-Extra: transformers
Requires-Dist: accelerate>=0.30; extra == 'transformers'
Requires-Dist: torch>=2.0; extra == 'transformers'
Requires-Dist: transformers>=4.40; extra == 'transformers'
Description-Content-Type: text/markdown

# mlango

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

*Read this in [Русский](README.ru.md).*

[![CI](https://github.com/DenisDrobyshev/mlango/actions/workflows/ci.yml/badge.svg)](https://github.com/DenisDrobyshev/mlango/actions/workflows/ci.yml)
[![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)
[![License](https://img.shields.io/badge/license-MIT-green)](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.

```python
# 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"
```

```python
# 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),
        )
```

```bash
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

```bash
pip install "mlango[sklearn]"
```

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

## Five minutes from nothing

```bash
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:

```python
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

```bash
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:

```python
@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

```python
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

```python
# 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

```bash
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:

```python
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](CONTRIBUTING.md) for the
development setup, and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community
expectations. Good first issues are labelled
[`good first issue`](https://github.com/DenisDrobyshev/mlango/labels/good%20first%20issue).

## License

MIT — see [LICENSE](LICENSE).

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