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:
- Basic typo detection.
- Grammar and punctuation suggestions.
- Style feedback.
- Multilingual checking.
- Real-time editor suggestions.
- Bulk proofreading.
- LLM-based rewriting.
- 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:
| Feature | Why it matters |
|---|---|
| Issue type | Grammar, spelling, punctuation, style |
| Original text span | Shows what needs fixing |
| Suggestions | Lets users apply corrections |
| Offset positions | Helps highlight text in an editor |
| Confidence | Helps decide whether to auto-fix |
| Rule ID | Useful for debugging and filtering |
| Language | Important for multilingual apps |
| Explanation | Helps users understand the correction |
| Corrected text | Good for one-click cleanup |
| Review warnings | Useful 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:
- Run local spell checkers.
- Call grammar-checking APIs.
- Build a proofreading API with FastAPI.
- Add custom dictionaries.
- Process text in batches.
- Check documents before publishing.
- Combine rule-based checks with LLM rewriting.
- 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:
- The message.
- The text offset.
- The error length.
- Suggested replacements.
- Rule IDs.
- 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:
- Simple spell checks.
- CLI tools.
- Internal scripts.
- Lightweight apps.
- Custom dictionary workflows.
Less good for:
- Grammar.
- Punctuation.
- Style.
- Sentence-level rewriting.
- 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:
- Search query correction.
- Autocomplete.
- Large batch typo cleanup.
- Product/catalog search.
- Domain dictionary correction.
- 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:
- Dictionary-based spelling.
- Language-specific morphology.
- Custom dictionaries.
- Offline spell checking.
- Existing dictionary files.
- 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.
| Need | Best starting point |
|---|---|
| Grammar + spelling + style | LanguageTool |
| Simple spelling only | pyspellchecker |
| Fast spelling correction | SymSpellPy |
| Dictionary-based offline checking | Hunspell |
| Query correction | SymSpellPy |
| Multilingual rule-based checks | LanguageTool |
| Custom domain dictionary | pyspellchecker or Hunspell |
| Rewrite awkward phrasing | LLMAPI |
| Editor suggestions | LanguageTool + offsets |
| Batch proofreading | LanguageTool 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:
| Score | Label |
|---|---|
| 90-100 | Clean |
| 70-89 | Needs light edits |
| 40-69 | Needs review |
| 0-39 | Needs 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:
| Endpoint | Purpose |
|---|---|
/check | Finds grammar and spelling issues |
/rewrite | Rewrites 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:
- Show individual grammar issues.
- Offer quick fixes.
- Offer a full rewrite.
- 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.
| Need | Better fit |
|---|---|
| Highlight exact typo | Rule-based checker |
| Show offset and suggestion | Rule-based checker |
| Grammar explanation | LanguageTool |
| Fast spelling correction | pyspellchecker or SymSpellPy |
| Personal dictionary | pyspellchecker/Hunspell |
| Rewrite awkward phrasing | LLMAPI |
| Change tone | LLMAPI |
| Make text more concise | LLMAPI |
| Preserve exact legal/technical wording | Rule-based check + human review |
| Bulk proofreading | Depends 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:
- Brand names get “fixed.”
- Technical terms get changed.
- Slang gets flattened.
- Voice gets too formal.
- Meaning changes.
- Quotes get altered.
- Code snippets get rewritten.
- Names get corrected into other names.
- User tone gets sanitized.
- Multilingual text gets misread.
Add guardrails:
| Problem | Guardrail |
|---|---|
| Brand names | Custom dictionary |
| Code snippets | Skip fenced code blocks |
| URLs/emails | Ignore patterns |
| User names | Personal dictionary |
| Legal text | Suggest-only mode |
| Medical/financial text | Human review |
| Multilingual text | Detect language first |
| Creative writing | Style-preserving prompt |
| Support messages | Keep factual meaning unchanged |
| App editor | Let 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:
- Let users choose the language.
- Auto-detect language.
- Store default language per user.
- Use region variants like
en-USoren-GB. - 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:
- Blog platforms.
- CMS tools.
- Review moderation.
- Email campaign QA.
- Product description cleanup.
- 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:
| Task | Example |
|---|---|
| Rewrite text | Make a paragraph clearer and more natural |
| Tone adjustment | Make text more friendly, formal, or concise |
| Explanation | Explain why a sentence sounds awkward |
| Brand voice | Rewrite in your product’s style |
| Support replies | Fix grammar without changing meaning |
| Email polish | Clean up spelling and phrasing |
| Multilingual cleanup | Improve translated text |
| Content QA | Summarize recurring writing problems |
| Editor assistant | Suggest better wording |
| Fallback | Use 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:
- Highlight problems.
- Show suggestions.
- Let users apply fixes.
- Offer a polished rewrite.
- Track quality score.
Common mistakes
These are the usual tiny text gremlins in grammar-checking apps.
| Mistake | Better approach |
|---|---|
| Auto-fixing everything | Let users review changes |
| Ignoring brand names | Add custom dictionaries |
| Checking code blocks | Skip code and structured snippets |
| Using one language variant | Support en-US, en-GB, etc. |
| Returning only corrected text | Return issue spans and suggestions |
| No confidence/review logic | Treat suggestions as suggestions |
| No max text length | Add limits to protect the API |
| No batching strategy | Batch carefully for large datasets |
| No user dictionary | Let users ignore valid terms |
| Using only spell check | Add 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.