📖 A Golang library for text processing, including tokenization, part-of-speech tagging, and named-entity extraction.
Go
3,089
291 commits
updated Jul 29, 2026
prose is a natural language processing library for Go — pure Go, no cgo, no
external services. English only.
go get github.com/jdkato/prose/v3
package main
import (
"fmt"
"log"
"github.com/jdkato/prose/v3"
)
func main() {
doc, err := prose.NewDocument("Marie Curie studied in Paris. She won the Nobel Prize twice.")
if err != nil {
log.Fatal(err)
}
for _, sent := range doc.Sentences() {
fmt.Println(sent.Text)
}
for _, tok := range doc.Tokens() {
fmt.Println(tok.Text, tok.Tag)
}
for _, ent := range doc.Entities() {
fmt.Println(ent.Text, ent.Label)
// Marie Curie PERSON
// Paris GPE
}
}
Every token, sentence and entity carries a byte offset into the source, and the text at that offset is exactly the text you were given:
src[tok.Start:tok.End()] == tok.Text
That holds for multi-byte input, repeated words and irregular whitespace:
src := "He said “hi there” to me."
doc, _ := prose.NewDocument(src)
for _, tok := range doc.Tokens() {
fmt.Printf("%-8q [%d:%d] %q\n", tok.Text, tok.Start, tok.End(), tok.In(src))
}
It is also why prose never normalizes text before tokenizing. Replacing curly
quotes with straight ones, or ’ with ', changes byte lengths and
silently shifts every offset that follows. Normalization that would move bytes
is the caller's decision, made before prose sees the text.
prose ships two trained models: a part-of-speech tagger (~3 MB) and an entity
recognizer (~4 MB). Each lives in its own package, so the Go linker leaves out
whatever you do not import.
Importing the root package links both. If you only need tags, import the tagger directly and the entity model never enters your binary — a 4.5 MB difference:
import "github.com/jdkato/prose/v3/tag"
tagger, err := tag.New()
if err != nil {
log.Fatal(err)
}
for _, tok := range tagger.Tag([]string{"Kubernetes", "orchestrates", "containers"}) {
fmt.Println(tok.Text, tok.Tag)
}
Models load once per process and are shared, so building many documents does not repeat the work.
Each component stands alone if you want just that piece:
| Package | What it does |
|---|---|
prose | Document, the whole pipeline |
tokenize | words and punctuation |
segment | sentences |
tag | part-of-speech tags |
ner | named entities |
token | the shared token type |
summarize | readability metrics |
strcase | title and sentence case |
Skipping entity extraction avoids most of the work:
doc, err := prose.NewDocument(text, prose.WithExtraction(false))
WithTokenization, WithSegmentation, WithTagging and WithExtraction each
take a bool. Extraction requires tagging, and tagging requires tokenization, so
those are enabled automatically when needed.
Work scales with the length of the input — roughly 1.4 s for a million words —
so NewDocumentContext accepts a context.Context:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
doc, err := prose.NewDocumentContext(ctx, veryLargeText)
The individual tokenizers and taggers take no context: they work at microsecond scale, where there is nothing worth cancelling.
Tokenizer, Segmenter, Tagger and Extracter are all safe for concurrent
use. Their models are immutable once loaded, and per-call scratch space is
pooled.
import "github.com/jdkato/prose/v3/summarize"
d := summarize.NewDocument("The quick brown fox jumps over the lazy dog.")
fmt.Println(d.FleschKincaid(), d.GunningFog(), d.SMOG())
import "github.com/jdkato/prose/v3/strcase"
tc := strcase.NewTitleConverter(strcase.APStyle)
fmt.Println(tc.Convert("the quick brown fox"))
MIT. See LICENSE, which also records the upstream work prose
derives from.
Overall
Maintained
Code review
Go module
Latest release
Releases this year
(top 24 of 34)
54,603 followers · starred Aug 2019
686 followers · starred Jul 2018
365 followers · starred Aug 2019
407 followers · starred Jul 2018
📖 A Golang library for text processing, including tokenization, part-of-speech tagging, and named-entity extraction.
Go
3,089
291 commits
updated Jul 29, 2026
prose is a natural language processing library for Go — pure Go, no cgo, no
external services. English only.
go get github.com/jdkato/prose/v3
package main
import (
"fmt"
"log"
"github.com/jdkato/prose/v3"
)
func main() {
doc, err := prose.NewDocument("Marie Curie studied in Paris. She won the Nobel Prize twice.")
if err != nil {
log.Fatal(err)
}
for _, sent := range doc.Sentences() {
fmt.Println(sent.Text)
}
for _, tok := range doc.Tokens() {
fmt.Println(tok.Text, tok.Tag)
}
for _, ent := range doc.Entities() {
fmt.Println(ent.Text, ent.Label)
// Marie Curie PERSON
// Paris GPE
}
}
Every token, sentence and entity carries a byte offset into the source, and the text at that offset is exactly the text you were given:
src[tok.Start:tok.End()] == tok.Text
That holds for multi-byte input, repeated words and irregular whitespace:
src := "He said “hi there” to me."
doc, _ := prose.NewDocument(src)
for _, tok := range doc.Tokens() {
fmt.Printf("%-8q [%d:%d] %q\n", tok.Text, tok.Start, tok.End(), tok.In(src))
}
It is also why prose never normalizes text before tokenizing. Replacing curly
quotes with straight ones, or ’ with ', changes byte lengths and
silently shifts every offset that follows. Normalization that would move bytes
is the caller's decision, made before prose sees the text.
prose ships two trained models: a part-of-speech tagger (~3 MB) and an entity
recognizer (~4 MB). Each lives in its own package, so the Go linker leaves out
whatever you do not import.
Importing the root package links both. If you only need tags, import the tagger directly and the entity model never enters your binary — a 4.5 MB difference:
import "github.com/jdkato/prose/v3/tag"
tagger, err := tag.New()
if err != nil {
log.Fatal(err)
}
for _, tok := range tagger.Tag([]string{"Kubernetes", "orchestrates", "containers"}) {
fmt.Println(tok.Text, tok.Tag)
}
Models load once per process and are shared, so building many documents does not repeat the work.
Each component stands alone if you want just that piece:
| Package | What it does |
|---|---|
prose | Document, the whole pipeline |
tokenize | words and punctuation |
segment | sentences |
tag | part-of-speech tags |
ner | named entities |
token | the shared token type |
summarize | readability metrics |
strcase | title and sentence case |
Skipping entity extraction avoids most of the work:
doc, err := prose.NewDocument(text, prose.WithExtraction(false))
WithTokenization, WithSegmentation, WithTagging and WithExtraction each
take a bool. Extraction requires tagging, and tagging requires tokenization, so
those are enabled automatically when needed.
Work scales with the length of the input — roughly 1.4 s for a million words —
so NewDocumentContext accepts a context.Context:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
doc, err := prose.NewDocumentContext(ctx, veryLargeText)
The individual tokenizers and taggers take no context: they work at microsecond scale, where there is nothing worth cancelling.
Tokenizer, Segmenter, Tagger and Extracter are all safe for concurrent
use. Their models are immutable once loaded, and per-call scratch space is
pooled.
import "github.com/jdkato/prose/v3/summarize"
d := summarize.NewDocument("The quick brown fox jumps over the lazy dog.")
fmt.Println(d.FleschKincaid(), d.GunningFog(), d.SMOG())
import "github.com/jdkato/prose/v3/strcase"
tc := strcase.NewTitleConverter(strcase.APStyle)
fmt.Println(tc.Convert("the quick brown fox"))
MIT. See LICENSE, which also records the upstream work prose
derives from.
Overall
Maintained
Code review
Go module
Latest release
Releases this year
(top 24 of 34)
54,603 followers · starred Aug 2019
686 followers · starred Jul 2018
365 followers · starred Aug 2019
407 followers · starred Jul 2018