The dragonfarm app

library(dragonfarm)
dragon_app()

Or, in RStudio, pick dragonfarm from the Addins menu: no code at all.

The app is a thin client over the R functions. Nothing happens in the app that you cannot do from the console, and every run it starts is a normal run directory.

Two routes through the tabs

The app opens on documents: 1 Documents, 2 Model, 3 Train, 4 Monitor, 5 Try it, 6 Chat, 7 Pipeline. “Have a table of prompts and replies instead?” at the bottom of the Documents card switches to the table route, 1 Data, 2 Map, 3 Model, …, which is the same app with two tabs in front of Model; the Data tab has the link back. Nothing is lost when switching. dragon_app(route = "table") starts on the table route.

The Documents tab

Documents card. Drop PDF, Word, HTML, Markdown, text, or spreadsheet files (several at once), add web pages by URL, name the set, and press Read and index; or click “Load the example policies”, or open a saved set. Files are copied into the set’s folder under the runs directory, so the set survives the session, and reading the same set again picks up only what changed. The summary lists each source with its chunk count and flags changelog-like files, which are down-weighted when retrieving.

Ask card. Type a question, choose who answers (the model recommended for this machine, any run with an adapter, or a preset), and press Ask. The answer appears with numbered, expandable sources under it; cited ones are bold. “Continue in Chat” opens the Chat tab with the same set selected.

Teach card. “Write practice questions” has a model write questions and answers from every chunk (the recommended local model in the background, the Claude API when ANTHROPIC_API_KEY is set, or a preset), with a live line showing pairs written and kept so far. The review that follows groups pairs by chunk with a checkbox each; rejected pairs are unticked and carry their reason. Save decisions writes them back; Continue to Model builds the training set from the ticked pairs, with retrieved excerpts in every prompt, and skips Data and Map.

The other tabs

Data (table route). Drop a CSV, TSV, JSONL, JSON, or Parquet file on the input, or click “Load the example tickets”. You see row and column counts and the first eight rows.

Map (table route). Choose the kind of data first: prompt and response rows for fine-tuning, chosen and rejected pairs for preference optimization, or prompts with an optional reference for reinforcement learning. The columns appear as chips in the left bucket. Drag one or more into System, Prompt, or Response; several chips in one slot are joined with a blank line. The template fields below fill in as you drag and can be edited by hand, for example Ticket #{id}: {subject}. The right side shows the first three rows exactly as the model will see them.

Model. Pick a preset or type a Hugging Face model id. “Detect hardware” runs the same check as dragon_check() and reports whether the chosen model fits in GPU memory, whether it needs a token, and the default answering model for this machine.

Train. Epochs, learning rate, rank, sequence length, and batch size are on the front. Everything else is under Advanced. The stage follows the data kind: preference pairs show the DPO or ORPO method and beta, RL prompts show a reward builder. “Start from” continues from an earlier run instead of a base model. The panel refuses to start until there are training rows and a model, and estimates the number of optimizer steps from your settings. “No GPU here?” packages the run for a cloud GPU instead.

Monitor. A live loss curve with evaluation points (and mean reward for RL runs), step count, ETA, the trainer log, and the R code for the run. Cancel asks the trainer to stop after the current step and save. Resume picks up from the last checkpoint. Every run in the runs directory is listed, including ones started from the console. Below: task metrics over the held-out rows, a comparison table of every run, and import of results trained in the cloud.

Try it. Type a prompt and see the fine-tuned reply next to the base model’s reply. With “Answer from documents” set, both replies come from the chosen set and show their sources. Judge compares the run against what it started from with a language model as referee. Improve samples the run’s own replies, has the judge rank them, and loads the result as preference pairs for the next stage. “Merge and save” writes a standalone model directory.

Chat. A conversation with memory: every turn sends the whole history. Answer from a run on this machine, an Ollama model, or any OpenAI-compatible server, optionally from a document set with sources under each reply. Rate replies, edit them, or ask again; dragon_feedback() turns those verdicts into training data. Save the transcript as JSON.

Pipeline. Runs the standard loop in a background R process: fine-tune, sample and judge into pairs, DPO, judge again, metrics. Shows each pipeline’s steps as they finish and the lineage of every run in the directory.

Running it for other people

The app has to run on the machine with the GPU, and training runs as a child process of the app. Shiny Server, Posit Connect, or a plain shiny::runApp() on a workstation all work. Hosted services without GPUs, such as shinyapps.io, can run the app but training will be CPU only.

Set the runs directory so every session sees the same runs:

dragon_app(runs_dir = "/srv/dragonfarm/runs", host = "0.0.0.0", port = 3838)

Under the hood

The Map panel uses the sortable package for the drag-and-drop buckets. Progress in the Monitor panel comes from polling progress.jsonl and status.json once a second with reactivePoll(), so it works over a remote connection and survives a browser refresh. Retrieval and generation go through the local inference worker, a Python process that keeps the embedding model and the last two language models loaded, so the first question in a session is slow and the rest are quick. Question generation runs as its own background process and writes a progress file the Teach card polls.