import OpenAI from "openai"; import type { AssistantRuntimeConfig } from "../config.js"; import type { ConversationMemory, UserUtterance } from "./conversation.js"; const ASSISTANT_INSTRUCTIONS = [ "너는 디스코드 음성 채널에서 동작하는 한국어 음성 비서다.", "답변은 짧고 실용적으로 한다.", "기본은 한 문장, 길어도 두 문장을 넘기지 않는다.", "말투는 자연스러운 한국어로 유지한다.", "speaker_id와 speaker_name은 화자 구분용이므로 필요할 때만 자연스럽게 반영한다.", "잘 못 들었거나 의미가 불명확하면 짧게 다시 물어본다.", "목록, 마크다운, 코드블록은 쓰지 않는다.", ].join(" "); function normalizeReply(text: string): string { const compact = text.replace(/\s+/g, " ").trim(); if (compact.length <= 180) { return compact; } const sentences = compact.match(/[^.!?]+[.!?]?/g); if (!sentences || sentences.length === 0) { return compact.slice(0, 180).trim(); } return sentences.slice(0, 2).join(" ").trim().slice(0, 180).trim(); } export class OpenAiLlmService { private readonly client: OpenAI; constructor(private readonly config: AssistantRuntimeConfig) { this.client = new OpenAI({ apiKey: this.config.OPENAI_API_KEY, }); } async generateReply(memory: ConversationMemory, utterance: UserUtterance): Promise { const response = await this.client.responses.create({ model: this.config.OPENAI_MODEL, instructions: ASSISTANT_INSTRUCTIONS, input: [ { role: "user", content: [ { type: "input_text", text: memory.buildPrompt(utterance), }, ], }, ], max_output_tokens: 120, }); const output = response.output_text?.trim(); if (!output) { return "잘 못 들었습니다. 한 번만 다시 말씀해 주세요."; } return normalizeReply(output); } }