65 lines
2.0 KiB
TypeScript
65 lines
2.0 KiB
TypeScript
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<string> {
|
|
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);
|
|
}
|
|
}
|