addrkit: strict ordinal matching, LLM-variant guard, externalized prompt
- match: require candidate ordinal to equal the query ordinal in fuzzy search (deterministic typo recall 73% -> 80%, no wrong houses) - resolver: compatibleVariant guard — LLM may only fix street spelling, keeping the same words, ordinal and house unit; damerau distance for transpositions - llm: prompt moved out of code (go:embed default_prompt.txt) with llm.system_prompt / llm.system_prompt_file overrides; guided JSON parsing tolerant of thinking blocks - address: recognize ordinals appearing after the street name - tests: first coverage for the LLM adapter and config prompt loading; README Measured (sample 200): deterministic 94.7-95.8% exact / 0 wrong across 4 seeds; LLM+guard typo 90.7-89.8% / 0 wrong. LLM fallback latency ~1.2-1.9s.
This commit is contained in:
@@ -182,9 +182,9 @@ func (ix *Index) fuzzyStreets(p address.ParsedAddress) (entries []address.Entry,
|
||||
best := 0.0
|
||||
bestKey := ""
|
||||
for _, s := range ix.streets {
|
||||
// If both sides carry an ordinal and they differ, the streets are
|
||||
// different even if the names rhyme.
|
||||
if p.Ordinal != "" && s.ordinal != "" && p.Ordinal != s.ordinal {
|
||||
// If the query carries an ordinal, the candidate's ordinal must match:
|
||||
// a user who says "1-й Северный" does not mean the plain "Северная".
|
||||
if !ordinalCompatible(p.Ordinal, s.ordinal) {
|
||||
continue
|
||||
}
|
||||
if sim := nameSimilarity(p.NameKey, s.nameKey); sim > best {
|
||||
@@ -197,7 +197,7 @@ func (ix *Index) fuzzyStreets(p address.ParsedAddress) (entries []address.Entry,
|
||||
}
|
||||
|
||||
for _, s := range ix.streets {
|
||||
if p.Ordinal != "" && s.ordinal != "" && p.Ordinal != s.ordinal {
|
||||
if !ordinalCompatible(p.Ordinal, s.ordinal) {
|
||||
continue
|
||||
}
|
||||
if nameSimilarity(p.NameKey, s.nameKey) >= best-ix.opts.AmbiguityMargin {
|
||||
@@ -210,6 +210,16 @@ func (ix *Index) fuzzyStreets(p address.ParsedAddress) (entries []address.Entry,
|
||||
return ix.byKey[bestKey], best, nil
|
||||
}
|
||||
|
||||
// ordinalCompatible reports whether a candidate street may compete with the
|
||||
// query given the query's ordinal. When the query specifies an ordinal, only
|
||||
// candidates with the same ordinal are allowed; otherwise any candidate is.
|
||||
func ordinalCompatible(queryOrdinal, candidateOrdinal string) bool {
|
||||
if queryOrdinal == "" {
|
||||
return true
|
||||
}
|
||||
return queryOrdinal == candidateOrdinal
|
||||
}
|
||||
|
||||
// representatives returns one candidate per competing street, which is far
|
||||
// more useful to a user than five houses of a single street.
|
||||
func (ix *Index) representatives(keys []string, score float64) []Candidate {
|
||||
|
||||
Reference in New Issue
Block a user