From 31d74211c5870440eac685e4d6b35956cb829d4b Mon Sep 17 00:00:00 2001 From: happybell80 Date: Thu, 4 Dec 2025 22:07:47 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20plan=20=EB=AC=B8=EC=84=9C=20=EA=B0=84?= =?UTF-8?q?=EA=B2=B0=ED=99=94=20-=20100=EC=A4=84=20=EC=9D=B4=ED=95=98,=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EC=84=B9=EC=85=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...‹œ์Šคํ…œ_ํ˜„์‹ค์ ์šฉ_5๋‹จ๊ณ„_๋กœ๋“œ๋งต.md | 500 ++---------------- .../plans/251016_emotion_integration_plan.md | 271 +--------- ...251016_ontology_coldmail_implementation.md | 346 ++---------- ...b8001_๊ณ„์ธต_๋ถ„๋ฆฌ_๋ฆฌํŒฉํ† ๋ง_๊ณ„ํš.md | 229 ++------ 4 files changed, 183 insertions(+), 1163 deletions(-) diff --git a/journey/plans/250808_๊ฐ์ •์‹œ์Šคํ…œ_ํ˜„์‹ค์ ์šฉ_5๋‹จ๊ณ„_๋กœ๋“œ๋งต.md b/journey/plans/250808_๊ฐ์ •์‹œ์Šคํ…œ_ํ˜„์‹ค์ ์šฉ_5๋‹จ๊ณ„_๋กœ๋“œ๋งต.md index 6dc2f23..439344d 100644 --- a/journey/plans/250808_๊ฐ์ •์‹œ์Šคํ…œ_ํ˜„์‹ค์ ์šฉ_5๋‹จ๊ณ„_๋กœ๋“œ๋งต.md +++ b/journey/plans/250808_๊ฐ์ •์‹œ์Šคํ…œ_ํ˜„์‹ค์ ์šฉ_5๋‹จ๊ณ„_๋กœ๋“œ๋งต.md @@ -1,465 +1,73 @@ -# ๋กœ๋น™ ๊ฐ์ • ์‹œ์Šคํ…œ ํ˜„์‹ค ์ ์šฉ 5๋‹จ๊ณ„ ๋กœ๋“œ๋งต +# ๋กœ๋น™ ๊ฐ์ • ์‹œ์Šคํ…œ 5๋‹จ๊ณ„ ๋กœ๋“œ๋งต -**์ž‘์„ฑ์ž**: happybell80 & Claude -**๋ชฉ์ **: ๊ฐ์ • ์‹œ์Šคํ…œ ์„ค๊ณ„์„œ๋ฅผ ํ˜„์‹ค์ ์œผ๋กœ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•œ ๋‹จ๊ณ„๋ณ„ ๊ณ„ํš์œผ๋กœ ๊ตฌ์ฒดํ™” - -## ํ•ต์‹ฌ ์›์น™ -- **MVP ์šฐ์„ **: ๋ณต์žกํ•œ ๊ธฐ๋Šฅ๋ณด๋‹ค ์ž‘๋™ํ•˜๋Š” ๊ธฐ๋ณธ ๊ธฐ๋Šฅ -- **์ธก์ • ๊ฐ€๋Šฅํ•œ ์„ฑ๊ณผ**: ๊ฐ ๋‹จ๊ณ„๋งˆ๋‹ค ๋ช…ํ™•ํ•œ KPI -- **์ ์ง„์  ๋ณต์žก๋„**: ๋‹จ์ˆœ โ†’ ๋ณต์žก์œผ๋กœ ์ง„ํ™” -- **์„œ๋น„์Šค ๋ถ„๋ฆฌ**: ๋‹จ์ผ ์žฅ์• ์  ๋ฐฉ์ง€ -- **ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ 100%**: ํ•˜๋“œ์ฝ”๋”ฉ 0% ๋ชฉํ‘œ. ๊ทผ๊ฑฐ์—†๋Š” ํ•˜๋“œ์ฝ”๋”ฉ ๊ฐ’ ์ ˆ๋Œ€ ์‚ฌ์šฉ ๊ธˆ์ง€ - - ๋ชจ๋“  ๊ฐ’์€ ์„ค์ • ํŒŒ์ผ์ด๋‚˜ ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๋กœ๋“œ - - ์ˆœ์ˆ˜ ํ•จ์ˆ˜ ์ฒด์ธ์œผ๋กœ ๊ตฌ์„ฑ - - ์ƒํƒœ ๋ณ€๊ฒฝ ์ตœ์†Œํ™”, ๋ถˆ๋ณ€์„ฑ ์œ ์ง€ +**์ž‘์„ฑ์ผ**: 2025-08-08 +**๋ชฉ์ **: 7๊ฐœ ๊ฐ์ • ๋ชจ๋ธ โ†’ 32๊ฐœ ๊ฐ์ • + ์˜จํ†จ๋กœ์ง€ ์ ์ง„ ํ™•์žฅ --- -## Phase 1: 7๊ฐœ ๊ฐ์ • ๊ธฐ๋ณธ ๊ตฌํ˜„ (๋ชจ๋ธ ์ค€๋น„ ์™„๋ฃŒ, ํ†ตํ•ฉ ๋Œ€๊ธฐ) +## Phase 1: 7๊ฐœ ๊ธฐ๋ณธ ๊ฐ์ • (์™„๋ฃŒ) + +**๊ตฌํ˜„**: `troubleshooting/250815_emotion_model_training.md` ์ฐธ์กฐ +- ๋ชจ๋ธ: klue/bert-base โ†’ ONNX (442MB, F1 56.3%) +- ๊ฐ์ •: fear, surprise, anger, sadness, neutral, happiness, disgust +- ์„œ๋น„์Šค: skill-embedding ํ†ตํ•ฉ ์™„๋ฃŒ + +--- + +## Phase 2: ์—”ํŠธ๋กœํ”ผ ๊ธฐ๋ฐ˜ ๋ณต์žก๋„ (์™„๋ฃŒ) + +**๊ตฌํ˜„**: `troubleshooting/251016_emotion_entropy_integration.md` ์ฐธ์กฐ +- ์—”ํŠธ๋กœํ”ผ๋กœ ์‘๋‹ต ๋ณต์žก๋„ ๊ฒฐ์ • +- ์„ค์ •: confidence=0.35, entropy=2.0 + +--- + +## Phase 3: ๊ฐ์ • ์˜จํ†จ๋กœ์ง€ (๋ถ€๋ถ„ ์™„๋ฃŒ) + +**๊ตฌํ˜„ ์™„๋ฃŒ**: `troubleshooting/251016_emotion_ontology_basic.md` ์ฐธ์กฐ +- emotion_likelihood_ontology.py (11๊ฐœ ๊ทœ์น™) +- OntologyReasoner.reason_with_emotion() + +**๋ฏธ๊ตฌํ˜„**: +- ๊ฐ์ • ๊ธฐ๋ฐ˜ ์‘๋‹ต ํ†ค ์กฐ์ • (ํ˜„์žฌ LLM์—๋งŒ ์˜์กด) +- ๊ฐ์ • ๊ธฐ๋ก ๋ฐ ํŒจํ„ด ๋ถ„์„ DB + +--- + +## Phase 4: 32๊ฐœ ์„ธ๋ฐ€ ๊ฐ์ • (๋ฏธ๊ตฌํ˜„) ### ๋ชฉํ‘œ -"์ด๋ฏธ ํ•™์Šต๋œ 7๊ฐœ ํ•œ๊ตญ์–ด ๊ฐ์ • ๋ชจ๋ธ์„ skill-embedding์— ํ†ตํ•ฉ" +- Plutchik ๊ฐ์ • ํœ  ๊ธฐ๋ฐ˜ 32๊ฐœ ๊ฐ์ • +- Primary(8) โ†’ Secondary(8) โ†’ Tertiary(16) -### ์ง„ํ–‰ ํ˜„ํ™ฉ (2025-08-15 ๊ธฐ์ค€) -- โœ… ๋ชจ๋ธ ํ•™์Šต ์™„๋ฃŒ (F1 56.3%) -- โœ… ONNX ๋ณ€ํ™˜ ์™„๋ฃŒ (442MB) -- โณ skill-embedding ์„œ๋น„์Šค ํ†ตํ•ฉ ๋Œ€๊ธฐ -- โณ /emotion ์—”๋“œํฌ์ธํŠธ ๊ตฌํ˜„ ํ•„์š” +### ํ•„์š” ์ž‘์—… +1. AI Hub ๋ฐ์ดํ„ฐ ์žฌ๋ผ๋ฒจ๋ง (7 โ†’ 32๊ฐœ) +2. ๋ชจ๋ธ ์žฌํ•™์Šต (๋‹ค์ค‘ ๋ ˆ์ด๋ธ” ๋ถ„๋ฅ˜) +3. skill-embedding API ํ™•์žฅ -### ๊ตฌํ˜„ ๋ฒ”์œ„ -```python -# AI Hub ๋ฐ์ดํ„ฐ๋กœ ํ•™์Šต ์™„๋ฃŒ๋œ 7๊ฐœ ๊ฐ์ • -EMOTIONS = [ - "fear", # ๊ณตํฌ (๊ธฐ๋ณธ์ •์„œ) - "surprise", # ๋†€๋žŒ (๊ธฐ๋ณธ์ •์„œ) - "anger", # ๋ถ„๋…ธ (๊ธฐ๋ณธ์ •์„œ) - "sadness", # ์Šฌํ”” - "neutral", # ์ค‘๋ฆฝ - "happiness", # ํ–‰๋ณต - "disgust" # ํ˜์˜ค -] -# ๋ชจ๋ธ ์„ฑ๋Šฅ: F1 56.3%, Temperature Scaling 1.232 - -# ์—”ํŠธ๋กœํ”ผ ๊ณ„์‚ฐ (7๊ฐœ ๊ฐ์ •) -def calculate_entropy(probs: List[float]) -> float: - """7๊ฐœ ๊ฐ์ • ํ™•๋ฅ ๊ฐ’์œผ๋กœ ์—”ํŠธ๋กœํ”ผ ๊ณ„์‚ฐ""" - return -sum(p * log(p) for p in probs if p > 0) -``` - -### ๊ธฐ์ˆ  ์Šคํƒ -- **๊ฐ์ • ๋ชจ๋ธ**: klue/bert-base ๊ธฐ๋ฐ˜ โ†’ ONNX ๋ณ€ํ™˜ ์™„๋ฃŒ -- **์„œ๋น„์Šค**: skill-embedding ํ™•์žฅ (ํฌํŠธ 8515, /emotion ์ถ”๊ฐ€) -- **์ €์žฅ**: ๊ธฐ์กด ChromaDB ํ™œ์šฉ (๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์— ๊ฐ์ • ์ถ”๊ฐ€) -- **์˜์‚ฌ๊ฒฐ์ •**: ์—”ํŠธ๋กœํ”ผ ๊ธฐ๋ฐ˜ ๋ณต์žก๋„ ํŒ๋‹จ -- **๊ธฐ์กด ์ฝ”๋“œ**: rb10508_micro์˜ memory/storage.py ์žฌ์‚ฌ์šฉ -- **์„ค์ •๊ฐ’**: confidence=0.35, entropy=2.0, temperature=1.232 - -### ์„ฑ๋Šฅ ๋ชฉํ‘œ -- ์‘๋‹ต์‹œ๊ฐ„: 500ms ์ด๋‚ด -- ์ •ํ™•๋„: ์‚ฌ์šฉ์ž ํ‰๊ฐ€ 3.5/5.0 -- ๋ฉ”๋ชจ๋ฆฌ: 200MB ์ด๋‚ด - -### ๋ฐ์ดํ„ฐ ์ค€๋น„ (์™„๋ฃŒ) -- AI Hub ํ•œ๊ตญ์–ด ๋Œ€ํ™” ๋ฐ์ดํ„ฐ์…‹ 38,594๊ฐœ ์ƒ˜ํ”Œ -- 7๊ฐœ ๊ฐ์ • ๊ท ํ˜• ๋ถ„ํฌ -- ํ•™์Šต/๊ฒ€์ฆ/ํ…Œ์ŠคํŠธ ๋ถ„ํ•  ์™„๋ฃŒ -- ํด๋ž˜์Šค ๊ฐ€์ค‘์น˜ ์ ์šฉ - -### ๊ฒ€์ฆ ๋ฐฉ๋ฒ• -```bash -# ๋‹จ์œ„ ํ…Œ์ŠคํŠธ -pytest tests/test_basic_emotions.py - -# ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ -locust -f tests/load_test.py --users 10 - -# ์‘๋‹ต์‹œ๊ฐ„ ์ธก์ • -curl -w "@curl-format.txt" http://localhost:8503/analyze -``` - -### ์‚ฐ์ถœ๋ฌผ -- [x] 7๊ฐœ ๊ฐ์ • ๋ชจ๋ธ ํ•™์Šต ์™„๋ฃŒ (training_emotion) โœ… 2025-08-08 -- [x] ONNX ๋ณ€ํ™˜ ์™„๋ฃŒ (442MB) โœ… 2025-08-13 -- [x] ๋ชจ๋ธ ํŒŒ์ผ 51124 ์„œ๋ฒ„ ๋ฐฐ์น˜ ์™„๋ฃŒ โœ… 2025-08-13 -- [ ] skill-embedding ์„œ๋น„์Šค์— /emotion ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€ โณ -- [ ] Temperature Scaling ์ ์šฉ (1.232) โณ -- [ ] ์—”ํŠธ๋กœํ”ผ ๊ณ„์‚ฐ๊ธฐ ๊ตฌํ˜„ โณ -- [ ] ChromaDB ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ†ตํ•ฉ โณ -- [ ] rb10508_micro ์—ฐ๋™ โณ - -### ์•„ํ‚คํ…์ฒ˜ ๊ฒฐ์ • ์‚ฌํ•ญ (2025-08-12) -**๋ฌธ์ œ**: ๊ฐ์ • ๋ถ„์„์„ ์–ด๋””์— ๊ตฌํ˜„ํ•  ๊ฒƒ์ธ๊ฐ€? -- Option A: ๋ณ„๋„ skill-emotion ์„œ๋น„์Šค (์•„ํ‚คํ…์ฒ˜ ์›์น™) -- Option B: skill-embedding์— ํ†ตํ•ฉ (์ž์› ํšจ์œจ์„ฑ) - -**์ตœ์ข… ๊ฒฐ์ •**: skill-embedding์— ํ†ตํ•ฉ -- ์ด์œ : ONNX Runtime ๊ณต์œ , ๋ฉ”๋ชจ๋ฆฌ 200MB ์ ˆ์•ฝ, ๋ ˆ์ดํ„ด์‹œ ๊ฐ์†Œ -- ํŠธ๋ ˆ์ด๋“œ์˜คํ”„: ์„œ๋น„์Šค ์—ญํ•  ํ˜ผ์žฌ vs ์‹ค์šฉ์„ฑ -- ํ–ฅํ›„: ์„œ๋น„์Šค๋ช… ๋ณ€๊ฒฝ ๊ณ ๋ ค (skill-embedding โ†’ skill-ai) - -### ํ˜„์žฌ ๋ณด์œ  ๋ชจ๋ธ (2025-08-15) -1. **aihub-7emotions** (๋ฉ”์ธ ๋ชจ๋ธ) - - ํฌ๊ธฐ: 442MB (ONNX) - - ๊ฐ์ •: fear, surprise, anger, sadness, neutral, happiness, disgust - - ์„ฑ๋Šฅ: F1 56.3%, ECE 0.090 - - ์œ„์น˜: /home/admin/ivada_project/onnx_models/aihub-7emotions/ - -2. **korean-sentiment-kcelectra** (๋ณด์กฐ ๋ชจ๋ธ) - - ํฌ๊ธฐ: 511MB (ONNX) - - ๊ฐ์ •: 11๊ฐœ ํ•œ๊ตญ์–ด ์„ธ๋ถ„ํ™” ๊ฐ์ • - - ์„ฑ๋Šฅ: F1 70.72% - - ์œ„์น˜: /home/admin/ivada_project/onnx_models/korean-sentiment-kcelectra/ +**์˜ˆ์ƒ ๊ธฐ๊ฐ„**: 2-3๊ฐœ์›” --- -## Phase 2: ์„œ๋น„์Šค ํ†ตํ•ฉ ๋ฐ ์ตœ์ ํ™” +## Phase 5: ๊ฐ์ • ๊ธฐ์–ต ์‹œ์Šคํ…œ (๋ฏธ๊ตฌํ˜„) -### ๋ชฉํ‘œ -"skill-embedding ์„œ๋น„์Šค ํ†ตํ•ฉ, ์บ์‹ฑ ๊ตฌํ˜„, rb10508_micro ์—ฐ๋™" +### ์„ค๊ณ„ +- Neo4j์— ๊ฐ์ • ๋…ธ๋“œ ์ €์žฅ +- ์‹œ๊ฐ„๋ณ„ ๊ฐ์ • ๋ณ€ํ™” ์ถ”์  +- ํŒจํ„ด ๋ถ„์„ (์šฐ์šธ์ฆ ์กฐ๊ธฐ ๊ฐ์ง€ ๋“ฑ) -### ์šฐ์„  ์ž‘์—… (๋กœ์ปฌ ๊ฐœ๋ฐœ์ž) -1. skill-embedding์— /emotion ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€ -2. ONNX ๋ชจ๋ธ ๋กœ๋”ฉ ๋ฐ ์ถ”๋ก  ์ฝ”๋“œ ๊ตฌํ˜„ -3. Temperature Scaling (T=1.232) ์ ์šฉ -4. rb10508_micro์—์„œ ๊ฐ์ • API ํ˜ธ์ถœ ํ†ตํ•ฉ - -### ์ตœ์ ํ™” ์ „๋žต -```python -# LRU ์บ์‹œ ์ ์šฉ -from functools import lru_cache - -@lru_cache(maxsize=1000) -def get_emotion_embedding(text: str) -> np.ndarray: - """์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ํ…์ŠคํŠธ์˜ ์ž„๋ฒ ๋”ฉ ์บ์‹ฑ""" - return model.encode(text) - -# ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ -async def batch_analyze(texts: List[str]): - """์—ฌ๋Ÿฌ ํ…์ŠคํŠธ ๋™์‹œ ์ฒ˜๋ฆฌ""" - embeddings = model.encode(texts, batch_size=32) - return [analyze_emotion(emb) for emb in embeddings] +### ๊ตฌ์กฐ +``` +User -feelsโ†’ Emotion -atโ†’ Time + -talks_aboutโ†’ Topic ``` -### ChromaDB ํŠœ๋‹ -- ์ธ๋ฑ์Šค ์ตœ์ ํ™”: HNSW ํŒŒ๋ผ๋ฏธํ„ฐ ์กฐ์ • -- ์ฟผ๋ฆฌ ์ตœ์ ํ™”: top-k๋ฅผ 10์œผ๋กœ ์ œํ•œ -- ์—ฐ๊ฒฐ ํ’€๋ง: ์ปค๋„ฅ์…˜ ์žฌ์‚ฌ์šฉ - -### ํ”„๋กœํŒŒ์ผ๋ง -```python -# ๋ณ‘๋ชฉ ์ง€์  ์ฐพ๊ธฐ -import cProfile -import pstats - -cProfile.run('analyze_emotion(text)', 'profile_stats') -stats = pstats.Stats('profile_stats') -stats.sort_stats('cumulative').print_stats(10) -``` - -### ์„ฑ๋Šฅ ๋ชฉํ‘œ -- ์‘๋‹ต์‹œ๊ฐ„: 200ms (60% ๊ฐœ์„ ) -- ๋™์‹œ ์ฒ˜๋ฆฌ: 50 req/s -- ์บ์‹œ ์ ์ค‘๋ฅ : 30% - -### ์‚ฐ์ถœ๋ฌผ -- [x] ONNX ๋ชจ๋ธ ๋ณ€ํ™˜ ์™„๋ฃŒ (442MB) โœ… -- [ ] skill-embedding /emotion ์—”๋“œํฌ์ธํŠธ ๊ตฌํ˜„ โณ -- [ ] LRU ์บ์‹œ ์‹œ์Šคํ…œ (5๋ถ„ TTL) -- [ ] ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ API -- [ ] ChromaDB ๊ฐ์ • ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ธ๋ฑ์‹ฑ -- [ ] ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง (Grafana) -- [ ] rb10508_micro ๊ฐ์ • ๊ธฐ๋ฐ˜ ์‘๋‹ต ํ†ค ์กฐ์ • +**์˜ˆ์ƒ ๊ธฐ๊ฐ„**: 1๊ฐœ์›” --- -## Phase 3: ๊ฐ์ • ํŒจํ„ด ๋ถ„์„ ๋ฐ ๊ฐœ์ธํ™” +## ์ฐธ๊ณ  -### ๋ชฉํ‘œ -"์žฅ๊ธฐ ๊ฐ์ • ํŒจํ„ด ์ถ”์ , ์‚ฌ์šฉ์ž๋ณ„ ๊ฐ์ • ํ”„๋กœํŒŒ์ผ ๊ตฌ์ถ•" - -### ๊ฐ์ • ํŒจํ„ด ๋ถ„์„ -```python -# ์‹œ๊ฐ„๋ณ„ ๊ฐ์ • ์ถ”์  -class EmotionTracker: - def __init__(self, user_id: str): - self.user_id = user_id - self.history = [] # ์‹œ๊ณ„์—ด ๊ฐ์ • ๋ฐ์ดํ„ฐ - - def track(self, emotion_result: dict): - """๊ฐ์ • ๊ฒฐ๊ณผ๋ฅผ ์‹œ๊ณ„์—ด๋กœ ์ €์žฅ""" - self.history.append({ - "timestamp": datetime.now(), - "emotions": emotion_result["emotions"], - "dominant": emotion_result["dominant"], - "entropy": emotion_result["entropy"] - }) - - def get_pattern(self, period: str = "day"): - """์ผ/์ฃผ/์›” ๋‹จ์œ„ ๊ฐ์ • ํŒจํ„ด ๋ถ„์„""" - # ์‹œ๊ฐ„๋Œ€๋ณ„ ์ฃผ์š” ๊ฐ์ • - # ๊ฐ์ • ๋ณ€ํ™” ์ถ”์ด - # ์—”ํŠธ๋กœํ”ผ ํŒจํ„ด - return analyze_temporal_pattern(self.history, period) -``` - -### ๊ฐœ์ธํ™” ์ „๋žต -- ์‚ฌ์šฉ์ž๋ณ„ ๊ฐ์ • ํ”„๋กœํŒŒ์ผ ์ƒ์„ฑ -- ๊ฐ์ • ์‘๋‹ต ํžˆ์Šคํ† ๋ฆฌ ํ•™์Šต -- ๊ฐœ์ธ๋ณ„ ๊ฐ์ • ์ž„๊ณ„๊ฐ’ ์กฐ์ • -- ์—”ํŠธ๋กœํ”ผ ํŠน์ด์  ํ™œ์šฉ (์ฐฝ๋ฐœ์  ์‘๋‹ต) - -### ์—”ํŠธ๋กœํ”ผ ๊ธฐ๋ฐ˜ ์˜์‚ฌ๊ฒฐ์ • -```python -class EntropyBasedDecision: - def __init__(self): - self.entropy_threshold = 2.5 # ํŠน์ด์  ์ž„๊ณ„๊ฐ’ - - def should_be_creative(self, entropy: float) -> bool: - """๋†’์€ ์—”ํŠธ๋กœํ”ผ์ผ ๋•Œ ์ฐฝ์˜์  ์‘๋‹ต""" - return entropy > self.entropy_threshold - - def adjust_response(self, response: str, emotion_result: dict): - """๊ฐ์ •์— ๋”ฐ๋ฅธ ์‘๋‹ต ํ†ค ์กฐ์ •""" - if emotion_result["dominant"] == "sadness": - return make_empathetic(response) - elif emotion_result["dominant"] == "anger": - return make_calm(response) - elif self.should_be_creative(emotion_result["entropy"]): - return make_creative(response) - return response -``` - -### ์„ฑ๋Šฅ ๋ชฉํ‘œ -- ํŒจํ„ด ๋ถ„์„: ์ผ 1ํšŒ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ -- ํ”„๋กœํŒŒ์ผ ์—…๋ฐ์ดํŠธ: ์‹ค์‹œ๊ฐ„ -- ๊ฐ์ • ํžˆ์Šคํ† ๋ฆฌ: 30์ผ ๋ณด๊ด€ -- ๊ฐœ์ธํ™” ์ •ํ™•๋„: 70% ์ด์ƒ - -### ์‚ฐ์ถœ๋ฌผ -- [ ] ๊ฐ์ • ํŒจํ„ด ๋ถ„์„๊ธฐ -- [ ] ์‚ฌ์šฉ์ž ๊ฐ์ • ํ”„๋กœํŒŒ์ผ DB -- [ ] ์—”ํŠธ๋กœํ”ผ ๊ธฐ๋ฐ˜ ์˜์‚ฌ๊ฒฐ์ • ๋ชจ๋“ˆ -- [ ] ์‹œ๊ณ„์—ด ๊ฐ์ • ์‹œ๊ฐํ™” -- [ ] ๊ฐœ์ธํ™” ์‘๋‹ต ์ „๋žต - ---- - -## Phase 4: ๋ฒ ์ด์ง€์•ˆ ํ•™์Šต ์‹œ์Šคํ…œ - -### ๋ชฉํ‘œ -"์‹ค์‹œ๊ฐ„ ํ•™์Šต๊ณผ ๊ฐœ์ธํ™”๋œ ๊ฐ์ • ๋ชจ๋ธ ๊ตฌ์ถ•" - -### ๋ฒ ์ด์ง€์•ˆ ํŒŒ๋ผ๋ฏธํ„ฐ -```python -class BayesianEmotionModel: - def __init__(self): - # Dirichlet ์‚ฌ์ „๋ถ„ํฌ (9๊ฐœ ๊ฐ์ •) - self.emotion_prior = np.ones(9) - - # Beta ๋ถ„ํฌ (์ €์žฅ ๊ฒฐ์ •) - self.save_alpha = 1 - self.save_beta = 1 - - # Gamma ๋ถ„ํฌ (์‘๋‹ต ๊ธธ์ด) - self.length_k = 2 - self.length_theta = 50 - - def update_posterior(self, observation): - """๊ด€์ธก๊ฐ’์œผ๋กœ ์‚ฌํ›„๋ถ„ํฌ ์—…๋ฐ์ดํŠธ""" - self.emotion_prior += observation['emotion_counts'] - - if observation['saved']: - self.save_alpha += 1 - else: - self.save_beta += 1 - - # Gamma ์—…๋ฐ์ดํŠธ (moment matching) - self.length_k, self.length_theta = \ - self.update_gamma(observation['response_length']) -``` - -### ์˜ˆ์ธก-ํ‰๊ฐ€ ๋ฃจํ”„ -```python -async def prediction_evaluation_loop(user_input): - # 1. ์˜ˆ์ธก - prediction = model.predict_user_response(user_input) - - # 2. ์‹ค์ œ ์‘๋‹ต ์ƒ์„ฑ - actual_response = await generate_response(user_input) - - # 3. ์‚ฌ์šฉ์ž ๋ฐ˜์‘ ์ˆ˜์ง‘ - user_reaction = await collect_feedback() - - # 4. ์˜ค์ฐจ ๊ณ„์‚ฐ (3์ข…) - kl_div = calculate_kl(prediction, user_reaction) - brier = calculate_brier(prediction, user_reaction) - ece = calculate_ece(prediction, user_reaction) - - # 5. ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ - if max(kl_div, brier, ece) > threshold: - model.update_posterior(user_reaction) - - return actual_response -``` - -### ๊ฐœ์ธํ™” -- ์‚ฌ์šฉ์ž๋ณ„ ๋ฒ ์ด์ง€์•ˆ ํŒŒ๋ผ๋ฏธํ„ฐ ์ €์žฅ -- ์กฐ์ง/ํŒ€/๊ฐœ์ธ 3๋‹จ๊ณ„ ๊ณ„์ธต ๊ตฌ์กฐ -- Cold start: ์กฐ์ง ํ‰๊ท ๊ฐ’ ์‚ฌ์šฉ - -### ์„ฑ๋Šฅ ๋ชฉํ‘œ -- ECE: โ‰ค 0.08 -- Brier Score: โ‰ค 0.20 -- ํ•™์Šต ์ˆ˜๋ ด: 50ํšŒ ์ƒํ˜ธ์ž‘์šฉ -- ๊ฐœ์ธํ™” ํšจ๊ณผ: +15% ๋งŒ์กฑ๋„ - -### ์‚ฐ์ถœ๋ฌผ -- [ ] ๋ฒ ์ด์ง€์•ˆ ๋ชจ๋ธ ํด๋ž˜์Šค -- [ ] ์˜ˆ์ธก-ํ‰๊ฐ€ ํŒŒ์ดํ”„๋ผ์ธ -- [ ] 3์ข… ์˜ค์ฐจ ๋ฉ”ํŠธ๋ฆญ -- [ ] ์‚ฌ์šฉ์ž๋ณ„ ํŒŒ๋ผ๋ฏธํ„ฐ ์ €์žฅ์†Œ -- [ ] ํ•™์Šต ๊ณก์„  ๋ถ„์„ - ---- - -## Phase 5: ํ”„๋กœ๋•์…˜ ๋ฐ ํ™•์žฅ - -### ๋ชฉํ‘œ -"์•ˆ์ •์ ์ธ ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ์™€ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ ์ถ”๊ฐ€" - -### ํ”„๋ผ์ด๋ฒ„์‹œ ๊ฒŒ์ดํŠธ -```python -class PrivacyGate: - def __init__(self): - self.pii_patterns = load_pii_patterns() - self.sensitive_topics = load_sensitive_topics() - - def filter(self, text, metadata): - # PII ๊ฐ์ง€ - if self.detect_pii(text): - return self.anonymize(text) - - # ๋ฏผ๊ฐ ์ฃผ์ œ ํ•„ํ„ฐ - if self.is_sensitive(text): - return {"summary": self.summarize(text), - "original": None} - - # 24์‹œ๊ฐ„ ์˜ตํŠธ์•„์›ƒ - if metadata.get('opt_out_requested'): - return None - - return text -``` - -### ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ -```yaml -# prometheus metrics -metrics: - - emotion_analysis_duration_seconds - - emotion_cache_hit_ratio - - bayesian_update_count - - prediction_error_rate - - privacy_filter_triggers - -alerts: - - name: HighECE - expr: emotion_ece > 0.1 - for: 5m - - - name: SlowResponse - expr: emotion_p95_latency > 500 - for: 10m -``` - -### ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ -- HDBSCAN ํด๋Ÿฌ์Šคํ„ฐ๋ง ๋„์ž… -- ๊ฐ์ • ์ „ํ™˜ ํŒจํ„ด ํ•™์Šต -- ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ํ™•์žฅ ์ค€๋น„ (์Œ์„ฑ/ํ‘œ์ •) -- ์„ค๋ช…๊ฐ€๋Šฅ AI (LIME/SHAP) - -### ํ™•์žฅ์„ฑ -```python -# ์ˆ˜ํ‰ ํ™•์žฅ ์ค€๋น„ -class EmotionAnalyzerCluster: - def __init__(self, workers=4): - self.workers = workers - self.load_balancer = ConsistentHash() - - async def analyze(self, text, user_id): - # ์‚ฌ์šฉ์ž๋ณ„๋กœ ์ผ๊ด€๋œ ์›Œ์ปค ํ• ๋‹น - worker = self.load_balancer.get_worker(user_id) - return await worker.analyze(text) -``` - -### ์ตœ์ข… KPI -- ECE: โ‰ค 0.05 -- Brier Score: โ‰ค 0.18 -- NDCG@10: โ‰ฅ 0.6 -- ์‘๋‹ต์‹œ๊ฐ„ P95: โ‰ค 300ms -- ๊ฐ€์šฉ์„ฑ: 99.9% - -### ์‚ฐ์ถœ๋ฌผ -- [ ] ํ”„๋ผ์ด๋ฒ„์‹œ ๊ฒŒ์ดํŠธ ์‹œ์Šคํ…œ -- [ ] Prometheus/Grafana ๋Œ€์‹œ๋ณด๋“œ -- [ ] ์ˆ˜ํ‰ ํ™•์žฅ ์•„ํ‚คํ…์ฒ˜ -- [ ] HDBSCAN ํด๋Ÿฌ์Šคํ„ฐ๋ง -- [ ] ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ (Docker/K8s) -- [ ] ์šด์˜ ๋ฌธ์„œ ๋ฐ Runbook - ---- - -## ๋ฆฌ์Šคํฌ ๋ฐ ์™„ํ™” ๋ฐฉ์•ˆ - -### ๊ธฐ์ˆ ์  ๋ฆฌ์Šคํฌ -1. **ChromaDB ์„ฑ๋Šฅ ํ•œ๊ณ„** - - ์™„ํ™”: Redis ์บ์‹œ ๋ ˆ์ด์–ด ์ถ”๊ฐ€ - - ๋Œ€์•ˆ: Pinecone/Weaviate ๊ฒ€ํ†  - -2. **๋ชจ๋ธ ์ถ”๋ก  ์†๋„** - - ์™„ํ™”: ONNX ๋ณ€ํ™˜, ์–‘์žํ™” - - ๋Œ€์•ˆ: DistilBERT ๊ธฐ๋ฐ˜ ๊ฒฝ๋Ÿ‰ ๋ชจ๋ธ - -3. **๋ฒ ์ด์ง€์•ˆ ๊ณ„์‚ฐ ๋ณต์žก๋„** - - ์™„ํ™”: ๊ทผ์‚ฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์‚ฌ์šฉ - - ๋Œ€์•ˆ: ๋‹จ์ˆœ EMA๋กœ ๋Œ€์ฒด - -### ๋ฐ์ดํ„ฐ ๋ฆฌ์Šคํฌ -1. **๋ผ๋ฒจ ํ’ˆ์งˆ** - - ์™„ํ™”: ๋‹ค์ค‘ ๋ผ๋ฒจ๋Ÿฌ, ํ•ฉ์˜ ๋ฉ”์ปค๋‹ˆ์ฆ˜ - - ๋Œ€์•ˆ: ์•ฝ์ง€๋„ ํ•™์Šต - -2. **๊ฐœ์ธ์ •๋ณด ์œ ์ถœ** - - ์™„ํ™”: ๋กœ์ปฌ ์ฒ˜๋ฆฌ, ์•”ํ˜ธํ™” - - ๋Œ€์•ˆ: ์—ฐํ•ฉ ํ•™์Šต - -### ์šด์˜ ๋ฆฌ์Šคํฌ -1. **์„œ๋น„์Šค ์žฅ์• ** - - ์™„ํ™”: Circuit breaker, ํด๋ฐฑ - - ๋Œ€์•ˆ: ๊ธฐ๋ณธ ๊ฐ์ •๋งŒ ์ œ๊ณต - -2. **๋น„์šฉ ์ฆ๊ฐ€** - - ์™„ํ™”: ์‚ฌ์šฉ๋Ÿ‰ ๊ธฐ๋ฐ˜ ์Šค์ผ€์ผ๋ง - - ๋Œ€์•ˆ: ์—ฃ์ง€ ๋””๋ฐ”์ด์Šค ์ฒ˜๋ฆฌ - ---- - -## ์„ฑ๊ณต ๊ธฐ์ค€ - -### Phase๋ณ„ ์ฒดํฌํฌ์ธํŠธ -- **Phase 1**: 5๊ฐœ ๊ฐ์ • ์ธ์‹ ์ž‘๋™ ํ™•์ธ -- **Phase 2**: 200ms ์‘๋‹ต์‹œ๊ฐ„ ๋‹ฌ์„ฑ -- **Phase 3**: 9๊ฐœ ๊ฐ์ • ์ •ํ™•๋„ 80% -- **Phase 4**: ๊ฐœ์ธํ™” ํšจ๊ณผ ์ธก์ • ๊ฐ€๋Šฅ -- **Phase 5**: ํ”„๋กœ๋•์…˜ ์•ˆ์ •์„ฑ 99.9% - -### ์ „์ฒด ํ”„๋กœ์ ํŠธ ์„ฑ๊ณต ์ง€ํ‘œ -1. **์‚ฌ์šฉ์ž ๋งŒ์กฑ๋„**: NPS 40 ์ด์ƒ -2. **๊ธฐ์ˆ  ์„ฑ๋Šฅ**: ๋ชจ๋“  KPI ๋ชฉํ‘œ์น˜ ๋‹ฌ์„ฑ -3. **๋น„์ฆˆ๋‹ˆ์Šค ๊ฐ€์น˜**: ์‚ฌ์šฉ์ž ์ดํƒˆ๋ฅ  20% ๊ฐ์†Œ -4. **ํ™•์žฅ ๊ฐ€๋Šฅ์„ฑ**: ์ผ 100๋งŒ ์š”์ฒญ ์ฒ˜๋ฆฌ - ---- - ---- - -*์ด ๋กœ๋“œ๋งต์€ ์ด์ƒ์ ์ธ ์„ค๊ณ„๋ฅผ ํ˜„์‹ค์ ์œผ๋กœ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•œ ๋‹จ๊ณ„๋กœ ๋‚˜๋ˆˆ ์‹คํ–‰ ๊ณ„ํš์ž…๋‹ˆ๋‹ค. ๊ฐ Phase๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐ€์น˜๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ค‘๋‹จํ•˜๊ฑฐ๋‚˜ ๋ฐฉํ–ฅ์„ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.* \ No newline at end of file +- `book/200_core_design/225_์˜จํ†จ๋กœ์ง€_๊ธฐ๋ฐ˜_์ง€์‹_ํ‘œํ˜„.md` +- `troubleshooting/250815_emotion_model_training.md` +- `troubleshooting/251016_emotion_ontology_basic.md` diff --git a/journey/plans/251016_emotion_integration_plan.md b/journey/plans/251016_emotion_integration_plan.md index 18f65ef..31512ac 100644 --- a/journey/plans/251016_emotion_integration_plan.md +++ b/journey/plans/251016_emotion_integration_plan.md @@ -1,264 +1,49 @@ -# ๊ฐ์ • ๋ถ„๋ฅ˜๊ธฐ ํ†ตํ•ฉ ๊ณ„ํš +# ๊ฐ์ • ๋ถ„๋ฅ˜๊ธฐ Router ํ†ตํ•ฉ ๊ณ„ํš **๋‚ ์งœ**: 2025-10-16 -**์ž‘์„ฑ์ž**: Claude -**๋ชฉํ‘œ**: Phase 3 ๊ฐ์ • ์˜จํ†จ๋กœ์ง€๋ฅผ ์‹ค์ œ ๋Œ€ํ™”์— ํ†ตํ•ฉ +**๋ชฉํ‘œ**: ๊ฐ์ • ๋ถ„์„์„ ๋Œ€ํ™” ํ๋ฆ„์— ํ†ตํ•ฉ --- -## ํ˜„์žฌ ์ƒํƒœ +## ๊ตฌํ˜„ ์™„๋ฃŒ -### ๊ตฌํ˜„ ์™„๋ฃŒ -- โœ… EmotionClassifier (skill-embedding ์—ฐ๋™) -- โœ… emotion_likelihood_ontology.py (11๊ฐœ ๊ทœ์น™) -- โœ… OntologyReasoner.reason_with_emotion() -- โœ… ethics_constraints_ontology.py (Router ํ†ตํ•ฉ) - -### ๋ฏธ๊ตฌํ˜„ -- โŒ Router์— ๊ฐ์ • ๋ถ„์„ ํ†ตํ•ฉ -- โŒ ๊ฐ์ • ์ •๋ณด๋ฅผ LLM ์ปจํ…์ŠคํŠธ์— ์ „๋‹ฌ -- โŒ ์‹ค์ œ ๋Œ€ํ™”์—์„œ ๊ฐ์ • ๊ธฐ๋ฐ˜ ์‘๋‹ต +**์ƒ์„ธ**: `troubleshooting/251016_emotion_router_integration.md` ์ฐธ์กฐ +- EmotionClassifier โ†’ skill-embedding ์—ฐ๋™ +- Router์— ๊ฐ์ • ๋ถ„์„ ์ถ”๊ฐ€ +- LLM ํ”„๋กฌํ”„ํŠธ์— ๊ฐ์ • ์ •๋ณด ์ „๋‹ฌ --- -## ํ†ตํ•ฉ ๋ฐฉ์‹ ๊ฒฐ์ • +## ๋ฏธ๊ตฌํ˜„: ๊ฐ์ • ๊ธฐ๋ฐ˜ ์‘๋‹ต ํ†ค ์ž๋™ ์กฐ์ • -### ๋ฐฉ์‹ 1: ์ฆ๊ฑฐ ๊ธฐ๋ฐ˜ ์šฐ๋„ ์กฐ์ • (๋ณต์žก) +### ํ˜„์žฌ ํ•œ๊ณ„ +- ๊ฐ์ • ๋ถ„์„ โ†’ LLM ํ”„๋กฌํ”„ํŠธ ์ „๋‹ฌ๋งŒ +- ํ†ค ์กฐ์ •์€ LLM ์ž์œจ ํŒ๋‹จ (๋ถˆ์•ˆ์ •) +- ๋ช…์‹œ์  ๊ทœ์น™ ์—†์Œ -**ํ๋ฆ„**: -``` -์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ โ†’ ๊ฐ์ • ๋ถ„์„ โ†’ ์ฆ๊ฑฐ ์ถ”์ถœ โ†’ reason_with_emotion() โ†’ ์กฐ์ •๋œ ์ฆ๊ฑฐ โ†’ LLM -``` +### ํ•„์š” ์ž‘์—… -**๋ฌธ์ œ์ **: -- "์ฆ๊ฑฐ"๋ฅผ ์–ด๋–ป๊ฒŒ ์ถ”์ถœํ•  ๊ฒƒ์ธ๊ฐ€? -- ์ผ๋ฐ˜ ๋Œ€ํ™”์—์„œ๋Š” ๋ช…ํ™•ํ•œ ์ฆ๊ฑฐ๊ฐ€ ์—†์Œ -- Coldmail์ฒ˜๋Ÿผ ๊ตฌ์กฐํ™”๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹˜ - -**์ ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ**: -- ์˜์‚ฌ๊ฒฐ์ • ์ง€์› (ํˆฌ์ž, ๊ตฌ๋งค ๋“ฑ) -- ์ •๋ณด ๋น„๊ต (์žฅ๋‹จ์  ๋ถ„์„) - -### ๋ฐฉ์‹ 2: ๊ฐ์ • ์ปจํ…์ŠคํŠธ ์ „๋‹ฌ (์‹ค์šฉ์ ) โœ… ์„ ํƒ - -**ํ๋ฆ„**: -``` -์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ โ†’ ๊ฐ์ • ๋ถ„์„ โ†’ context์— ๊ฐ์ • ์ถ”๊ฐ€ โ†’ LLM (๊ฐ์ • ๊ณ ๋ ค ์‘๋‹ต) -``` - -**์žฅ์ **: -- ๊ตฌํ˜„ ๊ฐ„๋‹จ -- ๋ชจ๋“  ๋Œ€ํ™”์— ์ ์šฉ ๊ฐ€๋Šฅ -- LLM์ด ๊ฐ์ •์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ™œ์šฉ -- ์ถ”ํ›„ ํ™•์žฅ ์šฉ์ด - -**๊ตฌํ˜„ ๋ฐฉ๋ฒ•**: +**๊ฐ์ •-ํ†ค ๋งคํ•‘**: ```python -# 1. ๊ฐ์ • ๋ถ„์„ -emotion_result = await emotion_classifier.predict_async(message) -user_emotion = emotion_result['top_label'] # fear, joy, sadness, etc. -emotion_confidence = emotion_result['top_p'] - -# 2. context์— ์ถ”๊ฐ€ -context['user_emotion'] = user_emotion -context['emotion_confidence'] = emotion_confidence - -# 3. LLM์— ์ „๋‹ฌ (ํ”„๋กฌํ”„ํŠธ์—์„œ ํ™œ์šฉ) -llm_response = await llm_service.process_request(llm_request) -``` - ---- - -## ๊ตฌํ˜„ ๊ณ„ํš - -### 1๋‹จ๊ณ„: ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ถ”๊ฐ€ - -**.env**: -```bash -# Emotion Analysis (Phase 3 Ontology) -USE_EMOTION_ANALYSIS=false # ๊ธฐ๋ณธ๊ฐ’: ๋น„ํ™œ์„ฑํ™” -``` - -**config.py**: -```python -USE_EMOTION_ANALYSIS: bool = os.getenv("USE_EMOTION_ANALYSIS", "false").lower() == "true" -``` - -### 2๋‹จ๊ณ„: Router์— ๊ฐ์ • ๋ถ„์„ ํ†ตํ•ฉ - -**์œ„์น˜**: `router.py` โ†’ `_call_internal_llm()` ๋ฉ”์„œ๋“œ - -**์ถ”๊ฐ€ ์ฝ”๋“œ**: -```python -# Phase 3: ๊ฐ์ • ๋ถ„์„ (์˜ต์…˜) -if settings.USE_EMOTION_ANALYSIS: - try: - from app.core.emotion.emotion_classifier import get_classifier - emotion_classifier = get_classifier() - - emotion_result = await emotion_classifier.predict_async(message) - user_emotion = emotion_result['top_label'] - emotion_confidence = emotion_result['top_p'] - - # context์— ์ถ”๊ฐ€ - if context is None: - context = {} - context['user_emotion'] = user_emotion - context['emotion_confidence'] = emotion_confidence - - logger.info(f"Emotion detected: {user_emotion} (confidence: {emotion_confidence:.2f})") - - except Exception as e: - logger.error(f"Emotion analysis failed: {e}") -``` - -### 3๋‹จ๊ณ„: LLM ํ”„๋กฌํ”„ํŠธ์— ๊ฐ์ • ์ •๋ณด ํ™œ์šฉ - -**์œ„์น˜**: `llm_service.py` ๋˜๋Š” ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ๋ถ€๋ถ„ - -**ํ”„๋กฌํ”„ํŠธ ์˜ˆ์‹œ**: -```python -if context.get('user_emotion'): - emotion = context['user_emotion'] - emotion_map = { - 'fear': '๋ถˆ์•ˆ', - 'joy': '๊ธฐ์จ', - 'sadness': '์Šฌํ””', - 'anger': '๋ถ„๋…ธ', - 'surprise': '๋†€๋žŒ', - 'disgust': 'ํ˜์˜ค', - 'trust': '์‹ ๋ขฐ', - 'neutral': 'ํ‰์˜จ' - } - emotion_kr = emotion_map.get(emotion, emotion) - - prompt += f"\n\n[์ฐธ๊ณ ] ์‚ฌ์šฉ์ž์˜ ํ˜„์žฌ ๊ฐ์ • ์ƒํƒœ: {emotion_kr}" - prompt += f"\n์ด ๊ฐ์ •์„ ๊ณ ๋ คํ•˜์—ฌ ๊ณต๊ฐ์ ์ด๊ณ  ์ ์ ˆํ•œ ์‘๋‹ต์„ ์ œ๊ณตํ•ด์ฃผ์„ธ์š”." -``` - -### 4๋‹จ๊ณ„: ์‘๋‹ต์— ๊ฐ์ • ์ •๋ณด ํฌํ•จ (๋””๋ฒ„๊น…์šฉ) - -**์‘๋‹ต ํ˜•์‹**: -```python -result = { - "success": True, - "content": final_content, - "model_used": llm_response.model_used, - "emotion_detected": user_emotion, # ์ถ”๊ฐ€ - "emotion_confidence": emotion_confidence # ์ถ”๊ฐ€ +EMOTION_TONE_MAP = { + "fear": {"style": "reassuring", "emoji": False, "length": "short"}, + "anger": {"style": "calm", "emoji": False, "length": "medium"}, + "sadness": {"style": "empathetic", "emoji": True, "length": "medium"} } ``` ---- +**LLM ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ๋™์  ์ƒ์„ฑ**: +- `services/llm/gemini_handler.py` ์ˆ˜์ • +- ๊ฐ์ •๋ณ„ ํ†ค ์ง€์‹œ ์ž๋™ ์‚ฝ์ž… +- ์˜ˆ: "์‚ฌ์šฉ์ž๊ฐ€ ๋ถˆ์•ˆ(fear)์„ ๋А๋‚๋‹ˆ๋‹ค. ์•ˆ์‹ฌ์‹œํ‚ค๋Š” ํ†ค์œผ๋กœ ์งง๊ฒŒ ๋‹ต๋ณ€ํ•˜์„ธ์š”." -## ํ…Œ์ŠคํŠธ ๊ณ„ํš - -### ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค - -1. **๋ถˆ์•ˆ (fear)**: - - ์ž…๋ ฅ: "์š”์ฆ˜ ํšŒ์‚ฌ๊ฐ€ ๊ฑฑ์ •๋ผ์š”..." - - ๊ธฐ๋Œ€: ์œ„๋กœ์™€ ์•ˆ์ •๊ฐ ์žˆ๋Š” ์‘๋‹ต - -2. **๊ธฐ์จ (joy)**: - - ์ž…๋ ฅ: "์˜ค๋Š˜ ์Šน์ง„ํ–ˆ์–ด์š”!" - - ๊ธฐ๋Œ€: ์ถ•ํ•˜์™€ ๊ธ์ •์ ์ธ ์‘๋‹ต - -3. **์Šฌํ”” (sadness)**: - - ์ž…๋ ฅ: "์‹คํŒจํ•ด์„œ ๋„ˆ๋ฌด ์†์ƒํ•ด์š”" - - ๊ธฐ๋Œ€: ๊ณต๊ฐ๊ณผ ์œ„๋กœ์˜ ์‘๋‹ต - -4. **์ค‘๋ฆฝ (neutral)**: - - ์ž…๋ ฅ: "๋‚ ์”จ๊ฐ€ ์–ด๋•Œ์š”?" - - ๊ธฐ๋Œ€: ์ผ๋ฐ˜์ ์ธ ์ •๋ณด ์ œ๊ณต - -### ๊ฒ€์ฆ ๋ฐฉ๋ฒ• - -```bash -# ๋กœ๊ทธ ํ™•์ธ -docker logs rb8001 --tail 100 | grep -E "Emotion detected|user_emotion" - -# ์‘๋‹ต ํ™•์ธ -# Slack์—์„œ ๋Œ€ํ™” โ†’ ๋กœ๊ทธ์—์„œ ๊ฐ์ • ์ •๋ณด ํ™•์ธ -``` +**A/B ํ…Œ์ŠคํŠธ**: +- ON/OFF ๋น„๊ต +- ์‚ฌ์šฉ์ž ๋งŒ์กฑ๋„ ์ธก์ • --- -## ํ–ฅํ›„ ํ™•์žฅ (Phase 3.5) +## ์ฐธ๊ณ  -### reason_with_emotion() ํ†ตํ•ฉ - -**์ ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค**: ์˜์‚ฌ๊ฒฐ์ • ์ง€์› - -**์˜ˆ์‹œ**: -```python -# ์‚ฌ์šฉ์ž: "์ด ํˆฌ์ž ์–ด๋–ป๊ฒŒ ์ƒ๊ฐํ•˜์„ธ์š”?" -# ๊ฐ์ •: fear (๋ถˆ์•ˆ) - -# 1. LLM์ด ์ฆ๊ฑฐ ์ƒ์„ฑ -evidences = [ - {"type": "์œ„ํ—˜", "content": "์‹œ์žฅ ๋ณ€๋™์„ฑ", "prior_likelihood": 0.6}, - {"type": "๊ธ์ •", "content": "์„ฑ์žฅ ๊ฐ€๋Šฅ์„ฑ", "prior_likelihood": 0.5} -] - -# 2. ๊ฐ์ • ๊ธฐ๋ฐ˜ ์กฐ์ • -adjusted, explanation = reasoner.reason_with_emotion("fear", evidences) -# ์œ„ํ—˜ 0.6 โ†’ 0.78 (+30%) - -# 3. ์กฐ์ •๋œ ์ฆ๊ฑฐ๋กœ ์ตœ์ข… ์‘๋‹ต -``` - ---- - -## ์„ฑ๋Šฅ ์˜ํ–ฅ - -### ์ถ”๊ฐ€ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ -- ๊ฐ์ • ๋ถ„์„ API ํ˜ธ์ถœ: ~50-100ms -- skill-embedding ์„œ๋น„์Šค ์‘๋‹ต ์‹œ๊ฐ„ - -### ์™„ํ™” ๋ฐฉ๋ฒ• -- ๋น„๋™๊ธฐ ํ˜ธ์ถœ (await) -- ํƒ€์ž„์•„์›ƒ ์„ค์ • (10์ดˆ) -- ์‹คํŒจ ์‹œ graceful degradation - ---- - -## ๋กค๋ฐฑ ๋ฐฉ๋ฒ• - -### ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋น„ํ™œ์„ฑํ™” -```bash -USE_EMOTION_ANALYSIS=false -docker compose down && docker compose up -d -``` - ---- - -## ์ผ์ • - -### ์ฆ‰์‹œ (์˜ค๋Š˜) -- [x] ํ†ตํ•ฉ ๊ณ„ํš ์ˆ˜๋ฆฝ -- [ ] ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ถ”๊ฐ€ -- [ ] Router ์ฝ”๋“œ ์ˆ˜์ • -- [ ] ํ…Œ์ŠคํŠธ ๋ฐ ๊ฒ€์ฆ - -### ๋‚ด์ผ (2025-10-17) -- [ ] ์‹ค์ „ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ -- [ ] ๊ฐ์ • ๋ถ„์„ ์ •ํ™•๋„ ํ™•์ธ - -### ํ–ฅํ›„ (Phase 3.5) -- [ ] reason_with_emotion() ํ†ตํ•ฉ (์˜์‚ฌ๊ฒฐ์ • ์ง€์›) -- [ ] ๊ฐ์ • ๊ธฐ๋ฐ˜ ์ถ”์ฒœ ์‹œ์Šคํ…œ -- [ ] ๋ฒ ์ด์ง€์•ˆ ํ•™์Šต (ํ”ผ๋“œ๋ฐฑ ๊ธฐ๋ฐ˜) - ---- - -## ๊ฒฐ๋ก  - -**์„ ํƒํ•œ ๋ฐฉ์‹**: ๊ฐ์ • ์ปจํ…์ŠคํŠธ ์ „๋‹ฌ (๋ฐฉ์‹ 2) - -**์ด์œ **: -- ๊ฐ„๋‹จํ•˜๊ณ  ์‹ค์šฉ์  -- ๋ชจ๋“  ๋Œ€ํ™”์— ์ ์šฉ ๊ฐ€๋Šฅ -- LLM์˜ ์ž์—ฐ์–ด ์ดํ•ด ๋Šฅ๋ ฅ ํ™œ์šฉ -- ์ ์ง„์  ํ™•์žฅ ๊ฐ€๋Šฅ - -**๋‹ค์Œ ๋‹จ๊ณ„**: Router์— ๊ฐ์ • ๋ถ„์„ ์ฝ”๋“œ ์ถ”๊ฐ€ +- `troubleshooting/251016_emotion_router_integration.md` +- `311_FastAPI_๊ตฌ์กฐ_์›์น™.md` diff --git a/journey/plans/251016_ontology_coldmail_implementation.md b/journey/plans/251016_ontology_coldmail_implementation.md index bbd6078..b6037ec 100644 --- a/journey/plans/251016_ontology_coldmail_implementation.md +++ b/journey/plans/251016_ontology_coldmail_implementation.md @@ -1,22 +1,13 @@ -# ์˜จํ†จ๋กœ์ง€ ๊ธฐ๋ฐ˜ Coldmail ํ•„ํ„ฐ ๋ฐ ๊ธฐ์–ต ์‹œ์Šคํ…œ ๊ตฌํ˜„ ๊ณ„ํš +# ์˜จํ†จ๋กœ์ง€ ๊ธฐ๋ฐ˜ Coldmail ํ•„ํ„ฐ ๊ตฌํ˜„ ๊ณ„ํš **๋‚ ์งœ**: 2025-10-16 -**์ž‘์„ฑ์ž**: Claude -**๊ด€๋ จ ๋ฌธ์„œ**: 200_core_design/225_์˜จํ†จ๋กœ์ง€_๊ธฐ๋ฐ˜_์ง€์‹_ํ‘œํ˜„.md +**๋ชฉํ‘œ**: ์ž„๋ฒ ๋”ฉ ํ•œ๊ณ„(ํŒŒ์ธํ‹ฐ์ฒ˜ ๋ฉ”์ผ ๋ˆ„๋ฝ)๋ฅผ ์˜จํ†จ๋กœ์ง€ ์ถ”๋ก ์œผ๋กœ ํ•ด๊ฒฐ --- -## ๋ชฉํ‘œ - -ํŒŒ์ธํ‹ฐ์ฒ˜ ๋ฉ”์ผ ๋ˆ„๋ฝ ๊ฐ™์€ ์ž„๋ฒ ๋”ฉ ํ•œ๊ณ„(75% ์ •ํ™•๋„)๋ฅผ ์˜จํ†จ๋กœ์ง€ ์ถ”๋ก ์œผ๋กœ ํ•ด๊ฒฐํ•˜๊ณ , ๊ธฐ์–ต-๊ฐ์ •-์œค๋ฆฌ ์‚ผ๊ฐํ˜•์„ ์˜จํ†จ๋กœ์ง€๋กœ ๊ตฌ์กฐํ™”ํ•˜์—ฌ ๋กœ๋น™์˜ ์กด์žฌ์„ฑ ๊ฐ•ํ™”. - ---- - -## Phase 1: Coldmail ์˜จํ†จ๋กœ์ง€ ํŒŒ์ผ๋Ÿฟ (2์ฃผ) - -### ๊ฐœ๋… ๊ณ„์ธต ๊ตฌ์ถ• -**ํŒŒ์ผ**: rb8001/app/ontology/coldmail_schema.owl +## Phase 1: Coldmail ์˜จํ†จ๋กœ์ง€ (๋ฏธ์ฐฉ์ˆ˜) +### ๊ฐœ๋… ๊ณ„์ธต ``` ๋ฉ”์ผ โ””โ”€โ”€ ์™ธ๋ถ€๋ฉ”์ผ @@ -27,314 +18,75 @@ โ””โ”€โ”€ ์‚ฌ์—…๊ณ„ํš์„œ ``` -### ๊ด€๊ณ„ ์ •์˜ -- `๋ฐœ์‹ ์ž -์†ํ•œ๋‹คโ†’ ํšŒ์‚ฌ` -- `ํšŒ์‚ฌ -ํˆฌ์ž๋‹จ๊ณ„โ†’ {์‹œ๋“œ, ์‹œ๋ฆฌ์ฆˆA, ...}` -- `๋ฉ”์ผ -ํฌํ•จํ•œ๋‹คโ†’ ์ฒจ๋ถ€ํŒŒ์ผ` - ### ์ถ”๋ก  ๊ทœ์น™ (10๊ฐœ) -**ํŒŒ์ผ**: rb8001/app/services/coldmail_ontology_reasoner.py -#### Coldmail ํŒ์ • ๊ทœ์น™ (6๊ฐœ) -1. IF ์ œ๋ชฉ CONTAINS ["ํˆฌ์ž", "IR", "ํ”ผ์นญ", "์‚ฌ์—…๊ณ„ํš"] AND ์ฒจ๋ถ€ HAS PDF THEN coldmail (ํ™•๋ฅ  0.9) -2. IF ์ฒจ๋ถ€ํŒŒ์ผ๋ช… CONTAINS ["ํšŒ์‚ฌ์†Œ๊ฐœ์„œ", "IR_Deck", "Pitch", "์‚ฌ์—…๊ณ„ํš์„œ"] THEN coldmail (ํ™•๋ฅ  0.85) -3. IF ์ œ๋ชฉ CONTAINS "๊ฒ€ํ† ์š”์ฒญ" AND ์ฒจ๋ถ€ EXISTS THEN coldmail (ํ™•๋ฅ  0.8) -4. IF ๋ฐœ์‹ ์ž NOT IN known_contacts AND ์ฒจ๋ถ€ HAS PDF THEN coldmail (ํ™•๋ฅ  0.7) -5. IF ๋ณธ๋ฌธ CONTAINS ["ํˆฌ์ž ์œ ์น˜", "ํŽ€๋”ฉ", "์‹œ๋ฆฌ์ฆˆ", "๋ฐธ๋ฅ˜์—์ด์…˜"] THEN coldmail (ํ™•๋ฅ  0.75) -6. IF ๋ฐœ์‹ ์ž.๋„๋ฉ”์ธ IN ["startup", "ventures", "capital"] THEN coldmail (ํ™•๋ฅ  0.6) +**Coldmail ํŒ์ • (6๊ฐœ)**: +1. ์ œ๋ชฉ CONTAINS ["ํˆฌ์ž", "IR", "ํ”ผ์นญ"] + PDF ์ฒจ๋ถ€ โ†’ 0.9 +2. ์ฒจ๋ถ€ํŒŒ์ผ๋ช… CONTAINS ["ํšŒ์‚ฌ์†Œ๊ฐœ์„œ", "IR_Deck"] โ†’ 0.85 +3. ์ œ๋ชฉ "๊ฒ€ํ† ์š”์ฒญ" + ์ฒจ๋ถ€ โ†’ 0.8 +4. ๋ฏธ๋“ฑ๋ก ๋ฐœ์‹ ์ž + PDF โ†’ 0.7 +5. ๋ณธ๋ฌธ CONTAINS ["ํˆฌ์ž ์œ ์น˜", "ํŽ€๋”ฉ", "๋ฐธ๋ฅ˜์—์ด์…˜"] โ†’ 0.75 +6. ๋ฐœ์‹ ์ž ๋„๋ฉ”์ธ IN ["startup", "ventures", "capital"] โ†’ 0.6 -#### Normal ํŒ์ • ๊ทœ์น™ (4๊ฐœ) -7. IF ์ œ๋ชฉ CONTAINS ["ํ–‰์‚ฌ", "์ดˆ๋Œ€", "์•ˆ๋‚ด", "์„ธ๋ฏธ๋‚˜"] THEN normal (ํ™•๋ฅ  0.9) -8. IF ์ œ๋ชฉ CONTAINS ["์˜์ˆ˜์ฆ", "๋ฐœ๊ธ‰", "์„ธ๊ธˆ๊ณ„์‚ฐ์„œ", "๊ฒฌ์ ์„œ"] THEN normal (ํ™•๋ฅ  0.95) -9. IF ์ œ๋ชฉ CONTAINS ["ํšŒ์˜", "๊ณต์ง€", "๋ณด๊ณ ", "์—…๋ฌด"] THEN normal (ํ™•๋ฅ  0.85) -10. IF ๋ฐœ์‹ ์ž IN known_contacts AND NOT (๊ทœ์น™ 1-6) THEN normal (ํ™•๋ฅ  0.8) +**Normal ํŒ์ • (4๊ฐœ)**: +7. ์ œ๋ชฉ CONTAINS ["ํ–‰์‚ฌ", "์ดˆ๋Œ€", "์„ธ๋ฏธ๋‚˜"] โ†’ 0.9 +8. ์ œ๋ชฉ CONTAINS ["์˜์ˆ˜์ฆ", "์„ธ๊ธˆ๊ณ„์‚ฐ์„œ"] โ†’ 0.95 +9. ์ œ๋ชฉ CONTAINS ["ํšŒ์˜", "๊ณต์ง€", "๋ณด๊ณ "] โ†’ 0.85 +10. ๋“ฑ๋ก ๋ฐœ์‹ ์ž + ๊ทœ์น™ 1-6 ๋ฏธํ•ด๋‹น โ†’ 0.8 -#### ์ตœ์ข… ํŒ๋‹จ ๋กœ์ง -- Coldmail ๊ทœ์น™ ๋งค์นญ: ๊ฐ€์žฅ ๋†’์€ ํ™•๋ฅ  ์„ ํƒ -- Normal ๊ทœ์น™ ๋งค์นญ: ๊ฐ€์žฅ ๋†’์€ ํ™•๋ฅ  ์„ ํƒ -- Coldmail ํ™•๋ฅ  > Normal ํ™•๋ฅ  โ†’ Coldmail -- Threshold: 0.7 ์ด์ƒ ์‹œ ํ™•์ •, 0.4-0.7์€ LLM fallback +### ๊ตฌํ˜„ ํ•„์š” -### ๊ฒ€์ฆ -- ํŒŒ์ธํ‹ฐ์ฒ˜ ๋ฉ”์ผ: coldmail ํ™•๋ฅ  0.9+ (ํ˜„์žฌ 0.28) +**ํŒŒ์ผ**: `rb8001/app/services/coldmail_ontology_reasoner.py` +- ๊ทœ์น™ ์—”์ง„ ๊ตฌํ˜„ +- ์ž„๋ฒ ๋”ฉ ๋ถ„๋ฅ˜(ํ˜„์žฌ 75%) + ์˜จํ†จ๋กœ์ง€ ์ถ”๋ก  โ†’ 90%+ ๋ชฉํ‘œ +- Threshold: 0.7 ์ด์ƒ ํ™•์ •, 0.4-0.7์€ LLM fallback + +**๊ฒ€์ฆ**: +- ํŒŒ์ธํ‹ฐ์ฒ˜ ๋ฉ”์ผ: coldmail 0.9+ (ํ˜„์žฌ 0.28) - ๊ธฐ์กด 17๊ฑด ์žฌํ…Œ์ŠคํŠธ: ์ •ํ™•๋„ 90%+ --- -## Phase 2: ๊ธฐ์–ต ์‹œ์Šคํ…œ ์˜จํ†จ๋กœ์ง€ ํ†ตํ•ฉ (1๊ฐœ์›”) +## Phase 2: Neo4j ๊ธฐ์–ต ์‹œ์Šคํ…œ (๋ฏธ์ฐฉ์ˆ˜) -### Neo4j ๋„์ž… -**์œ„์น˜**: 51123 ์„œ๋ฒ„ (192.168.219.45) -**์„ค์น˜ ์ƒํƒœ**: โœ… ์ด๋ฏธ ์„ค์น˜๋จ (์‹œ์Šคํ…œ ์ง์ ‘ ์„ค์น˜, Docker ์•„๋‹˜) +### ์ธํ”„๋ผ -**์„ค์น˜ ์ •๋ณด**: -- ๋ฒ„์ „: Neo4j 2025.06.2 Community Edition -- ์„œ๋น„์Šค ์ƒํƒœ: Active (running) since 2025-08-20 -- ๊ฐ€๋™ ์‹œ๊ฐ„: 1๊ฐœ์›” 26์ผ -- HTTP ํฌํŠธ: 7474 (๋ธŒ๋ผ์šฐ์ € ์ ‘์†) -- Bolt ํฌํŠธ: 7687 (๋“œ๋ผ์ด๋ฒ„ ์—ฐ๊ฒฐ) -- ์ธ์ฆ: ํ™œ์„ฑํ™” (๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ๋จ) +**Neo4j ์„ค์น˜ ์™„๋ฃŒ** (51123 ์„œ๋ฒ„): +- ๋ฒ„์ „: 2025.06.2 Community +- Bolt: neo4j://192.168.219.45:7687 +- HTTP: http://192.168.219.45:7474 -**์—ฐ๊ฒฐ ์ •๋ณด**: -- Bolt URI: `neo4j://192.168.219.45:7687` -- HTTP API: `http://192.168.219.45:7474` -- ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: neo4j (๊ธฐ๋ณธ DB), system (์‹œ์Šคํ…œ DB) +### ์Šคํ‚ค๋งˆ ์„ค๊ณ„ -**์Šคํ‚ค๋งˆ** (์„ค๊ณ„์•ˆ): ``` -(์‚ฌ๊ฑด)-[:๋ฐœ์ƒ์‹œ๊ฐ]->(์‹œ๊ฐ„) -(์‚ฌ๊ฑด)-[:๊ด€๋ จ๊ฐ์ •]->(๊ฐ์ •) -(์‚ฌ๊ฑด)-[:์ฐธ์—ฌ์ž]->(์‚ฌ์šฉ์ž) -(์‚ฌ๊ฑด)-[:๊ฒฐ๊ณผ]->(๊ฒฐ๊ณผ) +(:User)-[:SENDS]->(:Email)-[:CONTAINS]->(:Attachment) +(:Email)-[:CLASSIFIED_AS]->(:Category {name:"coldmail"}) +(:Email)-[:HAS_EMOTION]->(:Emotion {type:"fear", confidence:0.8}) ``` -### ChromaDB + Neo4j ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•Œ๊ณ ๋ฆฌ์ฆ˜ -**rb8001/app/services/memory_hybrid_retrieval.py** - -#### 1๋‹จ๊ณ„: ChromaDB ๋ฒกํ„ฐ ๊ฒ€์ƒ‰ (๋น ๋ฅธ ํ•„ํ„ฐ๋ง) -```python -# ์ž…๋ ฅ: ์‚ฌ์šฉ์ž ์ฟผ๋ฆฌ (์˜ˆ: "์ž‘๋…„ ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๋•Œ ์–ด๋–ป๊ฒŒ ํ–ˆ์ง€?") -# 1. ์ฟผ๋ฆฌ ์ž„๋ฒ ๋”ฉ: skill-embedding (8515) /embed ํ˜ธ์ถœ -# 2. ChromaDB ์œ ์‚ฌ๋„ ๊ฒ€์ƒ‰: top_k=20 (์ถฉ๋ถ„ํ•œ ํ›„๋ณด) -# 3. ์ถœ๋ ฅ: 20๊ฐœ ํ›„๋ณด ๋Œ€ํ™” (๋ฒกํ„ฐ ์ ์ˆ˜ ํฌํ•จ) +### ์ฟผ๋ฆฌ ์˜ˆ์‹œ +```cypher +// ํˆฌ์ž ์ œ์•ˆ ์ด๋ฉ”์ผ ์ค‘ ๊ธ์ •์  ๊ฐ์ • ๋ฉ”์ผ ์ฐพ๊ธฐ +MATCH (u:User)-[:SENDS]->(e:Email)-[:CLASSIFIED_AS]->(:Category {name:"coldmail"}) +WHERE e.emotion IN ["happiness", "neutral"] +RETURN e.subject, e.sender, e.timestamp ``` -#### 2๋‹จ๊ณ„: Neo4j ๊ทธ๋ž˜ํ”„ ์ถ”๋ก  (์˜๋ฏธ์  ์—ฐ๊ฒฐ) -```python -# ์ž…๋ ฅ: ChromaDB ํ›„๋ณด 20๊ฐœ -# Cypher ์ฟผ๋ฆฌ: -MATCH (event:์‚ฌ๊ฑด)-[:๊ด€๋ จ๊ฐ์ •]->(emotion) -MATCH (event)-[:๊ฒฐ๊ณผ]->(result) -WHERE event.id IN [ํ›„๋ณด 20๊ฐœ ID] -RETURN event, emotion, result, event.์‹œ๊ฐ„ -ORDER BY event.์‹œ๊ฐ„ DESC - -# ์šฐ์„ ์ˆœ์œ„: -# - [:๊ฒฐ๊ณผ]->(์„ฑ๊ณต) ๊ด€๊ณ„ ์žˆ๋Š” ์‚ฌ๊ฑด ์šฐ์„  (๊ฐ€์ค‘์น˜ 1.5๋ฐฐ) -# - [:๊ด€๋ จ๊ฐ์ •]->(๊ธด์žฅ) ๋งค์นญ ์‹œ ๊ฐ€์ค‘์น˜ 1.3๋ฐฐ -# - ์‹œ๊ฐ„์  ๊ทผ์ ‘์„ฑ: 1๋…„ ์ „ > 2๋…„ ์ „ (๊ฑฐ๋ฆฌ ์—ญ์ˆ˜) -``` - -#### 3๋‹จ๊ณ„: ์ ์ˆ˜ ํ†ตํ•ฉ ๋ฐ ์ˆœ์œ„ ๊ฒฐ์ • -```python -์ตœ์ข…์ ์ˆ˜ = (๋ฒกํ„ฐ์ ์ˆ˜ * 0.4) + (๊ทธ๋ž˜ํ”„์ ์ˆ˜ * 0.6) -# ๊ทธ๋ž˜ํ”„์ ์ˆ˜ = ๊ด€๊ณ„๊ฐ€์ค‘์น˜ * ์‹œ๊ฐ„๊ฐ€์ค‘์น˜ -# ์ถœ๋ ฅ: ์ƒ์œ„ 5๊ฐœ ์‚ฌ๊ฑด ๋ฐ˜ํ™˜ -``` - -### API ์„ค๊ณ„ -**rb8001/app/router/memory_ontology.py**: -- POST /memory/event: ์‚ฌ๊ฑด ์ €์žฅ (์ž๋™ ๊ด€๊ณ„ ์ถ”๋ก ) -- GET /memory/recall: ์ฟผ๋ฆฌ ๊ธฐ๋ฐ˜ ํšŒ์ƒ (3๋‹จ๊ณ„ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ) - --- -## Phase 3: ๊ฐ์ •-์œค๋ฆฌ ์˜จํ†จ๋กœ์ง€ ๊ทœ์น™ํ™” (1๊ฐœ์›”) +## Phase 3: ๊ฐ์ •-๊ธฐ์–ต-์œค๋ฆฌ ์‚ผ๊ฐํ˜• (๋ฏธ์ฐฉ์ˆ˜) -### ๊ฐ์ •-์šฐ๋„ ์˜จํ†จ๋กœ์ง€ -**ํŒŒ์ผ**: rb8001/app/ontology/emotion_likelihood.owl +### ํ†ตํ•ฉ ์„ค๊ณ„ +- ๊ฐ์ • ๋ถ„์„ โ†’ ๊ธฐ์–ต ๊ฒ€์ƒ‰ โ†’ ์œค๋ฆฌ ์ œ์•ฝ โ†’ ์‘๋‹ต ์ƒ์„ฑ +- Neo4j์— ๊ฐ์ • ์ด๋ ฅ ์ €์žฅ +- ํŒจํ„ด ๋ถ„์„ (์šฐ์šธ์ฆ ์กฐ๊ธฐ ๊ฐ์ง€) -``` -๋ถˆ์•ˆ -์กฐ์ •โ†’ ์œ„ํ—˜๊ด€๋ จ์ฆ๊ฑฐ (๊ฐ€์ค‘์น˜ 1.3) -ํฅ๋ถ„ -์กฐ์ •โ†’ ๊ธ์ •๊ด€๋ จ์ฆ๊ฑฐ (๊ฐ€์ค‘์น˜ 1.2) -์Šฌํ”” -์กฐ์ •โ†’ ์œ„๋กœ๊ด€๋ จ์ฆ๊ฑฐ (๊ฐ€์ค‘์น˜ 1.5) -``` - -### ์œค๋ฆฌ ์ œ์•ฝ ์˜จํ†จ๋กœ์ง€ -**ํŒŒ์ผ**: rb8001/app/ontology/ethics_constraints.owl - -``` -์ •๋ณด์ˆ˜์ง‘ ํ–‰๋™ -์ œ์•ฝโ†’ [๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ, ํˆฌ๋ช…์„ฑ, ๋™์˜] -์กฐ์–ธ ํ–‰๋™ -์ œ์•ฝโ†’ [ํ•ด์•…๊ธˆ์ง€, ์ž์œจ์„ฑ์กด์ค‘] -``` - -### ์ถ”๋ก  ์—”์ง„ ํ†ตํ•ฉ -**rb8001/app/services/ontology_explainer.py** - -#### ์ถ”๋ก  ๊ณผ์ • ์ถ”์  (Jena Rules) -```python -# ๊ทœ์น™ ์‹คํ–‰ ์‹œ ์ถ”์  ๋กœ๊ทธ ์ƒ์„ฑ -trace = [] -for rule in matched_rules: - trace.append({ - "rule_id": rule.id, - "condition": rule.condition, # "์‚ฌ์šฉ์ž.๊ฐ์ • = ๋ถˆ์•ˆ" - "action": rule.action, # "์œ„ํ—˜๊ด€๋ จ์ฆ๊ฑฐ.์šฐ๋„ *= 1.3" - "matched_value": matched_value # "๊ฐ์ •: ๋ถˆ์•ˆ" - }) -``` - -#### ์„ค๋ช… ํ…œํ”Œ๋ฆฟ (์ž์—ฐ์–ด ์ƒ์„ฑ) -```python -def generate_explanation(trace): - explanation = [] - for step in trace: - if step['rule_id'].startswith('emotion_'): - template = "์‚ฌ์šฉ์ž์˜ {emotion} ๊ฐ์ • ๋•Œ๋ฌธ์— {evidence} ๊ด€๋ จ ์ฆ๊ฑฐ์˜ ์ค‘์š”๋„๋ฅผ {weight}๋ฐฐ ์กฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค." - explanation.append(template.format(...)) - elif step['rule_id'].startswith('ethics_'): - template = "{action} ํ–‰๋™์€ {constraint} ์›์น™์— ์œ„๋ฐฐ๋˜์–ด ๊ฑฐ๋ถ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋Œ€์•ˆ: {alternative}" - explanation.append(template.format(...)) - return " ".join(explanation) -``` - -#### ์œค๋ฆฌ ์ถฉ๋Œ ํ•ด๊ฒฐ ์šฐ์„ ์ˆœ์œ„ (OWL Ontology) -```turtle -# ethics_constraints.owl -:ํ•ด์•…๊ธˆ์ง€ rdf:type :์œค๋ฆฌ์›์น™ ; :priority 1 . -:ํˆฌ๋ช…์„ฑ rdf:type :์œค๋ฆฌ์›์น™ ; :priority 2 . -:์ž์œจ์„ฑ์กด์ค‘ rdf:type :์œค๋ฆฌ์›์น™ ; :priority 3 . -:๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ rdf:type :์œค๋ฆฌ์›์น™ ; :priority 1 . - -# ์ถฉ๋Œ ์‹œ priority ๋†’์€ ๊ฒƒ ์šฐ์„  (1 > 2 > 3) -``` - -#### HermiT ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ -- ๊ทœ์น™ ๊ฐ„ ๋ชจ์ˆœ ์ž๋™ ํƒ์ง€ (์˜ˆ: "๋ถˆ์•ˆ โ†’ ์šฐ๋„ ์ฆ๊ฐ€" vs "๋ถˆ์•ˆ โ†’ ์šฐ๋„ ๊ฐ์†Œ") -- ๋ฐฐํฌ ์ „ ontology validation ์ž๋™ํ™” - ---- - -## ๊ธฐ์ˆ  ์Šคํƒ - -| ๊ตฌ๋ถ„ | ๋„๊ตฌ | ์šฉ๋„ | -|------|------|------| -| ์˜จํ†จ๋กœ์ง€ ํŽธ์ง‘ | Protรฉgรฉ | OWL ์Šคํ‚ค๋งˆ ์„ค๊ณ„ | -| ๊ทธ๋ž˜ํ”„ DB | Neo4j | ์‚ฌ๊ฑด-๊ด€๊ณ„ ์ €์žฅ | -| ์ถ”๋ก  ์—”์ง„ | HermiT | ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ | -| ๋ฒกํ„ฐ DB | ChromaDB | ๊ธฐ์กด ์œ ์ง€ (ํ•˜์ด๋ธŒ๋ฆฌ๋“œ) | -| ํ‘œ์ค€ | RDF/OWL | ์˜จํ†จ๋กœ์ง€ ํ‘œํ˜„ | - ---- - -## ๋งˆ์ผ์Šคํ†ค - -| ๋‚ ์งœ | ๋‹จ๊ณ„ | ๋ชฉํ‘œ | -|------|------|------| -| Week 1-2 | Phase 1 | Coldmail ์ •ํ™•๋„ 90%+ | -| Week 3-6 | Phase 2 | ๊ธฐ์–ต ํšŒ์ƒ ์˜๋ฏธ์  ์—ฐ๊ฒฐ | -| Week 7-10 | Phase 3 | ๊ฐ์ •-์œค๋ฆฌ ๊ทœ์น™ ํˆฌ๋ช…ํ™” | - ---- - -## ์ฒดํฌ๋ฆฌ์ŠคํŠธ - -Phase 1: โœ… ์™„๋ฃŒ (2025-10-16) -- [x] Coldmail ์˜จํ†จ๋กœ์ง€ ์Šคํ‚ค๋งˆ ์„ค๊ณ„ (๊ทœ์น™ ๊ธฐ๋ฐ˜, Python ๊ตฌํ˜„) -- [x] ์ถ”๋ก  ๊ทœ์น™ 11๊ฐœ ๊ตฌํ˜„ (coldmail_ontology_reasoner.py) -- [x] ํŒŒ์ธํ‹ฐ์ฒ˜ ๋ฉ”์ผ ์žฌํ…Œ์ŠคํŠธ (0.90, ๋ชฉํ‘œ 0.9+ ๋‹ฌ์„ฑ) -- [x] Hybrid Filter ํ†ตํ•ฉ (USE_ONTOLOGY_FILTER ํ™˜๊ฒฝ๋ณ€์ˆ˜, ๋กค๋ฐฑ ๊ฐ€๋Šฅ) -- [ ] Slack ํ”ผ๋“œ๋ฐฑ โ†’ ๊ด€๊ณ„ ๊ฐ€์ค‘์น˜ ๋ฒ ์ด์ง€์•ˆ ์—…๋ฐ์ดํŠธ (Phase 1.5) - -Phase 2: -- [x] Neo4j ์„ค์น˜ ํ™•์ธ (51123 ์„œ๋ฒ„) - โœ… ์ด๋ฏธ ์„ค์น˜๋จ (2025.06.2) -- [ ] Neo4j Python ๋“œ๋ผ์ด๋ฒ„ ์—ฐ๋™ (neo4j ํŒจํ‚ค์ง€) -- [ ] ChromaDB + Neo4j ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ฟผ๋ฆฌ ๊ตฌํ˜„ -- [ ] "1๋…„ ์ „ ๋น„์Šทํ•œ ์ƒํ™ฉ" ํšŒ์ƒ ํ…Œ์ŠคํŠธ - -Phase 3: -- [ ] ๊ฐ์ •-์šฐ๋„ ์˜จํ†จ๋กœ์ง€ 7๊ฐ€์ง€ ๊ฐ์ • -- [ ] ์œค๋ฆฌ ์ œ์•ฝ ์˜จํ†จ๋กœ์ง€ ์‚ฌ๋ž‘ ๊ธฐ๋ฐ˜ ์›์น™ -- [ ] HermiT ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ ์ž๋™ํ™” - ---- - ---- - -## Phase 1 ๊ตฌํ˜„ ์™„๋ฃŒ (2025-10-16) - -### ๊ตฌํ˜„ ํŒŒ์ผ -- **rb8001/app/services/coldmail_ontology_reasoner.py**: 11๊ฐœ ์ถ”๋ก  ๊ทœ์น™ -- **rb8001/app/services/coldmail_hybrid_filter.py**: ์˜จํ†จ๋กœ์ง€ ํ†ตํ•ฉ (ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ œ์–ด) -- **rb8001/tests/test_coldmail_ontology.py**: 7๊ฐœ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค (100% ํ†ต๊ณผ) -- **rb8001/tests/test_hybrid_simple.py**: 5๊ฐœ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ (100% ํ†ต๊ณผ) - -### ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ -``` -ํŒŒ์ธํ‹ฐ์ฒ˜ ๋ฉ”์ผ: 0.28 โ†’ 0.90 (4๊ฐœ ๊ทœ์น™ ๋งค์นญ) -- R1: ํˆฌ์ž ํ‚ค์›Œ๋“œ + PDF (0.90) -- R2: ํšŒ์‚ฌ์†Œ๊ฐœ์„œ ์ฒจ๋ถ€๋ช… (0.85) -- R3: ๊ฒ€ํ† ์š”์ฒญ + ์ฒจ๋ถ€ (0.80) -- R3B: ํˆฌ์ž๊ฒ€ํ†  ํ‚ค์›Œ๋“œ (0.65) -- R4: ์‹ ๊ทœ ๋ฐœ์‹ ์ž + PDF (0.70) -``` - -### ๋กค๋ฐฑ ์‹œ๋‚˜๋ฆฌ์˜ค - -#### ์‹œ๋‚˜๋ฆฌ์˜ค 1: ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋กค๋ฐฑ (์ฆ‰์‹œ, ๊ถŒ์žฅ) -**์ฆ์ƒ**: ์˜จํ†จ๋กœ์ง€ ์˜คํŒ, ์„ฑ๋Šฅ ์ €ํ•˜ -**๋ฐฉ๋ฒ•**: -```bash -cd /home/admin/ivada_project/rb8001 - -# .env ํŒŒ์ผ ์ˆ˜์ • -USE_ONTOLOGY_FILTER=false - -# Docker ์žฌ์‹œ์ž‘ (5์ดˆ ์†Œ์š”) -docker compose down && docker compose up -d -``` -**๊ฒฐ๊ณผ**: ๊ธฐ์กด ์ž„๋ฒ ๋”ฉ ํ•„ํ„ฐ๋กœ ์ฆ‰์‹œ ๋ณต๊ท€, ์ฝ”๋“œ ๋ณ€๊ฒฝ ์—†์Œ - -#### ์‹œ๋‚˜๋ฆฌ์˜ค 2: Git ๋ถ€๋ถ„ ๋กค๋ฐฑ (์˜จํ†จ๋กœ์ง€๋งŒ ์ œ๊ฑฐ) -**์ฆ์ƒ**: ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋กค๋ฐฑ์œผ๋กœ๋„ ํ•ด๊ฒฐ ์•ˆ ๋  ๋•Œ -**๋ฐฉ๋ฒ•**: -```bash -cd /home/admin/ivada_project/rb8001 - -# ์˜จํ†จ๋กœ์ง€ ํŒŒ์ผ๋งŒ ์ œ๊ฑฐ -git rm app/services/coldmail_ontology_reasoner.py -git rm tests/test_coldmail_ontology.py tests/test_hybrid_simple.py - -# hybrid_filter.py๋ฅผ ์˜จํ†จ๋กœ์ง€ ํ†ตํ•ฉ ์ด์ „ ๋ฒ„์ „์œผ๋กœ ๋ณต๊ตฌ -git checkout 48aacfa^ -- app/services/coldmail_hybrid_filter.py - -# ์ปค๋ฐ‹ ๋ฐ ๋ฐฐํฌ -git commit -m "Rollback: Remove ontology reasoner" -git push origin main -docker compose down && docker compose up -d --build -``` - -#### ์‹œ๋‚˜๋ฆฌ์˜ค 3: ์ „์ฒด ๋กค๋ฐฑ (Phase 1 ์ด์ „) -**์ฆ์ƒ**: ์‹ฌ๊ฐํ•œ ์˜ค๋ฅ˜, ์ „์ฒด ๋˜๋Œ๋ฆฌ๊ธฐ ํ•„์š” -**๋ฐฉ๋ฒ•**: -```bash -cd /home/admin/ivada_project/rb8001 - -# Phase 1 ํŒŒ์ผ๋Ÿฟ ์ง์ „ ์ปค๋ฐ‹์œผ๋กœ ๋ณต๊ท€ -git checkout 28ef36c # 48aacfa ์ด์ „ ์ปค๋ฐ‹ -git push origin main --force - -# Docker ์žฌ๋นŒ๋“œ -docker compose down && docker compose up -d --build -``` -**์ฃผ์˜**: force push๋Š” ์ตœํ›„ ์ˆ˜๋‹จ - -#### ๋กค๋ฐฑ ํฌ์ธํŠธ ์ปค๋ฐ‹ -- **28ef36c**: Phase 1 ์ด์ „ (์•ˆ์ „ํ•œ ๋ณต๊ท€ ์ง€์ ) -- **48aacfa**: Phase 1 ํŒŒ์ผ๋Ÿฟ ์™„๋ฃŒ (์˜จํ†จ๋กœ์ง€ ๋‹จ๋… ๋™์ž‘) -- **7a122f4**: Hybrid Filter ํ†ตํ•ฉ -- **88636cf**: UnboundLocalError ํ•ซํ”ฝ์Šค (ํ˜„์žฌ) - -#### ๋กค๋ฐฑ ๊ฒ€์ฆ -```bash -# ๋กค๋ฐฑ ํ›„ ํ™•์ธ -docker logs rb8001 --tail 50 | grep -i "ontology\|embedding" -# "Embedding" ๋ฉ”์‹œ์ง€๋งŒ ๋ณด์ด๋ฉด ๋กค๋ฐฑ ์„ฑ๊ณต -``` - -### ์‹ค์ „ ๊ฒ€์ฆ ๊ณ„ํš -- **์ผ์‹œ**: 2025-10-17 09:05 Coldmail Daily Briefing -- **ํ™•์ธ ์‚ฌํ•ญ**: - - [ ] ๋กœ๊ทธ์—์„œ "Stage 1 (Ontology)" ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ - - [ ] ํŒŒ์ธํ‹ฐ์ฒ˜ ์œ ์‚ฌ ์ผ€์ด์Šค ๋ฐœ์ƒ ์‹œ ์˜จํ†จ๋กœ์ง€ ํŒ์ • ํ™•์ธ - - [ ] ์˜ค๋ฅ˜ ์—†์ด ์ •์ƒ ๋™์ž‘ ํ™•์ธ - - [ ] ์‘๋‹ต ์†๋„ ์ธก์ • (์˜จํ†จ๋กœ์ง€ vs ๊ธฐ์กด ์ž„๋ฒ ๋”ฉ) +**์˜ˆ์ƒ ๊ธฐ๊ฐ„**: 3-4๊ฐœ์›” --- ## ์ฐธ๊ณ  -- ์„ค๊ณ„ ์›์น™: 200_core_design/225_์˜จํ†จ๋กœ์ง€_๊ธฐ๋ฐ˜_์ง€์‹_ํ‘œํ˜„.md -- ๋ฌธ์ œ ๋ฐฐ๊ฒฝ: troubleshooting/251014_claude_coldmail_filter_tokenization_issue.md -- ์˜จํ†จ๋กœ์ง€ ์—ฐ๊ตฌ: research/ontology/ -- **๊ตฌํ˜„ ์ปค๋ฐ‹**: rb8001 88636cf (hotfix), 7a122f4 (ํ†ตํ•ฉ), 48aacfa (ํŒŒ์ผ๋Ÿฟ) +- `book/200_core_design/225_์˜จํ†จ๋กœ์ง€_๊ธฐ๋ฐ˜_์ง€์‹_ํ‘œํ˜„.md` +- `troubleshooting/250815_emotion_model_training.md` +- `troubleshooting/251016_emotion_ontology_basic.md` diff --git a/journey/plans/251123_rb8001_๊ณ„์ธต_๋ถ„๋ฆฌ_๋ฆฌํŒฉํ† ๋ง_๊ณ„ํš.md b/journey/plans/251123_rb8001_๊ณ„์ธต_๋ถ„๋ฆฌ_๋ฆฌํŒฉํ† ๋ง_๊ณ„ํš.md index b7b22aa..8068774 100644 --- a/journey/plans/251123_rb8001_๊ณ„์ธต_๋ถ„๋ฆฌ_๋ฆฌํŒฉํ† ๋ง_๊ณ„ํš.md +++ b/journey/plans/251123_rb8001_๊ณ„์ธต_๋ถ„๋ฆฌ_๋ฆฌํŒฉํ† ๋ง_๊ณ„ํš.md @@ -1,210 +1,85 @@ # rb8001 ๊ณ„์ธต ๋ถ„๋ฆฌ ๋ฆฌํŒฉํ† ๋ง ๊ณ„ํš **๋‚ ์งœ**: 2025-11-23 -**์ž‘์„ฑ์ž**: admin -**๊ด€๋ จ ํŒŒ์ผ**: `rb8001/main.py`, `rb8001/app/router/router.py` -**์ฐธ๊ณ **: `DOCS/book/300_architecture/311_FastAPI_๊ตฌ์กฐ_์›์น™.md` +**์ฐธ๊ณ **: `311_FastAPI_๊ตฌ์กฐ_์›์น™.md` --- ## ๋ชฉ์  -rb8001์˜ ์ค‘๋ณต ๋Œ€ํ™” ์ €์žฅ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ , FastAPI ๊ตฌ์กฐ ์›์น™(311_FastAPI_๊ตฌ์กฐ_์›์น™.md)์„ ์ค€์ˆ˜ํ•˜๋Š” ๊ณ„์ธต ๋ถ„๋ฆฌ ๊ตฌ์กฐ๋กœ ๋ฆฌํŒฉํ† ๋ง - -### ์ฆ‰์‹œ ํ•ด๊ฒฐ ํ•„์š” ๋ฌธ์ œ -- ๋Œ€ํ™”๊ฐ€ DB์— 2๋ฒˆ ์ €์žฅ๋˜๋Š” ์ค‘๋ณต ์ €์žฅ ๋ฒ„๊ทธ (1ํšŒ ์š”์ฒญ โ†’ 2ํšŒ DB INSERT) - -### ์žฅ๊ธฐ ๊ฐœ์„  ๋ชฉํ‘œ -- ๊ณ„์ธต ๋ถ„๋ฆฌ ์›์น™ ์œ„๋ฐ˜ 15๊ฑด ํ•ด๊ฒฐ -- ์œ ์ง€๋ณด์ˆ˜์„ฑ ๋ฐ ํ™•์žฅ์„ฑ ํ–ฅ์ƒ -- Conversation/Message ์˜จํ†จ๋กœ์ง€ ๊ธฐ๋ฐ˜ ํ†ตํ•ฉ ๋กœ๊ทธ(actor, source_channel, channel_id, context_node ๋“ฑ)๋กœ ์ „ํ™˜ + Celery/Redis ๋น„๋™๊ธฐ ํ ๋„์ž… ๊ฒ€ํ†  (ChromaDB, intent_review_queue ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ฒ˜๋ฆฌ) +์ค‘๋ณต ๋Œ€ํ™” ์ €์žฅ ๋ฒ„๊ทธ ํ•ด๊ฒฐ ๋ฐ ๊ณ„์ธต ๋ถ„๋ฆฌ ์›์น™ ์ค€์ˆ˜ --- -## ํ˜„์žฌ ๋ฌธ์ œ +## Phase 1: ์ค‘๋ณต ์ €์žฅ ํ•ด๊ฒฐ (์™„๋ฃŒ) -### 1. ์ค‘๋ณต ์ €์žฅ (๊ธด๊ธ‰) - -**๋ฌธ์ œ ์ƒํ™ฉ:** -- router.py:876 - `_call_internal_llm`์—์„œ 1์ฐจ ์ €์žฅ (router โ†’ state ์ง์ ‘ ํ˜ธ์ถœ) -- main.py:110 - `save_message_conversation`์—์„œ 2์ฐจ ์ €์žฅ (์ด๋ฏธ ์ œ๊ฑฐ๋จ) -- ๊ฒฐ๊ณผ: conversation_log ํ…Œ์ด๋ธ”์— ๋™์ผ ๋Œ€ํ™” 2๋ฒˆ ์‚ฝ์ž… - -**์›์น™ ์œ„๋ฐ˜:** -- 311_FastAPI_๊ตฌ์กฐ_์›์น™.md:92 - ๊ณ„์ธต ๊ฑด๋„ˆ๋›ฐ๊ธฐ (router โ†’ state ์ง์ ‘ ํ˜ธ์ถœ) -- DRY ์›์น™ ์œ„๋ฐ˜ (๊ฐ™์€ ๋กœ์ง 2๊ณณ ์‹คํ–‰) - -### 2. ๊ณ„์ธต ๊ตฌ์กฐ ์œ„๋ฐ˜ (15๊ฑด) - -#### ๊ณ„์ธต ๊ฑด๋„ˆ๋›ฐ๊ธฐ -1. router.py:876 - router โ†’ state ์ง์ ‘ ํ˜ธ์ถœ (์›์น™:92) -2. router/feedback_handler.py:58 - SessionLocal() ์ง์ ‘ ์‚ฌ์šฉ (์›์น™:21, 164) -3. router/intent_review_endpoint.py:21 - SessionLocal() ์ง์ ‘ ์‚ฌ์šฉ -4. router/slack_handler.py:77,202,361 - SessionLocal() ์ง์ ‘ ์‚ฌ์šฉ -5. services/startup_valuation.py:475 - asyncpg.connect() ์ง์ ‘ ์‚ฌ์šฉ (์›์น™:164) -6. services/intent_bayes.py:57,117,160 - psycopg2.connect() ์ง์ ‘ ์‚ฌ์šฉ -7. services/coldmail_filter.py:182,208,255 - asyncpg.connect() ์ง์ ‘ ์‚ฌ์šฉ -8. main.py:694,711 - state/database ์ง์ ‘ ํ˜ธ์ถœ (์›์น™:92) - -#### main.py ์›์น™ ์œ„๋ฐ˜ -9. main.py:84-176 - ์—”๋“œํฌ์ธํŠธ ์ง์ ‘ ์ •์˜ (์›์น™:30 - "์•ฑ ์‹คํ–‰, ๋ผ์šฐํ„ฐ ๋“ฑ๋ก๋งŒ") -10. main.py:118-137 - ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ํฌํ•จ (์›์น™:21) -11. main.py:211,620 - router ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ(_call_internal_llm) ์ง์ ‘ ํ˜ธ์ถœ - -#### ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ฐฐ์น˜ ์˜ค๋ฅ˜ -12. router.py:128-682 - route_message()๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง, services/์— ์žˆ์–ด์•ผ ํ•จ (์›์น™:21) -13. app/brain/, app/llm/, app/memory/ - services/brain/, services/llm/๋กœ ์ด๋™ ํ•„์š” (์›์น™:28-42) - -#### ๊ตฌ์กฐ ๋ˆ„๋ฝ -14. schemas/ ํด๋” ์—†์Œ (์›์น™:49) -15. models์™€ state ๋ฏธ๋ถ„๋ฆฌ - state/database.py์— ORM ๋ชจ๋ธ + DB ์ ‘๊ทผ ํ˜ผ์žฌ (์›์น™:36,47) - -#### ํŒŒ์ผ ํฌ๊ธฐ ์ดˆ๊ณผ -- 24๊ฐœ ํŒŒ์ผ์ด 300์ค„ ์ดˆ๊ณผ (router.py 915์ค„, main.py 747์ค„ ๋“ฑ) +**๋ฌธ์ œ**: router.py + main.py ์ด์ค‘ ์ €์žฅ +**ํ•ด๊ฒฐ**: `troubleshooting/250924_๋Œ€ํ™”์ €์žฅ_์˜ค๋ฅ˜.md` ์ฐธ์กฐ --- -## ํ•ด๊ฒฐ ๋ฐฉ์•ˆ +## Phase 2: ๊ณ„์ธต ๊ฑด๋„ˆ๋›ฐ๊ธฐ ํ•ด๊ฒฐ (๋ถ€๋ถ„ ์™„๋ฃŒ) -### Phase 1: ์ค‘๋ณต ์ €์žฅ ํ•ด๊ฒฐ (๊ธด๊ธ‰, ์™„๋ฃŒ) +**๊ตฌํ˜„ ์™„๋ฃŒ**: `troubleshooting/251123_rb8001_endpoint_service_separation.md` ์ฐธ์กฐ +- main.py ์—”๋“œํฌ์ธํŠธ โ†’ routers/๋กœ ์ด๋™ +- router โ†’ services ํ˜ธ์ถœ ๊ตฌ์กฐ ์ผ๋ถ€ ์ •๋ฆฌ -**์ˆ˜์ • ์™„๋ฃŒ:** -- router.py:873-884 - `_save_conversation` ํ˜ธ์ถœ ์ œ๊ฑฐ -- router.py:628-638, 670-680 - services ํ˜ธ์ถœ ์ถ”๊ฐ€ -- main.py:108-115 - `save_message_conversation` ํ˜ธ์ถœ ์ œ๊ฑฐ +**๋ฏธ์™„๋ฃŒ ์œ„๋ฐ˜ ์‚ฌํ•ญ**: +1. router/feedback_handler.py:58 - SessionLocal() ์ง์ ‘ ์‚ฌ์šฉ (์›์น™:164) +2. router/intent_review_endpoint.py:21 - SessionLocal() ์ง์ ‘ ์‚ฌ์šฉ +3. router/slack_handler.py:77,202,361 - SessionLocal() ์ง์ ‘ ์‚ฌ์šฉ +4. services/startup_valuation.py:475 - asyncpg.connect() ์ง์ ‘ ์‚ฌ์šฉ +5. services/intent_bayes.py:57,117,160 - psycopg2.connect() ์ง์ ‘ ์‚ฌ์šฉ +6. services/coldmail_filter.py:182,208,255 - asyncpg.connect() ์ง์ ‘ ์‚ฌ์šฉ -**๊ฒฐ๊ณผ:** -- ๋Œ€ํ™” ์ €์žฅ 1ํšŒ๋กœ ์ถ•์†Œ -- ํ•˜์ง€๋งŒ router์—์„œ services ํ˜ธ์ถœ (์ž„์‹œ) - -### Phase 2 Step 1-2: ์—”๋“œํฌ์ธํŠธ ๋ฐ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ถ„๋ฆฌ (์™„๋ฃŒ) - -**์ˆ˜์ • ์™„๋ฃŒ:** -- app/router/message_endpoint.py ์ƒ์„ฑ - ์—”๋“œํฌ์ธํŠธ ๋ถ„๋ฆฌ -- main.py:84-176 ์ œ๊ฑฐ โ†’ message_endpoint.py๋กœ ์ด๋™ -- main.py์— include_router ์ถ”๊ฐ€ -- app/services/message_service.py ์ƒ์„ฑ - route_message ์ด๋™ -- router.py:128-682 (route_message) โ†’ message_service.py๋กœ ์ด๋™ -- router.py๋Š” message_service ํ˜ธ์ถœ๋งŒ (~10์ค„) - -**๊ฒฐ๊ณผ:** -- ๊ณ„์ธต ๋ถ„๋ฆฌ ์›์น™ ์ค€์ˆ˜ (router โ†’ services โ†’ state) -- ์ค‘๋ณต ์ €์žฅ ๋ฌธ์ œ ํ•ด๊ฒฐ (services ๋ ˆ์ด์–ด์—์„œ ํ•œ ๋ฒˆ๋งŒ ์ €์žฅ) -- router.py ํฌ๊ธฐ 927์ค„ โ†’ 366์ค„๋กœ ์ถ•์†Œ - -### Phase 2 Step 3: ํด๋” ๊ตฌ์กฐ ์ •๋ฆฌ (์™„๋ฃŒ) - -**์ˆ˜์ • ์™„๋ฃŒ:** -- app/brain/ โ†’ app/services/brain/ ์ด๋™ -- app/llm/ โ†’ app/services/llm/ ์ด๋™ -- app/memory/ โ†’ app/services/memory/ ์ด๋™ -- app/skills/ โ†’ app/services/skills/ ์ด๋™ -- ๋ชจ๋“  import ๊ฒฝ๋กœ ์ˆ˜์ • (28๊ฐœ ํŒŒ์ผ) -- main.py์˜ skills import ๊ฒฝ๋กœ ์ˆ˜์ • - -**๊ฒฐ๊ณผ:** -- ์„œ๋น„์Šค ๋ ˆ์ด์–ด ํ†ตํ•ฉ ์™„๋ฃŒ -- import ๊ฒฝ๋กœ ์ผ๊ด€์„ฑ ํ™•๋ณด - -### Phase 2 Step 4: DB ์ ‘๊ทผ ์ •๋ฆฌ (์™„๋ฃŒ) - -**์ˆ˜์ • ์™„๋ฃŒ:** -- feedback_handler.py: SessionLocal ์ œ๊ฑฐ, repository ์‚ฌ์šฉ - - handle_chat_feedback๋ฅผ async๋กœ ๋ณ€๊ฒฝ - - update_or_create_feedback๋ฅผ async๋กœ ๋ณ€๊ฒฝ (๋‚ด๋ถ€ Session ๊ด€๋ฆฌ) - - get_conversation_by_id ์ถ”๊ฐ€ -- slack_handler.py: SessionLocal ์ œ๊ฑฐ, slack_repository ์‚ฌ์šฉ - - get_team_uuid_by_slack_team_id ์ถ”๊ฐ€ - - 3๊ณณ์˜ SessionLocal ์ง์ ‘ ํ˜ธ์ถœ ์ œ๊ฑฐ -- intent_review_endpoint.py: FastAPI Depends ํŒจํ„ด ์œ ์ง€ (ํ—ˆ์šฉ) - -**๊ฒฐ๊ณผ:** -- router/services์—์„œ SessionLocal ์ง์ ‘ ์‚ฌ์šฉ ์ œ๊ฑฐ -- repository ํŒจํ„ด์œผ๋กœ ํ†ต์ผ -- TDD ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ๋ฐ ํ†ต๊ณผ ํ™•์ธ - -### Phase 2: ๊ณ„์ธต ๋ถ„๋ฆฌ ๋ฆฌํŒฉํ† ๋ง (๋Œ€๊ทœ๋ชจ) - -#### Step 1: ์—”๋“œํฌ์ธํŠธ ๋ถ„๋ฆฌ -**์ƒ์„ฑ:** -- app/router/message_endpoint.py (~100์ค„) - -**์ˆ˜์ •:** -- main.py:84-176 ์ œ๊ฑฐ โ†’ message_endpoint.py๋กœ ์ด๋™ -- main.py์— include_router ์ถ”๊ฐ€ - -#### Step 2: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ถ„๋ฆฌ -**์ƒ์„ฑ:** -- app/services/message_service.py (~550์ค„) - -**์ˆ˜์ •:** -- router.py:128-682 (route_message) โ†’ message_service.py๋กœ ์ด๋™ -- router.py๋Š” message_service ํ˜ธ์ถœ๋งŒ (~10์ค„) -- slack_handler.py:424,437,448,893 - message_service ํ˜ธ์ถœ๋กœ ๋ณ€๊ฒฝ - -#### Step 3: ํด๋” ๊ตฌ์กฐ ์ •๋ฆฌ -**์ด๋™:** -- app/brain/ โ†’ app/services/brain/ -- app/llm/ โ†’ app/services/llm/ -- app/memory/ โ†’ app/services/memory/ -- app/skills/ โ†’ app/services/skills/ -- app/notifications/ โ†’ app/services/notifications/ -- app/pipelines/ โ†’ app/services/pipelines/ - -**์ƒ์„ฑ:** -- app/schemas/ - API ์š”์ฒญ/์‘๋‹ต ์Šคํ‚ค๋งˆ ๋ถ„๋ฆฌ - -#### Step 4: DB ์ ‘๊ทผ ์ •๋ฆฌ -**์ƒ์„ฑ:** -- app/models/{domain}_model.py - ORM ๋ชจ๋ธ ๋ถ„๋ฆฌ - -**์ˆ˜์ •:** -- state/database.py - models ์ œ๊ฑฐ, Repository๋งŒ ๋‚จ๊น€ -- router/feedback_handler.py, intent_review_endpoint.py, slack_handler.py - state ํ†ตํ•ด์„œ๋งŒ DB ์ ‘๊ทผ -- services ํŒŒ์ผ๋“ค - asyncpg.connect() ์ œ๊ฑฐ, state ํ˜ธ์ถœ๋กœ ๋ณ€๊ฒฝ +**ํ•„์š” ์ž‘์—…**: +- state/repositories/ ํด๋” ์ƒ์„ฑ +- DB ์ ‘๊ทผ ๋กœ์ง์„ repositories๋กœ ๋ถ„๋ฆฌ +- services์—์„œ repositories ํ˜ธ์ถœ --- -## ๊ธฐ๋Œ€ํšจ๊ณผ +## Phase 3: ํด๋” ๊ตฌ์กฐ ์ •๋ฆฌ (๋ฏธ์ฐฉ์ˆ˜) -### ์ฆ‰์‹œ ํšจ๊ณผ -- ์ค‘๋ณต ์ €์žฅ ๋ฒ„๊ทธ ํ•ด๊ฒฐ โ†’ DB ์šฉ๋Ÿ‰ ์ ˆ์•ฝ, ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ํ™•๋ณด +### ํ˜„์žฌ ๊ตฌ์กฐ +``` +app/ +โ”œโ”€โ”€ brain/ # services/brain/์œผ๋กœ ์ด๋™ ํ•„์š” +โ”œโ”€โ”€ llm/ # services/llm/์œผ๋กœ ์ด๋™ ํ•„์š” +โ”œโ”€โ”€ memory/ # services/memory/๋กœ ์ด๋™ ํ•„์š” +โ”œโ”€โ”€ router/ # routers/๋กœ ์ด๋ฆ„ ๋ณ€๊ฒฝ ํ•„์š” +โ”œโ”€โ”€ services/ +โ””โ”€โ”€ state/ +``` -### ์žฅ๊ธฐ ํšจ๊ณผ -1. **์œ ์ง€๋ณด์ˆ˜์„ฑ**: ๊ณ„์ธต๋ณ„ ์ฑ…์ž„ ๋ช…ํ™• โ†’ ์ˆ˜์ • ์˜ํ–ฅ ๋ฒ”์œ„ ์ตœ์†Œํ™” -2. **ํ™•์žฅ์„ฑ**: ์ƒˆ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ์‹œ ์–ด๋А ๊ณ„์ธต์— ๋„ฃ์„์ง€ ๋ช…ํ™• -3. **ํ…Œ์ŠคํŠธ**: ๊ณ„์ธต๋ณ„ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์šฉ์ด -4. **ํ˜‘์—…**: ์›์น™ ์ค€์ˆ˜๋กœ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ -5. **๊ธฐ์ˆ ๋ถ€์ฑ„ ๊ฐ์†Œ**: 15๊ฐœ ์œ„๋ฐ˜ ์‚ฌํ•ญ ํ•ด๊ฒฐ +### ๋ชฉํ‘œ ๊ตฌ์กฐ (311_FastAPI_๊ตฌ์กฐ_์›์น™.md:28-42) +``` +app/ +โ”œโ”€โ”€ routers/ # ์—”๋“œํฌ์ธํŠธ๋งŒ +โ”œโ”€โ”€ services/ # ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง +โ”œโ”€โ”€ state/ +โ”‚ โ”œโ”€โ”€ models/ # ORM ๋ชจ๋ธ +โ”‚ โ””โ”€โ”€ repositories/ # DB ์ ‘๊ทผ +โ””โ”€โ”€ schemas/ # Pydantic ๋ชจ๋ธ +``` --- -## ๊ตฌํ˜„ ๊ทœ๋ชจ +## Phase 4: ํŒŒ์ผ ํฌ๊ธฐ ์ œํ•œ (๋ฏธ์ฐฉ์ˆ˜) -### ์ตœ์†Œ ์ˆ˜์ • (Phase 1๋งŒ) -- ์˜ํ–ฅ ํŒŒ์ผ: 2๊ฐœ (router.py, main.py) -- ์ˆ˜์ • ๋ผ์ธ: ~50์ค„ -- ์†Œ์š” ์‹œ๊ฐ„: ์™„๋ฃŒ +**์ดˆ๊ณผ ํŒŒ์ผ** (300์ค„ ๊ธฐ์ค€): +- router.py: 915์ค„ +- main.py: 747์ค„ +- calendar_handler.py: 800์ค„+ -### ์ „์ฒด ๋ฆฌํŒฉํ† ๋ง (Phase 2) -- ์˜ํ–ฅ ํŒŒ์ผ: ์ตœ์†Œ 30๊ฐœ -- ์‹ ๊ทœ ์ƒ์„ฑ: ~650์ค„ -- ์ˆ˜์ •/์ œ๊ฑฐ: ~1,300์ค„ -- ์ด ๋ณ€๊ฒฝ: ~2,000์ค„ -- ์˜ˆ์ƒ ์†Œ์š”: 1-2์ผ - ---- - -## ์šฐ์„ ์ˆœ์œ„ - -1. **๊ธด๊ธ‰ (์™„๋ฃŒ)**: ์ค‘๋ณต ์ €์žฅ ๋ฒ„๊ทธ ํ•ด๊ฒฐ -2. **๋†’์Œ**: ์—”๋“œํฌ์ธํŠธ ๋ถ„๋ฆฌ (main.py ์›์น™ ์ค€์ˆ˜) -3. **์ค‘๊ฐ„**: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ถ„๋ฆฌ (route_message โ†’ service) -4. **๋‚ฎ์Œ**: ํด๋” ๊ตฌ์กฐ ์ •๋ฆฌ, ํŒŒ์ผ ํฌ๊ธฐ ์ถ•์†Œ +**๋ถ„๋ฆฌ ๊ณ„ํš**: +- ํŒŒ์ผ๋ณ„ ๊ธฐ๋Šฅ ๋‹จ์œ„๋กœ ๋ถ„ํ•  +- ๋‹จ์ผ ์ฑ…์ž„ ์›์น™ ์ ์šฉ --- ## ์ฐธ๊ณ  -- 311_FastAPI_๊ตฌ์กฐ_์›์น™.md - ๊ณ„์ธต ๋ถ„๋ฆฌ ์›์น™ -- 312_๋ฌธ์„œ_์ž‘์„ฑ_์›์น™.md - ๋ฌธ์„œ ์ž‘์„ฑ ๊ทœ์น™ +- `311_FastAPI_๊ตฌ์กฐ_์›์น™.md` +- `troubleshooting/250924_๋Œ€ํ™”์ €์žฅ_์˜ค๋ฅ˜.md` +- `troubleshooting/251123_rb8001_endpoint_service_separation.md`