这个问题本质是:
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 版本。
你要的是:
告诉我你的使用场景,我给你最合适的架构。
Github开源生信云平台 DEMO