Fix repeated local replies

This commit is contained in:
2026-04-30 18:18:16 +09:00
parent 133118ca29
commit 0005352be7
4 changed files with 18 additions and 7 deletions

View File

@@ -361,7 +361,6 @@ export class GuildVoiceSession extends EventEmitter {
}; };
this.options.logger.info("Transcript committed", this.guildId, hydratedUtterance.speakerName, hydratedUtterance.text); this.options.logger.info("Transcript committed", this.guildId, hydratedUtterance.speakerName, hydratedUtterance.text);
this.memory.addUserTurn(hydratedUtterance);
if (this.options.config.DEBUG_TEXT_EVENTS) { if (this.options.config.DEBUG_TEXT_EVENTS) {
await this.announce(`🗣️ ${hydratedUtterance.speakerName}: ${hydratedUtterance.text}`); await this.announce(`🗣️ ${hydratedUtterance.speakerName}: ${hydratedUtterance.text}`);
@@ -375,6 +374,7 @@ export class GuildVoiceSession extends EventEmitter {
reply = "지금은 답변 생성에 실패했습니다. 잠시 후 다시 말씀해 주세요."; reply = "지금은 답변 생성에 실패했습니다. 잠시 후 다시 말씀해 주세요.";
} }
this.memory.addUserTurn(hydratedUtterance);
this.memory.addAssistantTurn(reply); this.memory.addAssistantTurn(reply);
if (this.options.config.DEBUG_TEXT_EVENTS) { if (this.options.config.DEBUG_TEXT_EVENTS) {
await this.announce(`🤖 ${reply}`); await this.announce(`🤖 ${reply}`);

View File

@@ -362,7 +362,6 @@ export class LocalVoiceSession {
} }
utterance.text = transcript.trim(); utterance.text = transcript.trim();
this.memory.addUserTurn(utterance);
this.options.logger.info("Local transcript", utterance.text); this.options.logger.info("Local transcript", utterance.text);
if (this.options.config.DEBUG_TEXT_EVENTS) { if (this.options.config.DEBUG_TEXT_EVENTS) {
console.log(`\n[you] ${utterance.text}`); console.log(`\n[you] ${utterance.text}`);
@@ -376,6 +375,7 @@ export class LocalVoiceSession {
reply = "지금은 답변 생성에 실패했습니다. 잠시 후 다시 말씀해 주세요."; reply = "지금은 답변 생성에 실패했습니다. 잠시 후 다시 말씀해 주세요.";
} }
this.memory.addUserTurn(utterance);
this.memory.addAssistantTurn(reply); this.memory.addAssistantTurn(reply);
this.options.logger.info("Local reply", reply); this.options.logger.info("Local reply", reply);
if (this.options.config.DEBUG_TEXT_EVENTS) { if (this.options.config.DEBUG_TEXT_EVENTS) {

View File

@@ -12,6 +12,14 @@ export interface UserUtterance {
text: string; text: string;
} }
function renderSpeakerLabel(speakerName?: string): string {
const normalized = speakerName?.trim();
if (!normalized || normalized === "unknown" || normalized === "local-user") {
return "사용자";
}
return `사용자(${normalized})`;
}
export class ConversationMemory { export class ConversationMemory {
private readonly turns: ConversationTurn[] = []; private readonly turns: ConversationTurn[] = [];
@@ -50,9 +58,9 @@ export class ConversationMemory {
.slice(-this.maxTurns) .slice(-this.maxTurns)
.map((turn) => { .map((turn) => {
if (turn.role === "assistant") { if (turn.role === "assistant") {
return `[assistant]\n${turn.text}`; return `비서: ${turn.text}`;
} }
return `[user speaker_id=${turn.speakerId ?? "unknown"} speaker_name=${turn.speakerName ?? "unknown"}]\n${turn.text}`; return `${renderSpeakerLabel(turn.speakerName)}: ${turn.text}`;
}) })
.join("\n\n"); .join("\n\n");
@@ -62,9 +70,10 @@ export class ConversationMemory {
"최근 대화:", "최근 대화:",
historyBlock, historyBlock,
"", "",
"이번 발화:", "지금 방금 들은 말:",
`[user speaker_id=${currentUtterance.speakerId} speaker_name=${currentUtterance.speakerName}]`, `${renderSpeakerLabel(currentUtterance.speakerName)}: ${currentUtterance.text}`,
currentUtterance.text, "",
"위 마지막 말에 자연스럽게 바로 답변해라. speaker_id나 speaker_name 같은 메타 정보를 자기소개처럼 반복하지 마라.",
].join("\n"); ].join("\n");
} }

View File

@@ -8,6 +8,8 @@ const ASSISTANT_INSTRUCTIONS = [
"기본은 한 문장, 길어도 두 문장을 넘기지 않는다.", "기본은 한 문장, 길어도 두 문장을 넘기지 않는다.",
"말투는 자연스러운 한국어로 유지한다.", "말투는 자연스러운 한국어로 유지한다.",
"speaker_id와 speaker_name은 화자 구분용이므로 필요할 때만 자연스럽게 반영한다.", "speaker_id와 speaker_name은 화자 구분용이므로 필요할 때만 자연스럽게 반영한다.",
"자기소개를 반복하지 말고, 사용자가 정체를 물었을 때만 짧게 소개한다.",
"\"저는 화자입니다\" 같은 어색한 메타 응답은 하지 않는다.",
"잘 못 들었거나 의미가 불명확하면 짧게 다시 물어본다.", "잘 못 들었거나 의미가 불명확하면 짧게 다시 물어본다.",
"목록, 마크다운, 코드블록은 쓰지 않는다.", "목록, 마크다운, 코드블록은 쓰지 않는다.",
"생각 과정을 드러내지 말고 최종 답변만 말한다.", "생각 과정을 드러내지 말고 최종 답변만 말한다.",