Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 85 additions & 59 deletions TASK.md
Original file line number Diff line number Diff line change
@@ -1,67 +1,93 @@
## Redis ๊ธฐ๋ฐ˜ ์ž„๋ฒ ๋”ฉ ํ ๋„์ž… ๊ณ„ํš
## ์ค‘๋ณต ์งˆ๋ฌธ ํŒ๋ณ„ ๊ฐœ์„  ๊ณ„ํš
- ๊ธฐ์กด ask_message_embedding์€ ๋‹จ์ง€ ์ด๋ณธ ์งˆ๋ฌธ์˜ ๋ฒกํ„ฐ ๊ฐ’๋งŒ ์ €์žฅํ•˜๋ฏ€๋กœ ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์—์„œ ์ž‘๋™์ค‘์ธ ๋งฅ๋ฝ ์ฃผ์ž… ๋ถ€๋ถ„์—์„œ ๋ถˆ์ผ์น˜๊ฐ€ ๊ฐ€๋Šฅํ•จ
- ์ตœ๊ทผ 2ํ„ด(์‚ฌ์šฉ์ž ์งˆ๋ฌธ)์„ ๋‹ค ๋ถˆ๋Ÿฌ์™€ ์ด๋ฒˆ ์งˆ๋ฌธ๊ณผ ํ•ฉ์ณ ํ•˜๋‚˜์˜ ํ…์ŠคํŠธ ๋ธ”๋ก์œผ๋กœ ๋งŒ๋“ ๋‹ค. ์˜ˆ: `[Q-2]\n[Q-1]\n[Q-now]`.
- ์—†์„ ์‹œ ํ˜„์žฌ ์งˆ๋ฌธ๋งŒ ์ €์žฅ
- ํ”„๋กฌํ”„ํŠธ์— ์ฃผ์ž…ํ•˜๋Š” ํžˆ์Šคํ† ๋ฆฌ๋„ 2ํ„ด์ด๋ฏ€๋กœ ์บ์‹œ ๋น„๊ต ๊ธฐ์ค€๊ณผ ์™„์ „ํžˆ ์ผ์น˜ํ•ด follow-up ์งˆ๋ฌธ ๋ฐ˜๋ณต ์‹œ ์บ์‹œ ์ •ํ™•๋„๊ฐ€ ์˜ฌ๋ผ๊ฐ„๋‹ค.
- ์ž„๋ฒ ๋”ฉ์€ ์งˆ๋ฌธ๋‹น 1ํšŒ๋งŒ ์ถ”๊ฐ€๋กœ ์ˆ˜ํ–‰๋˜๋ฏ€๋กœ ๋น„์šฉ ์ฆ๊ฐ€๋Š” ๋ฏธ๋ฏธํ•˜๋ฉฐ, ๊ธธ์ด๊ฐ€ ๊ธธ ๊ฒฝ์šฐ ์•ž ํ„ด์„ ์ค„์ด๋Š” ๋กœ์ง์„ ํ—ฌํผ์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.

### 1. ์ ํ•ฉ์„ฑ ๊ฒ€ํ† 
- ๊ธฐ์กด Node.js ์ž„๋ฒ ๋”ฉ API๋Š” ์œ ์ง€ํ•˜๋ฉด์„œ๋„ Redis ํ๋ฅผ ์‚ฌ์ด์— ๋‘๋ฉด Spring Boot โ†’ Node.js ๊ฐ„์˜ ๋А์Šจํ•œ ๊ฒฐํ•ฉ๊ณผ ์žฌ์‹œ๋„๋ฅผ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์Œ.
- Spring Boot ํ”„๋กœ๋“€์„œ๋Š” ์ด๋ฏธ Redis LPUSH ๋กœ์ง์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์–ด ์ถ”๊ฐ€ ๊ฐœ๋ฐœ ๋ถ€๋‹ด์ด ๋‚ฎ์Œ.
- Node.js ์ปจ์Šˆ๋จธ๋Š” BRPOP ๊ธฐ๋ฐ˜ ๋ฌดํ•œ ๋ฃจํ”„๋กœ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ํ˜„์žฌ OpenAI ์ž„๋ฒ ๋”ฉ ํ˜ธ์ถœ ํ๋ฆ„๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์—ฐ๊ฒฐ๋จ.
- Redis ๋ฆฌ์ŠคํŠธ๋Š” ์„ ์ž…์„ ์ถœ ํŠน์„ฑ์„ ์ œ๊ณตํ•˜๊ณ , ์žฅ์•  ์‹œ ์‹คํŒจ ํ(`embedding:failed`)๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ์šด์˜ํŒ€์ด ๋ชจ๋‹ˆํ„ฐ๋ง/์žฌ์ฒ˜๋ฆฌํ•˜๊ธฐ ์šฉ์ดํ•จ.
- ๊ณ ๊ฐ€์šฉ์„ฑ Redis ์ธํ”„๋ผ๊ฐ€ ์ „์ œ๋ผ์•ผ ํ•˜๋ฉฐ, ํ ์ ์ฒด/์ค‘๋ณต ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ์•Œ๋žŒ ์ฒด๊ณ„๊ฐ€ ํ•„์š”ํ•จ.
### ๋งํˆฌ ID ๋…๋ฆฝ ์ปฌ๋Ÿผ ๋ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ณ„ํš
1. **DB ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ**: ์ค‘๋ณต ์งˆ๋ฌธ ํŒ๋ณ„ ์ „์šฉ์ž„์„ ๋ช…์‹œํ•˜๊ธฐ ์œ„ํ•ด `ask_message_embedding`์„ `ask_question_cache`(๋˜๋Š” `ask_duplicate_embedding`)์œผ๋กœ ๋ฆฌ๋„ค์ž„ํ•œ๋‹ค. ๋ฆฌ๋„ค์ž„ ํ›„ `speech_tone_id integer NOT NULL DEFAULT -1` ์ปฌ๋Ÿผ์„ ์ถ”๊ฐ€ํ•˜๊ณ , ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ๋Š” tone ์ •๋ณด๊ฐ€ ์—†์–ด ์žฌ์‚ฌ์šฉ ๊ฐ€์น˜๊ฐ€ ๋‚ฎ์œผ๋ฏ€๋กœ ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ์งํ›„ `TRUNCATE` ๋˜๋Š” `DELETE`๋กœ ์ „๋Ÿ‰ ์‚ญ์ œํ•œ๋‹ค.
2. **์—”ํ‹ฐํ‹ฐ/๋ ˆํฌ์ง€ํ† ๋ฆฌ ์—…๋ฐ์ดํŠธ**: `ask-message-embedding.repository.ts`์—์„œ `MessageEmbedding` ํƒ€์ž…๊ณผ `upsertEmbedding`/`findSimilarEmbeddings` ๊ฒฐ๊ณผ์— `speechToneId` ํ•„๋“œ๋ฅผ ๋…ธ์ถœํ•œ๋‹ค.
3. **persistConversation ์ˆ˜์ •**: `session-history.service.ts`์—์„œ `persistConversation` ํ˜ธ์ถœ ์‹œ `speechTone` ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ƒˆ๋กœ ๋ฐ›์•„, ๋ฉ”์‹œ์ง€ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์—๋Š” ์•„๋ฌด ๋ณ€ํ™” ์—†์ด `embeddingRepository.upsertEmbedding`์—๋งŒ ์ „๋‹ฌํ•œ๋‹ค.
4. **์บ์‹œ ๋น„๊ต ๋ฐ ์žฌ์ž‘์„ฑ ๋กœ์ง**: `findCachedAnswer`๊ฐ€ `speechToneId`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ˆ˜์ •ํ•˜๊ณ , `qa.service.ts`/`qa.v2.service.ts`์—์„œ tone ID ๋น„๊ต ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ์บ์‹œ ์žฌ์ƒ ๋˜๋Š” tone ์žฌ์ž‘์„ฑ ๋ถ„๊ธฐ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค.
5. **๋ฐฑํ•„ ์ „๋žต(์˜ต์…˜)**: ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ด๊ด€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๋ณ„๋„ ๋ฐฐ์น˜๋ฅผ ์„ค๊ณ„ํ•ด `speech_tone_id`๋ฅผ ์ฑ„์šธ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ดˆ๊ธฐ์—๋Š” ๊ธฐ๋ณธ๊ฐ’ `-1`์„ tone ๋ถˆ๋ช… ๊ฐ’์œผ๋กœ ์‚ผ๊ณ  rewrite ํ”Œ๋กœ์šฐ๋ฅผ ๋”ฐ๋ฅธ๋‹ค.
6. **๋ช…๋ช… ๊ฐœ์„  ๊ฒ€ํ† **: ํ…Œ์ด๋ธ” ๋ฆฌ๋„ค์ž„๊ณผ ์ปฌ๋Ÿผ ์ถ”๊ฐ€๋Š” ๊ฐ™์€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์—์„œ ์ฒ˜๋ฆฌํ•˜๊ณ , ๊ด€๋ จ ์ฝ”๋“œ/SQL ๋ช…์นญ๋„ ์ผ๊ด„ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.

### 2. ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์š”
```
[Spring Boot] โ†’ LPUSH โ†’ [Redis List embedding:queue] โ†’ BRPOP โ†’ [Node.js Consumer] โ†’ OpenAI ์ž„๋ฒ ๋”ฉ โ†’ DB ์ €์žฅ
โ†˜ ์‹คํŒจ ์‹œ LPUSH embedding:failed
```
## ์บ์‹œ ์‘๋‹ต ๋งํˆฌ ์ •ํ•ฉ์„ฑ ๊ณ„ํš
1. **๋ชฉํ‘œ**: ์บ์‹œ์—์„œ ๊บผ๋‚ธ ๋‹ต๋ณ€์˜ ๋งํˆฌ๊ฐ€ API ์š”์ฒญ ๊ฐ’๊ณผ ์ผ์น˜ํ•˜๋ฉด ์ฆ‰์‹œ ์žฌ์‚ฌ์šฉํ•˜๊ณ , ๋ถˆ์ผ์น˜ํ•˜๋ฉด ๋™์ผ ๋‹ต๋ณ€์„ tone ์ „์šฉ LLM์œผ๋กœ ์žฌ์ž‘์„ฑํ•œ ๋’ค ์ „๋‹ฌํ•œ๋‹ค.
2. **tone ๊ฒ€์ฆ ์ˆœ์„œ**
- (a) `findCachedAnswer`๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ํ›„๋ณด ๋ฐฐ์—ด(์œ ์‚ฌ๋„ ๊ธฐ์ค€ ์ •๋ ฌ)์„ ์ˆœํšŒํ•˜๋ฉฐ `speechToneId === ์š”์ฒญ ๊ฐ’`์ธ ํ•ญ๋ชฉ์„ ์ฐพ๋Š”๋‹ค.
- (b) ๊ฐ™์€ ID๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ํ›„๋ณด๋ฅผ ์ฆ‰์‹œ ์žฌ์ƒํ•˜๊ณ , tone ์žฌ์ž‘์„ฑ์€ ์ƒ๋žตํ•œ๋‹ค.
- (c) ๊ฐ™์€ ID๊ฐ€ ํ•˜๋‚˜๋„ ์—†์œผ๋ฉด ์œ ์‚ฌ๋„ 1์ˆœ์œ„ ํ›„๋ณด๋ฅผ ์„ ํƒํ•ด `replace-tone.service.ts`์— ์ „๋‹ฌํ•˜๊ณ , tone๋งŒ ๋ฐ”๊พผ ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ „์†กํ•œ๋‹ค. (threshold ๋ฏธ๋‹ฌ์ด๋ฉด ๊ธฐ์กด์ฒ˜๋Ÿผ ์ƒˆ LLM ๋‹ต๋ณ€ ์ƒ์„ฑ)
3. **replace-tone.service.ts**
- ์‹œ๊ทธ๋‹ˆ์ฒ˜: `rewriteTone(answer: string, opts: { speechToneId: number; speechTonePrompt: string; llm?: LlmOverride })`.
- ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ
- System: "๋„ˆ๋Š” ํŽธ์ง‘์ž๋‹ค. ์•„๋ž˜ ์ฝ˜ํ…์ธ ์˜ ์˜๋ฏธ, ์‚ฌ์‹ค, ๊ตฌ์กฐ๋ฅผ ํ›ผ์†ํ•˜์ง€ ๋ง๊ณ , ์š”์ฒญ๋œ ๋งํˆฌ ์ง€์‹œ๋งŒ ๋ฐ˜์˜ํ•ด ๋‹ค์‹œ ์ž‘์„ฑํ•ด."
- User: ```tone ์ง€์‹œ: ${speechTonePrompt}
์›๋ฌธ: ${answer}```
- ๋ชจ๋ธ/ํ”„๋กœ๋ฐ”์ด๋”๋Š” ์šด์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด ๊ธฐ์กด QA ํŒŒ์ดํ”„๋ผ์ธ๊ณผ ๋™์ผํ•œ `generate` ๋ž˜ํผ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•œ๋‹ค(์ฆ‰, Ask ์š”์ฒญ์—์„œ ์„ ํƒ๋œ LLM ์„ค์ •์„ ์žฌ์‚ฌ์šฉ). temperature๋Š” 0~0.2, max_tokens๋Š” ์›๋ฌธ ๊ธธ์ด์™€ ๋น„์Šทํ•˜๊ฒŒ ๋งž์ถ˜๋‹ค.
- tone ์žฌ์ž‘์„ฑ ๊ฒฐ๊ณผ๊ฐ€ ๋น„์–ด ์žˆ๊ฑฐ๋‚˜ ์›๋ฌธ๊ณผ ์ง€๋‚˜์น˜๊ฒŒ ๋‹ค๋ฅด๋ฉด ์‹คํŒจ๋กœ ๊ฐ„์ฃผํ•˜๊ณ  ์บ์‹œ๋ฅผ ํฌ๊ธฐํ•œ ๋’ค RAG/LLM ๊ฒฝ๋กœ๋กœ ํด๋ฐฑํ•œ๋‹ค.
4. **์„œ๋น„์Šค ์—ฐ๋™** (`qa.service.ts`, `qa.v2.service.ts`)
- `findCachedAnswer`๊ฐ€ tone ID์™€ ํ•จ๊ป˜ ํ›„๋ณด ๋ฐฐ์—ด์„ ๋Œ๋ ค์ค„ ์ˆ˜ ์žˆ๋„๋ก ํ™•์žฅํ•˜๊ฑฐ๋‚˜, tone๋ณ„ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
- tone ๋™์ผ ํ›„๋ณด๊ฐ€ ์žˆ์œผ๋ฉด ๊ธฐ์กด `replayCachedAnswer`๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
- tone ๋ถˆ์ผ์น˜๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ์—” `rewriteTone` ํ˜ธ์ถœ ํ›„, SSE `answer` ์ด๋ฒคํŠธ์™€ `persistConversation`์— ์žฌ์ž‘์„ฑ๋œ ํ…์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  `speech_tone_id`๋ฅผ ๋ชฉํ‘œ ๊ฐ’์œผ๋กœ ์ €์žฅํ•œ๋‹ค.
5. **์šด์˜ ๊ณ ๋ ค์‚ฌํ•ญ**: tone ID์˜ ๊ธฐ๋ณธ๊ฐ’์„ `-1`(unknown)์œผ๋กœ ๋‘๊ณ , ์ด ๊ฐ’์€ tone ๋™์ผ ํ›„๋ณด ๊ฒ€์ƒ‰์—์„œ ๋งค์นญ๋˜์ง€ ์•Š๋„๋ก ์ฒ˜๋ฆฌํ•œ๋‹ค. ์ฆ‰, ๋ชจ๋“  ํ›„๋ณด๊ฐ€ `-1`์ด๋ฉด top-1 rewrite ๋Œ€์ƒ์œผ๋กœ๋งŒ ์‚ฌ์šฉ๋œ๋‹ค.

### 3. ๊ตฌํ˜„ ๊ณ„ํš
1. **์ปจ์Šˆ๋จธ ์›Œ์ปค ์ดˆ์•ˆ ์ž‘์„ฑ**
- `services/embedding.service.ts` ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” `processEmbeddingQueue` ๋ชจ๋“ˆ ์ž‘์„ฑ.
- Graceful shutdown, Concurrency(๋™์‹œ ์›Œ์ปค ์ˆ˜) ์˜ต์…˜, ๋กœ๊น…(์„ฑ๊ณต/์‹คํŒจ/์ฒ˜๋ฆฌ์‹œ๊ฐ„)์„ ํฌํ•จ.
2. **ํ ๋ฉ”์‹œ์ง€ ์Šคํ‚ค๋งˆ ํ™•์ •**
- `post_id`, `title`, `content`, `retryCount` ๋“ฑ์„ ํฌํ•จํ•˜๋Š” JSON ๊ตฌ์กฐ ์ •์˜ ๋ฐ ๋ฌธ์„œํ™”.
- ์ถ”ํ›„ schema ๋ณ€๊ฒฝ ๋Œ€๋น„ ๋ฒ„์ „ ํ•„๋“œ ๋„์ž… ๊ฒ€ํ† .
3. **์‹คํŒจ ์ฒ˜๋ฆฌ ๋ฐ ์žฌ์‹œ๋„ ์ •์ฑ…**
- ์‹คํŒจ ์‹œ `embedding:failed` ๋กœ ์ด๋™ ํ›„ ๊ฒฝ๊ณ  ๋กœ๊ทธ ๊ธฐ๋ก.
- ์žฌ์‹œ๋„ ์›Œ์ปค(์ฃผ๊ธฐ์  RPOP โ†’ LPUSH) ๋˜๋Š” Ops ์ˆ˜๋™ ํŠธ๋ฆฌ๊ฑฐ ์ „๋žต ๊ฒฐ์ •.
4. **์šด์˜ ๋ชจ๋‹ˆํ„ฐ๋ง**
- `LLEN embedding:queue`, `embedding:failed` ๋ฉ”ํŠธ๋ฆญ์„ Prometheus/Grafana ๋˜๋Š” ๊ธฐ์กด ๋ชจ๋‹ˆํ„ฐ๋ง์— ์—ฐ๋™.
- ์•Œ๋žŒ ๊ธฐ์ค€: ํ ๊ธธ์ด ์ž„๊ณ„์น˜, ์‹คํŒจ ํ ๋ˆ„์ , ์›Œ์ปค ๋ฏธ์‘๋‹ต.
5. **๋ฐฐํฌ ์ „๋žต**
- Node.js ์ปจ์Šˆ๋จธ๋ฅผ ๊ธฐ์กด ์„œ๋ฒ„ ํ”„๋กœ์„ธ์Šค์™€ ๋ถ„๋ฆฌ( Docker ์ปจํ…Œ์ด๋„ˆ)ํ•˜์—ฌ ๋…๋ฆฝ ์šด์˜.
- Spring Boot ์ธก์€ ์ด๋ฏธ ๊ตฌํ˜„๋œ LPUSH ๋กœ์ง์„ ํ™œ์„ฑํ™”ํ•˜๊ณ , ๊ธฐ์กด REST ์ž„๋ฒ ๋”ฉ ํ˜ธ์ถœ์€ ์ ์ง„์ ์œผ๋กœ ๊ฐ์ถ•.
### ๊ตฌํ˜„ ์šฐ์„ ์ˆœ์œ„ ๋ฐ ๋‹จ๊ณ„
1. **์ค‘๋ณต ์งˆ๋ฌธ ํŒ๋ณ„ ๊ฐœ์„ **: ํžˆ์Šคํ† ๋ฆฌ 2ํ„ด์„ ํ•ฉ์นœ ํ…์ŠคํŠธ ๋ธ”๋ก ๊ธฐ๋ฐ˜์œผ๋กœ ์ž„๋ฒ ๋”ฉ์„ ์ €์žฅํ•˜๊ณ  ์บ์‹œ ๋น„๊ต์— ํ™œ์šฉํ•œ๋‹ค. (์ƒ๋‹จ ๊ณ„ํš์„ ๋จผ์ € ์ ์šฉ)
2. **๋งํˆฌ ID ์ปฌ๋Ÿผ ์ถ”๊ฐ€**: ์œ„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ณ„ํš๋Œ€๋กœ DB ๋ฐ ๋ ˆํฌ์ง€ํ† ๋ฆฌ๋ฅผ ํ™•์žฅํ•ด tone ์ •๋ณด๊ฐ€ ์˜์†๋˜๋„๋ก ํ•œ๋‹ค.
3. **๋งํˆฌ ์กฐ์ • ๊ธฐ๋Šฅ ๋„์ž…**: `replace-tone.service.ts` ๊ตฌํ˜„๊ณผ `replayCachedAnswer` ํ†ตํ•ฉ์œผ๋กœ ์บ์‹œ ํžˆํŠธ ์‹œ tone ๊ฒ€์ฆ/์žฌ์ž‘์„ฑ ํ”Œ๋กœ์šฐ๋ฅผ ์™„์„ฑํ•œ๋‹ค.
4. **ํ›„์† ์ตœ์ ํ™”**: tone ์ปฌ๋Ÿผ์ด ์ฑ„์›Œ์ง„ ์ดํ›„์—๋Š” tone ์ผ์น˜ ์—ฌ๋ถ€๋ฅผ ๋จผ์ € ํ™•์ธํ•ด tone ๋ถ„์„/์žฌ์ž‘์„ฑ ํ˜ธ์ถœ์„ ์ตœ์†Œํ™”ํ•œ๋‹ค.

### 4. ์ถ”๊ฐ€ ๊ณ ๋ ค์‚ฌํ•ญ
- ๋ฉฑ๋“ฑ์„ฑ ํ™•๋ณด๋ฅผ ์œ„ํ•ด ์ปจ์Šˆ๋จธ ์ฒ˜๋ฆฌ ์™„๋ฃŒ ํ›„ Redis ์ธก์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ฑฐํ–ˆ๋Š”์ง€(์ด๋ฏธ BRPOP ๋กœ ์ œ๊ฑฐ) ํ™•์ธํ•˜๊ณ , ์‹คํŒจ ์žฌ์ฒ˜๋ฆฌ ์‹œ ์ค‘๋ณต ์‚ฝ์ž… ๋ฐฉ์ง€ ๋กœ์ง ๊ฒ€ํ† .
- OpenAI API ํ˜ธ์ถœ ์‹คํŒจ ์‹œ exponential backoff ์ ์šฉ ์—ฌ๋ถ€.
- ๊ธด ์ฝ˜ํ…์ธ  ์ž„๋ฒ ๋”ฉ ์‹œ chunking ๋กœ์ง(`chunkText`)๊ณผ ํ ๋ฉ”์‹œ์ง€ ํฌ๊ธฐ ์ œํ•œ ๊ฒ€ํ† .
- ๋ณด์•ˆ: Redis ์ ‘๊ทผ ์ œ์–ด, TLS ํ•„์š” ์—ฌ๋ถ€ ํ™•์ธ.
## ์ƒ์„ธ ๊ตฌํ˜„ ์„ค๊ณ„
1. **๋งˆ์ด๊ทธ๋ ˆ์ด์…˜**
- ์ƒˆ SQL ํŒŒ์ผ(์˜ˆ: `docs/migrations/2025-XX-ask-question-cache-tone.sql`)์„ ์ž‘์„ฑํ•˜์—ฌ ํ…Œ์ด๋ธ” ๋ฆฌ๋„ค์ž„(`ALTER TABLE ask_message_embedding RENAME TO ask_question_cache;`) โ†’ ์ปฌ๋Ÿผ ์ถ”๊ฐ€(`ADD COLUMN speech_tone_id integer NOT NULL DEFAULT -1;`) โ†’ ๊ธฐ์กด ๋ฐ์ดํ„ฐ ์‚ญ์ œ(`TRUNCATE ask_question_cache;`)๋ฅผ ์ˆœ์ฐจ ์ง„ํ–‰ํ•œ๋‹ค.
- ํ•„์š”ํ•œ ๊ฒฝ์šฐ `speech_tone_id`์— ์ธ๋ฑ์Šค(`CREATE INDEX ... ON ask_question_cache(owner_user_id, requester_user_id, speech_tone_id)`)๋ฅผ ์ถ”๊ฐ€ํ•ด tone ๋ณ„ ๊ฒ€์ƒ‰์„ ๋น ๋ฅด๊ฒŒ ํ•œ๋‹ค.
2. **๋ ˆํฌ์ง€ํ† ๋ฆฌ ๊ณ„์ธต**
- `ask-message-embedding.repository.ts` (๋ฆฌ๋„ค์ž„ ํ›„ `ask-question-cache.repository.ts` ๊ณ ๋ ค)
- `MessageEmbedding`/`SimilarMessage` ์ธํ„ฐํŽ˜์ด์Šค์— `speechToneId: number` ์ถ”๊ฐ€. ๊ธฐ๋ณธ๊ฐ’ `-1`์€ ๋ณ„๋„ ์ƒ์ˆ˜๋กœ ๊ด€๋ฆฌํ•œ๋‹ค.
- `upsertEmbedding` INSERT/UPDATE ๋ฌธ์— `speech_tone_id` ์ปฌ๋Ÿผ์„ ํฌํ•จํ•˜๊ณ , ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ tone ID๋ฅผ ๋ฐ›๋Š”๋‹ค.
- `findSimilarEmbeddings` SELECT์— `speech_tone_id AS "speechToneId"`๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , ๋ฐ˜ํ™˜ ํƒ€์ž…์— ํฌํ•จ.
- `session-history.service.ts`
- `persistConversation` ํŒŒ๋ผ๋ฏธํ„ฐ์— `speechTone?: number`๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ assistant ํ†ค์„ ์ „๋‹ฌ.
- `embeddingRepository.upsertEmbedding` ํ˜ธ์ถœ ์‹œ ์ƒˆ tone ๊ฐ’์„ ์ „๋‹ฌ.
- `findCachedAnswer`๊ฐ€ `speechToneId`๋ฅผ ํ•จ๊ป˜ ํฌํ•จํ•œ ํ›„๋ณด ๋ฐฐ์—ด์„ ๋ฆฌํ„ดํ•˜๋„๋ก ์ˆ˜์ • (ex: `{ answer, searchPlan, retrievalMeta, similarity, speechToneId }`).
3. **์„œ๋น„์Šค ๊ณ„์ธต (QA)**
- `qa.service.ts` / `qa.v2.service.ts`
- ์บ์‹œ ์กฐํšŒ ๊ฒฐ๊ณผ๋ฅผ tone๋ณ„๋กœ ๋ถ„๋ฅ˜: `const matched = candidates.find(c => c.speechToneId === speechTone)`.
- `matched`๊ฐ€ ์žˆ์œผ๋ฉด ๊ธฐ์กด `replayCachedAnswer(matched)` ์‹คํ–‰.
- ์—†๊ณ  ํ›„๋ณด ๋ฐฐ์—ด์ด ์กด์žฌํ•˜๋ฉด `const primary = candidates[0];` ๋กœ ์„ ์ • ํ›„ `replaceTone.rewriteTone(primary.answer, speechTonePrompt)` ํ˜ธ์ถœ.
- ์žฌ์ž‘์„ฑ ๊ฒฐ๊ณผ ํ…์ŠคํŠธ๋ฅผ SSE `answer` ์ด๋ฒคํŠธ๋กœ ํ˜๋ ค๋ณด๋‚ด๊ณ , `persistConversation`์— `speechTone` ๊ฐ’์„ ๋ช…์‹œํ•ด ์ €์žฅ.
- ์žฌ์ž‘์„ฑ ์—ฌ๋ถ€๋ฅผ `DebugLogger`์— ๋‚จ๊ธฐ๊ณ , ์‹คํŒจ ์‹œ ๊ธฐ์กด RAG/LLM ๊ฒฝ๋กœ๋กœ ํด๋ฐฑํ•œ๋‹ค.
4. **replace-tone.service.ts**
- ์ตœ์ข… ์‹œ๊ทธ๋‹ˆ์ฒ˜: `export const rewriteTone = async (
answer: string,
opts: { speechToneId: number; speechTonePrompt: string; llm?: LlmOverride }
): Promise<string>`.
- ๋‚ด๋ถ€์—์„œ `generate`๋ฅผ ํ˜ธ์ถœํ•˜๋ฉฐ, ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์— "๋‹ค์Œ ๋‹ต๋ณ€์˜ ๋‚ด์šฉ์€ ์œ ์ง€ํ•˜๊ณ  tone๋งŒ ์•„๋ž˜ ์ง€์‹œ์— ๋งž์ถฐ๋ผ" ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
- ์‘๋‹ต์ด ๋น„๊ฑฐ๋‚˜ ๋„ˆ๋ฌด ์งง์œผ๋ฉด ์‹คํŒจ๋กœ ๊ฐ„์ฃผํ•˜๊ณ  ์˜ค๋ฅ˜๋ฅผ throw.
5. **SSE / ์ด๋ฒคํŠธ**
- tone ์žฌ์ž‘์„ฑ ์‹œ์—๋„ `search_plan`, `context` ์ด๋ฒคํŠธ๋Š” ์บ์‹œ๋œ ๊ฐ’ ๊ทธ๋Œ€๋กœ ์žฌ์ƒํ•˜๊ณ  `answer` ์ด๋ฒคํŠธ์—๋งŒ ์ˆ˜์ •๋œ ํ…์ŠคํŠธ๋ฅผ ์ „์†ก.
- `session_saved` ์ด๋ฒคํŠธ์— `cached: true`์™€ `tone_rewritten: true` (์ถ”๊ฐ€ ์†์„ฑ) ๋“ฑ์„ ํฌํ•จํ•ด ํ”„๋ก ํŠธ์—์„œ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.
## ์ปค๋ฐ‹ ๋‹จ์œ„ ๊ตฌํ˜„ ๊ณ„ํš
1. **๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ + DB ๋ช…๋ช… ์ •๋ฆฌ**
- `docs/migrations/2025-XX-ask-question-cache-tone.sql` ์ถ”๊ฐ€: ํ…Œ์ด๋ธ” ๋ฆฌ๋„ค์ž„ โ†’ ์ปฌ๋Ÿผ ์ถ”๊ฐ€ โ†’ TRUNCATE โ†’ ์ธ๋ฑ์Šค ์ƒ์„ฑ.
- `README.md` ๋“ฑ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€์ด๋“œ์— ์ƒˆ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ๋ฐฉ๋ฒ• ์ถ”๊ฐ€.

### 5. ๋‹ค์Œ ๋‹จ๊ณ„
- [x] Node.js ์ปจ์Šˆ๋จธ ์ดˆ์•ˆ ์ฝ”๋“œ ์ž‘์„ฑ ๋ฐ ํ™˜๊ฒฝ ๋ณ€์ˆ˜(`REDIS_URL`, `EMBEDDING_QUEUE_KEY`) ์ •๋ฆฌ.
- [ ] ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ Redis ๋กœ์ปฌ ์ธ์Šคํ„ด์Šค์™€ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ง„ํ–‰.
- [ ] ๋ชจ๋‹ˆํ„ฐ๋ง/์•Œ๋žŒ ๊ตฌ์„ฑ ๋…ผ์˜.
2. **๋ ˆํฌ์ง€ํ† ๋ฆฌ ๊ณ„์ธต ์—…๋ฐ์ดํŠธ**
- (์„ ํƒ) `ask-message-embedding.repository.ts` ํŒŒ์ผ๋ช…์„ `ask-question-cache.repository.ts`๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  import ๊ฒฝ๋กœ ์ˆ˜์ •.
- ์ธํ„ฐํŽ˜์ด์Šค/์ฟผ๋ฆฌ์— `speechToneId` ๋ฐ˜์˜, upsert ํŒŒ๋ผ๋ฏธํ„ฐ์— tone ID ์ถ”๊ฐ€.

### 6. ์ปจํ…Œ์ด๋„ˆ/๋ฐฐํฌ ์„ค๊ณ„
- **๊ธฐ๋ณธ ์ด๋ฏธ์ง€ ์žฌ์‚ฌ์šฉ**: ๊ธฐ์กด `Dockerfile` ๋กœ ๋นŒ๋“œํ•œ ๋™์ผ ์ด๋ฏธ์ง€๋ฅผ `api`(Express)์™€ `worker`(์ปจ์Šˆ๋จธ)๊ฐ€ ๊ณต์œ , ๊ฐ ์ปจํ…Œ์ด๋„ˆ๋Š” `command` ๋งŒ ๋‹ค๋ฅด๊ฒŒ ์ง€์ •.
- **์—”ํŠธ๋ฆฌํฌ์ธํŠธ ๋ถ„๋ฆฌ**: `src/worker/queue-consumer.ts` ์ถ”๊ฐ€ โ†’ `tsc` ๊ฒฐ๊ณผ๊ฐ€ `dist/worker/queue-consumer.js` ๋กœ ์ƒ์„ฑ๋˜๋„๋ก ๋นŒ๋“œ ๊ฒฝ๋กœ ํ™•์ธ. `package.json` ์— `worker` ์Šคํฌ๋ฆฝํŠธ(`node dist/worker/queue-consumer.js`) ๋“ฑ๋ก.
- **Docker Compose ์ดˆ์•ˆ**
```yaml
services:
api:
build: .
command: ["node", "dist/server.js"]
ports: ["3000:3000"]
env_file: .env
depends_on: [redis]
3. **์„ธ์…˜ ํžˆ์Šคํ† ๋ฆฌ ์„œ๋น„์Šค ์ˆ˜์ •**
- `persistConversation` ์‹œ๊ทธ๋‹ˆ์ฒ˜์— `speechTone?: number` ์ถ”๊ฐ€.
- `embeddingRepository.upsertEmbedding` ํ˜ธ์ถœ๋ถ€์— tone ์ „๋‹ฌ.
- `findCachedAnswer` ๋ฐ˜ํ™˜ ํƒ€์ž…์„ tone ์ •๋ณด ํฌํ•จ ๋ฐฐ์—ด๋กœ ๋ณ€๊ฒฝ.

worker:
build: .
command: ["node", "dist/worker/queue-consumer.js"]
env_file: .env
depends_on: [redis]
4. **QA ์„œ๋น„์Šค ์บ์‹œ ๋กœ์ง ๊ฐœํŽธ**
- `qa.service.ts`/`qa.v2.service.ts`์—์„œ tone ์ผ์น˜ ํ›„๋ณด ์šฐ์„  ์‚ฌ์šฉ, ๋ถˆ์ผ์น˜ ์‹œ `replaceTone` ๊ฒฝ๋กœ๋กœ ๋ถ„๊ธฐ.
- SSE ์ด๋ฒคํŠธ/`persistConversation`์— ์žฌ์ž‘์„ฑ ๊ฒฐ๊ณผ ๋ฐ tone ID ๋ฐ˜์˜.
- DebugLogger ๋กœ๊น… ์ถ”๊ฐ€.

redis:
image: redis:7-alpine
```
- **ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ณต์œ **: `.env` ์— Redis ์ ‘์† ์ •๋ณด(`REDIS_HOST`, `REDIS_PORT`, `REDIS_URL` ๋“ฑ)์™€ ํ ์ด๋ฆ„, ์‹คํŒจ ํ ์ด๋ฆ„ ๋“ฑ์„ ๋ช…์‹œํ•˜๊ณ  ๋‘ ์„œ๋น„์Šค ๋ชจ๋‘ ๋กœ๋“œ.
- **์šด์˜ ๊ณ ๋ ค**: `worker` ์ปจํ…Œ์ด๋„ˆ ์Šค์ผ€์ผ ์•„์›ƒ(์˜ˆ: `docker compose up --scale worker=3`)์— ๋Œ€๋น„ํ•ด ์ž‘์—… ๋ฉฑ๋“ฑ์„ฑ ํ™•์ธ. ์žฅ์•  ์‹œ ๊ฐœ๋ณ„ ์ปจํ…Œ์ด๋„ˆ ์žฌ์‹œ์ž‘ ์ „๋žต, ๋กœ๊ทธ ์ˆ˜์ง‘ ๊ฒฝ๋กœ(์˜ˆ: stdoutโ†’EFK) ์ •์˜.
5. **replace-tone.service.ts ์‹ ๊ทœ ์ถ”๊ฐ€**
- `rewriteTone` ํ•จ์ˆ˜ ๊ตฌํ˜„, ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ/์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ํฌํ•จ.
- ํ•„์š” ์‹œ `qa.prompts.ts`์— tone ์ „์šฉ ํ”„๋กฌํ”„ํŠธ ์ž์‚ฐ ์ถ”๊ฐ€.
46 changes: 46 additions & 0 deletions docs/history-tasks/ASK_DUPLICATE_CACHE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# ASK ์ค‘๋ณต ์งˆ๋ฌธ ์บ์‹œ & Tone Rewrite ํšŒ๊ณ 

์ด๋ฒˆ ์ž‘์—…์€ โ€œ์ค‘๋ณต ์งˆ๋ฌธ ์บ์‹œ๋ฅผ ๋” ๋˜‘๋˜‘ํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ , tone ์ •ํ•ฉ์„ฑ์„ ์ง€ํ‚ค๋ฉด์„œ๋„ ๋น„์šฉ์„ ์ค„์ด์žโ€๋ผ๋Š” ๋ชฉํ‘œ๋กœ ์ง„ํ–‰ํ–ˆ๋‹ค. ์•„๋ž˜ ์ •๋ฆฌ๋Š” ๊ณผ์žฅ ์—†์ด ์šฐ๋ฆฌ๊ฐ€ ์‹ค์ œ๋กœ ๋งˆ์ฃผ์นœ ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ ๊ณผ์ •, ๊ทธ๋ฆฌ๊ณ  ๊ทธ ๊ฒฐ๊ณผ๋‹ค.

## ์™œ ์†๋ดค๋‚˜
- **Follow-up ์งˆ๋ฌธ ํ’ˆ์งˆ**: ํ”„๋กฌํ”„ํŠธ์—๋Š” ํ•ญ์ƒ ์ง์ „ 2ํ„ด์ด ๋ถ™์ง€๋งŒ, ์บ์‹œ๋Š” ๋‹จ์ผ ์งˆ๋ฌธ ๋ฒกํ„ฐ๋งŒ ์ €์žฅํ–ˆ๋‹ค. follow-up ์งˆ๋ฌธ์ด ๋“ค์–ด์˜ค๋ฉด ์บ์‹œ๊ฐ€ ์—‡๋‚˜๊ฐ€๊ฑฐ๋‚˜, ์œ ์‚ฌ๋„ ๋น„๊ต ์ž์ฒด๊ฐ€ ์–ด๋ ต๋‹ค.
- **Tone ๋ถˆ์ผ์น˜**: ์บ์‹œ์—์„œ ๊บผ๋‚ธ ๋‹ต๋ณ€์˜ ๋งํˆฌ๊ฐ€ ์š”์ฒญ ๊ฐ’๊ณผ ๋‹ค๋ฅด๋ฉด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ๊นจ์ง„๋‹ค. tone ์ •๋ณด๋ฅผ ์บ์‹œ์— ๊ฐ™์ด ์ €์žฅํ•˜์ง€ ์•Š์œผ๋ฉด, ๊ฒฐ๊ตญ ์ƒˆ LLM ํ˜ธ์ถœ์„ ํ•ด์•ผ ํ–ˆ๋‹ค.
- **์ฝ˜ํ…์ธ  ์ •ํ•ฉ์„ฑ**: ์‚ฌ์šฉ์ž๊ฐ€ ๊ธ€์„ ์ˆ˜์ •/์‚ญ์ œํ•ด ์ž„๋ฒ ๋”ฉ์ด ์ƒˆ๋กœ ์ƒ์„ฑ๋  ๋•Œ, ์˜ˆ์ „ ์ค‘๋ณต ์งˆ๋ฌธ ์บ์‹œ๊ฐ€ ๋‚จ์•„ ์žˆ์œผ๋ฉด ์ตœ์‹  ๋ณธ๋ฌธ๊ณผ ์–ด๊ธ‹๋‚œ ๋‹ต๋ณ€์ด ํŠ€์–ด๋‚˜์˜จ๋‹ค.

## ์–ด๋–ป๊ฒŒ ํ’€์—ˆ๋‚˜

### 1. ์บ์‹œ ์Šคํ‚ค๋งˆ + tone ๋ฉ”ํƒ€
- `ask_message_embedding`์„ `ask_question_cache`๋กœ ๋ฆฌ๋„ค์ž„ํ•˜๊ณ , `speech_tone_id integer NOT NULL DEFAULT -1`์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค. (ํŒŒ์ผ: `docs/migrations/2025-03-ask-question-cache-tone.sql`)
- ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์งํ›„ `TRUNCATE`๋กœ tone ์ •๋ณด๊ฐ€ ์—†๋Š” ์บ์‹œ๋ฅผ ๋น„์› ๋‹ค. ๋•๋ถ„์— tone-aware ๋กœ์ง๊ณผ ์ถฉ๋Œํ•˜๋Š” ๋ ˆ์ฝ”๋“œ๊ฐ€ ๋‚จ์ง€ ์•Š์•˜๋‹ค.

### 2. ํžˆ์Šคํ† ๋ฆฌ 2ํ„ด์„ ํ•ฉ์ณ ์ž„๋ฒ ๋”ฉ
- `buildDuplicateQuestionBlock`(`session-history.service.ts:22`)์ด `[Q-2]`, `[Q-1]`, `[Q-now]` ๋ธ”๋ก์„ ๋งŒ๋“ค์–ด์ค€๋‹ค. ๊ธธ์ด๊ฐ€ ๊ธธ๋ฉด ์•ž์„  ํ„ด๋ถ€ํ„ฐ ์ž๋ฅธ๋‹ค.
- `qa.service.ts:172`, `qa.v2.service.ts:154`์—์„œ `[ํ˜„์žฌ ์งˆ๋ฌธ, ์ค‘๋ณต ์งˆ๋ฌธ ๋ธ”๋ก]`์„ ๋™์‹œ์— ์ž„๋ฒ ๋”ฉํ•œ๋‹ค.
- ์ฒซ ๋ฒˆ์งธ ๋ฒกํ„ฐ โ†’ RAG ๊ฒ€์ƒ‰์šฉ
- ๋‘ ๋ฒˆ์งธ ๋ฒกํ„ฐ โ†’ ์บ์‹œ ์ €์žฅ/์กฐํšŒ์šฉ
- `persistConversation`(`session-history.service.ts:68`)์ด answer tone๊ณผ ์ค‘๋ณต ์งˆ๋ฌธ ๋ฒกํ„ฐ๋ฅผ ํ•จ๊ป˜ `ask_question_cache`์— upsertํ•œ๋‹ค.

### 3. tone-aware ์บ์‹œ ์กฐํšŒ
- `findCachedAnswer`(`session-history.service.ts:146`)๊ฐ€ owner, requester, post, category ์กฐ๊ฑด์— ๋งž๋Š” ํ›„๋ณด๋ฅผ tone ID์™€ ํ•จ๊ป˜ ๋Œ๋ ค์ค€๋‹ค.
- `selectToneAwareCacheCandidate`(`session-history.service.ts:36`)๊ฐ€ ์š”์ฒญ tone๊ณผ ๋™์ผํ•œ ํ›„๋ณด๋ฅผ ๊ณ ๋ฅด๊ณ , ์—†์œผ๋ฉด top-1 ํ›„๋ณด๋ฅผ rewrite ๋Œ€์ƒ์œผ๋กœ ์ง€์ •ํ•œ๋‹ค.
- `qa.service.ts:192`, `qa.v2.service.ts:175`์—์„œ tone์ด ๋งž๋Š” ์บ์‹œ๋Š” ๊ทธ๋Œ€๋กœ ์žฌ์ƒํ•˜๊ณ , tone์ด ๋‹ค๋ฅด๋ฉด rewrite ๊ฒฝ๋กœ๋กœ ๋ถ„๊ธฐํ•œ๋‹ค.

### 4. `replace-tone.service.ts` ๋””ํ…Œ์ผ
- ๊ธฐ์กด `generate` ๋ž˜ํผ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด์„œ system/user ํ”„๋กฌํ”„ํŠธ๋ฅผ tone ๊ต์ฒด์— ๋งž์ถฐ ๊ณ ์ •ํ–ˆ๋‹ค.
- **Ask v2**: ์บ์‹œ โ†’ tone mismatch โ†’ `rewriteTone`๋งŒ ํ˜ธ์ถœํ•˜๋ฏ€๋กœ LLM ํ˜ธ์ถœ ์ˆ˜๊ฐ€ 2 โ†’ 1๋กœ ์ค„์–ด๋“ ๋‹ค.
**Ask v1**: LLM ํ˜ธ์ถœ ์ˆ˜๋Š” ๋™์ผํ•˜์ง€๋งŒ tone ์žฌ์ž‘์„ฑ์€ ์›๋ฌธ๋งŒ ๋„ฃ์œผ๋‹ˆ ์ž…๋ ฅ ํ† ํฐ์ด ์ค„์–ด ๋น„์šฉ ์ ˆ๊ฐ์ด ๋œ๋‹ค.
- tone ์žฌ์ž‘์„ฑ ์‹คํŒจ ์‹œ์—๋Š” ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ณ  RAG ๊ฒฝ๋กœ๋กœ ํด๋ฐฑํ•œ๋‹ค. ์„ฑ๊ณตํ•˜๋ฉด SSE `answer` ์ด๋ฒคํŠธ์™€ `persistConversation`์— tone ID๋ฅผ ์ €์žฅํ•œ๋‹ค.

### 5. ์ž„๋ฒ ๋”ฉ ์›Œ์ปค์—์„œ ์ •ํ•ฉ์„ฑ ๋ณด์žฅ
- ํฌ์ŠคํŠธ ์ž„๋ฒ ๋”ฉ ์ž‘์—…(์ˆ˜์ •/์‚ญ์ œ ํฌํ•จ)์ด ๋๋‚˜๋ฉด `queue-consumer.ts`๊ฐ€ `deleteEmbeddingsByOwner`(`ask-question-cache.repository.ts:166`)๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
- ๊ทธ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ค‘๋ณต ์งˆ๋ฌธ ์บ์‹œ๊ฐ€ ์ „๋ถ€ ์ง€์›Œ์ ธ์„œ, ์ƒˆ๋กœ์šด ์ž„๋ฒ ๋”ฉ๊ณผ ์บ์‹œ๊ฐ€ ํ•ญ์ƒ ๊ฐ™์€ ์‹œ์ ์„ ๋ฐ”๋ผ๋ณด๊ฒŒ ๋œ๋‹ค. ์ด ์ •ํ•ฉ์„ฑ ๋•๋ถ„์— โ€œ๋ณธ๋ฌธ์€ ์ตœ์‹ ์ธ๋ฐ ์บ์‹œ๋Š” ์˜›๋‚  ๊ธฐ๋กโ€ ๊ฐ™์€ ์ƒํ™ฉ์„ ํ™•์‹คํ•˜๊ฒŒ ๋ง‰์•˜๋‹ค.

## ์šด์˜ & ๋””๋ฒ„๊น… ํŒ
- `speech_tone_id = -1`์€ tone ๋ฏธํ™•์ธ ์ƒํƒœ๋กœ ๊ฐ„์ฃผํ•œ๋‹ค. rewrite ํ•œ ๋ฒˆ๋งŒ ์„ฑ๊ณตํ•˜๋ฉด tone์ด ์ฑ„์›Œ์ ธ ์ดํ›„์—๋Š” ์žฌ์ž‘์„ฑ ์—†์ด ์บ์‹œ๋ฅผ ์žฌ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.
- `DEBUG_CHANNELS=qa` + `DEBUG_EXCLUDE_TYPES` ์กฐํ•ฉ์œผ๋กœ `debug.qa.cache_candidates` / `debug.qa.v2.cache_candidates` ๋กœ๊ทธ๋งŒ ์ถ”๋ ค๋ณด๋ฉด tone ๋งค์นญ ์ƒํƒœ๋ฅผ ๋ฐ”๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
- ํŠน์ • ์‚ฌ์šฉ์ž์˜ ์ค‘๋ณต ์งˆ๋ฌธ ์บ์‹œ๋ฅผ ๋น„์šฐ๊ณ  ์‹ถ์œผ๋ฉด `deleteEmbeddingsByOwner`๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋œ๋‹ค. ์›Œ์ปค์—์„œ ์ด๋ฏธ ์ž๋™์œผ๋กœ ํ˜ธ์ถœํ•˜์ง€๋งŒ, ํ•„์š” ์‹œ ์ˆ˜๋™์œผ๋กœ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

## ๊ฒฐ๊ณผ
- follow-up ์งˆ๋ฌธ์—์„œ๋„ ๋™์ผํ•œ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์บ์‹œ๊ฐ€ ๋น„๊ต๋˜๋‹ˆ, ์ค‘๋ณต ์งˆ๋ฌธ ํƒ์ง€๊ฐ€ ๋” ์ •ํ™•ํ•ด์กŒ๋‹ค.
- tone mismatch ์ƒํ™ฉ์—์„œ๋„ `rewriteTone`๋งŒ ํ˜ธ์ถœํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์—, Ask v2์—์„œ๋Š” LLM ํ˜ธ์ถœ์„ 1๋ฒˆ์œผ๋กœ ์ค„์˜€๊ณ  v1์—์„œ๋„ ์ž…๋ ฅ ํ† ํฐ์ด ์ค„์–ด ๋น„์šฉ์ด ๋‚ด๋ ค๊ฐ”๋‹ค.
- ์ž„๋ฒ ๋”ฉ ์›Œ์ปค ๋‹จ๊ณ„์—์„œ ์บ์‹œ๋ฅผ ์ •๋ฆฌํ•˜๋‹ˆ, ์ฝ˜ํ…์ธ  ์ •ํ•ฉ์„ฑ์„ ๊ฑฑ์ •ํ•  ์ผ์ด ์—†์–ด์กŒ๋‹ค. โ€œ์ตœ์‹  ๊ธ€๊ณผ tone๊นŒ์ง€ ๋งž์ถ˜ ์บ์‹œโ€๋ผ๋Š” ๋ชฉํ‘œ๋ฅผ ๊ณผ์žฅ ์—†์ด ๋‹ฌ์„ฑํ–ˆ๋‹ค.
Loading