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>
This commit is contained in:
@@ -3,6 +3,7 @@ package eino
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/cloudwego/eino/compose"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
|
||||
@@ -11,28 +12,33 @@ import (
|
||||
"github.com/hhs/camtalk/internal/models"
|
||||
)
|
||||
|
||||
// HistoryInput 历史组装节点的输入,包含 STT 输出和原始请求信息。
|
||||
type HistoryInput struct {
|
||||
STTOutput *STTOutput
|
||||
SessionID string
|
||||
RequestID string
|
||||
ImageData []byte
|
||||
Scenario string
|
||||
DetailLevel string
|
||||
}
|
||||
|
||||
// NewHistoryLambda 创建历史组装 Lambda 节点。
|
||||
// 输入: HistoryInput → 输出: []*schema.Message
|
||||
// 输入: *STTOutput → 输出: []*schema.Message
|
||||
//
|
||||
// 从 PipelineState 读取请求元数据(SessionID、Scenario、ImageData 等),
|
||||
// 构建系统提示词,组装历史消息和当前用户输入(含多模态图片)。
|
||||
func NewHistoryLambda(historyFetcher func(ctx context.Context, sessionID string, maxHistory int) ([]models.Message, error), maxHistory int) *compose.Lambda {
|
||||
return compose.InvokableLambda(func(ctx context.Context, input *HistoryInput) ([]*schema.Message, error) {
|
||||
func NewHistoryLambda(historyFetcher func(ctx context.Context, sessionID string, limit int) ([]models.Message, error), maxHistory int) *compose.Lambda {
|
||||
return compose.InvokableLambda(func(ctx context.Context, sttOut STTOutput) ([]*schema.Message, error) {
|
||||
log := logger.Log
|
||||
requestID := input.RequestID
|
||||
|
||||
// 从 State 读取请求元数据
|
||||
state := stateFromCtx(ctx)
|
||||
if state == nil {
|
||||
return []*schema.Message{}, nil
|
||||
}
|
||||
|
||||
state.mu.Lock()
|
||||
sessionID := state.SessionID
|
||||
requestID := state.RequestID
|
||||
imageData := state.ImageData
|
||||
scenario := state.Scenario
|
||||
detailLevel := state.DetailLevel
|
||||
language := sttOut.Language
|
||||
state.mu.Unlock()
|
||||
|
||||
// 构建系统提示词
|
||||
scenarioPrompt := llm.GetScenarioPrompt(input.Scenario, input.STTOutput.Language)
|
||||
systemPrompt := llm.BuildSystemPrompt(input.STTOutput.Language, input.DetailLevel, scenarioPrompt)
|
||||
scenarioPrompt := llm.GetScenarioPrompt(scenario, language)
|
||||
systemPrompt := llm.BuildSystemPrompt(language, detailLevel, scenarioPrompt)
|
||||
|
||||
// 构建 system message(含图片)
|
||||
systemMsg := &schema.Message{
|
||||
@@ -41,9 +47,9 @@ func NewHistoryLambda(historyFetcher func(ctx context.Context, sessionID string,
|
||||
}
|
||||
|
||||
// 如果有图片,添加到 system message 的多模态内容中
|
||||
if len(input.ImageData) > 0 {
|
||||
base64Str := base64.StdEncoding.EncodeToString(input.ImageData)
|
||||
mimeType := detectImageMimeType(input.ImageData)
|
||||
if len(imageData) > 0 {
|
||||
base64Str := base64.StdEncoding.EncodeToString(imageData)
|
||||
mimeType := detectImageMimeType(imageData)
|
||||
systemMsg.UserInputMultiContent = []schema.MessageInputPart{
|
||||
{
|
||||
Type: schema.ChatMessagePartTypeImageURL,
|
||||
@@ -61,8 +67,8 @@ func NewHistoryLambda(historyFetcher func(ctx context.Context, sessionID string,
|
||||
messages := []*schema.Message{systemMsg}
|
||||
|
||||
// 获取并追加历史消息
|
||||
if historyFetcher != nil && input.SessionID != "" {
|
||||
history, err := historyFetcher(ctx, input.SessionID, maxHistory)
|
||||
if historyFetcher != nil && sessionID != "" {
|
||||
history, err := historyFetcher(ctx, sessionID, maxHistory)
|
||||
if err != nil {
|
||||
log.Warnw("获取历史消息失败,继续处理", "error", err, "request_id", requestID)
|
||||
} else {
|
||||
@@ -78,14 +84,14 @@ func NewHistoryLambda(historyFetcher func(ctx context.Context, sessionID string,
|
||||
// 追加当前用户输入
|
||||
messages = append(messages, &schema.Message{
|
||||
Role: schema.User,
|
||||
Content: input.STTOutput.Text,
|
||||
Content: sttOut.Text,
|
||||
})
|
||||
|
||||
log.Infow("历史组装完成",
|
||||
"request_id", requestID,
|
||||
"message_count", len(messages),
|
||||
"has_image", len(input.ImageData) > 0,
|
||||
"scenario", input.Scenario)
|
||||
"has_image", len(imageData) > 0,
|
||||
"scenario", scenario)
|
||||
|
||||
return messages, nil
|
||||
})
|
||||
@@ -96,22 +102,17 @@ func detectImageMimeType(data []byte) string {
|
||||
if len(data) < 4 {
|
||||
return "image/jpeg"
|
||||
}
|
||||
// JPEG: FF D8 FF
|
||||
if data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF {
|
||||
return "image/jpeg"
|
||||
}
|
||||
// PNG: 89 50 4E 47
|
||||
if data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47 {
|
||||
return "image/png"
|
||||
}
|
||||
// GIF: 47 49 46 38
|
||||
if data[0] == 0x47 && data[1] == 0x49 && data[2] == 0x46 {
|
||||
return "image/gif"
|
||||
}
|
||||
// WebP: 52 49 46 46
|
||||
if data[0] == 0x52 && data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x46 {
|
||||
return "image/webp"
|
||||
}
|
||||
return "image/jpeg" // 默认
|
||||
return "image/jpeg"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user