A grammatical rewrite should not alter a customer’s amount, order ID, negation or uncertainty. Represent each edit so it can be reviewed and rejected.
Grammar correction: edit spans without changing protected meaning
Set the editor’s authority
The editor may fix spelling and grammar in a support draft, but it must not decide whether a refund is approved. Mark identifiers, amounts, dates, product names and placeholders as protected fields. Keep the unedited original and return an ordered list of replacement spans rather than a bare new string. A reviewer can then inspect what changed. Translation token protection covers a related boundary, but grammar editing also must preserve negation and modality.
Validate a proposed edit
Each edit uses half-open original offsets, old text and replacement. Reject overlap and stale source mismatches. Apply edits from right to left so earlier offsets remain valid. Some correct grammar changes span multiple words; do not assume every edit is one token. Preserve Unicode representation and avoid normalizing the source after offsets are recorded. Original span alignment defines the coordinate system.
Review meaning preservation
“The refund may arrive tomorrow” must not become “The refund will arrive tomorrow.” A grammar model can produce fluent, confident text while strengthening a promise or deleting “not.” Compare protected tokens, critical entities and explicit status words before presentation, then sample outputs for human adequacy review. Multiple valid corrections may exist, so exact match to one reference is an incomplete quality metric. Review gates decide when the editor should abstain.
Measure the edits users accept
Report accepted edit rate, unnecessary edit rate, protected-field failures, meaning reversals and reviewer time saved. Break out non-native writing, mixed-language messages and technical identifiers. A model that changes many words may have high fluency yet create more review work than a conservative rule baseline. The support reply project puts these tradeoffs into a real draft workflow.
Implementation
def apply_reviewable_edits(original_text, edits):
ordered = sorted(edits, key=lambda edit: edit["start"])
previous_end = 0
for edit in ordered:
start, end = edit["start"], edit["end"]
if not 0 <= start < end <= len(original_text) or start < previous_end:
raise ValueError("invalid or overlapping edit")
if original_text[start:end] != edit["old"]:
raise ValueError("stale edit source")
previous_end = end
corrected = original_text
for edit in reversed(ordered):
corrected = (corrected[:edit["start"]] + edit["replacement"]
+ corrected[edit["end"]:])
return corrected
draft = "Refund are pending for order ZX-47."
assert apply_reviewable_edits(draft, [{"start": 7, "end": 10,
"old": "are", "replacement": "is"}]) == "Refund is pending for order ZX-47."
Performance and operating cost
Sorting e edits takes O(e log e); repeated immutable-string replacements can copy O(n·e) characters for n source characters. For large documents, assemble output once after validation. Model generation and reviewer inspection dominate cost for short support drafts. Measure meaning-change failures separately; lower latency does not justify an editor that strengthens a customer-facing promise.
Common Mistakes
- Returning only a rewritten string with no inspectable edit trail.
- Applying original offsets left to right after text length changes.
- Treating one reference correction as the only acceptable output.
- Accepting a grammatical sentence that changes “may” to “will.”
