save code
This commit is contained in:
Binary file not shown.
Binary file not shown.
+53
-17
@@ -5,6 +5,7 @@ import time
|
||||
from typing import Awaitable, Callable, Optional
|
||||
import numpy as np
|
||||
|
||||
from config import settings
|
||||
from core.state_machine import Arbitrator
|
||||
from services.asr import ASRService
|
||||
from services.llm import LLMService
|
||||
@@ -36,12 +37,10 @@ class ChatPipeline:
|
||||
await self.arbitrator.on_avatar_done()
|
||||
|
||||
@staticmethod
|
||||
def _split_sentences(text: str, max_len: int = 24) -> list[str]:
|
||||
# Two-stage split:
|
||||
# 1) hard sentence boundaries
|
||||
# 2) soft commas / max length chunks for lower first-chunk latency
|
||||
def _split_sentences(text: str, max_len: Optional[int] = None) -> list[str]:
|
||||
max_len = max(16, int(max_len or settings.tts_sentence_max_len))
|
||||
hard_seps = "。!?!?;;\n"
|
||||
soft_seps = ",、, "
|
||||
soft_seps = ",、,::"
|
||||
|
||||
stage1 = []
|
||||
buf = []
|
||||
@@ -60,21 +59,43 @@ class ChatPipeline:
|
||||
|
||||
out = []
|
||||
for seg in stage1:
|
||||
cur = []
|
||||
for ch in seg:
|
||||
cur.append(ch)
|
||||
cur_len = len(cur)
|
||||
if ch in soft_seps and cur_len >= 8:
|
||||
out.append("".join(cur).strip())
|
||||
cur = []
|
||||
elif cur_len >= max_len:
|
||||
out.append("".join(cur).strip())
|
||||
cur = []
|
||||
if cur:
|
||||
out.append("".join(cur).strip())
|
||||
seg = seg.strip()
|
||||
if len(seg) <= max_len:
|
||||
out.append(seg)
|
||||
continue
|
||||
|
||||
start = 0
|
||||
while start < len(seg):
|
||||
end = min(len(seg), start + max_len)
|
||||
if end >= len(seg):
|
||||
out.append(seg[start:].strip())
|
||||
break
|
||||
|
||||
cut = end
|
||||
probe = seg[start:end]
|
||||
soft_cut = max(18, max_len // 2)
|
||||
for idx in range(len(probe) - 1, soft_cut - 1, -1):
|
||||
if probe[idx] in soft_seps:
|
||||
cut = start + idx + 1
|
||||
break
|
||||
|
||||
out.append(seg[start:cut].strip())
|
||||
start = cut
|
||||
|
||||
return [s for s in out if s]
|
||||
|
||||
@staticmethod
|
||||
def _append_segment_pause(audio: np.ndarray, sr: int, is_last: bool) -> np.ndarray:
|
||||
if is_last or audio.size == 0:
|
||||
return audio
|
||||
pause_ms = max(0, int(settings.tts_segment_pause_ms))
|
||||
if pause_ms <= 0:
|
||||
return audio
|
||||
pad = np.zeros(int(sr * pause_ms / 1000.0), dtype=np.float32)
|
||||
if pad.size == 0:
|
||||
return audio
|
||||
return np.concatenate([audio, pad])
|
||||
|
||||
async def _synthesize_sentence_first(
|
||||
self,
|
||||
reply_text: str,
|
||||
@@ -94,6 +115,7 @@ class ChatPipeline:
|
||||
if should_interrupt and should_interrupt():
|
||||
break
|
||||
sr = seg_sr
|
||||
seg_audio = self._append_segment_pause(seg_audio, seg_sr, is_last=(i == len(segments) - 1))
|
||||
if i == 0:
|
||||
first_chunk_ms = int((time.perf_counter() - t0) * 1000)
|
||||
if on_text_segment is not None:
|
||||
@@ -117,6 +139,20 @@ class ChatPipeline:
|
||||
asr_t0 = time.perf_counter()
|
||||
user_text = await self.asr.transcribe(audio_16k)
|
||||
asr_latency_ms = int((time.perf_counter() - asr_t0) * 1000)
|
||||
if not user_text.strip():
|
||||
await self.arbitrator.on_avatar_done()
|
||||
payload = {
|
||||
"user_text": "",
|
||||
"reply_text": "",
|
||||
"audio_samples": 0,
|
||||
"sample_rate": 24000,
|
||||
"asr_latency_ms": asr_latency_ms,
|
||||
"llm_latency_ms": 0,
|
||||
"llm_source": "skipped",
|
||||
"tts_latency_ms": 0,
|
||||
"tts_first_chunk_ms": 0,
|
||||
}
|
||||
return payload, np.zeros(1, dtype=np.float32), 24000
|
||||
if on_user_text is not None:
|
||||
await on_user_text(user_text)
|
||||
llm_t0 = time.perf_counter()
|
||||
|
||||
Reference in New Issue
Block a user