feat: 引入 Eino 框架并实现 AI 编排层基础设施与节点
- 引入 cloudwego/eino v0.9.9 和 eino-ext/components/model/openai v0.1.13 - 新增 internal/eino/ 包: - types.go: PipelineInput/Output、STTOutput、TokenUsage 类型定义 - state.go: PipelineState 跨节点状态收集(线程安全) - callback.go: ChatModel OnEndWithStreamOutput 回调,逐 token 推送 llm_chunk - nodes_stt.go: STT Lambda,支持文本/语音输入模式 - nodes_history.go: 历史组装 Lambda,含多模态图片支持 - nodes_splitter.go: 句子分割 Transform Lambda - nodes_tts.go: TTS Lambda,逐句合成推送音频 - nodes_done.go: Done Lambda,发送 llm_done 并追加历史 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
114
backend/internal/eino/nodes_done.go
Normal file
114
backend/internal/eino/nodes_done.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package eino
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/eino/compose"
|
||||
|
||||
"github.com/hhs/camtalk/internal/logger"
|
||||
"github.com/hhs/camtalk/internal/models"
|
||||
"github.com/hhs/camtalk/internal/session"
|
||||
)
|
||||
|
||||
// NewDoneLambda 创建 Done Lambda 节点。
|
||||
// 输入: struct{}(TTS 完成信号)→ 输出: PipelineOutput
|
||||
//
|
||||
// 从 PipelineState 读取完整回复和 token 用量,发送 llm_done 到客户端,
|
||||
// 追加助手消息到会话历史,返回 PipelineOutput。
|
||||
func NewDoneLambda(sessionMgr session.Manager, model string) *compose.Lambda {
|
||||
return compose.InvokableLambda(func(ctx context.Context, _ struct{}) (*PipelineOutput, error) {
|
||||
log := logger.Log
|
||||
sender := senderFromCtx(ctx)
|
||||
requestID := requestIDFromCtx(ctx)
|
||||
state := stateFromCtx(ctx)
|
||||
|
||||
if state == nil {
|
||||
return &PipelineOutput{}, nil
|
||||
}
|
||||
|
||||
state.mu.Lock()
|
||||
fullResponse := state.FullResponse.String()
|
||||
transcribedText := state.TranscribedText
|
||||
tokenUsage := state.TokenUsage
|
||||
modelName := model
|
||||
state.mu.Unlock()
|
||||
|
||||
// 追加助手消息到会话历史
|
||||
sessionID := ""
|
||||
if state != nil {
|
||||
// 从 context 获取 sessionID(由适配器注入)
|
||||
if sid, ok := ctx.Value(ctxKeySessionID{}).(string); ok {
|
||||
sessionID = sid
|
||||
}
|
||||
}
|
||||
if sessionID != "" && sessionMgr != nil && fullResponse != "" {
|
||||
if err := sessionMgr.AppendMessage(ctx, sessionID, models.Message{
|
||||
Role: "assistant",
|
||||
Content: fullResponse,
|
||||
}); err != nil {
|
||||
log.Errorw("追加助手消息到历史失败", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 发送 llm_done
|
||||
if sender != nil && requestID != "" {
|
||||
done := models.WsLLMDone{
|
||||
Type: "llm_done",
|
||||
RequestID: requestID,
|
||||
FullText: fullResponse,
|
||||
Model: modelName,
|
||||
LatencyMs: 0, // 由适配器计算
|
||||
}
|
||||
if tokenUsage != nil {
|
||||
done.TokensUsed = struct {
|
||||
Prompt int `json:"prompt"`
|
||||
Completion int `json:"completion"`
|
||||
Total int `json:"total"`
|
||||
}{
|
||||
Prompt: tokenUsage.Prompt,
|
||||
Completion: tokenUsage.Completion,
|
||||
Total: tokenUsage.Total,
|
||||
}
|
||||
}
|
||||
if err := sender.SendLLMDone(done); err != nil {
|
||||
log.Errorw("发送 llm_done 失败", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infow("查询处理完成",
|
||||
"request_id", requestID,
|
||||
"text_length", len(fullResponse))
|
||||
|
||||
return &PipelineOutput{
|
||||
TranscribedText: transcribedText,
|
||||
FullResponse: fullResponse,
|
||||
Model: modelName,
|
||||
TokenUsage: tokenUsage,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// ctxKeySessionID sessionID 的 context key。
|
||||
type ctxKeySessionID struct{}
|
||||
|
||||
// WithSessionID 将 sessionID 注入 context。
|
||||
func WithSessionID(ctx context.Context, sessionID string) context.Context {
|
||||
return context.WithValue(ctx, ctxKeySessionID{}, sessionID)
|
||||
}
|
||||
|
||||
// latencyFromCtx 从 context 获取开始时间并计算延迟。
|
||||
func latencyFromCtx(ctx context.Context) int64 {
|
||||
if startTime, ok := ctx.Value(ctxKeyStartTime{}).(time.Time); ok {
|
||||
return time.Since(startTime).Milliseconds()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ctxKeyStartTime 请求开始时间的 context key。
|
||||
type ctxKeyStartTime struct{}
|
||||
|
||||
// WithStartTime 将请求开始时间注入 context。
|
||||
func WithStartTime(ctx context.Context, t time.Time) context.Context {
|
||||
return context.WithValue(ctx, ctxKeyStartTime{}, t)
|
||||
}
|
||||
Reference in New Issue
Block a user