Files
zeekayandHanzo Dev fc9599de5e examples: one runnable program per capability
Eighteen programs under examples/, each a directory with a main.go that says
what it demonstrates and runs on its own. None reaches the internet or asks a
model: where a stage needs something this module does not compute — an
embedding, a completion, a running Qdrant — the example supplies a
deterministic stand-in behind the same interface and says so, and the HTTP
paths run against servers started on loopback so the requests the drivers send
are real and visible.

examples/README.md maps them onto the Python cookbook's forty notebooks and
says what did not port, grouped by why: it needs a model, it needs a library
outside the standard one, it is a driver nobody has written, it is a notebook
rather than a library, the service is not one we run, or Go's types already say
it. The last section lists what the examples surface that the Python does not,
including three places where this module's own behaviour is worth knowing
before relying on it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 23:02:44 -07:00

133 lines
4.5 KiB
Go

// Command coref resolves the words in a text that point at the same thing.
// It runs between the entity pass and the relation pass rather than after
// both, because "she founded it" is a relation between two names only once the
// pronouns are bound to them.
//
// Nothing here needs a model. A pronoun is resolved to the nearest preceding
// name whose label it admits — "she" takes a person, "it" takes an
// organisation or a place — and to the nearest preceding name of any label
// when none does, since a wrong antecedent of the right shape tells a reader
// more than no antecedent.
//
// go run ./examples/coref
package main
import (
"fmt"
"sort"
"strings"
"github.com/hanzoai/semantic/extract"
)
const text = "Babbage Engines was founded by Charles Babbage. " +
"Ada Lovelace is an analyst. " +
"She works for it. " +
"It is headquartered in Montréal."
// aside is the same rule showing its shape. A pronoun takes the nearest
// preceding name whose label it admits, and "it" admits a date, so a date
// standing between the pronoun and the thing it means wins.
const aside = "Babbage Engines opened in 1852. It employs forty people."
func main() {
rules := extract.Rules{Names: map[string]string{
"Ada Lovelace": "PERSON",
"Charles Babbage": "PERSON",
"Babbage Engines": "ORG",
"Montréal": "LOC",
}}
known := rules.Entities(text)
mentions(known)
pronouns(known)
chains(known)
why(rules, known)
}
// mentions is every reference in the text: the names the entity pass found and
// the pronouns standing in for them, in the order they appear.
func mentions(known []extract.Entity) {
fmt.Println("mentions")
for _, m := range extract.Mentions(text, known) {
kind := "name"
if m.Pronoun {
kind = "pronoun"
}
fmt.Printf(" %4d %-7s %-16q %s\n", m.Start, kind, m.Text, m.Label)
}
}
// pronouns binds each one. Refer records what it settled on and returns the
// pairs, so a caller can see the decision as well as act on it.
func pronouns(known []extract.Entity) {
ms := extract.Mentions(text, known)
fmt.Println("\nresolved")
for _, r := range extract.Refer(ms) {
fmt.Printf(" %-6q → %q\n", r.Pronoun, r.Of)
}
}
// chains group the mentions that name one thing. Head is the mention that
// names it best — a name rather than a pronoun, and the earliest of those —
// which is the form to use when the graph needs one name for the node.
func chains(known []extract.Entity) {
fmt.Println("\nchains")
for _, c := range extract.Coref(text, known) {
var said []string
for _, m := range c.Mentions {
said = append(said, m.Text)
}
fmt.Printf(" %-16q %-8s %d mentions: %s\n", c.Head.Text, c.Label, len(c.Mentions), strings.Join(said, ", "))
}
fmt.Println(" a thing mentioned once is not a chain, so it is not here")
}
// why is what the pass is for. The relation extractor joins entities it was
// given; a sentence that names one endpoint with a pronoun offers it only one.
// Rewriting each pronoun as the head of its chain turns those sentences into
// relations, and the count is the difference.
func why(rules extract.Rules, known []extract.Entity) {
before := rules.Find(text)
fmt.Printf("\nas written %d relations\n", len(before.Relations))
for _, r := range before.Relations {
fmt.Printf(" %-16s %-12s %s\n", r.Subject.Text, r.Predicate, r.Object.Text)
}
resolved := substitute(text, extract.Coref(text, known))
after := rules.Find(resolved)
fmt.Printf("\nwith pronouns replaced by the head of their chain %d relations\n", len(after.Relations))
fmt.Printf(" %q\n", resolved)
for _, r := range after.Relations {
fmt.Printf(" %-16s %-12s %s\n", r.Subject.Text, r.Predicate, r.Object.Text)
}
// And where the rule shows its shape.
fmt.Printf("\nnearest wins %q\n", aside)
for _, r := range extract.Refer(extract.Mentions(aside, rules.Entities(aside))) {
fmt.Printf(" %-4q → %q — the date is nearer than the company, and \"it\" admits a date\n", r.Pronoun, r.Of)
}
}
// substitute rewrites each resolved pronoun as the name it stands for,
// working back to front so the offsets it has not reached yet stay valid.
func substitute(text string, chains []extract.Chain) string {
type edit struct {
start, end int
name string
}
var edits []edit
for _, c := range chains {
for _, m := range c.Mentions {
if m.Pronoun {
edits = append(edits, edit{m.Start, m.End, c.Head.Text})
}
}
}
sort.Slice(edits, func(i, j int) bool { return edits[i].start > edits[j].start })
for _, e := range edits {
text = text[:e.start] + e.name + text[e.end:]
}
return text
}