Files
CamTalk/backend/internal/eino/nodes_stt.go

139 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package eino
import (
"context"
"fmt"
"strings"
"github.com/cloudwego/eino/compose"
"github.com/hhs/camtalk/internal/ai/stt"
"github.com/hhs/camtalk/internal/logger"
"github.com/hhs/camtalk/internal/models"
"github.com/hhs/camtalk/internal/util"
)
// NewSTTLambda 创建 STT Lambda 节点。
// 输入: PipelineInput → 输出: STTOutput
//
// 文本输入模式:跳过 STT直接返回用户输入文本。
// 语音模式:调用 sttService.Recognize() 进行语音识别。
// 识别结果通过 Sender 发送 stt_result 到客户端。
func NewSTTLambda(sttService stt.Service) *compose.Lambda {
return compose.InvokableLambda(func(ctx context.Context, input PipelineInput) (STTOutput, error) {
log := logger.Log
sender := senderFromCtx(ctx)
requestID := requestIDFromCtx(ctx)
// 将输入元数据写入 State供下游节点History、Done读取
if state := stateFromCtx(ctx); state != nil {
state.mu.Lock()
state.SessionID = input.SessionID
state.RequestID = input.RequestID
state.ImageData = input.ImageData
state.Scenario = input.Scenario
state.DetailLevel = "low"
state.Language = input.Language
state.TTSEnabled = input.TTSEnabled
state.mu.Unlock()
}
// 文本输入模式:跳过 STT
if input.Text != "" {
log.Debugw("text input mode, skipping stt",
"request_id", requestID,
"text_len", len(input.Text),
"text_preview", util.Truncate(input.Text, 50))
// 发送 stt_result 保持前端消息流一致性
if sender != nil {
if err := sender.SendSTTResult(models.WsSTTResult{
Type: "stt_result",
RequestID: requestID,
Text: input.Text,
IsFinal: true,
}); err != nil {
log.Errorw("发送 stt_result 失败", "error", err)
}
}
// 写入 State
if state := stateFromCtx(ctx); state != nil {
state.mu.Lock()
state.TranscribedText = input.Text
state.mu.Unlock()
}
return STTOutput{
Text: input.Text,
Language: input.Language,
IsSkipped: true,
}, nil
}
// 语音模式:解码音频
if len(input.AudioData) == 0 {
return STTOutput{}, fmt.Errorf("stt: no audio data provided")
}
log.Infow("开始语音识别",
"request_id", requestID, "audio_bytes", len(input.AudioData))
// 调用 STT 服务
text, err := sttService.Recognize(ctx, input.AudioData, stt.Options{
Encoding: "pcm_s16le",
SampleRate: 16000,
Language: input.Language,
})
if err != nil {
log.Errorw("语音识别失败", "error", err, "request_id", requestID)
if sender != nil {
sender.SendError(models.WsError{
Type: "error",
RequestID: requestID,
Code: "STT_ERROR",
Message: "语音识别失败: " + err.Error(),
})
}
return STTOutput{}, fmt.Errorf("stt: recognize: %w", err)
}
// STT 返回空文本
if strings.TrimSpace(text) == "" {
log.Infow("语音识别结果为空", "request_id", requestID)
text = "(未识别到语音)"
}
log.Debugw("stt recognition completed",
"request_id", requestID,
"text_len", len(text),
"text_preview", util.Truncate(text, 50))
// 发送 stt_result
if sender != nil {
if err := sender.SendSTTResult(models.WsSTTResult{
Type: "stt_result",
RequestID: requestID,
Text: text,
IsFinal: true,
}); err != nil {
log.Errorw("发送 stt_result 失败", "error", err)
}
}
// 写入 State
if state := stateFromCtx(ctx); state != nil {
state.mu.Lock()
state.TranscribedText = text
state.mu.Unlock()
}
return STTOutput{
Text: text,
Language: input.Language,
IsSkipped: false,
}, nil
})
}