Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

co-draft

Writeup: Let the model propose, keep the human holding the pen

Human-in-the-loop text editing with an LLM. You draft with a model, then select a span and give an instruction. The model's rewrite comes back as a before/after diff that you accept or reject — nothing is written to the document until you approve it.

The default backend is a deterministic stub, so the demo runs offline with no GPU, no network, and no API key. Point it at a real model when you want actual rewrites.

Example

Below is a real run against a local model (google/gemma-4-e4b, served by LM Studio over its OpenAI-compatible API). The input is samples/lesson.txt, an original short lesson on the water cycle with some deliberately wordy sentences. The reviewer selected a sentence in the "Evaporation" section and asked the model to make it more concise. The rendered HTML diff:

Before/after visual diff of a targeted concise-rewrite, produced by google/gemma-4-e4b

Two of the rewrites the model proposed in that run:

Instruction: Make this more concise
before: Due to the fact that the sun heats the surface of oceans, lakes, and
        rivers, a lot of the water there slowly turns into an invisible gas
        called water vapour and rises up into the atmosphere.
after:  Because the sun heats the surfaces of oceans, lakes, and rivers, much
        of the water slowly turns into invisible water vapour that rises into
        the atmosphere.

Instruction: Rewrite in a more formal tone
before: Once the water reaches the ground it does a bunch of different things.
after:  Upon reaching the surface, the water undergoes several distinct
        processes.

The HTML artifacts from this run are in samples/diffs-llm/ (open index.html). Real-model output is non-deterministic, so re-running produces different — though similar — rewrites; the stub backend is the reproducible default and its artifacts in samples/diffs/ are byte-stable across runs. To reproduce this run yourself:

export CODRAFT_BASE_URL=http://localhost:1234/v1
python -m codraft draft samples/lesson.txt --provider openai --model-name google/gemma-4-e4b
python -m codraft refine samples/lesson.txt \
    --select "Once the water reaches the ground it does a bunch of different things." \
    --instruction "Rewrite in a more formal tone" \
    --provider openai --model-name google/gemma-4-e4b \
    --out samples/diffs-llm/formal.html

Why

An LLM asked to "rewrite this document" hands back a wall of new text and hides what it changed. That is fine until it quietly drops a qualifier or invents a detail. co-draft keeps the edit small and visible: you pick the span, the model proposes a replacement, and the change is a word-level diff you sign off on. Accepted edits go into a per-section history that can be undone.

How it works

raw text ──▶ draft_sections() ──▶ [Section, Section, ...]
                                        │
                                        ▼
                              RefinementSession
                                        │
   select span + instruction ──▶ propose() ──▶ Proposal(before, after)
                                        │
                            render_html_diff() ──▶ self-contained HTML diff
                                        │
                          accept() ──▶ apply + record in history
                          reject() ──▶ discard, document unchanged

Everything lives in codraft/:

Module What it does
providers.py Model backends behind one interface (rewrite, title): StubModel (offline, no deps), OllamaModel, OpenAICompatibleModel.
drafting.py Splits raw text into titled Sections — by Markdown headings if present, otherwise by paragraph.
refine.py The propose/accept/reject loop. A Proposal is not applied until accept(); reject() is a no-op; undo() reverts the last accepted edit.
diff_engine.py Word-level diff (difflib.SequenceMatcher) plus a self-contained HTML renderer and an index page.
cli.py The demo, draft, and refine subcommands.

The diff engine tokenises both versions, classifies each span as added, removed, modified, or unchanged, and writes one HTML file per change with no external CSS, JS, or fonts. The file opens straight from disk and follows the system light/dark theme.

Quick start

The stub demo needs only the Python standard library (Python 3.8+). No install step.

git clone https://github.com/batinium/co-draft.git
cd co-draft

# Draft sections, run three scripted refinements, write HTML diffs. A few seconds.
python -m codraft demo samples/input.txt --out samples/diffs

# Open the result (macOS; use xdg-open on Linux)
open samples/diffs/index.html

On systems where python is not on PATH, use python3.

Other commands

# Draft only: raw text -> section outline
python -m codraft draft samples/input.txt

# One targeted refinement -> a single HTML diff
python -m codraft refine samples/input.txt \
    --select "very sleepy and stupid" \
    --instruction "make this more concise" \
    --out samples/diffs/my_edit.html

Using a real model

Copy .env.example to .env (gitignored) and pick a backend. Real backends need requests (pip install -r requirements.txt); it is imported lazily, so the stub path does not. Endpoints and model ids come from environment variables or flags — nothing is hard-coded.

LM Studio or any OpenAI-compatible server (local, usually no key):

export CODRAFT_BASE_URL=http://localhost:1234/v1
python -m codraft demo samples/input.txt --provider openai --model-name <model-id>

Use whatever model id your server reports at /v1/models.

Ollama:

export OLLAMA_HOST=http://localhost:11434
python -m codraft demo samples/input.txt --provider ollama --model-name llama3

An optional conda/micromamba environment is in environment.yml:

micromamba env create -f environment.yml
micromamba activate co-draft

Sample input

samples/input.txt is a short public-domain excerpt from Alice's Adventures in Wonderland (Lewis Carroll, 1865; Project Gutenberg), lightly adapted. The diff artifacts committed in samples/diffs/ were produced by the stub model and are reproduced exactly by re-running the demo.

samples/lesson.txt is an original, openly-shareable lesson written for this repo (the water cycle), with a few intentionally wordy sentences to give a real model something to tighten. The artifacts in samples/diffs-llm/ and the screenshot in the Example section came from the google/gemma-4-e4b run described above.

Limitations

  • The stub model does not understand language. It maps instruction intents (concise, formal, simplify, expand) to fixed text transforms. It exists to make the pipeline reproducible and runnable offline — a test double, not a writing assistant. Use a real backend for real edits.
  • No change is applied without an explicit accept. That is the whole point; if you wire this into a pipeline that auto-accepts, you have removed the one safeguard it provides.
  • An LLM can change meaning, invent facts, or drop nuance even when asked only to tighten a sentence. The diff lets a human catch that; it does not prevent it.
  • Not production software: no auth, no persistence beyond files, single-user, English-tuned tokenisation. It is a prototype of one idea — reviewable, human-approved edits.
  • The diff is word-level, so a large rewrite shows up as one big modified block rather than fine-grained intra-word changes.

Author

Batın Örene — PhD researcher in AI (LLM evaluation, responsible AI, human-AI teaming). GitHub: batinium

About

Human-in-the-loop LLM content editing with visual diffs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages