118 lines
3.3 KiB
Go
118 lines
3.3 KiB
Go
|
|
package eino
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"encoding/base64"
|
|||
|
|
"github.com/cloudwego/eino/compose"
|
|||
|
|
"github.com/cloudwego/eino/schema"
|
|||
|
|
|
|||
|
|
"github.com/hhs/camtalk/internal/ai/llm"
|
|||
|
|
"github.com/hhs/camtalk/internal/logger"
|
|||
|
|
"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
|
|||
|
|
//
|
|||
|
|
// 构建系统提示词,组装历史消息和当前用户输入(含多模态图片)。
|
|||
|
|
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) {
|
|||
|
|
log := logger.Log
|
|||
|
|
requestID := input.RequestID
|
|||
|
|
|
|||
|
|
// 构建系统提示词
|
|||
|
|
scenarioPrompt := llm.GetScenarioPrompt(input.Scenario, input.STTOutput.Language)
|
|||
|
|
systemPrompt := llm.BuildSystemPrompt(input.STTOutput.Language, input.DetailLevel, scenarioPrompt)
|
|||
|
|
|
|||
|
|
// 构建 system message(含图片)
|
|||
|
|
systemMsg := &schema.Message{
|
|||
|
|
Role: schema.System,
|
|||
|
|
Content: systemPrompt,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果有图片,添加到 system message 的多模态内容中
|
|||
|
|
if len(input.ImageData) > 0 {
|
|||
|
|
base64Str := base64.StdEncoding.EncodeToString(input.ImageData)
|
|||
|
|
mimeType := detectImageMimeType(input.ImageData)
|
|||
|
|
systemMsg.UserInputMultiContent = []schema.MessageInputPart{
|
|||
|
|
{
|
|||
|
|
Type: schema.ChatMessagePartTypeImageURL,
|
|||
|
|
Image: &schema.MessageInputImage{
|
|||
|
|
MessagePartCommon: schema.MessagePartCommon{
|
|||
|
|
Base64Data: &base64Str,
|
|||
|
|
MIMEType: mimeType,
|
|||
|
|
},
|
|||
|
|
Detail: schema.ImageURLDetailAuto,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
messages := []*schema.Message{systemMsg}
|
|||
|
|
|
|||
|
|
// 获取并追加历史消息
|
|||
|
|
if historyFetcher != nil && input.SessionID != "" {
|
|||
|
|
history, err := historyFetcher(ctx, input.SessionID, maxHistory)
|
|||
|
|
if err != nil {
|
|||
|
|
log.Warnw("获取历史消息失败,继续处理", "error", err, "request_id", requestID)
|
|||
|
|
} else {
|
|||
|
|
for _, msg := range history {
|
|||
|
|
messages = append(messages, &schema.Message{
|
|||
|
|
Role: schema.RoleType(msg.Role),
|
|||
|
|
Content: msg.Content,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 追加当前用户输入
|
|||
|
|
messages = append(messages, &schema.Message{
|
|||
|
|
Role: schema.User,
|
|||
|
|
Content: input.STTOutput.Text,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
log.Infow("历史组装完成",
|
|||
|
|
"request_id", requestID,
|
|||
|
|
"message_count", len(messages),
|
|||
|
|
"has_image", len(input.ImageData) > 0,
|
|||
|
|
"scenario", input.Scenario)
|
|||
|
|
|
|||
|
|
return messages, nil
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// detectImageMimeType 简单检测图片 MIME 类型。
|
|||
|
|
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" // 默认
|
|||
|
|
}
|
|||
|
|
|