Internal error occurred but is skipped: FindTagsByCommitIDs

Files
CamTalk/backend/internal/eino/nodes_stt.go
cfy666 4b731b5ac0 feat: 实现 Eino Graph 构建与 Orchestrator 适配器,切换 main.go
- graph.go: 构建 Graph 拓扑 START→STT→History→ChatModel→Splitter→TTS→Done→END
  - 创建 eino-ext ChatModel 对接 DashScope OpenAI 兼容接口
  - 统一使用值类型(PipelineInput/PipelineOutput)
  - Callback 在运行时通过 Stream option 传入
- adapter.go: EinoOrchestrator 实现 orchestrator.Orchestrator 接口
  - 解码 base64 音频/图片,注入 context 值
  - 调用 Graph.Stream() 触发惰性执行并消费输出
  - 追加用户/助手消息到历史
- main.go: 移除旧 llmService + orchestrator.New()
  替换为 eino.NewPipelineGraph() + eino.NewEinoOrchestrator()
- 各节点统一使用值类型,State 传递请求元数据

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-19 21:58:17 +08:00

133 lines
3.4 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"
)
// 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.Infow("使用文本输入,跳过 STT",
"request_id", requestID, "text", input.Text)
// 发送 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.Infow("语音识别完成", "request_id", requestID, "text", text)
// 发送 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
})
}