You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
6.3 KiB
165 lines
6.3 KiB
package comm
|
|
|
|
import "strings"
|
|
|
|
// 通话翻译规则(表 TableCallTranslateRule,存 console 共享库 postgres)。
|
|
//
|
|
// 与 svcpool.go / agentcfg.go 同一模式:console 后台读写(modules/console 用 type alias 引用),
|
|
// 业务服务运行时直读并用 MatchCallTranslateRule 选路。
|
|
//
|
|
// 取代旧的 call_translate_pair(一行只能表达一个 src→dst、且直接拼 asr/mt/tts 服务):
|
|
// 一条规则可覆盖「多源语言 × 多目标语言」,并按优先级择一命中;具体用什么服务不再由规则拼装,
|
|
// 而是引用一个 AgentConfig(类型必须是 translate 三段式 或 ast-translate 端到端),
|
|
// 服务编排收敛到 agent 里,规则只负责「什么语言走哪个 agent」。
|
|
|
|
// LangAny 语言通配符:出现在 SrcLangs/DstLangs 里表示「任意语言」。
|
|
const LangAny = "*"
|
|
|
|
// RegionAny 区域通配:Region=0 表示该规则不限区域(REGIONS 枚举从 1 开始,0 未占用)。
|
|
const RegionAny int32 = 0
|
|
|
|
// CallTranslateRule 通话翻译选路规则。一行 = 某应用作用域 + 某区域下的一条「语言集合 → agent」映射。
|
|
//
|
|
// SrcLangs / DstLangs:逗号分隔 BCP-47(与 ThirdSvcConfig.Languages、AgentConfig.SupportLangs 同一约定),
|
|
// 单个 "*" 表示任意语言。Bidir=true 时反向(dst→src)也命中同一条规则。
|
|
// Priority 越大越优先;同优先级时精确规则先于通配规则(见 MatchCallTranslateRule)。
|
|
type CallTranslateRule struct {
|
|
Id uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
|
AppName string `gorm:"column:app_name;size:64;index" json:"app_name"` // ''=全局默认
|
|
Region int32 `gorm:"column:region;index" json:"region"` // 0=任意区域
|
|
Name string `gorm:"column:name;size:64" json:"name"` // 规则名,仅后台展示用
|
|
SrcLangs string `gorm:"column:src_langs;size:512" json:"src_langs"`
|
|
DstLangs string `gorm:"column:dst_langs;size:512" json:"dst_langs"`
|
|
Bidir bool `gorm:"column:bidir" json:"bidir"`
|
|
AgentId string `gorm:"column:agent_id;size:64" json:"agent_id"` // 引用 AgentConfig.Id(translate / ast-translate)
|
|
Priority int32 `gorm:"column:priority;index" json:"priority"` // 越大越优先
|
|
Enable bool `gorm:"column:enable" json:"enable"`
|
|
Remark string `gorm:"column:remark" json:"remark"`
|
|
|
|
Createtime int64 `gorm:"column:createtime" json:"createtime"`
|
|
Updatetime int64 `gorm:"column:updatetime" json:"updatetime"`
|
|
}
|
|
|
|
// SplitLangs 把逗号分隔的语言串切成切片(去空白、丢空项)。
|
|
func SplitLangs(s string) []string {
|
|
out := make([]string, 0, 4)
|
|
for _, x := range strings.Split(s, ",") {
|
|
if v := strings.TrimSpace(x); v != "" {
|
|
out = append(out, v)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// baseLang 取 BCP-47 的主语言子标签:zh-CN → zh。
|
|
func baseLang(s string) string {
|
|
s = strings.TrimSpace(strings.ToLower(s))
|
|
if i := strings.IndexByte(s, '-'); i > 0 {
|
|
return s[:i]
|
|
}
|
|
return s
|
|
}
|
|
|
|
// 单侧语言的匹配质量。不能用「先严格扫一轮、没命中再放宽」的两轮做法:
|
|
// 通配规则在严格轮就能接住任意输入,宽松轮永远轮不到,zh-CN 的规则会被 * 兜底规则吃掉。
|
|
// 故把质量并入排序——全等 > 主语言相同 > 通配。
|
|
const (
|
|
langMiss = -1 // 不命中
|
|
langWild = 0 // 被 "*" 接住
|
|
langBase = 1 // 主语言子标签相同(zh-CN 规则接住 zh / zh-TW 的请求)
|
|
langExact = 2 // 全等
|
|
)
|
|
|
|
// langSetScore 给「语言集合 × 目标语言」打分,取集合内的最好一档;不命中返回 langMiss。
|
|
func langSetScore(set, lang string) int {
|
|
lang = strings.TrimSpace(lang)
|
|
if lang == "" {
|
|
return langMiss
|
|
}
|
|
best := langMiss
|
|
for _, x := range SplitLangs(set) {
|
|
s := langMiss
|
|
switch {
|
|
case x == LangAny:
|
|
s = langWild
|
|
case strings.EqualFold(x, lang):
|
|
s = langExact
|
|
case baseLang(x) == baseLang(lang):
|
|
s = langBase
|
|
}
|
|
if s > best {
|
|
best = s
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
// ruleMatchScore 单条规则对 src→dst 的匹配得分(两侧分数之和,0~4);不命中返回 langMiss。
|
|
// Bidir 规则同时试反向,取更好的一次。
|
|
func ruleMatchScore(r *CallTranslateRule, src, dst string) int {
|
|
score := langMiss
|
|
if a, b := langSetScore(r.SrcLangs, src), langSetScore(r.DstLangs, dst); a >= 0 && b >= 0 {
|
|
score = a + b
|
|
}
|
|
if r.Bidir {
|
|
if a, b := langSetScore(r.SrcLangs, dst), langSetScore(r.DstLangs, src); a >= 0 && b >= 0 && a+b > score {
|
|
score = a + b
|
|
}
|
|
}
|
|
return score
|
|
}
|
|
|
|
// ruleSpecificity 规则精确度:src/dst 每一侧非通配各记 1 分。用于同优先级下让精确规则压过兜底规则。
|
|
func ruleSpecificity(r *CallTranslateRule) int {
|
|
n := 0
|
|
if strings.TrimSpace(r.SrcLangs) != LangAny {
|
|
n++
|
|
}
|
|
if strings.TrimSpace(r.DstLangs) != LangAny {
|
|
n++
|
|
}
|
|
return n
|
|
}
|
|
|
|
// betterRule 判断 a 是否比 b 更该被选中:优先级大 > 匹配得分高 > 精确度高 > id 小(先建的先生效)。
|
|
func betterRule(a *CallTranslateRule, aScore int, b *CallTranslateRule, bScore int) bool {
|
|
if a.Priority != b.Priority {
|
|
return a.Priority > b.Priority
|
|
}
|
|
if aScore != bScore {
|
|
return aScore > bScore
|
|
}
|
|
if sa, sb := ruleSpecificity(a), ruleSpecificity(b); sa != sb {
|
|
return sa > sb
|
|
}
|
|
return a.Id < b.Id
|
|
}
|
|
|
|
// MatchCallTranslateRule 从规则集里为 region + src→dst 选出唯一生效的规则,无命中返回 nil。
|
|
//
|
|
// 选路顺序:
|
|
// 1. 只看 Enable 的规则;规则的 Region 须等于入参 region,或为 RegionAny(0) 表示不限区域。
|
|
// 2. 语言两侧都命中才算候选("*" 通配、全等、主语言子标签相同都算命中,见 langSetScore)。
|
|
// 3. 候选间按 betterRule 取最优:优先级 > 匹配得分 > 精确度 > 建立顺序。
|
|
//
|
|
// 调用方拿到规则后用 AgentId 去 agent_config 取具体服务编排(三段式或端到端由 agent 类型决定)。
|
|
func MatchCallTranslateRule(rules []*CallTranslateRule, region int32, srcLang, dstLang string) *CallTranslateRule {
|
|
var best *CallTranslateRule
|
|
bestScore := langMiss
|
|
for _, r := range rules {
|
|
if r == nil || !r.Enable {
|
|
continue
|
|
}
|
|
if r.Region != RegionAny && r.Region != region {
|
|
continue
|
|
}
|
|
score := ruleMatchScore(r, srcLang, dstLang)
|
|
if score < 0 {
|
|
continue
|
|
}
|
|
if best == nil || betterRule(r, score, best, bestScore) {
|
|
best, bestScore = r, score
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|