diff --git a/sundynix-gateway/cmd/voicesim/main.go b/sundynix-gateway/cmd/voicesim/main.go index 4395a76..ee480a9 100644 --- a/sundynix-gateway/cmd/voicesim/main.go +++ b/sundynix-gateway/cmd/voicesim/main.go @@ -69,7 +69,8 @@ func main() { answer := make([]byte, 0, 1<<20) done := make(chan struct{}) - go func() { // 读循环:文本帧=事件,二进制帧=回答 TTS 音频 + var endAt, tTask, tFirstAudio, tDone time.Time // 时延测量:以"说完(ClientEnd)"为起点 + go func() { // 读循环:文本帧=事件,二进制帧=回答 TTS 音频 defer close(done) for { mt, data, err := conn.ReadMessage() @@ -77,6 +78,9 @@ func main() { return } if mt == websocket.BinaryMessage { + if tFirstAudio.IsZero() { + tFirstAudio = time.Now() // 首个音频帧=听到第一声 + } answer = append(answer, data...) continue } @@ -94,10 +98,12 @@ func main() { } fmt.Printf(" ← 转写[%s]:%q\n", tag, m.Text) case voice.ServerTask: + tTask = time.Now() fmt.Printf(" ← 任务已提交 task_id=%s(Agent 正在思考…)\n", m.TaskID) case voice.ServerSpeaking: fmt.Println(" ← Agent 开始朗读回答…") case voice.ServerTTSEnd: + tDone = time.Now() fmt.Println(" ← 回答朗读完毕") return case voice.ServerError: @@ -119,6 +125,7 @@ func main() { time.Sleep(90 * time.Millisecond) } send(conn, voice.ClientMsg{Type: voice.ClientEnd}) + endAt = time.Now() // 时延起点:用户"说完"这一刻 fmt.Println("④ 已说完,等 Agent 回答 + 朗读(大模型 + TTS,稍候)…") // 5) 等回答朗读完(或超时)。 @@ -129,9 +136,23 @@ func main() { } if len(answer) > 0 { + dur := float64(len(answer)/2) / float64(voice.TTSSampleRate) _ = writeWAV("sim_answer.wav", answer, voice.TTSSampleRate) - fmt.Printf("\n🔊 回答音频 %d 字节(%.1f 秒)→ 存 sim_answer.wav\n", len(answer), float64(len(answer)/2)/float64(voice.TTSSampleRate)) + fmt.Printf("\n🔊 回答音频 %d 字节(时长 %.1f 秒)→ 存 sim_answer.wav\n", len(answer), dur) fmt.Println(" afplay sim_answer.wav # 听 JARVIS 的语音回答") + + // 时延拆解(以"说完"为 0 点)。首字延迟 = 听到第一声的时间,是体感关键。 + fmt.Println("\n⏱️ 时延拆解(从「说完」起算):") + if !tTask.IsZero() { + fmt.Printf(" · 提交任务 %.2fs(含 ClientEnd 后 0.5s 兜底等待)\n", tTask.Sub(endAt).Seconds()) + } + if !tFirstAudio.IsZero() { + fmt.Printf(" · 👂 首字出声 %.2fs ← 体感响应速度就看这个\n", tFirstAudio.Sub(endAt).Seconds()) + } + if !tDone.IsZero() { + fmt.Printf(" · 朗读完毕 %.2fs(= 首字 %.2fs + 念完 %.1fs 那段话)\n", + tDone.Sub(endAt).Seconds(), tFirstAudio.Sub(endAt).Seconds(), dur) + } } else { fmt.Println("\n⚠️ 没收到回答音频(看上面事件流定位:转写?任务?朗读?)") } diff --git a/sundynix-gateway/internal/handler/voice.go b/sundynix-gateway/internal/handler/voice.go index 8cdd862..d26e8d1 100644 --- a/sundynix-gateway/internal/handler/voice.go +++ b/sundynix-gateway/internal/handler/voice.go @@ -156,9 +156,10 @@ func (s *voiceSession) onControl(m voice.ClientMsg) (done bool) { _ = s.asr.Finish() // 告知火山本轮说完 } // 火山流式 ASR 只在 VAD 静音时才发 Final;客户端显式 end(点停)时不能干等—— - // 给点收尾时间让末尾部分结果到齐,再用"最新转写"兜底提交(trySubmit 去重,Final 先到就它先提交)。 + // 给一小段收尾时间让末尾部分结果到齐,再用"最新转写"兜底提交(trySubmit 去重,Final 先到就它先提交)。 + // 500ms 是"够接住末尾 partial"与"别拖慢首字出声"的折中(此前 1.2s 白白吃掉一秒多时延)。 go func() { - time.Sleep(1200 * time.Millisecond) + time.Sleep(500 * time.Millisecond) s.turnMu.Lock() txt := s.latestText s.turnMu.Unlock() diff --git a/sundynix-gateway/internal/handler/voice_task.go b/sundynix-gateway/internal/handler/voice_task.go index 6828ffb..02ba684 100644 --- a/sundynix-gateway/internal/handler/voice_task.go +++ b/sundynix-gateway/internal/handler/voice_task.go @@ -14,9 +14,11 @@ import ( // 语音只是"嘴替键盘",一行编排/工具/计费逻辑都不新造:走的正是 HTTP SubmitTask 那条关卡 // (见记忆 execution-single-entry「提交必走 preflight()+launch() 共用关卡」)。 -// voiceAgentSystem 是语音默认单 agent 的系统提示词:口语化、简洁、适合朗读(下行要过 TTS)。 -const voiceAgentSystem = "你是用户的语音助手 JARVIS。用简洁、口语化、适合朗读的中文回答," + - "避免冗长的列表和代码块;必要时可调用工具获取信息后再作答。" +// voiceAgentSystem 是语音默认单 agent 的系统提示词:口语化、**极简**、适合朗读(下行要过 TTS)。 +// 语音场景务必短——长答案既拖慢首字出声(要等第一句合成),又让人干听十几秒。 +const voiceAgentSystem = "你是用户的语音助手 JARVIS。这是语音对话,回答必须简短——像真人聊天," + + "通常一两句话说清重点即可,最多不超过三句,别长篇大论、别列清单、别念代码。" + + "先直接给结论。口语化、自然、适合朗读。需要更多细节时再由用户追问。" // buildVoiceGraph 把一句转写组成最简可执行图:input(转写) → agent(JARVIS)。 // 与前端画布 exportDsl 同构(kind=input/agent、config.text/system),dispatcher 直接吃。 diff --git a/sundynix-gateway/sim_answer.wav b/sundynix-gateway/sim_answer.wav index 0378772..495a601 100644 Binary files a/sundynix-gateway/sim_answer.wav and b/sundynix-gateway/sim_answer.wav differ diff --git a/sundynix-gateway/sim_question.wav b/sundynix-gateway/sim_question.wav index 2b8ae27..e564474 100644 Binary files a/sundynix-gateway/sim_question.wav and b/sundynix-gateway/sim_question.wav differ diff --git a/sundynix-gateway/voicesim b/sundynix-gateway/voicesim index f2a3729..469d6de 100755 Binary files a/sundynix-gateway/voicesim and b/sundynix-gateway/voicesim differ