refactor: 简化热词配置为豆包控制台 ID
- 移除本地热词列表配置,改为直接使用豆包控制台的热词表 ID - 删除 internal/asr/hotwords.go(不再需要本地解析) - 简化 client.go 逻辑,直接传递 boosting_table_id - 移除 protocol.go 中的 boosting_table_name 字段 - 更新配置示例,添加控制台链接说明 使用方法: 1. 在豆包控制台创建热词表:https://console.volcengine.com/speech/hotword 2. 复制热词表 ID 到 config.yaml 的 boosting_table_id 字段
This commit is contained in:
@@ -20,10 +20,10 @@ const (
|
||||
|
||||
// Config holds Doubao ASR connection parameters.
|
||||
type Config struct {
|
||||
AppID string
|
||||
AccessToken string
|
||||
ResourceID string
|
||||
Hotwords []string // 热词列表,格式 "词|权重" 或 "词"
|
||||
AppID string
|
||||
AccessToken string
|
||||
ResourceID string
|
||||
BoostingTableID string // 热词表 ID(从控制台创建)
|
||||
}
|
||||
|
||||
// Client manages a single ASR session with Doubao.
|
||||
@@ -58,21 +58,6 @@ func Dial(cfg Config, resultCh chan<- wsMsg.ServerMsg) (*Client, error) {
|
||||
closeCh: make(chan struct{}),
|
||||
log: slog.With("conn_id", connID),
|
||||
}
|
||||
// Parse hotwords configuration
|
||||
var boostingTableName string
|
||||
if len(cfg.Hotwords) > 0 {
|
||||
entries, err := ParseHotwords(cfg.Hotwords)
|
||||
if err != nil {
|
||||
slog.Warn("invalid hotwords config, skipping", "err", err)
|
||||
} else {
|
||||
boostingTableName = GenerateTableName(entries)
|
||||
tableContent := FormatHotwordsTable(entries)
|
||||
slog.Info("hotwords enabled",
|
||||
"count", len(entries),
|
||||
"table_name", boostingTableName,
|
||||
"content", tableContent)
|
||||
}
|
||||
}
|
||||
// Send FullClientRequest
|
||||
req := &FullClientRequest{
|
||||
User: UserMeta{UID: connID},
|
||||
@@ -84,15 +69,15 @@ func Dial(cfg Config, resultCh chan<- wsMsg.ServerMsg) (*Client, error) {
|
||||
Channel: 1,
|
||||
},
|
||||
Request: RequestMeta{
|
||||
ModelName: "seedasr-2.0",
|
||||
EnableITN: true,
|
||||
EnablePUNC: true,
|
||||
EnableDDC: true,
|
||||
ShowUtterances: true,
|
||||
ResultType: "full",
|
||||
EnableNonstream: true,
|
||||
EndWindowSize: 800,
|
||||
BoostingTableName: boostingTableName,
|
||||
ModelName: "seedasr-2.0",
|
||||
EnableITN: true,
|
||||
EnablePUNC: true,
|
||||
EnableDDC: true,
|
||||
ShowUtterances: true,
|
||||
ResultType: "full",
|
||||
EnableNonstream: true,
|
||||
EndWindowSize: 800,
|
||||
BoostingTableID: cfg.BoostingTableID,
|
||||
},
|
||||
}
|
||||
data, err := EncodeFullClientRequest(req)
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
package asr
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultHotwordWeight is the default weight when not specified.
|
||||
DefaultHotwordWeight = 4
|
||||
// MaxHotwordWeight is the maximum allowed weight.
|
||||
MaxHotwordWeight = 10
|
||||
// MinHotwordWeight is the minimum allowed weight.
|
||||
MinHotwordWeight = 1
|
||||
// MaxHotwordLength is the maximum character count per hotword.
|
||||
MaxHotwordLength = 10
|
||||
)
|
||||
|
||||
// HotwordEntry represents a single hotword with its weight.
|
||||
type HotwordEntry struct {
|
||||
Word string
|
||||
Weight int // 1-10, default 4
|
||||
}
|
||||
|
||||
// ParseHotwords parses raw hotword strings from config.
|
||||
// Format: "word|weight" or "word" (default weight 4).
|
||||
// Returns error if any hotword is invalid.
|
||||
func ParseHotwords(raw []string) ([]HotwordEntry, error) {
|
||||
if len(raw) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
entries := make([]HotwordEntry, 0, len(raw))
|
||||
for i, line := range raw {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Split(line, "|")
|
||||
word := strings.TrimSpace(parts[0])
|
||||
|
||||
// Validate word length
|
||||
if utf8.RuneCountInString(word) > MaxHotwordLength {
|
||||
return nil, fmt.Errorf("hotword %d: exceeds %d characters: %q", i+1, MaxHotwordLength, word)
|
||||
}
|
||||
if word == "" {
|
||||
return nil, fmt.Errorf("hotword %d: empty word", i+1)
|
||||
}
|
||||
|
||||
// Parse weight
|
||||
weight := DefaultHotwordWeight
|
||||
if len(parts) > 1 {
|
||||
weightStr := strings.TrimSpace(parts[1])
|
||||
if weightStr != "" {
|
||||
w, err := strconv.Atoi(weightStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hotword %d: invalid weight %q: %w", i+1, weightStr, err)
|
||||
}
|
||||
if w < MinHotwordWeight || w > MaxHotwordWeight {
|
||||
return nil, fmt.Errorf("hotword %d: weight %d out of range [%d, %d]", i+1, w, MinHotwordWeight, MaxHotwordWeight)
|
||||
}
|
||||
weight = w
|
||||
}
|
||||
}
|
||||
|
||||
entries = append(entries, HotwordEntry{
|
||||
Word: word,
|
||||
Weight: weight,
|
||||
})
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// FormatHotwordsTable generates Doubao-compatible hotword table content.
|
||||
// Format: each line "word|weight\n".
|
||||
func FormatHotwordsTable(entries []HotwordEntry) string {
|
||||
if len(entries) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
for _, e := range entries {
|
||||
sb.WriteString(e.Word)
|
||||
sb.WriteString("|")
|
||||
sb.WriteString(strconv.Itoa(e.Weight))
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// GenerateTableName generates a unique table name based on hotword content.
|
||||
// Uses SHA256 hash of the formatted table content (first 16 hex chars).
|
||||
func GenerateTableName(entries []HotwordEntry) string {
|
||||
if len(entries) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
content := FormatHotwordsTable(entries)
|
||||
hash := sha256.Sum256([]byte(content))
|
||||
return "voicepaste_" + hex.EncodeToString(hash[:])[:16]
|
||||
}
|
||||
@@ -111,9 +111,8 @@ type RequestMeta struct {
|
||||
ShowUtterances bool `json:"show_utterances"`
|
||||
ResultType string `json:"result_type,omitempty"`
|
||||
EnableNonstream bool `json:"enable_nonstream,omitempty"`
|
||||
EndWindowSize int `json:"end_window_size,omitempty"`
|
||||
BoostingTableID string `json:"boosting_table_id,omitempty"` // 热词表 ID
|
||||
BoostingTableName string `json:"boosting_table_name,omitempty"` // 热词表名称
|
||||
EndWindowSize int `json:"end_window_size,omitempty"`
|
||||
BoostingTableID string `json:"boosting_table_id,omitempty"` // 热词表 ID
|
||||
}
|
||||
// EncodeFullClientRequest builds the binary message for the initial handshake.
|
||||
// nostream mode: header(4) + payload_size(4) + gzip(json)
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
|
||||
// DoubaoConfig holds 火山引擎豆包 ASR credentials.
|
||||
type DoubaoConfig struct {
|
||||
AppID string `yaml:"app_id"`
|
||||
AccessToken string `yaml:"access_token"`
|
||||
ResourceID string `yaml:"resource_id"`
|
||||
Hotwords []string `yaml:"hotwords"` // 热词列表,格式 "词|权重" 或 "词"
|
||||
AppID string `yaml:"app_id"`
|
||||
AccessToken string `yaml:"access_token"`
|
||||
ResourceID string `yaml:"resource_id"`
|
||||
BoostingTableID string `yaml:"boosting_table_id"` // 热词表 ID(从控制台创建)
|
||||
}
|
||||
|
||||
// SecurityConfig holds authentication settings.
|
||||
|
||||
Reference in New Issue
Block a user