2026-06-19 21:49:28 +08:00
|
|
|
|
package eino
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"io"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/cloudwego/eino/compose"
|
|
|
|
|
|
"github.com/cloudwego/eino/schema"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// sentenceDelimiters 句子分隔符集合。
|
|
|
|
|
|
var sentenceDelimiters = map[rune]bool{
|
|
|
|
|
|
'。': true,
|
|
|
|
|
|
'!': true,
|
|
|
|
|
|
'?': true,
|
|
|
|
|
|
'\n': true,
|
|
|
|
|
|
'.': true,
|
|
|
|
|
|
'!': true,
|
|
|
|
|
|
'?': true,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-19 23:24:01 +08:00
|
|
|
|
// NewMessageToStringLambda 创建 Message → String 转换 Lambda 节点。
|
|
|
|
|
|
// 输入: *schema.Message → 输出: string
|
|
|
|
|
|
//
|
|
|
|
|
|
// 提取 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
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-19 21:49:28 +08:00
|
|
|
|
// NewSplitterLambda 创建句子分割 Transform Lambda 节点。
|
2026-06-19 23:24:01 +08:00
|
|
|
|
// 输入: StreamReader[string](LLM token 流)→ 输出: StreamReader[string](完整句子流)
|
2026-06-19 21:49:28 +08:00
|
|
|
|
//
|
2026-06-19 23:24:01 +08:00
|
|
|
|
// 逐字符累积,按句子分隔符切分。每切出一个完整句子就输出一次,
|
|
|
|
|
|
// 供下游 TTS 节点实时合成。
|
2026-06-19 21:49:28 +08:00
|
|
|
|
func NewSplitterLambda() *compose.Lambda {
|
2026-06-19 23:24:01 +08:00
|
|
|
|
return compose.TransformableLambda(func(ctx context.Context, input *schema.StreamReader[string]) (*schema.StreamReader[string], error) {
|
|
|
|
|
|
sr, sw := schema.Pipe[string](8)
|
2026-06-19 21:49:28 +08:00
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
|
defer sw.Close()
|
2026-06-19 23:24:01 +08:00
|
|
|
|
defer input.Close()
|
2026-06-19 21:49:28 +08:00
|
|
|
|
|
|
|
|
|
|
var buffer strings.Builder
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
|
chunk, err := input.Recv()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
|
// 流结束,flush 剩余缓冲
|
|
|
|
|
|
if buffer.Len() > 0 {
|
|
|
|
|
|
text := strings.TrimSpace(buffer.String())
|
|
|
|
|
|
if text != "" {
|
2026-06-19 23:24:01 +08:00
|
|
|
|
sw.Send(text, nil)
|
2026-06-19 21:49:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-06-19 23:24:01 +08:00
|
|
|
|
sw.Send("", err)
|
2026-06-19 21:49:28 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 逐字符累积,按句子分隔符切分
|
|
|
|
|
|
for _, r := range chunk {
|
|
|
|
|
|
buffer.WriteRune(r)
|
|
|
|
|
|
if sentenceDelimiters[r] {
|
|
|
|
|
|
text := strings.TrimSpace(buffer.String())
|
|
|
|
|
|
if text != "" {
|
2026-06-19 23:24:01 +08:00
|
|
|
|
sw.Send(text, nil)
|
2026-06-19 21:49:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
buffer.Reset()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
return sr, nil
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|