3.2 conversation depth: cross-intent anaphora + fact-by-key query
- session.go: add History []Turn + Turn type for multi-turn context - slots.go: add AnaphoraResolver with Resolve() for RU pronoun detection (это/он/она/оно/тот/мой and inflected forms) - followup.go: extend followUpMerge with cross-intent inheritance: Query/Fact/Reminder after a Fact with anaphora inherits the key. Same-intent path unchanged. Anaphora detection from utterance. - voice.go: add fact-by-key lookup path in applyAction for IntentQuery when dialogue resolved an anaphoric reference (calls LatestFact, formats with formatTime helper). History tracked in Session.History capped at 4 most recent turns. - followup_test.go: 7 new test cases: anaphora query-after-fact, no-inheritance-without-anaphora, three-turn break, anaphora in reminder, anaphora in fact, explicit key wins, time inheritance. make test green (303+, -race, all 29 packages).
This commit is contained in:
@@ -27,11 +27,21 @@ type Slots struct {
|
||||
HasFn bool
|
||||
}
|
||||
|
||||
// Turn represents one utterance in a multi-turn dialogue history.
|
||||
// Carried by Session.History for cross-intent reference and anaphora
|
||||
// resolution (a later turn's pronoun points to an earlier turn's entity).
|
||||
type Turn struct {
|
||||
Intent Intent
|
||||
Slots Slots
|
||||
Text string // raw utterance
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
Intent Intent
|
||||
Slots Slots
|
||||
Timestamp time.Time
|
||||
TTL time.Duration
|
||||
History []Turn // most recent turns, newest last; used for anaphora + cross-intent
|
||||
}
|
||||
|
||||
func (s *Session) IsExpired(now time.Time) bool {
|
||||
|
||||
@@ -328,6 +328,41 @@ func parseDurationValue(s string) (string, bool) {
|
||||
return strconv.Itoa(n) + unit, true
|
||||
}
|
||||
|
||||
// AnaphoraResolver resolves pronouns like "это", "он", "она" to the prior
|
||||
// turn's key entity. Returns a (key, value) pair the prior fact carried,
|
||||
// or ("", "", false) when no pronoun is detected.
|
||||
type AnaphoraResolver struct{}
|
||||
|
||||
// Resolve checks if text contains an anaphoric reference to a prior turn's
|
||||
// entity. For MVP this handles the common Russian pronouns:
|
||||
// - "это" / "этого" / "этому" / "этим" / "этом" → "this" (most common)
|
||||
// - "он" / "его" / "ему" / "ним" → "he/it", masc
|
||||
// - "она" / "её" / "ей" / "ней" → "she/it", fem
|
||||
// - "оно" → "it", neuter
|
||||
//
|
||||
// Returns the matching pronoun type for cross-referencing with prior slots.
|
||||
func (AnaphoraResolver) Resolve(text string) (ref string, ok bool) {
|
||||
s := strings.ToLower(strings.TrimSpace(text))
|
||||
toks := strings.Fields(s)
|
||||
for _, tok := range toks {
|
||||
switch tok {
|
||||
case "это", "этого", "этому", "этим", "этом", "эти", "эта":
|
||||
return "this", true
|
||||
case "он", "его", "ему", "ним":
|
||||
return "he", true
|
||||
case "она", "её", "ей", "ней":
|
||||
return "she", true
|
||||
case "оно":
|
||||
return "it", true
|
||||
case "тот", "та", "то", "те":
|
||||
return "that", true
|
||||
case "мой", "моего", "моему", "моим", "моём", "моя", "моей", "моё":
|
||||
return "mine", true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// ParseCalendarDate detects RU calendar date words in text and returns the
|
||||
// resolved time (midnight UTC+0 for "сегодня"/"today", next day for "завтра"/"tomorrow").
|
||||
// Returns zero time + false if no match.
|
||||
|
||||
Reference in New Issue
Block a user