Skip to main content

dtflow

PyPI version Tests Python versions License pypi downloads

English | 中文

Inspect, clean, transform and ship LLM training data from one CLI; dt view is the interactive entry point.

A training file's row is not a row: it is a conversation, a preference pair, an instruction. Most tools see opaque JSON; dtflow sees the sample, and everything follows from that — a terminal browser that renders samples by their detected format, ~30 pipeable commands that filter on conversation structure, conversion and framework export with lineage, and an agent that can drive all of it.

dt view: table + detail terminal browser with full-file search, filters, value picker, zoom and dpo comparison

dt view data.jsonl — one screen, one complete sample. Press P and what you just did becomes a dt filter … \| dt sort … pipeline.

Try it in 30 seconds

pip install dtflow                      # or, without installing: uvx --from dtflow dt view data.jsonl

dt view data.jsonl                                              # browse (press ? for keys)
dt stats data.jsonl --schema                                    # nested schema: types, null rates, values
dt filter data.jsonl "turns(x) >= 2 and search(x, 'refund')" \
  | dt select - "id,turns=turns(x),last=x.messages[-1].content" \
  | dt sort - --by x.turns --desc | dt head - 5
dt transform data.jsonl --preset=openai_chat -o train.jsonl     # sharegpt / alpaca / dpo → OpenAI messages

Reads JSONL/NDJSON (also .gz), JSON, CSV/TSV, Parquet, Arrow and Excel. Every data command accepts - for stdin and writes to stdout when no -o is given, so everything composes. The interface is in English; dt lang zh switches it to Chinese. No data at hand? examples/ has small synthetic chat / ShareGPT / Alpaca / DPO files to try these on.

Why dtflow

  • It knows what a training sample is. Generic table tools show messages as {3} or a truncated string. dtflow detects openai_chat / sharegpt / dpo / alpaca and works on the sample everywhere: the viewer renders one complete sample per screen (turns colored by role, tool calls formatted with bad JSON flagged, VLM images one key away), the row helpers turns(x) / roles(x) / calls(x) / search(x, 'refund') filter on conversation structure, presets convert between layouts, token-stats splits tokens by role.
  • Looking and processing are one loop, in one language. Conditions everywhere are Python with the current row as x — no DSL, and the viewer compiles the same string as dt filter. The loop closes: P in the viewer turns what you did into dt filter … | dt sort …; | runs a shell pipe over the whole file with its output browsable right there; w exports the filtered subset with lineage. What you did while browsing becomes a script; a script's output comes back to the viewer with dt … | dt view -.
  • An agent can drive all of it. stdout carries only data, exit codes are contractual, errors are structured JSON when stderr is not a terminal, --dry-run previews side effects, dt schema prints the command tree as JSON. dt install-skill installs the full reference into Claude Code or Codex — one command, and "clean this dataset" becomes a task your agent can actually do.

Underneath: filter / select / map / clean / dedupe / transform stream and never load the file; a bare field name or an unknown helper is a compile-time error, not an empty result with exit code 0.

The workbench in practice

Decontaminate — drop training rows that overlap your test set. The join key is a Python expression, so it matches on the first user turn rather than the whole JSON line; rows that differ only in metadata still get caught.

dt join train.jsonl test.jsonl --on "first_user(x)" --anti -o clean.jsonl
dt view clean.jsonl                    # eyeball what survived before training on it

Browse until you understand the data, then turn what you did into a pipeline.

dt view data.jsonl        # / search every value · F tick values to keep · f filter by expression
                          # P copies what you did as:  dt filter … | dt sort …
dt filter data.jsonl "turns(x) >= 4 and x.score > 0.7" | dt view -    # process, then look again

Convert and ship to a training framework.

dt transform shards/ --preset=openai_chat -o sft.jsonl   # a directory works too; sharegpt/alpaca/dpo → messages
dt validate sft.jsonl --preset=openai_chat               # schema check; --filter keeps valid rows only
dt token-stats sft.jsonl --model=gpt-4                   # token distribution per role
dt export sft.jsonl -f llama-factory                     # data + training config template (ms-swift, Axolotl too)

Or hand the whole job to your agent.

dt install-skill        # teaches Claude Code (or Codex) the full reference

Then: "drop rows whose last turn isn't the assistant, dedupe by first user message, export for LLaMA-Factory." The agent reads dt schema, previews with --dry-run, and runs the same commands above.

dt view: the eye of the loop

dt view <file> opens a master-detail browser: the table summarizes each sample (derived columns turns/roles/first_user/chars/calls plus your metadata), the detail pane renders the current row by format. JSONL, CSV and Parquet open; a 910k-row file opens instantly at ~90 MB RAM.

openai_chat main screen: table and detail linked, code blocks highlighted / full-file search across every value, hits highlighted, hit ratio in the status bar
Main screen: pick a row, detail renders by role with code highlighting / full-file search: every value of every record (assistant replies included), hits in yellow
F column value picker: unique values with counts, tick to keep dpo format: prompt / chosen / rejected rendered side by side
F value picker: Excel AutoFilter style, unique values + counts, keep what you tick dpo comparison: chosen / rejected in different colors, format auto-detected
dt view data.jsonl                          # open, press ? for keys
dt view app.jsonl -100 -f                   # follow a growing JSONL (log mode)
dt view data.jsonl -w "turns(x)>=6" -s error   # start filtered + searched
dt sample data.jsonl 500 | dt view -        # pipe: inspect sampled / processed output
Keys What they do
/ f F Full-file search · expression filter (the same Python as dt filter) · Excel-style column value picker. They stack; r clears all
Enter n/N * Zoom into the sample · jump field by field · jump only between search hits
w C P Export the filtered subset (lineage written) · copy a reproducible dt view command · copy the same conditions as dt filter … | dt sort …
| Run a shell pipe (dt filter - … | dt sort - …, jq works too) over the whole file and browse its output right there; r returns to the file
mouse Click a header to open its value picker, drag column widths and the split, drag-select text to copy (over SSH and inside tmux too)

Against other terminal viewers: VisiData, tabiew, jless, fx and csvlens are generic table/JSON tools — none of them knows what a training sample is, none has mouse filtering, and opening a 155 MB / 910k-line JSONL costs them 550 MB–2.3 GB of RAM against dt view's 91 MB. Where they win (pivots, SQL, deep JSON folding) and the full comparison table: docs/view.md.

The commands

Every data command follows one contract: FILE may be - (stdin), a directory or a quoted glob; without -o data goes to stdout while progress goes to stderr; output format follows the extension. Conditions, keys and derived fields are Python expressions with the current row as x.

# look before you touch
dt stats   data.jsonl --schema                        # nested schema: types, null rates, sample values
dt describe data.jsonl "turns(x)" "x.score"           # quantiles + histogram per expression
dt token-stats data.jsonl --model=gpt-4               # token distribution per role
dt diff    a.jsonl b.jsonl --key=id                   # what changed between two versions

# shape
dt select  data.jsonl "id,n=len(x.messages)"          # project / rename / derive
dt map     data.jsonl "x.text = x.text.strip(); del x.debug"   # edit in place
dt explode data.jsonl --field messages --index-as turn         # one row per list element
dt group   data.jsonl --by "roles(x)" --top 10        # {"key","count","pct"}
dt sort    data.jsonl --by "len(x.messages)" --desc
dt sample  data.jsonl 1000 --by=meta.source           # stratified sampling
dt split   data.jsonl --ratio=0.9 --seed=42           # train/test files

# combine & clean
dt dedupe  data.jsonl --key=messages[0].content -i    # exact dedupe, in place
dt clean   data.jsonl --drop-empty=text --min-len=messages.#:2
dt concat  a.jsonl b.jsonl -o merged.parquet          # one file in = format conversion
dt run     pipeline.yaml                              # reproducible pipeline, steps = CLI commands

Rows whose expression fails (missing field, None > 0.5) don't abort the run — filter drops them, select sets the item to null, map keeps the row, and stderr prints one summary at the end; --strict turns the first failure into exit code 1. In a terminal, results preview as a 50-row table with the same columns as dt view; piped, they emit NDJSON.

Command reference: docs/cli.md · expressions and field paths: docs/expressions.md · pipelines: docs/pipeline.md.

Python API

The same operations as a chainable class; x.field attribute access works on nested dicts and lists.

from dtflow import DataTransformer, load_stream

(DataTransformer.load("data.jsonl")
    .filter(lambda x: x.score > 0.8)
    .transform(preset="openai_chat", user_field="q", assistant_field="a")
    .dedupe("messages[0].content")
    .save("train.jsonl"))

(load_stream("huge.jsonl")                 # constant memory for files that don't fit
    .filter(lambda x: x["score"] > 0.5)
    .save("filtered.parquet"))

Presets (openai_chat, alpaca, sharegpt, dpo_pair), schema validation, token counting with tiktoken or HuggingFace tokenizers, converters for LLaMA-Factory / ms-swift / Axolotl / HuggingFace datasets / OpenAI Batch, lineage tracking: docs/python-api.md.

Agent skill

dt install-skill                         # Claude Code (default)
dt install-skill --target codex          # Codex

After installing, /dtflow in Claude Code or $dtflow in Codex gives the agent the full reference.

Documentation

License

MIT

Metadata

Release files for dtflow 0.10.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dtflow 0.10.5
File Size Uploaded
dtflow-0.10.5.tar.gz 461.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dtflow 0.10.5
File Interpreter ABI Platform
dtflow-0.10.5-py3-none-any.whl Python 3 none any Details

Total release size: 788.5 kB

Release files / dtflow-0.10.5.tar.gz

Download URL dtflow-0.10.5.tar.gz
Size 461.3 kB
Tags Source
SHA-256 checksum
How to use checksums
a07d1924270730bcda0e47dd38ed66c6ed6b750846fe6fba26d354082bde1cfe
BLAKE2b-256 checksum
How to use checksums
ed6cd367fadaf5d72f436feaf7ef59d5afc360978e576595817ecbf37d94827f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / dtflow-0.10.5-py3-none-any.whl

Download URL dtflow-0.10.5-py3-none-any.whl
Size 327.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e6927bef28803890717c2251ab6f26d38e8b7d9dd2a7651dced338555d4649d9
BLAKE2b-256 checksum
How to use checksums
3b5fc816c9d48041672fed5ea88dc6b2e7110e6032307e21d7f451753a51c9a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.10.5 This release

2 release files

0.10.3

2 release files

0.10.2

2 release files

0.10.1

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.7

2 release files

0.8.6

2 release files

0.8.5

2 release files

0.8.4

2 release files

0.8.3

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.8

2 release files

0.7.7

2 release files

0.7.6

2 release files

0.7.5

2 release files

0.7.4

2 release files

0.7.3

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.12

2 release files

0.6.11

2 release files

0.6.10

2 release files

0.6.9

2 release files

0.6.8

2 release files

0.6.7

2 release files

0.6.5

2 release files

0.6.4

2 release files

0.6.3

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.13

2 release files

0.5.12

2 release files

0.5.11

2 release files

0.5.9

2 release files

0.5.8

2 release files

0.5.7

2 release files

0.5.6

2 release files

0.5.5

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.0

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.3

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page