Internal error occurred but is skipped: FindTagsByCommitIDs

Files
CamTalk/backend/internal/eino/nodes_done.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

89 lines
2.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"
"time"
"github.com/cloudwego/eino/compose"
"github.com/hhs/camtalk/internal/logger"
"github.com/hhs/camtalk/internal/models"
)
// ctxKeyStartTime 请求开始时间的 context key。
type ctxKeyStartTime struct{}
// WithStartTime 将请求开始时间注入 context。
func WithStartTime(ctx context.Context, t time.Time) context.Context {
return context.WithValue(ctx, ctxKeyStartTime{}, t)
}
// latencyFromCtx 从 context 获取开始时间并计算延迟(毫秒)。
func latencyFromCtx(ctx context.Context) int64 {
if startTime, ok := ctx.Value(ctxKeyStartTime{}).(time.Time); ok {
return time.Since(startTime).Milliseconds()
}
return 0
}
// NewDoneLambda 创建 Done Lambda 节点。
// 输入: struct{}TTS 完成信号)→ 输出: *PipelineOutput
//
// 从 PipelineState 读取完整回复和 token 用量,发送 llm_done 到客户端。
// 历史消息追加由适配器负责(避免重复写入)。
func NewDoneLambda(defaultModel string) *compose.Lambda {
return compose.InvokableLambda(func(ctx context.Context, _ struct{}) (PipelineOutput, error) {
log := logger.Log
sender := senderFromCtx(ctx)
state := stateFromCtx(ctx)
if state == nil {
return PipelineOutput{}, nil
}
state.mu.Lock()
fullResponse := state.FullResponse.String()
transcribedText := state.TranscribedText
tokenUsage := state.TokenUsage
requestID := state.RequestID
modelName := defaultModel
state.mu.Unlock()
// 发送 llm_done
if sender != nil && requestID != "" {
done := models.WsLLMDone{
Type: "llm_done",
RequestID: requestID,
FullText: fullResponse,
Model: modelName,
LatencyMs: latencyFromCtx(ctx),
}
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,
"response_length", len(fullResponse))
return PipelineOutput{
TranscribedText: transcribedText,
FullResponse: fullResponse,
Model: modelName,
TokenUsage: tokenUsage,
}, nil
})
}