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:
addrkit
2026-10-09 20:40:20 +07:00
parent 5cc3556ac5
commit 78691f3a22
17 changed files with 590 additions and 48 deletions
+69
View File
@@ -0,0 +1,69 @@
package config
import (
"os"
"path/filepath"
"testing"
)
func writeFile(t *testing.T, dir, name, content string) string {
t.Helper()
path := filepath.Join(dir, name)
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatalf("write %s: %v", name, err)
}
return path
}
func baseConfig(promptFields string) string {
return `{
"addr": ":6171",
"data_path": "registry.db",
"portal": { "url": "http://portal" },
"llm": { "enabled": true, "url": "http://llm"` + promptFields + ` }
}`
}
func TestLoadInlineSystemPrompt(t *testing.T) {
dir := t.TempDir()
path := writeFile(t, dir, "config.json", baseConfig(`, "system_prompt": "INLINE PROMPT"`))
cfg, err := Load(path)
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.LLM.SystemPrompt != "INLINE PROMPT" {
t.Errorf("SystemPrompt = %q", cfg.LLM.SystemPrompt)
}
}
func TestLoadSystemPromptFromFile(t *testing.T) {
dir := t.TempDir()
promptFile := writeFile(t, dir, "prompt.txt", "PROMPT FROM FILE\n")
path := writeFile(t, dir, "config.json", baseConfig(`, "system_prompt_file": "`+promptFile+`"`))
cfg, err := Load(path)
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.LLM.SystemPrompt != "PROMPT FROM FILE\n" {
t.Errorf("SystemPrompt = %q", cfg.LLM.SystemPrompt)
}
}
func TestLoadMissingSystemPromptFileFailsClosed(t *testing.T) {
dir := t.TempDir()
path := writeFile(t, dir, "config.json", baseConfig(`, "system_prompt_file": "`+filepath.Join(dir, "nope.txt")+`"`))
if _, err := Load(path); err == nil {
t.Fatal("expected error for missing system_prompt_file")
}
}
func TestLoadRequiresPortalURL(t *testing.T) {
dir := t.TempDir()
path := writeFile(t, dir, "config.json", `{"addr": ":6171", "data_path": "x.db"}`)
if _, err := Load(path); err == nil {
t.Fatal("expected error for missing portal.url")
}
}