From 28ffbf02e111180bf94eb14679583ce020c9f064 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Sun, 3 May 2026 01:14:16 +0900 Subject: [PATCH] Repair non-Korean reply output --- README.md | 1 + src/services/ollama-llm.ts | 72 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0d968d3..6e2343a 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ bun run test:llm - 최신 정보, 뉴스, 사실 확인, 검색 요청일 때만 웹 도구 사용 - `test:sttllm` 에서는 먼저 "대답할 가치가 있는 텍스트인지" 판정한 뒤 필요할 때만 답변 - 웹 검색이 실제로 시작되면 결과 전에 `검색해볼게요.` 같은 진행 메시지를 먼저 출력 +- 답변에 한글 외 다른 문자군이 섞이면 한국어만으로 한 번 더 교정 ## Windows용 .env 예시 diff --git a/src/services/ollama-llm.ts b/src/services/ollama-llm.ts index cd8f35b..e430022 100644 --- a/src/services/ollama-llm.ts +++ b/src/services/ollama-llm.ts @@ -53,7 +53,7 @@ export interface ReplyAssessment { } const SYSTEM_PROMPT = - "너는 한국어로 짧고 자연스럽게 답하는 로컬 음성 비서다. 사용자의 말에 바로 답하고, 군더더기 없는 1~3문장으로 답해라. 정확한 시간, 설정 확인, 계산이 필요하면 도구를 우선 사용해라. 최신 정보, 오늘/최근 정보, 뉴스, 검색 요청, 사실 확인, 외부 웹페이지 내용이 필요한 경우에만 web_search 와 fetch_url 을 사용해라. 내부 지식만으로 충분한 일반 대화에는 웹 도구를 쓰지 마라. 너는 도구 호출 루프 안에 있으며 필요하면 여러 번 도구를 호출할 수 있다."; + "너는 한국어로 짧고 자연스럽게 답하는 로컬 음성 비서다. 사용자의 말에 바로 답하고, 군더더기 없는 1~3문장으로 답해라. 반드시 한국어로만 답하고, 한자, 중국어, 일본어, 아랍어, 키릴 문자, 기타 외국 문자, 이모지는 쓰지 마라. 영어 단어도 꼭 필요한 기술명 외에는 피하고 자연스러운 한국어 표현으로 바꿔라. 정확한 시간, 설정 확인, 계산이 필요하면 도구를 우선 사용해라. 최신 정보, 오늘/최근 정보, 뉴스, 검색 요청, 사실 확인, 외부 웹페이지 내용이 필요한 경우에만 web_search 와 fetch_url 을 사용해라. 내부 지식만으로 충분한 일반 대화에는 웹 도구를 쓰지 마라. 너는 도구 호출 루프 안에 있으며 필요하면 여러 번 도구를 호출할 수 있다."; const TOOL_DEFINITIONS: OllamaToolDefinition[] = [ { @@ -201,7 +201,8 @@ export class OllamaLlmService { { role: "user", content: userText }, ]; - const reply = await this.runAgentLoop(messages, options); + const rawReply = await this.runAgentLoop(messages, options); + const reply = await this.repairReplyLanguageIfNeeded(rawReply, userText); this.history.push({ role: "user", content: userText }); this.history.push({ role: "assistant", content: reply }); @@ -416,6 +417,73 @@ export class OllamaLlmService { return fallback; } + private async repairReplyLanguageIfNeeded(reply: string, userText: string): Promise { + if (!this.needsLanguageRepair(reply)) { + return reply; + } + + this.logger.warn("Reply language repair triggered", { + reply, + analysis: this.analyzeScriptUsage(reply), + }); + + const repaired = await this.chat( + [ + { + role: "system", + content: + "다음 답변을 의미를 유지한 채 자연스러운 한국어로만 다시 써라. 한글, 숫자, 기본 문장부호 외 다른 문자와 이모지는 쓰지 마라. 설명하지 말고 최종 답변 문장만 출력해라.", + }, + { + role: "user", + content: `원문 질문: ${userText}\n기존 답변: ${reply}`, + }, + ], + { enableTools: false }, + ); + + const normalized = repaired.content.trim(); + if (!normalized) { + return reply; + } + + return normalized; + } + + private needsLanguageRepair(text: string): boolean { + const analysis = this.analyzeScriptUsage(text); + if (analysis.otherLetters > 0) { + return true; + } + if (analysis.hangul === 0 && analysis.latin > 0) { + return true; + } + return false; + } + + private analyzeScriptUsage(text: string): { hangul: number; latin: number; otherLetters: number } { + let hangul = 0; + let latin = 0; + let otherLetters = 0; + + for (const char of text) { + if (!/\p{Letter}/u.test(char)) { + continue; + } + if (/\p{Script=Hangul}/u.test(char)) { + hangul += 1; + continue; + } + if (/\p{Script=Latin}/u.test(char)) { + latin += 1; + continue; + } + otherLetters += 1; + } + + return { hangul, latin, otherLetters }; + } + private getProgressMessage(toolName: string): string | null { switch (toolName) { case "web_search":