Files

131 lines
5.5 KiB
Go

package language
import (
"strings"
"ai-operator/internal/dialogue/state"
)
type Detector struct{}
func NewDetector() *Detector { return &Detector{} }
func (d *Detector) Normalize(input string) string { return Normalize(input) }
func (d *Detector) DetectToolLanguage(value string) DetectionResult {
return d.Detect(value, SourceToolArgs)
}
func (d *Detector) Detect(input string, source DetectionSource) DetectionResult {
n := Normalize(input)
res := DetectionResult{Language: LanguageUnknown, Intent: IntentNotLanguage, Source: source, NormalizedText: n, ReasonCode: "no_match"}
if n == "" {
res.ReasonCode = "empty_input"
res.NeedsClarification = true
return res
}
if len([]rune(n)) < 2 {
res.ReasonCode = "too_short"
return res
}
if containsAny(n, ambiguousPhrases) || mentionsBoth(n) {
res.Intent = IntentAmbiguous
res.ReasonCode = "ambiguous_ru_kk"
res.Confidence = 0.4
res.NeedsClarification = true
return res
}
if containsAny(n, falsePositivePhrases) {
res.ReasonCode = "no_match"
return res
}
if reason, ok := ruExact[n]; ok {
return result(LanguageRU, IntentLanguageSelect, 0.99, n, n, source, reason, false)
}
if reason, ok := kkExact[n]; ok {
return result(LanguageKK, IntentLanguageSelect, 0.95, n, n, source, reason, false)
}
if d.IsExplicitChangeRequest(n) {
if p := phraseMatch(n, explicitRu); p != "" {
return result(LanguageRU, IntentLanguageChange, 0.95, p, n, source, "explicit_language_request", false)
}
if p := phraseMatch(n, explicitKK); p != "" {
return result(LanguageKK, IntentLanguageChange, 0.95, p, n, source, "explicit_language_request", false)
}
return DetectionResult{Language: LanguageUnknown, Intent: IntentAmbiguous, Confidence: 0.5, NormalizedText: n, Source: source, ReasonCode: "explicit_language_request", NeedsClarification: true}
}
if p := exactOrPhrase(n, ruPhrases); p != "" {
conf := 0.95
if n == "я русский" {
conf = 0.55
}
return result(LanguageRU, IntentLanguageSelect, conf, p, n, source, "exact_phrase", conf < MediumConfidence)
}
if p := exactOrPhrase(n, kkPhrases); p != "" {
return result(LanguageKK, IntentLanguageSelect, 0.95, p, n, source, "exact_phrase", false)
}
if p := exactOrPhrase(n, ruASR); p != "" {
return result(LanguageRU, IntentLanguageSelect, 0.80, p, n, source, "weak_match", false)
}
if p := exactOrPhrase(n, kkASR); p != "" {
return result(LanguageKK, IntentLanguageSelect, 0.80, p, n, source, "weak_match", false)
}
if n == "я русский" {
return result(LanguageRU, IntentAmbiguous, 0.55, n, n, source, "weak_match", true)
}
if n == "я казах" {
return result(LanguageKK, IntentAmbiguous, 0.55, n, n, source, "weak_match", true)
}
return res
}
func (d *Detector) IsExplicitChangeRequest(input string) bool {
n := Normalize(input)
return containsAny(n, explicitChangeMarkers) || containsAny(n, explicitRu) || containsAny(n, explicitKK)
}
func ShouldApplyLanguageDetection(ctx SelectionContext, res DetectionResult) LanguageDecision {
if ctx.CurrentState == state.StateEnded || ctx.CurrentState == state.StateHandoff || ctx.CurrentState == state.StateClosing {
return LanguageDecision{ReasonCode: "state_not_allowed", MessageKey: "language.change.denied"}
}
if res.NeedsClarification || res.Intent == IntentAmbiguous {
return LanguageDecision{NeedsClarification: true, ReasonCode: res.ReasonCode, MessageKey: "language.ask_clarify"}
}
if res.Language != LanguageRU && res.Language != LanguageKK {
return LanguageDecision{ReasonCode: res.ReasonCode, MessageKey: "language.not_understood"}
}
switch ctx.CurrentState {
case state.StateLanguageSelection:
if res.Confidence >= MediumConfidence && res.Intent == IntentLanguageSelect {
return LanguageDecision{Apply: true, Language: res.Language, ReasonCode: res.ReasonCode, MessageKey: "language.selected." + string(res.Language)}
}
case state.StateRegionSelection:
if res.Confidence >= HighConfidence && (res.Intent == IntentLanguageChange || res.Intent == IntentLanguageSelect) {
return LanguageDecision{Apply: true, Language: res.Language, ReasonCode: res.ReasonCode, MessageKey: "language.changed." + string(res.Language)}
}
case state.StateReadyToHelp, state.StateQuestionAnswering:
if ctx.AllowChange && res.Intent == IntentLanguageChange && res.Confidence >= HighConfidence {
return LanguageDecision{Apply: true, Language: res.Language, ReasonCode: res.ReasonCode, MessageKey: "language.changed." + string(res.Language)}
}
}
return LanguageDecision{ReasonCode: "mixed_language_no_explicit_switch", MessageKey: "language.not_understood"}
}
func result(lang Language, intent DetectionIntent, conf float64, phrase, norm string, src DetectionSource, reason string, clarify bool) DetectionResult {
return DetectionResult{Language: lang, Intent: intent, Confidence: conf, MatchedPhrase: phrase, NormalizedText: norm, Source: src, ReasonCode: reason, NeedsClarification: clarify}
}
func exactOrPhrase(n string, phrases []string) string {
for _, p := range phrases {
p = Normalize(p)
if n == p || strings.Contains(n, p) && len([]rune(p)) > 4 {
return p
}
}
return ""
}
func phraseMatch(n string, phrases []string) string { return exactOrPhrase(n, phrases) }
func containsAny(n string, phrases []string) bool { return exactOrPhrase(n, phrases) != "" }
func mentionsBoth(n string) bool {
ru := exactOrPhrase(n, append(ruPhrases, ruASR...)) != "" || strings.Contains(" "+n+" ", " ru ")
kk := exactOrPhrase(n, append(kkPhrases, kkASR...)) != "" || strings.Contains(" "+n+" ", " kk ")
return ru && kk
}