chore: import clean Agent Desk snapshot (2/7)

This commit is contained in:
2026-10-16 11:57:21 +00:00
parent 79176541c7
commit 89c5231c4c
461 changed files with 60092 additions and 0 deletions

View File

@@ -0,0 +1,312 @@
package ai
import (
"strings"
"unicode"
)
// IsCustomerHandoffRequest determines whether a user's message expresses an intent
// to speak with a human operator, agent, specialist, manager, or live person, across
// varied natural phrasing, colloquialisms, and multiple languages (Russian, English,
// Spanish, German, French, Chinese, etc.).
func IsCustomerHandoffRequest(text string) bool {
raw := strings.TrimSpace(text)
if raw == "" {
return false
}
lower := strings.ToLower(raw)
// Direct Chinese keywords
if IsHan(lower) {
for _, kw := range []string{
"人工", "转人工", "真人", "人工客服", "接人工", "转接人工", "转坐席", "人工服务", "人工专员",
} {
if strings.Contains(lower, kw) {
return true
}
}
}
// Clean punctuation for token / n-gram checks
cleaned := strings.Map(func(r rune) rune {
if unicode.IsPunct(r) || unicode.IsSymbol(r) {
return ' '
}
return r
}, lower)
fields := strings.Fields(cleaned)
if len(fields) == 0 {
return false
}
normalized := " " + strings.Join(fields, " ") + " "
// Russian handoff patterns & stems:
// Anti-bot phrases: "хватит бота", "не хочу бота", "отключи бота", "надоел бот"
if strings.Contains(normalized, " хватит бота ") ||
strings.Contains(normalized, " хватит робота ") ||
strings.Contains(normalized, " не хочу бота ") ||
strings.Contains(normalized, " не хочу общаться с ботом ") ||
strings.Contains(normalized, " надоел бот ") ||
strings.Contains(normalized, " отключи бота ") ||
strings.Contains(normalized, " без бота ") ||
strings.Contains(normalized, " убрать бота ") ||
strings.Contains(normalized, " stop bot ") ||
strings.Contains(normalized, " no bot ") ||
strings.Contains(normalized, " hate bot ") ||
strings.Contains(normalized, " not a bot ") {
return true
}
russianTargetStems := []string{
"оператор", "человек", "специалист", "сотрудник", "консультант",
"менеджер", "живо", "живой", "поддержк", "саппорт", "люди", "людей",
"админ", "администратор",
}
russianActionStems := []string{
"переведи", "переключ", "соедин", "позов", "дайт", "дай", "свяж", "связать",
"вызов", "вызовите", "хочу", "нужен", "нужна", "нужно", "можно", "позовите",
"соедините", "переведите", "переключите", "пригласи", "пригласите",
"перенаправ", "перенаправь", "перенаправьте", "передай", "передайте",
"направь", "направьте",
}
// Action + Target combinations in Russian (e.g. "позови сотрудника", "соедини со специалистом", "хочу человека")
for _, action := range russianActionStems {
if strings.Contains(normalized, " "+action) {
for _, target := range russianTargetStems {
if strings.Contains(normalized, " "+target) {
return true
}
}
}
}
// Single-word requests (e.g. user just typed "оператор", "человек", "operator", "human")
if normalized == " оператор " ||
normalized == " оператора " ||
normalized == " человек " ||
normalized == " специалист " ||
normalized == " support " ||
normalized == " operator " ||
normalized == " agent " ||
normalized == " human " {
return true
}
// Direct targets in Russian with prepositions or adjectives (e.g. "к оператору", "с человеком", "со специалистом", "живой человек")
if strings.Contains(normalized, " к оператору") ||
strings.Contains(normalized, " на оператора") ||
strings.Contains(normalized, " с оператором") ||
strings.Contains(normalized, " со специалистом") ||
strings.Contains(normalized, " к специалисту") ||
strings.Contains(normalized, " с человеком") ||
strings.Contains(normalized, " с сотрудником") ||
strings.Contains(normalized, " живой человек") ||
strings.Contains(normalized, " живого человека") ||
strings.Contains(normalized, " живым человеком") ||
strings.Contains(normalized, " поговорить с кем") ||
strings.Contains(normalized, " поговорить со ") ||
strings.Contains(normalized, " реальный человек") ||
strings.Contains(normalized, " живой есть") ||
strings.Contains(normalized, " есть живой") ||
strings.Contains(normalized, " есть кто живой") ||
strings.Contains(normalized, " можно человека") ||
strings.Contains(normalized, " дай человека") ||
strings.Contains(normalized, " дай оператора") {
return true
}
// English handoff patterns
englishTargets := []string{
"operator", "agent", "human", "representative", "person", "specialist", "someone", "support",
}
englishActions := []string{
"talk to", "speak to", "speak with", "connect", "transfer", "get me", "call", "reach", "need", "want", "let me",
}
for _, action := range englishActions {
if strings.Contains(normalized, " "+action) {
for _, target := range englishTargets {
if strings.Contains(normalized, " "+target) {
return true
}
}
}
}
if strings.Contains(normalized, " real person ") ||
strings.Contains(normalized, " live agent ") ||
strings.Contains(normalized, " live person ") ||
strings.Contains(normalized, " human agent ") ||
strings.Contains(normalized, " human please ") ||
strings.Contains(normalized, " customer service ") ||
strings.Contains(normalized, " human support ") ||
strings.Contains(normalized, " talk to someone ") ||
strings.Contains(normalized, " speak to someone ") ||
strings.Contains(normalized, " representative please ") ||
strings.Contains(normalized, " representative ") {
return true
}
// Spanish handoff patterns
if strings.Contains(normalized, " hablar con ") ||
strings.Contains(normalized, " pasar con ") ||
strings.Contains(normalized, " contactar con ") ||
strings.Contains(normalized, " quiero hablar con ") ||
strings.Contains(normalized, " necesito hablar con ") {
if strings.Contains(normalized, " persona") ||
strings.Contains(normalized, " humano") ||
strings.Contains(normalized, " agente") ||
strings.Contains(normalized, " operador") ||
strings.Contains(normalized, " alguien") {
return true
}
}
if strings.Contains(normalized, " atencion al cliente") ||
strings.Contains(normalized, " persona real") ||
strings.Contains(normalized, " agente humano") {
return true
}
// German handoff patterns
if strings.Contains(normalized, " mitarbeiter ") ||
strings.Contains(normalized, " echter mensch ") ||
strings.Contains(normalized, " mit jemandem sprechen ") {
return true
}
// French handoff patterns
if strings.Contains(normalized, " parler a quelqu un ") ||
strings.Contains(normalized, " conseiller ") ||
strings.Contains(normalized, " agent humain ") ||
strings.Contains(normalized, " etre mis en relation ") {
return true
}
return false
}
// IsModelClaimingHandoff checks whether an AI model's text reply claims that it is
// transferring, connecting, or forwarding the conversation to a human operator/agent.
func IsModelClaimingHandoff(text string) bool {
raw := strings.TrimSpace(text)
if raw == "" {
return false
}
lower := strings.ToLower(raw)
// Direct Russian phrases (exact matches)
russianModelClaims := []string{
"перевожу вас на оператора",
"переключаю вас на оператора",
"перевожу на оператора",
"переключаю на оператора",
"соединяю с оператором",
"соединяю со специалистом",
"передаю диалог оператору",
"передаю ваш запрос оператору",
"перевожу диалог на оператора",
"переключаю на специалиста",
"перевожу на специалиста",
"соединяю с живым",
"связываю вас с оператором",
"сейчас переведу на оператора",
"сейчас переключу на оператора",
"перенаправляю оператору",
"перенаправляю вас к оператору",
"перенаправляю вас на оператора",
"перенаправляю на оператора",
"перенаправляю вас на специалиста",
"перенаправляю на специалиста",
"перенаправляю в службу поддержки",
"перенаправляю вас",
"передаю вас оператору",
"передаю диалог",
"направляю диалог",
"направляю ваш запрос",
}
for _, claim := range russianModelClaims {
if strings.Contains(lower, claim) {
return true
}
}
// Flexible Russian stem matching (Action + Target)
// Catches arbitrary phrasing like "Похоже я не могу решить ваш вопрос, перенаправляю оператору. Ожидайте!"
ruActionStems := []string{"перенаправл", "перевод", "переключ", "соедин", "передаю", "передаем", "передам", "направляю", "направляем", "направлю", "связываю", "свяжу"}
ruTargetStems := []string{"оператор", "специалист", "сотрудник", "человек", "поддержк", "менеджер", "коллег", "живой"}
for _, act := range ruActionStems {
if strings.Contains(lower, act) {
for _, tgt := range ruTargetStems {
if strings.Contains(lower, tgt) {
return true
}
}
}
}
// English model handoff statements
englishModelClaims := []string{
"transferring you to an operator",
"transferring you to an agent",
"transferring you to a human",
"transferring you to human",
"transferring you to our support",
"connecting you to an operator",
"connecting you to an agent",
"connecting you to a human",
"connecting you with an agent",
"connecting you with an operator",
"routing you to an agent",
"handing you over to",
}
for _, claim := range englishModelClaims {
if strings.Contains(lower, claim) {
return true
}
}
// Flexible English stem matching (Action + Target)
enActionStems := []string{"transfer", "connect", "rout", "hand over", "handing over", "forward", "pass you", "passing you"}
enTargetStems := []string{"operator", "agent", "human", "specialist", "representative", "support", "colleague"}
for _, act := range enActionStems {
if strings.Contains(lower, act) {
for _, tgt := range enTargetStems {
if strings.Contains(lower, tgt) {
return true
}
}
}
}
// Chinese model handoff statements
chineseModelClaims := []string{
"为您转接人工",
"正在为您转接",
"为您接入人工",
"转接人工客服",
"转接人工",
"已为您转接",
}
for _, claim := range chineseModelClaims {
if strings.Contains(lower, claim) {
return true
}
}
// Flexible Chinese stem matching
zhActionStems := []string{"转接", "转交", "接入", "转给", "转到", "交由"}
zhTargetStems := []string{"人工", "客服", "专员", "同事"}
for _, act := range zhActionStems {
if strings.Contains(lower, act) {
for _, tgt := range zhTargetStems {
if strings.Contains(lower, tgt) {
return true
}
}
}
}
return false
}