这个问题本质是:
LLM 是流式输出(streaming),TTS 可能延迟开启,需要“缓存 + 实时转发”机制。
你需要一个 可切换的文本流广播器。
LLM 正在 streaming 生成文本
如果 TTS 没开启:
当 TTS 开启:
LLM 仍然持续生成,不受影响
用:
type StreamDispatcher struct {
mu sync.Mutex
buffer strings.Builder // 缓存历史文本
ttsOn bool // 是否开启 TTS
ttsChan chan string // 给 TTS 的文本流
}
func NewStreamDispatcher() *StreamDispatcher {
return &StreamDispatcher{
ttsChan: make(chan string, 100),
}
}
func (d *StreamDispatcher) OnLLMDelta(text string) {
d.mu.Lock()
defer d.mu.Unlock()
// 始终保存
d.buffer.WriteString(text)
// 如果 TTS 已开启,实时发送
if d.ttsOn {
d.ttsChan <- text
}
}
func (d *StreamDispatcher) EnableTTS() {
d.mu.Lock()
defer d.mu.Unlock()
if d.ttsOn {
return
}
d.ttsOn = true
// 先发送历史缓存
history := d.buffer.String()
if history != "" {
d.ttsChan <- history
}
}
func (d *StreamDispatcher) StartTTSListener() {
go func() {
for text := range d.ttsChan {
SendToTTS(text)
}
}()
}
dispatcher := NewStreamDispatcher()
dispatcher.StartTTSListener()
// LLM streaming
go func() {
for delta := range llmStream {
dispatcher.OnLLMDelta(delta)
}
}()
// 用户稍后开启 TTS
dispatcher.EnableTTS()
LLM streaming
↓
OnLLMDelta()
↓
buffer += text
↓
if ttsOn → send to ttsChan
当开启 TTS:
EnableTTS()
↓
把 buffer 全部发给 ttsChan
↓
后续实时文本继续进 ttsChan
上面有一个小问题:
如果 buffer 很大,一次性发给 TTS 可能太长。
可以改成:
func splitSentence(text string) []string {
return strings.Split(text, "。")
}
开启 TTS 时:
history := d.buffer.String()
sentences := splitSentence(history)
for _, s := range sentences {
if strings.TrimSpace(s) != "" {
d.ttsChan <- s
}
}
如果你是做高并发系统(像你现在的架构):
建议做成:
这样:
LLM → EventBus →
→ UI
→ Logger
→ TTS (可动态订阅)
开启 TTS 就是:
bus.Subscribe("tts")
因为:
channel 不能回放历史数据
而你需要:
“开启时重放历史 + 实时接收新数据”
这必须要有 buffer。
这是典型的:
Replayable Stream / Hot Stream + Late Subscriber
类似:
我可以给你写一个:
的完整 Engine 版本。
你要的是:
告诉我你的使用场景,我给你最合适的架构。