본문으로 건너뛰기

[Open WebUI] TTS 문장 파싱을 showCallOverlay 가드로 감싸 불필요한 O(n^2) 연산 제거

PR 링크: open-webui/open-webui#22195 상태: Merged | 변경: +58 / -52

들어가며

Open WebUI의 스트리밍 이벤트 핸들러는 매 토큰마다 getMessageContentParts()removeAllDetails()를 호출하여 TTS(텍스트 음성 변환)를 위한 문장을 추출합니다. 이 함수들은 누적된 전체 메시지 내용에 대해 여러 번의 정규식 패스를 수행하므로, 응답 전체에 걸쳐 O(n^2) 복잡도가 됩니다. 그런데 이 이벤트의 유일한 소비자는 CallOverlay.svelte이며, 이 컴포넌트는 showCallOverlay가 true일 때만 마운트됩니다. 대다수 사용자에게 이 파싱은 리스너 없이 실행되는 순수한 낭비입니다.

핵심 코드 분석

Before: 항상 TTS 파싱 실행

// 매 스트리밍 토큰마다 실행
const messageContentParts = getMessageContentParts(
    removeAllDetails(message.content),
    $config?.audio?.tts?.split_on ?? 'punctuation'
);
messageContentParts.pop();

if (messageContentParts.length > 0 &&
    messageContentParts[messageContentParts.length - 1] !== message.lastSentence) {
    message.lastSentence = messageContentParts[messageContentParts.length - 1];
    eventTarget.dispatchEvent(new CustomEvent('chat', { ... }));
}

After: showCallOverlay 가드 적용

// 음성 통화 오버레이가 활성일 때만 파싱
if ($showCallOverlay) {
    const messageContentParts = getMessageContentParts(
        removeAllDetails(message.content),
        $config?.audio?.tts?.split_on ?? 'punctuation'
    );
    messageContentParts.pop();

    if (messageContentParts.length > 0 &&
        messageContentParts[messageContentParts.length - 1] !== message.lastSentence) {
        message.lastSentence = messageContentParts[messageContentParts.length - 1];
        eventTarget.dispatchEvent(new CustomEvent('chat', { ... }));
    }
}

이 패턴이 3곳의 TTS 파싱 블록 모두에 적용되었습니다.

왜 이게 좋은가

  • 대다수 사용자에게 즉각적 개선: 음성 통화 기능을 사용하지 않는 사용자(대부분)에게 매 토큰마다 수행되던 정규식 파싱이 완전히 제거됩니다.
  • O(n^2) -> O(1): 비활성 상태에서 removeAllDetails()getMessageContentParts()의 다중 정규식 패스가 사라집니다.
  • 기능 보존: showCallOverlay가 활성이면 기존과 동일하게 동작합니다.
  • 단순한 조건문 하나: if 가드 하나로 세 곳의 비용이 높은 코드 블록을 모두 보호합니다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글