sync: migrate ai-operator to Gitea (2026-08-10)
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user