feat: 实现本地热词管理,移除平台绑定
- 使用 corpus.context 参数直接传递热词列表(豆包文档支持)
- 移除 boosting_table_id 配置,避免绑定火山引擎控制台
- 实现 BuildHotwordsContext 函数,将本地热词转换为 JSON 格式
- 热词配置完全本地化,便于迁移到其他 ASR 平台
配置示例:
hotwords:
- 张三
- 李四
- VoicePaste
程序自动转换为豆包 API 要求的格式:
{"hotwords":[{"word":"张三"},{"word":"李四"},{"word":"VoicePaste"}]}
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
|
||||
BoostingTableID string // 热词表 ID(从控制台创建)
|
||||
AppID string
|
||||
AccessToken string
|
||||
ResourceID string
|
||||
Hotwords []string // 本地热词列表
|
||||
}
|
||||
|
||||
// Client manages a single ASR session with Doubao.
|
||||
@@ -58,6 +58,17 @@ func Dial(cfg Config, resultCh chan<- wsMsg.ServerMsg) (*Client, error) {
|
||||
closeCh: make(chan struct{}),
|
||||
log: slog.With("conn_id", connID),
|
||||
}
|
||||
// Build corpus configuration
|
||||
var corpus *Corpus
|
||||
if len(cfg.Hotwords) > 0 {
|
||||
contextJSON, err := BuildHotwordsContext(cfg.Hotwords)
|
||||
if err != nil {
|
||||
slog.Warn("failed to build hotwords context, skipping", "err", err)
|
||||
} else {
|
||||
corpus = &Corpus{Context: contextJSON}
|
||||
slog.Info("hotwords enabled", "count", len(cfg.Hotwords))
|
||||
}
|
||||
}
|
||||
// Send FullClientRequest
|
||||
req := &FullClientRequest{
|
||||
User: UserMeta{UID: connID},
|
||||
@@ -69,15 +80,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,
|
||||
BoostingTableID: cfg.BoostingTableID,
|
||||
ModelName: "seedasr-2.0",
|
||||
EnableITN: true,
|
||||
EnablePUNC: true,
|
||||
EnableDDC: true,
|
||||
ShowUtterances: true,
|
||||
ResultType: "full",
|
||||
EnableNonstream: true,
|
||||
EndWindowSize: 800,
|
||||
Corpus: corpus,
|
||||
},
|
||||
}
|
||||
data, err := EncodeFullClientRequest(req)
|
||||
|
||||
43
internal/asr/hotwords.go
Normal file
43
internal/asr/hotwords.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package asr
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// HotwordEntry represents a single hotword for context JSON.
|
||||
type HotwordEntry struct {
|
||||
Word string `json:"word"`
|
||||
}
|
||||
|
||||
// HotwordsContext represents the context JSON structure for hotwords.
|
||||
type HotwordsContext struct {
|
||||
Hotwords []HotwordEntry `json:"hotwords"`
|
||||
}
|
||||
|
||||
// BuildHotwordsContext converts a list of hotword strings to context JSON string.
|
||||
// Returns empty string if hotwords list is empty.
|
||||
func BuildHotwordsContext(hotwords []string) (string, error) {
|
||||
if len(hotwords) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
entries := make([]HotwordEntry, 0, len(hotwords))
|
||||
for _, word := range hotwords {
|
||||
if word != "" {
|
||||
entries = append(entries, HotwordEntry{Word: word})
|
||||
}
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
ctx := HotwordsContext{Hotwords: entries}
|
||||
data, err := json.Marshal(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("marshal hotwords context: %w", err)
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
@@ -103,16 +103,21 @@ type AudioMeta struct {
|
||||
Channel int `json:"channel"`
|
||||
}
|
||||
|
||||
// Corpus holds hotwords and context configuration.
|
||||
type Corpus struct {
|
||||
Context string `json:"context,omitempty"` // 热词直传 JSON
|
||||
}
|
||||
|
||||
type RequestMeta struct {
|
||||
ModelName string `json:"model_name"`
|
||||
EnableITN bool `json:"enable_itn"`
|
||||
EnablePUNC bool `json:"enable_punc"`
|
||||
EnableDDC bool `json:"enable_ddc"`
|
||||
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
|
||||
ModelName string `json:"model_name"`
|
||||
EnableITN bool `json:"enable_itn"`
|
||||
EnablePUNC bool `json:"enable_punc"`
|
||||
EnableDDC bool `json:"enable_ddc"`
|
||||
ShowUtterances bool `json:"show_utterances"`
|
||||
ResultType string `json:"result_type,omitempty"`
|
||||
EnableNonstream bool `json:"enable_nonstream,omitempty"`
|
||||
EndWindowSize int `json:"end_window_size,omitempty"`
|
||||
Corpus *Corpus `json:"corpus,omitempty"` // 语料/热词配置
|
||||
}
|
||||
// EncodeFullClientRequest builds the binary message for the initial handshake.
|
||||
// nostream mode: header(4) + payload_size(4) + gzip(json)
|
||||
|
||||
Reference in New Issue
Block a user