fix: 修复 Eino Graph 类型不匹配和多模态消息问题

- 添加 msg2str 转换节点解决 ChatModel 输出 *schema.Message 与 Splitter 期望 string 的类型不匹配
- 将多模态图片内容从 system 消息移到 user 消息(DashScope API 仅支持 user/tool 角色的多模态内容)
- 修复 Content 和 UserInputMultiContent 不能同时设置的问题
- Splitter 输出改为 StreamReader[string](单句),TTS 改为 TransformableLambda 流式消费
- 修复 .env 中 PostgreSQL DSN 和 Redis ADDR 的 http:// 前缀问题
This commit is contained in:
2026-06-19 23:24:01 +08:00
parent 9576884619
commit eb1b90445f
4 changed files with 171 additions and 106 deletions

View File

@@ -20,18 +20,49 @@ var sentenceDelimiters = map[rune]bool{
'?': true,
}
// NewSplitterLambda 创建句子分割 Transform Lambda 节点。
// 输入: StreamReader[string]LLM 完整文本的单帧流)→ 输出: StreamReader[[]string](句子数组流)
// NewMessageToStringLambda 创建 Message → String 转换 Lambda 节点。
// 输入: *schema.Message → 输出: string
//
// 在 Stream 模式下,框架自动将 ChatModel 的 StreamReader[*schema.Message]
// concat 为 string 后传入此节点。此节点将文本按句子边界切分,
// 每切出一个句子就输出一次,供 TTS 节点实时合成。
func NewSplitterLambda() *compose.Lambda {
return compose.TransformableLambda(func(ctx context.Context, input *schema.StreamReader[string]) (*schema.StreamReader[[]string], error) {
sr, sw := schema.Pipe[[]string](8)
// 提取 Message.Content 文本,供 Splitter 节点消费。
func NewMessageToStringLambda() *compose.Lambda {
return compose.TransformableLambda(func(ctx context.Context, input *schema.StreamReader[*schema.Message]) (*schema.StreamReader[string], error) {
sr, sw := schema.Pipe[string](8)
go func() {
defer sw.Close()
defer input.Close()
for {
msg, err := input.Recv()
if err != nil {
if err == io.EOF {
return
}
sw.Send("", err)
return
}
if msg != nil && msg.Content != "" {
sw.Send(msg.Content, nil)
}
}
}()
return sr, nil
})
}
// NewSplitterLambda 创建句子分割 Transform Lambda 节点。
// 输入: StreamReader[string]LLM token 流)→ 输出: StreamReader[string](完整句子流)
//
// 逐字符累积,按句子分隔符切分。每切出一个完整句子就输出一次,
// 供下游 TTS 节点实时合成。
func NewSplitterLambda() *compose.Lambda {
return compose.TransformableLambda(func(ctx context.Context, input *schema.StreamReader[string]) (*schema.StreamReader[string], error) {
sr, sw := schema.Pipe[string](8)
go func() {
defer sw.Close()
defer input.Close()
var buffer strings.Builder
@@ -43,23 +74,22 @@ func NewSplitterLambda() *compose.Lambda {
if buffer.Len() > 0 {
text := strings.TrimSpace(buffer.String())
if text != "" {
sw.Send([]string{text}, nil)
sw.Send(text, nil)
}
}
return
}
sw.Send(nil, err)
sw.Send("", err)
return
}
// chunk 是 concat 后的完整文本(单帧流)
// 逐字符累积,按句子分隔符切分
for _, r := range chunk {
buffer.WriteRune(r)
if sentenceDelimiters[r] {
text := strings.TrimSpace(buffer.String())
if text != "" {
sw.Send([]string{text}, nil)
sw.Send(text, nil)
}
buffer.Reset()
}