From ec02943538f70540f9bf3b03e38b6cedde388c9d Mon Sep 17 00:00:00 2001 From: claude-bot Date: Sun, 3 May 2026 01:04:31 +0900 Subject: [PATCH] Split STT-only and STT+LLM test modes --- README.md | 23 +++++++-- package.json | 1 + src/index.ts | 98 ++++++++++++++++++++++---------------- src/services/ollama-llm.ts | 1 + 4 files changed, 76 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 07c48e9..0d968d3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # realtime_voice_bot -출력장치로 재생되는 소리를 파일 저장 없이 바로 받아서 `faster-whisper`로 STT 테스트를 하고, 별도로 `Ollama` LLM 에이전트 CLI 테스트를 할 수 있는 최소 프로토타입입니다. +출력장치로 재생되는 소리를 파일 저장 없이 바로 받아서 `faster-whisper`로 STT 테스트를 하고, 필요하면 `Ollama` LLM과 연결된 통합 테스트와 LLM CLI 테스트를 할 수 있는 최소 프로토타입입니다. 현재 문서는 **Windows PC에서 실행하는 기준**으로 적었습니다. @@ -11,7 +11,8 @@ - 메모리 버퍼 기반 간단한 저지연 발화 분리 - 미리 로드한 `faster-whisper` 워커에 PCM 직접 전달 - 디스크에 WAV 저장 없이 바로 전사 -- STT 결과에 대해 답변 가치 판단 후 필요할 때만 LLM 답변 +- STT 전용 테스트 +- STT 결과에 대해 답변 가치 판단 후 필요할 때만 LLM 답변하는 통합 테스트 - 로컬 `Ollama` LLM 에이전트 CLI 테스트 ## 빠른 시작 @@ -34,6 +35,12 @@ bun run devices bun run test:stt ``` +STT + LLM 통합 테스트: + +```bat +bun run test:sttllm +``` + LLM 단독 테스트: ```bat @@ -76,7 +83,7 @@ bun run test:llm ## 메모 -- 이 버전은 `STT` 테스트와 `LLM` 테스트를 따로 합니다. +- 이 버전은 `STT`, `STT+LLM`, `LLM` 테스트를 따로 제공합니다. - 최소 지연을 위해 파일 저장은 하지 않습니다. - VAD는 현재 모델 기반이 아니라 진폭 기반 단순 분리입니다. - Windows에서는 보통 출력 루프백이 가능한 장치나 `Stereo Mix`, 오디오 인터페이스 loopback 채널을 `AUDIO_SOURCE`로 잡아야 합니다. @@ -92,7 +99,13 @@ bun run test:llm 4. `bun run devices` 5. `.env`에서 `AUDIO_SOURCE=`에 루프백 장치 이름 입력 6. `bun run test:stt` -7. 유튜브, 디스코드 통화, 동영상 같은 소리를 재생해서 전사와 LLM 답변 확인 +7. 유튜브, 디스코드 통화, 동영상 같은 소리를 재생해서 전사만 확인 + +## Windows STT+LLM 통합 테스트 순서 + +1. `bun run setup:llm` +2. `bun run test:sttllm` +3. 유튜브, 디스코드 통화, 동영상 같은 소리를 재생해서 전사와 답변 확인 ## Windows LLM 테스트 순서 @@ -112,7 +125,7 @@ bun run test:llm 동작 원칙: - 일반 대화는 로컬 LLM만 답변 - 최신 정보, 뉴스, 사실 확인, 검색 요청일 때만 웹 도구 사용 -- STT 경로에서는 먼저 "대답할 가치가 있는 텍스트인지" 판정한 뒤 필요할 때만 답변 +- `test:sttllm` 에서는 먼저 "대답할 가치가 있는 텍스트인지" 판정한 뒤 필요할 때만 답변 - 웹 검색이 실제로 시작되면 결과 전에 `검색해볼게요.` 같은 진행 메시지를 먼저 출력 ## Windows용 .env 예시 diff --git a/package.json b/package.json index 2d2978b..5c52456 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "setup:llm": "bun src/setup-llm.ts", "setup:python": "bun run setup:stt", "test:stt": "bun src/index.ts test-stt", + "test:sttllm": "bun src/index.ts test-sttllm", "test:llm": "bun src/index.ts test-llm", "devices": "bun src/index.ts devices", "check": "tsc --noEmit", diff --git a/src/index.ts b/src/index.ts index 1a316ee..dbe2224 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,11 +10,11 @@ import { OllamaLlmService } from "./services/ollama-llm.js"; const mode = process.argv[2] ?? "test-stt"; -async function runSttTest(): Promise { +async function runSttTest(enableLlm: boolean): Promise { const config = loadConfig(); const logger = new Logger(config.DEBUG ? config.LOG_LEVEL : "error"); const stt = new FasterWhisperSttService(config, logger); - const llm = new OllamaLlmService(config, logger); + const llm = enableLlm ? new OllamaLlmService(config, logger) : null; let capture = null as ReturnType | null; let shuttingDown: Promise | null = null; let receivedChunks = 0; @@ -69,10 +69,16 @@ async function runSttTest(): Promise { void stt.destroy(); }); + console.log("STT 준비중..."); await stt.warmup(); logger.info("STT warmup finished"); - await llm.warmup(); - logger.info("LLM warmup finished"); + console.log("STT 준비 완료"); + if (llm) { + console.log("LLM 준비중..."); + await llm.warmup(); + logger.info("LLM warmup finished"); + console.log("LLM 준비 완료"); + } const transcriptionQueue: Array<{ pcm16: Buffer; queuedAt: number; index: number }> = []; let transcribing = false; @@ -108,45 +114,47 @@ async function runSttTest(): Promise { console.log(`사용자> ${text}`); } - const assessmentStartedAt = Date.now(); - const assessment = await llm.assessReplyNeed(text); - logger.info("Reply assessment", { - index: next.index, - should_reply: assessment.shouldReply, - likely_needs_lookup: assessment.likelyNeedsLookup, - reason: assessment.reason, - assessment_ms: Date.now() - assessmentStartedAt, - }); + if (llm) { + const assessmentStartedAt = Date.now(); + const assessment = await llm.assessReplyNeed(text); + logger.info("Reply assessment", { + index: next.index, + should_reply: assessment.shouldReply, + likely_needs_lookup: assessment.likelyNeedsLookup, + reason: assessment.reason, + assessment_ms: Date.now() - assessmentStartedAt, + }); - if (!assessment.shouldReply) { - if (config.DEBUG) { - console.log(`[skip] ${assessment.reason}\n`); - } - return; - } - - const llmStartedAt = Date.now(); - const reply = await llm.generateReply(text, { - onProgress: (message) => { + if (!assessment.shouldReply) { if (config.DEBUG) { - console.log(`[assistant] ${message}`); - return; + console.log(`[skip] ${assessment.reason}\n`); } - console.log(`답변> ${message}`); - }, - }); - logger.info("LLM latency", { - index: next.index, - llm_ms: Date.now() - llmStartedAt, - }); - logger.info("LLM reply", { index: next.index, text: reply }); - - if (config.DEBUG) { - if (config.DEBUG_TRANSCRIPTS) { - console.log(`[assistant] ${reply}\n`); + return; + } + + const llmStartedAt = Date.now(); + const reply = await llm.generateReply(text, { + onProgress: (message) => { + if (config.DEBUG) { + console.log(`[assistant] ${message}`); + return; + } + console.log(`답변> ${message}`); + }, + }); + logger.info("LLM latency", { + index: next.index, + llm_ms: Date.now() - llmStartedAt, + }); + logger.info("LLM reply", { index: next.index, text: reply }); + + if (config.DEBUG) { + if (config.DEBUG_TRANSCRIPTS) { + console.log(`[assistant] ${reply}\n`); + } + } else { + console.log(`답변> ${reply}`); } - } else { - console.log(`답변> ${reply}`); } } } catch (error) { @@ -238,11 +246,14 @@ async function runSttTest(): Promise { }); if (config.DEBUG) { - console.log("실시간 출력장치 STT 테스트를 시작합니다. Ctrl+C 로 종료합니다."); + console.log(enableLlm ? "실시간 출력장치 STT+LLM 테스트를 시작합니다. Ctrl+C 로 종료합니다." : "실시간 출력장치 STT 테스트를 시작합니다. Ctrl+C 로 종료합니다."); console.log(`source: ${config.AUDIO_SOURCE ?? "unset"}`); console.log(`model: ${config.WHISPER_MODEL}`); console.log(`language: ${config.WHISPER_LANGUAGE}`); console.log(`beam: ${config.WHISPER_BEAM_SIZE}`); + if (enableLlm) { + console.log(`llm: ${config.OLLAMA_MODEL}`); + } } setInterval(() => { @@ -325,13 +336,16 @@ async function main(): Promise { await printAudioDevices(); return; case "test-stt": - await runSttTest(); + await runSttTest(false); + return; + case "test-sttllm": + await runSttTest(true); return; case "test-llm": await runLlmCli(); return; default: - throw new Error(`알 수 없는 실행 모드입니다: ${mode}. 사용 가능: test-stt, test-llm, devices`); + throw new Error(`알 수 없는 실행 모드입니다: ${mode}. 사용 가능: test-stt, test-sttllm, test-llm, devices`); } } diff --git a/src/services/ollama-llm.ts b/src/services/ollama-llm.ts index fe0dbf3..cd8f35b 100644 --- a/src/services/ollama-llm.ts +++ b/src/services/ollama-llm.ts @@ -376,6 +376,7 @@ export class OllamaLlmService { "bun run setup:llm", "bun run devices", "bun run test:stt", + "bun run test:sttllm", "bun run test:llm", ], };