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) }