Load LLM prompts from markdown files
This commit is contained in:
@@ -84,6 +84,7 @@ bun run test:llm
|
||||
## 메모
|
||||
|
||||
- 이 버전은 `STT`, `STT+LLM`, `LLM` 테스트를 따로 제공합니다.
|
||||
- LLM 프롬프트는 `prompts/*.md` 에 분리되어 있습니다.
|
||||
- 최소 지연을 위해 파일 저장은 하지 않습니다.
|
||||
- VAD는 현재 모델 기반이 아니라 진폭 기반 단순 분리입니다.
|
||||
- Windows에서는 보통 출력 루프백이 가능한 장치나 `Stereo Mix`, 오디오 인터페이스 loopback 채널을 `AUDIO_SOURCE`로 잡아야 합니다.
|
||||
@@ -129,6 +130,11 @@ bun run test:llm
|
||||
- 웹 검색이 실제로 시작되면 결과 전에 `검색해볼게요.` 같은 진행 메시지를 먼저 출력
|
||||
- 답변에 한글 외 다른 문자군이 섞이면 한국어만으로 한 번 더 교정
|
||||
|
||||
현재 프롬프트 파일:
|
||||
- `prompts/assistant.md`
|
||||
- `prompts/reply-gate.md`
|
||||
- `prompts/rewrite-korean.md`
|
||||
|
||||
## Windows용 .env 예시
|
||||
|
||||
```env
|
||||
|
||||
14
prompts/assistant.md
Normal file
14
prompts/assistant.md
Normal file
@@ -0,0 +1,14 @@
|
||||
너는 한국어로 짧고 자연스럽게 답하는 로컬 음성 비서다.
|
||||
|
||||
규칙:
|
||||
- 반드시 한국어로만 답한다.
|
||||
- 한자, 중국어, 일본어, 아랍어, 키릴 문자, 기타 외국 문자, 이모지 사용 금지.
|
||||
- 영어 단어는 꼭 필요한 기술명 외에는 피하고 자연스러운 한국어 표현으로 바꾼다.
|
||||
- 답변은 TTS가 읽기 쉽도록 짧고 단순한 문장으로 만든다.
|
||||
- 기본적으로 1~3문장으로 답한다.
|
||||
- 불필요한 장식, 불릿, 번호 목록, 괄호 남용, 과한 감탄 표현은 피한다.
|
||||
- 사용자의 말에 바로 답하고, 군더더기 없이 핵심만 말한다.
|
||||
- 정확한 시간, 설정 확인, 계산이 필요하면 도구를 우선 사용한다.
|
||||
- 최신 정보, 오늘/최근 정보, 뉴스, 검색 요청, 사실 확인, 외부 웹페이지 내용이 필요한 경우에만 `web_search` 와 `fetch_url` 을 사용한다.
|
||||
- 내부 지식만으로 충분한 일반 대화에는 웹 도구를 쓰지 않는다.
|
||||
- 도구가 필요한 작업이 시작되면 결과 전에 짧은 진행 메시지를 출력할 수 있다.
|
||||
10
prompts/reply-gate.md
Normal file
10
prompts/reply-gate.md
Normal file
@@ -0,0 +1,10 @@
|
||||
다음 텍스트에 로컬 비서가 실제로 대답해야 하는지 판정한다.
|
||||
|
||||
판정 기준:
|
||||
- 의미 없는 감탄사, 중얼거림, 문맥 없는 짧은 파편, 노래 가사 조각, 잡음성 문장은 `should_reply=false`
|
||||
- 질문, 요청, 확인, 명령, 대화 시도는 `should_reply=true`
|
||||
- 최신 정보나 사실 확인, 검색이 필요해 보이면 `likely_needs_lookup=true`
|
||||
- reason 은 아주 짧게 쓴다
|
||||
|
||||
반드시 JSON만 출력:
|
||||
{"should_reply":true,"likely_needs_lookup":false,"reason":"짧게"}
|
||||
8
prompts/rewrite-korean.md
Normal file
8
prompts/rewrite-korean.md
Normal file
@@ -0,0 +1,8 @@
|
||||
다음 답변을 의미를 유지한 채 자연스러운 한국어로만 다시 쓴다.
|
||||
|
||||
규칙:
|
||||
- 한글, 숫자, 기본 문장부호 외 다른 문자 사용 금지
|
||||
- 이모지 사용 금지
|
||||
- 짧고 읽기 쉬운 문장으로 만든다
|
||||
- TTS가 읽기 쉽도록 불필요한 기호와 장식을 줄인다
|
||||
- 설명하지 말고 최종 답변 문장만 출력한다
|
||||
20
src/prompt-loader.ts
Normal file
20
src/prompt-loader.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const cache = new Map<string, string>();
|
||||
|
||||
export function loadPrompt(name: string): string {
|
||||
const cached = cache.get(name);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const promptPath = path.resolve(process.cwd(), "prompts", name);
|
||||
const content = readFileSync(promptPath, "utf8").trim();
|
||||
if (!content) {
|
||||
throw new Error(`프롬프트 파일이 비어 있습니다: ${promptPath}`);
|
||||
}
|
||||
|
||||
cache.set(name, content);
|
||||
return content;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { AppConfig } from "../config.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import { loadPrompt } from "../prompt-loader.js";
|
||||
import { webFetch, webSearch } from "./web-tools.js";
|
||||
|
||||
interface OllamaChatMessage {
|
||||
@@ -52,8 +53,9 @@ export interface ReplyAssessment {
|
||||
reason: string;
|
||||
}
|
||||
|
||||
const SYSTEM_PROMPT =
|
||||
"너는 한국어로 짧고 자연스럽게 답하는 로컬 음성 비서다. 사용자의 말에 바로 답하고, 군더더기 없는 1~3문장으로 답해라. 반드시 한국어로만 답하고, 한자, 중국어, 일본어, 아랍어, 키릴 문자, 기타 외국 문자, 이모지는 쓰지 마라. 영어 단어도 꼭 필요한 기술명 외에는 피하고 자연스러운 한국어 표현으로 바꿔라. 정확한 시간, 설정 확인, 계산이 필요하면 도구를 우선 사용해라. 최신 정보, 오늘/최근 정보, 뉴스, 검색 요청, 사실 확인, 외부 웹페이지 내용이 필요한 경우에만 web_search 와 fetch_url 을 사용해라. 내부 지식만으로 충분한 일반 대화에는 웹 도구를 쓰지 마라. 너는 도구 호출 루프 안에 있으며 필요하면 여러 번 도구를 호출할 수 있다.";
|
||||
const ASSISTANT_PROMPT = loadPrompt("assistant.md");
|
||||
const REPLY_GATE_PROMPT = loadPrompt("reply-gate.md");
|
||||
const REWRITE_KOREAN_PROMPT = loadPrompt("rewrite-korean.md");
|
||||
|
||||
const TOOL_DEFINITIONS: OllamaToolDefinition[] = [
|
||||
{
|
||||
@@ -161,7 +163,7 @@ export class OllamaLlmService {
|
||||
async warmup(): Promise<void> {
|
||||
const reply = await this.chat(
|
||||
[
|
||||
{ role: "system", content: SYSTEM_PROMPT },
|
||||
{ role: "system", content: ASSISTANT_PROMPT },
|
||||
{ role: "user", content: "준비 상태 확인입니다. 한 단어로만 답하세요." },
|
||||
],
|
||||
);
|
||||
@@ -174,11 +176,8 @@ export class OllamaLlmService {
|
||||
return heuristic;
|
||||
}
|
||||
|
||||
const prompt =
|
||||
'다음 텍스트에 로컬 비서가 실제로 대답해야 하는지 판정해라. 의미 없는 감탄사, 중얼거림, 문맥 없는 짧은 파편, 노래 가사 조각, 잡음성 문장은 false. 질문, 요청, 확인, 명령, 대화 시도는 true. 최신 정보나 사실 확인이 필요하면 likely_needs_lookup 를 true 로 해라. JSON만 출력: {"should_reply":true,"likely_needs_lookup":false,"reason":"짧게"}';
|
||||
|
||||
const reply = await this.chat([
|
||||
{ role: "system", content: prompt },
|
||||
{ role: "system", content: REPLY_GATE_PROMPT },
|
||||
{ role: "user", content: userText },
|
||||
], { enableTools: false });
|
||||
|
||||
@@ -196,7 +195,7 @@ export class OllamaLlmService {
|
||||
|
||||
async generateReply(userText: string, options?: GenerateReplyOptions): Promise<string> {
|
||||
const messages: Array<OllamaChatMessage | OllamaToolResultMessage> = [
|
||||
{ role: "system", content: SYSTEM_PROMPT },
|
||||
{ role: "system", content: ASSISTANT_PROMPT },
|
||||
...this.history,
|
||||
{ role: "user", content: userText },
|
||||
];
|
||||
@@ -431,8 +430,7 @@ export class OllamaLlmService {
|
||||
[
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
"다음 답변을 의미를 유지한 채 자연스러운 한국어로만 다시 써라. 한글, 숫자, 기본 문장부호 외 다른 문자와 이모지는 쓰지 마라. 설명하지 말고 최종 답변 문장만 출력해라.",
|
||||
content: REWRITE_KOREAN_PROMPT,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
|
||||
Reference in New Issue
Block a user