LLM Guides

How to Check Grammar and Spelling with Python

Aug 03, 2026

Grammar and spelling checks sound simple until real user text enters the room.

Someone writes:

This are definetly the best featre we shipped.

Easy enough.

Then someone writes:

The app works good, but honestly the billing flow makes me wanna uninstall life.

Now we have spelling, grammar, tone, phrasing, and maybe a support escalation hiding in one sentence.

So yes, you can check grammar and spelling with Python.

But the best approach depends on what your app needs:

  1. Basic typo detection.
  2. Grammar and punctuation suggestions.
  3. Style feedback.
  4. Multilingual checking.
  5. Real-time editor suggestions.
  6. Bulk proofreading.
  7. LLM-based rewriting.
  8. A clean API response your frontend can actually use.

In this guide, we’ll build a practical Python workflow for grammar and spelling checks, so your app can catch typos, awkward phrasing, and tiny text gremlins before users do.

What does grammar and spelling checking mean?

Grammar and spelling checking means scanning text for writing issues and returning suggestions.

Input:

This are a smol mistake.

Output:

{
  "issues": [
    {
      "type": "grammar",
      "message": "Possible subject-verb agreement issue.",
      "text": "This are",
      "suggestions": ["This is"]
    },
    {
      "type": "spelling",
      "message": "Possible spelling mistake.",
      "text": "smol",
      "suggestions": ["small"]
    }
  ]
}

A good grammar and spelling checker can return:

FeatureWhy it matters
Issue typeGrammar, spelling, punctuation, style
Original text spanShows what needs fixing
SuggestionsLets users apply corrections
Offset positionsHelps highlight text in an editor
ConfidenceHelps decide whether to auto-fix
Rule IDUseful for debugging and filtering
LanguageImportant for multilingual apps
ExplanationHelps users understand the correction
Corrected textGood for one-click cleanup
Review warningsUseful when the tool is uncertain

The important thing: your app should not only return “corrected text.”

If users write in an editor, you probably need exact issue locations, suggestions, and categories.

Why use Python for grammar and spelling checks?

Python is a good fit because the NLP ecosystem is huge.

You can use Python to:

  1. Run local spell checkers.
  2. Call grammar-checking APIs.
  3. Build a proofreading API with FastAPI.
  4. Add custom dictionaries.
  5. Process text in batches.
  6. Check documents before publishing.
  7. Combine rule-based checks with LLM rewriting.
  8. Return structured results to your frontend.

A simple workflow looks like this:

user text
→ Python backend
→ grammar/spelling checker
→ normalized issues
→ frontend highlights suggestions

That gives you a reusable writing-quality layer instead of a one-off script.

Why we can write this guide

We’ve spent around 6 years working with AI APIs, NLP workflows, text processing, document parsing, content automation, and developer tutorials. We also checked current docs for LanguageTool, pyspellchecker, SymSpellPy, Hunspell, and recent research on grammar error correction.

The practical lesson is simple: grammar and spelling checks work best as a layered system.

Use rule-based tools for predictable spelling, punctuation, and grammar suggestions. Use dictionaries for domain words. Use LLMs for rewrite suggestions, tone cleanup, and awkward phrasing. Add validation so your app does not casually “fix” brand names, technical terms, or user intent into something wrong.

Recent grammar correction research keeps pointing in this direction too. A 2026 paper on English grammar error correction notes that the field has been shifting toward Transformer-based architectures and large pretrained language models, while also showing that classic sequence-to-sequence correction still struggles with accuracy and recall in complex grammar tasks. You can read that research in Springer’s article on English grammar error correction models based on Seq2Seq and feedback filtering.

So we’ll use practical tools first, then add LLM-based cleanup where it actually helps.

Option 1: Check grammar and spelling with LanguageTool

LanguageTool is one of the easiest ways to add grammar, spelling, punctuation, and style checks to a Python app.

It has a public HTTP API, premium API options, and local/server-based setups. The official LanguageTool API documentation describes the /v2/check endpoint for checking text, while the language-tool-python documentation explains how to use LanguageTool from Python against a local Java server, the public API, or a remote server.

Install:

pip install language-tool-python

Create check_languagetool.py:

import language_tool_python

tool = language_tool_python.LanguageTool("en-US")

text = "This are a simple sentence with a spelling mistke."

matches = tool.check(text)

for match in matches:
    print("Issue:", match.message)
    print("Text:", text[match.offset:match.offset + match.errorLength])
    print("Suggestions:", match.replacements)
    print("Rule:", match.ruleId)
    print("---")

Example output:

Issue: The verb 'are' does not seem to agree with the subject 'This'.
Text: are
Suggestions: ['is']
Rule: THIS_NNS
---
Issue: Possible spelling mistake found.
Text: mistke
Suggestions: ['mistake']
Rule: MORFOLOGIK_RULE_EN_US

That is already useful.

You get:

  1. The message.
  2. The text offset.
  3. The error length.
  4. Suggested replacements.
  5. Rule IDs.
  6. Categories.

That is exactly what you need for editor highlights.

Apply LanguageTool corrections automatically

LanguageTool can also apply suggestions.

import language_tool_python

tool = language_tool_python.LanguageTool("en-US")

text = "This are a simple sentence with a spelling mistke."

corrected_text = tool.correct(text)

print(corrected_text)

Output:

This is a simple sentence with a spelling mistake.

Nice.

But be careful with auto-correction.

For internal tools, auto-fixing may be fine. For user-generated content, brand copy, legal text, medical notes, or technical docs, suggestions should usually be reviewed before applying.

Auto-correction can accidentally change meaning.

Return structured grammar issues

For an app, you need JSON.

import language_tool_python

def check_text_with_languagetool(text: str, language: str = "en-US") -> dict:
    tool = language_tool_python.LanguageTool(language)
    matches = tool.check(text)

    issues = []

    for match in matches:
        original = text[match.offset:match.offset + match.errorLength]

        issues.append({
            "type": match.category.lower(),
            "message": match.message,
            "original": original,
            "offset": match.offset,
            "length": match.errorLength,
            "suggestions": match.replacements[:5],
            "rule_id": match.ruleId,
            "category": match.category,
            "context": match.context
        })

    return {
        "language": language,
        "issue_count": len(issues),
        "issues": issues,
        "corrected_text": tool.correct(text)
    }

result = check_text_with_languagetool(
    "This are a simple sentence with a spelling mistke."
)

print(result)

This is the kind of structure your frontend can use.

A text editor can highlight issue spans by using offset and length.

Option 2: Check spelling with pyspellchecker

Sometimes you only need spelling.

For that, pyspellchecker is a simple Python option. Its docs explain that it uses word frequency to determine whether a word is misspelled and what the likely correction should be.

Install:

pip install pyspellchecker

Basic example:

from spellchecker import SpellChecker

spell = SpellChecker()

text = "This sentense has a speling error."

words = text.split()
misspelled = spell.unknown(words)

for word in misspelled:
    print("Word:", word)
    print("Best correction:", spell.correction(word))
    print("Candidates:", spell.candidates(word))

Output may look like:

Word: sentense
Best correction: sentence
Candidates: {'sentence'}
Word: speling
Best correction: spelling
Candidates: {'spelling', 'spieling'}

This is lightweight and easy.

Good for:

  1. Simple spell checks.
  2. CLI tools.
  3. Internal scripts.
  4. Lightweight apps.
  5. Custom dictionary workflows.

Less good for:

  1. Grammar.
  2. Punctuation.
  3. Style.
  4. Sentence-level rewriting.
  5. Context-sensitive mistakes.

For example, a simple spell checker may not catch this:

Their going to the store.

Every word is spelled correctly. The grammar is still wrong.

Add custom words to the dictionary

Spell checkers often get angry at product names.

Your app might know words like:

LLMAPI
FastAPI
LangChain
PostgreSQL
OpenTelemetry

A normal dictionary may flag those as mistakes.

Add custom words:

from spellchecker import SpellChecker

spell = SpellChecker()

custom_words = [
    "LLMAPI",
    "FastAPI",
    "LangChain",
    "PostgreSQL",
    "OpenTelemetry"
]

spell.word_frequency.load_words(custom_words)

text = "LLMAPI works with FastAPI and PostgreSQL."

misspelled = spell.unknown(text.split())

print(misspelled)

This prevents your app from “correcting” brand names into nonsense.

Tiny but important.

Option 3: Use SymSpellPy for fast spelling correction

If you need fast spelling correction at scale, SymSpellPy is worth checking.

The SymSpellPy docs describe it as a Python port of SymSpell, a symmetric delete spelling correction algorithm designed for high speed and lower memory use. That makes it useful for autocomplete, fuzzy matching, search correction, and high-volume typo correction.

Install:

pip install symspellpy

You need a frequency dictionary. SymSpellPy’s docs show lookup examples in the lookup tutorial.

Example:

from symspellpy import SymSpell, Verbosity

sym_spell = SymSpell(
    max_dictionary_edit_distance=2,
    prefix_length=7
)

dictionary_path = "frequency_dictionary_en_82_765.txt"

sym_spell.load_dictionary(
    dictionary_path,
    term_index=0,
    count_index=1
)

input_term = "speling"

suggestions = sym_spell.lookup(
    input_term,
    Verbosity.CLOSEST,
    max_edit_distance=2
)

for suggestion in suggestions:
    print(suggestion.term, suggestion.distance, suggestion.count)

This is a better fit when performance matters.

Good use cases:

  1. Search query correction.
  2. Autocomplete.
  3. Large batch typo cleanup.
  4. Product/catalog search.
  5. Domain dictionary correction.
  6. Fast spell suggestions.

For full grammar checking, pair it with another tool.

Option 4: Use Hunspell dictionaries

Hunspell is a classic spell-checking engine used in many real-world tools. It supports dictionaries, affixes, stemming, and morphological analysis. The pyhunspell project provides Python bindings that let developers load Hunspell dictionaries, check words, get suggestions, and add words.

Hunspell is useful when you need:

  1. Dictionary-based spelling.
  2. Language-specific morphology.
  3. Custom dictionaries.
  4. Offline spell checking.
  5. Existing dictionary files.
  6. More traditional spell-check behavior.

Install can vary by OS because Hunspell may require system libraries.

Example style:

import hunspell

checker = hunspell.HunSpell(
    "/usr/share/hunspell/en_US.dic",
    "/usr/share/hunspell/en_US.aff"
)

word = "speling"

print(checker.spell(word))
print(checker.suggest(word))

This is more setup-heavy than pyspellchecker, but it can be useful when your app needs mature dictionary behavior.

Which Python grammar/spelling tool should you choose?

Here is the simple version.

NeedBest starting point
Grammar + spelling + styleLanguageTool
Simple spelling onlypyspellchecker
Fast spelling correctionSymSpellPy
Dictionary-based offline checkingHunspell
Query correctionSymSpellPy
Multilingual rule-based checksLanguageTool
Custom domain dictionarypyspellchecker or Hunspell
Rewrite awkward phrasingLLMAPI
Editor suggestionsLanguageTool + offsets
Batch proofreadingLanguageTool or LLMAPI workflow

A practical app may use more than one.

For example:

pyspellchecker for fast typo hints
+ LanguageTool for grammar/style checks
+ LLMAPI for rewrite suggestions

That gives you speed, structure, and better phrasing.

Build a small FastAPI grammar checker

Now let’s turn this into an API.

Install:

pip install fastapi uvicorn language-tool-python pydantic

Create app.py:

from pydantic import BaseModel, Field
from fastapi import FastAPI, HTTPException
import language_tool_python

app = FastAPI(
    title="Grammar and Spelling Checker API",
    description="Check grammar, spelling, punctuation, and style issues.",
    version="1.0.0"
)

tool = language_tool_python.LanguageTool("en-US")

class CheckRequest(BaseModel):
    text: str = Field(..., min_length=1, max_length=10000)
    language: str = "en-US"

@app.get("/health")
def health_check():
    return {
        "status": "ok"
    }

@app.post("/check")
def check_text(request: CheckRequest):
    if request.language != "en-US":
        raise HTTPException(
            status_code=400,
            detail="This demo only supports en-US. Add more languages as needed."
        )

    matches = tool.check(request.text)

    issues = []

    for match in matches:
        original = request.text[match.offset:match.offset + match.errorLength]

        issues.append({
            "message": match.message,
            "original": original,
            "offset": match.offset,
            "length": match.errorLength,
            "suggestions": match.replacements[:5],
            "rule_id": match.ruleId,
            "category": match.category,
            "type": match.category.lower()
        })

    return {
        "language": request.language,
        "issue_count": len(issues),
        "issues": issues,
        "corrected_text": tool.correct(request.text)
    }

Run:

uvicorn app:app --reload

Test:

curl -X POST "http://127.0.0.1:8000/check" \
  -H "Content-Type: application/json" \
  -d '{"text":"This are a sentence with a speling mistake."}'

Example response:

{
  "language": "en-US",
  "issue_count": 2,
  "issues": [
    {
      "message": "The verb 'are' does not seem to agree with the subject 'This'.",
      "original": "are",
      "offset": 5,
      "length": 3,
      "suggestions": ["is"],
      "rule_id": "THIS_NNS",
      "category": "GRAMMAR",
      "type": "grammar"
    },
    {
      "message": "Possible spelling mistake found.",
      "original": "speling",
      "offset": 28,
      "length": 7,
      "suggestions": ["spelling"],
      "rule_id": "MORFOLOGIK_RULE_EN_US",
      "category": "TYPOS",
      "type": "typos"
    }
  ],
  "corrected_text": "This is a sentence with a spelling mistake."
}

Now you have a grammar-checking API.

Add a frontend-friendly issue format

Frontends usually need simple fields.

Let’s normalize categories.

def normalize_issue_type(category: str) -> str:
    category = category.lower()

    if "typo" in category:
        return "spelling"

    if "grammar" in category:
        return "grammar"

    if "punctuation" in category:
        return "punctuation"

    if "style" in category:
        return "style"

    return "other"

Use it:

issues.append({
    "type": normalize_issue_type(match.category),
    "message": match.message,
    "original": original,
    "offset": match.offset,
    "length": match.errorLength,
    "suggestions": match.replacements[:5],
    "rule_id": match.ruleId
})

A frontend can now group issues like:

Spelling: 3
Grammar: 2
Style: 1

Much nicer.

Add severity levels

Not every issue deserves the same treatment.

A typo in a product name may need review.

A missing Oxford comma probably does not need a red blinking warning.

Add severity:

def get_issue_severity(issue_type: str) -> str:
    if issue_type in {"spelling", "grammar"}:
        return "medium"

    if issue_type == "punctuation":
        return "low"

    if issue_type == "style":
        return "low"

    return "info"

Then return it:

issue_type = normalize_issue_type(match.category)

issues.append({
    "type": issue_type,
    "severity": get_issue_severity(issue_type),
    "message": match.message,
    "original": original,
    "offset": match.offset,
    "length": match.errorLength,
    "suggestions": match.replacements[:5],
    "rule_id": match.ruleId
})

Now your UI can decide how loud to be.

Add custom ignored words

Your app should let users ignore words.

Example:

LLMAPI
PostgreSQL
IrynaTech
Notion-style

Add an ignore list:

IGNORED_WORDS = {
    "LLMAPI",
    "PostgreSQL",
    "FastAPI",
    "LangChain"
}

def should_ignore_issue(original: str) -> bool:
    clean = original.strip(".,!?;:()[]{}\"'")

    return clean in IGNORED_WORDS

Filter issues:

if should_ignore_issue(original):
    continue

For a real app, store ignored words per user, workspace, or project.

That gives users a personal dictionary.

Add a “quality score”

Sometimes dashboards need one score.

Be careful with this. Writing quality is not one perfect number, but a simple score can be useful for quick checks.

def calculate_quality_score(text: str, issues: list[dict]) -> int:
    if not text.strip():
        return 0

    penalty = 0

    for issue in issues:
        if issue["severity"] == "medium":
            penalty += 8
        elif issue["severity"] == "low":
            penalty += 3
        else:
            penalty += 1

    score = max(0, 100 - penalty)

    return score

Return:

quality_score = calculate_quality_score(request.text, issues)

return {
    "language": request.language,
    "quality_score": quality_score,
    "issue_count": len(issues),
    "issues": issues,
    "corrected_text": tool.correct(request.text)
}

This can power simple UI labels:

ScoreLabel
90-100Clean
70-89Needs light edits
40-69Needs review
0-39Needs heavy cleanup

Do not pretend the score is a universal truth. It is a product signal.

Add LLMAPI for better rewrites

Rule-based tools are good at catching many grammar and spelling issues.

LLMs are better at rewriting awkward phrasing, smoothing tone, and improving flow.

Example:

The app works good but support was not answer me for three days.

A grammar tool can suggest fixes.

An LLM can rewrite it naturally:

The app works well, but support has not replied to me for three days.

This is where LLMAPI can fit.

Use rule-based tools to detect issues. Use LLMAPI when the user wants a cleaner rewrite.

Install:

pip install openai python-dotenv

Create .env:

LLMAPI_API_KEY=your_llmapi_key_here
LLMAPI_BASE_URL=https://api.llmapi.ai/v1

Create rewrite_with_llmapi.py:

import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

client = OpenAI(
    api_key=os.environ["LLMAPI_API_KEY"],
    base_url=os.environ["LLMAPI_BASE_URL"]
)

def rewrite_text(text: str, tone: str = "clear and natural") -> str:
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": (
                    "Rewrite text for grammar, spelling, clarity, and flow. "
                    "Keep the meaning unchanged. Do not add new facts."
                )
            },
            {
                "role": "user",
                "content": f"Tone: {tone}\n\nText:\n{text}"
            }
        ],
        temperature=0.2
    )

    return response.choices[0].message.content

print(rewrite_text("The app works good but support was not answer me for three days."))

This gives you a rewrite layer.

Add LLMAPI rewrite endpoint to FastAPI

Add this to your FastAPI app.

import os
from openai import OpenAI

llm_client = OpenAI(
    api_key=os.getenv("LLMAPI_API_KEY"),
    base_url=os.getenv("LLMAPI_BASE_URL", "https://api.llmapi.ai/v1")
)

class RewriteRequest(BaseModel):
    text: str = Field(..., min_length=1, max_length=10000)
    tone: str = "clear and natural"

@app.post("/rewrite")
def rewrite(request: RewriteRequest):
    response = llm_client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": (
                    "Rewrite the text for grammar, spelling, clarity, and flow. "
                    "Keep the original meaning. Do not add new facts. "
                    "Return only the rewritten text."
                )
            },
            {
                "role": "user",
                "content": f"Tone: {request.tone}\n\nText:\n{request.text}"
            }
        ],
        temperature=0.2
    )

    return {
        "rewritten_text": response.choices[0].message.content
    }

Now your app has two modes:

EndpointPurpose
/checkFinds grammar and spelling issues
/rewriteRewrites text for clarity and flow

That is a cleaner product experience.

Add a combined check-and-rewrite workflow

Sometimes you want both.

class CheckAndRewriteRequest(BaseModel):
    text: str = Field(..., min_length=1, max_length=10000)
    language: str = "en-US"
    tone: str = "clear and natural"

@app.post("/check-and-rewrite")
def check_and_rewrite(request: CheckAndRewriteRequest):
    matches = tool.check(request.text)

    issues = []

    for match in matches:
        original = request.text[match.offset:match.offset + match.errorLength]

        if should_ignore_issue(original):
            continue

        issue_type = normalize_issue_type(match.category)

        issues.append({
            "type": issue_type,
            "severity": get_issue_severity(issue_type),
            "message": match.message,
            "original": original,
            "offset": match.offset,
            "length": match.errorLength,
            "suggestions": match.replacements[:5],
            "rule_id": match.ruleId
        })

    response = llm_client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": (
                    "Rewrite the text for grammar, spelling, clarity, and flow. "
                    "Keep the original meaning. Do not add new facts. "
                    "Return only the rewritten text."
                )
            },
            {
                "role": "user",
                "content": f"Tone: {request.tone}\n\nText:\n{request.text}"
            }
        ],
        temperature=0.2
    )

    return {
        "language": request.language,
        "quality_score": calculate_quality_score(request.text, issues),
        "issue_count": len(issues),
        "issues": issues,
        "rule_based_corrected_text": tool.correct(request.text),
        "llm_rewrite": response.choices[0].message.content
    }

This gives the frontend options:

  1. Show individual grammar issues.
  2. Offer quick fixes.
  3. Offer a full rewrite.
  4. Let the user choose.

That feels much better than forcing one “correct” version.

Rule-based checker vs LLM rewrite

Use both, but for different jobs.

NeedBetter fit
Highlight exact typoRule-based checker
Show offset and suggestionRule-based checker
Grammar explanationLanguageTool
Fast spelling correctionpyspellchecker or SymSpellPy
Personal dictionarypyspellchecker/Hunspell
Rewrite awkward phrasingLLMAPI
Change toneLLMAPI
Make text more conciseLLMAPI
Preserve exact legal/technical wordingRule-based check + human review
Bulk proofreadingDepends on cost, speed, and risk

The clean product pattern:

rules catch specific issues
LLM improves phrasing
user stays in control

That last part matters.

For writing tools, users should decide which corrections to accept.

How to avoid bad corrections

Grammar tools and LLMs can both make weird suggestions.

Classic problems:

  1. Brand names get “fixed.”
  2. Technical terms get changed.
  3. Slang gets flattened.
  4. Voice gets too formal.
  5. Meaning changes.
  6. Quotes get altered.
  7. Code snippets get rewritten.
  8. Names get corrected into other names.
  9. User tone gets sanitized.
  10. Multilingual text gets misread.

Add guardrails:

ProblemGuardrail
Brand namesCustom dictionary
Code snippetsSkip fenced code blocks
URLs/emailsIgnore patterns
User namesPersonal dictionary
Legal textSuggest-only mode
Medical/financial textHuman review
Multilingual textDetect language first
Creative writingStyle-preserving prompt
Support messagesKeep factual meaning unchanged
App editorLet users accept/reject suggestions

For example, skip code blocks before checking:

import re

def remove_code_blocks(text: str) -> str:
    return re.sub(r"```.*?```", "", text, flags=re.DOTALL)

Then check the cleaned text:

text_for_checking = remove_code_blocks(user_text)

Code is allowed to look “ungrammatical.” Let it live.

Add language detection

If your app supports multiple languages, detect the language before checking.

LanguageTool can use language codes like:

en-US
en-GB
de-DE
fr
es
uk

The LanguageTool HTTP API docs mention language=auto, but for some languages and variants, the docs also recommend setting preferred variants so spell checking works properly. You can see that in the official LanguageTool HTTP API documentation.

For a production app, you can:

  1. Let users choose the language.
  2. Auto-detect language.
  3. Store default language per user.
  4. Use region variants like en-US or en-GB.
  5. Avoid checking mixed-language text too aggressively.

Example:

SUPPORTED_LANGUAGES = {
    "en-US",
    "en-GB",
    "de-DE",
    "fr",
    "es"
}

def validate_language(language: str) -> str:
    if language not in SUPPORTED_LANGUAGES:
        raise HTTPException(
            status_code=400,
            detail=f"Unsupported language: {language}"
        )

    return language

Then create the tool per language.

For high traffic, do not create a new LanguageTool instance on every request. Cache tools by language.

Cache LanguageTool instances

from functools import lru_cache
import language_tool_python

@lru_cache(maxsize=10)
def get_language_tool(language: str):
    return language_tool_python.LanguageTool(language)

Use:

tool = get_language_tool(request.language)
matches = tool.check(request.text)

This keeps the API faster.

Batch-check many texts

For content platforms, you may need batch checks.

class BatchCheckRequest(BaseModel):
    texts: list[str] = Field(..., min_length=1, max_length=100)
    language: str = "en-US"

@app.post("/batch-check")
def batch_check(request: BatchCheckRequest):
    tool = get_language_tool(request.language)

    results = []

    for index, text in enumerate(request.texts):
        matches = tool.check(text)
        issues = []

        for match in matches:
            original = text[match.offset:match.offset + match.errorLength]
            issue_type = normalize_issue_type(match.category)

            issues.append({
                "type": issue_type,
                "message": match.message,
                "original": original,
                "offset": match.offset,
                "length": match.errorLength,
                "suggestions": match.replacements[:5],
                "rule_id": match.ruleId
            })

        results.append({
            "index": index,
            "issue_count": len(issues),
            "issues": issues
        })

    return {
        "language": request.language,
        "results": results
    }

Useful for:

  1. Blog platforms.
  2. CMS tools.
  3. Review moderation.
  4. Email campaign QA.
  5. Product description cleanup.
  6. Documentation linting.

Build a CLI grammar checker

If you want a quick command-line tool, use argparse.

Create grammar_cli.py:

import argparse
import language_tool_python

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("file", help="Text file to check")
    parser.add_argument("--language", default="en-US")
    args = parser.parse_args()

    with open(args.file, "r", encoding="utf-8") as f:
        text = f.read()

    tool = language_tool_python.LanguageTool(args.language)
    matches = tool.check(text)

    for match in matches:
        original = text[match.offset:match.offset + match.errorLength]

        print(f"[{match.category}] {match.message}")
        print(f"Text: {original}")
        print(f"Suggestions: {', '.join(match.replacements[:5])}")
        print(f"Rule: {match.ruleId}")
        print()

    print(f"Total issues: {len(matches)}")

if __name__ == "__main__":
    main()

Run:

python grammar_cli.py article.txt

This is useful for checking docs, articles, README files, or internal writing before publishing.

What about grammar correction models?

You can also use transformer models for grammar error correction.

These models try to rewrite incorrect sentences into corrected versions.

Research has been active here for years, and recent work still explores transformer-based grammar correction, better datasets, and language-specific models. For example, a 2025 study on transformer-based grammar correction for language accuracy assessment discusses how difficult grammatical accuracy measurement is because natural language errors are diverse and context-dependent. Another 2025 paper on context-aware semantic transformers for low-resource grammar correction focuses on Indonesian grammar correction and shows how low-resource languages need more than basic English-centric approaches.

The practical lesson: grammar correction is language-specific and context-heavy.

For a simple app, LanguageTool plus an LLM rewrite layer is easier.

For a research-heavy grammar product, transformer fine-tuning may be worth exploring.

Where LLMAPI fits

LLMAPI fits when your app needs more than rule-based issue detection.

Use it for:

TaskExample
Rewrite textMake a paragraph clearer and more natural
Tone adjustmentMake text more friendly, formal, or concise
ExplanationExplain why a sentence sounds awkward
Brand voiceRewrite in your product’s style
Support repliesFix grammar without changing meaning
Email polishClean up spelling and phrasing
Multilingual cleanupImprove translated text
Content QASummarize recurring writing problems
Editor assistantSuggest better wording
FallbackUse a stronger model for tricky text

A practical workflow:

text
→ LanguageTool checks grammar/spelling
→ Python returns issue spans
→ LLMAPI suggests full rewrite if user wants
→ user accepts edits

That is much better than forcing every sentence through an LLM.

Rule-based tools are precise. LLMs are flexible. Together, they make a nicer writing assistant.

What production output should look like?

A production response should include the original text, issues, suggestions, and optional rewrite.

Example:

{
  "language": "en-US",
  "quality_score": 84,
  "issue_count": 2,
  "issues": [
    {
      "type": "grammar",
      "severity": "medium",
      "message": "Possible subject-verb agreement issue.",
      "original": "This are",
      "offset": 0,
      "length": 8,
      "suggestions": ["This is"],
      "rule_id": "THIS_NNS"
    },
    {
      "type": "spelling",
      "severity": "medium",
      "message": "Possible spelling mistake found.",
      "original": "mistke",
      "offset": 34,
      "length": 6,
      "suggestions": ["mistake"],
      "rule_id": "MORFOLOGIK_RULE_EN_US"
    }
  ],
  "corrected_text": "This is a sentence with a spelling mistake.",
  "llm_rewrite": "This is a sentence with a spelling mistake."
}

That gives your frontend everything it needs:

  1. Highlight problems.
  2. Show suggestions.
  3. Let users apply fixes.
  4. Offer a polished rewrite.
  5. Track quality score.

Common mistakes

These are the usual tiny text gremlins in grammar-checking apps.

MistakeBetter approach
Auto-fixing everythingLet users review changes
Ignoring brand namesAdd custom dictionaries
Checking code blocksSkip code and structured snippets
Using one language variantSupport en-US, en-GB, etc.
Returning only corrected textReturn issue spans and suggestions
No confidence/review logicTreat suggestions as suggestions
No max text lengthAdd limits to protect the API
No batching strategyBatch carefully for large datasets
No user dictionaryLet users ignore valid terms
Using only spell checkAdd grammar/style checks too

The biggest mistake is treating grammar checking as one perfect answer.

Writing is contextual.

Your tool should help users edit, not wrestle control away from them.

A simple production workflow

Here is the workflow we would actually ship:

user text
→ validate length/language
→ skip code/URLs/emails if needed
→ run LanguageTool
→ filter ignored words
→ normalize issues
→ calculate score
→ optional LLMAPI rewrite
→ return suggestions to frontend

For editor apps:

text editor
→ debounce typing
→ check changed paragraph
→ highlight issues
→ user accepts/rejects suggestions

For content QA:

article/document
→ batch grammar check
→ style warnings
→ LLM rewrite suggestions
→ editor review

For support tools:

agent reply
→ grammar check
→ tone rewrite
→ preserve factual meaning
→ agent sends

That is how grammar checking becomes a useful product feature instead of an annoying red underline machine.

The practical takeaway

You can check grammar and spelling with Python by combining tools like LanguageTool, pyspellchecker, SymSpellPy, Hunspell, and LLMAPI.

Use LanguageTool when you need grammar, spelling, punctuation, and style suggestions with issue offsets. Use pyspellchecker for lightweight spelling checks. Use SymSpellPy when speed matters. Use Hunspell when dictionary-based offline checking is important. Use LLMAPI when you want cleaner rewrites, tone changes, and phrasing improvements.

A good workflow looks like this:

text
→ rule-based checks
→ structured issues
→ optional LLM rewrite
→ user review

That catches the obvious typos, the awkward grammar, and the tiny text gremlins before users do.

Deploy in minutes