From f46bdfe399d6c26b96c582cd8d429d705c154b59 Mon Sep 17 00:00:00 2001 From: happybell80 Date: Tue, 12 Aug 2025 23:10:21 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B0=90=EC=A0=95=20=EC=8B=9C=EC=8A=A4?= =?UTF-8?q?=ED=85=9C=20=EB=A1=9C=EB=93=9C=EB=A7=B5=20=ED=98=84=EC=8B=A4?= =?UTF-8?q?=ED=99=94=20-=20=EC=9D=B4=EB=AF=B8=20=ED=95=99=EC=8A=B5?= =?UTF-8?q?=EB=90=9C=207=EA=B0=9C=20=EA=B0=90=EC=A0=95=20=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...‹œ์Šคํ…œ_ํ˜„์‹ค์ ์šฉ_5๋‹จ๊ณ„_๋กœ๋“œ๋งต.md | 159 +++++----- ...812_๊ฐ์ •์‹œ์Šคํ…œ_Phase1_์‹คํ–‰๊ณ„ํš.md | 292 ++++++++++++++++++ 2 files changed, 378 insertions(+), 73 deletions(-) create mode 100644 plans/250812_๊ฐ์ •์‹œ์Šคํ…œ_Phase1_์‹คํ–‰๊ณ„ํš.md diff --git a/plans/250808_๊ฐ์ •์‹œ์Šคํ…œ_ํ˜„์‹ค์ ์šฉ_5๋‹จ๊ณ„_๋กœ๋“œ๋งต.md b/plans/250808_๊ฐ์ •์‹œ์Šคํ…œ_ํ˜„์‹ค์ ์šฉ_5๋‹จ๊ณ„_๋กœ๋“œ๋งต.md index 5cf7a7c..2213234 100644 --- a/plans/250808_๊ฐ์ •์‹œ์Šคํ…œ_ํ˜„์‹ค์ ์šฉ_5๋‹จ๊ณ„_๋กœ๋“œ๋งต.md +++ b/plans/250808_๊ฐ์ •์‹œ์Šคํ…œ_ํ˜„์‹ค์ ์šฉ_5๋‹จ๊ณ„_๋กœ๋“œ๋งต.md @@ -12,31 +12,35 @@ --- -## Phase 1: ์ตœ์†Œ ๊ธฐ๋Šฅ ๊ตฌํ˜„ +## Phase 1: 7๊ฐœ ๊ฐ์ • ๊ธฐ๋ณธ ๊ตฌํ˜„ (์ด๋ฏธ ํ•™์Šต ์™„๋ฃŒ) ### ๋ชฉํ‘œ -"5๊ฐœ ๊ธฐ๋ณธ์ •์„œ๋กœ ๊ฐ์ • ์ธ์‹์ด ์ž‘๋™ํ•˜๋Š” ์ตœ์†Œ ์‹œ์Šคํ…œ" +"์ด๋ฏธ ํ•™์Šต๋œ 7๊ฐœ ํ•œ๊ตญ์–ด ๊ฐ์ • ๋ชจ๋ธ์„ skill-embedding์— ํ†ตํ•ฉ" ### ๊ตฌํ˜„ ๋ฒ”์œ„ ```python -# ๊ธฐ๋ณธ์ •์„œ๋งŒ ๊ตฌํ˜„ -BASIC_EMOTIONS = [ - "joy", # ๊ธฐ์จ - "sadness", # ์Šฌํ”” - "anger", # ๋ถ„๋…ธ - "fear", # ๋‘๋ ค์›€ - "disgust" # ํ˜์˜ค +# 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: - """5๊ฐœ ํ™•๋ฅ ๊ฐ’์œผ๋กœ ์—”ํŠธ๋กœํ”ผ ๊ณ„์‚ฐ""" + """7๊ฐœ ๊ฐ์ • ํ™•๋ฅ ๊ฐ’์œผ๋กœ ์—”ํŠธ๋กœํ”ผ ๊ณ„์‚ฐ""" return -sum(p * log(p) for p in probs if p > 0) ``` ### ๊ธฐ์ˆ  ์Šคํƒ -- **์ž„๋ฒ ๋”ฉ**: ๊ธฐ์กด skill-embedding ์„œ๋น„์Šค ํ™œ์šฉ (ํฌํŠธ 8502) -- **์ €์žฅ**: ๊ธฐ์กด ChromaDB ํ™œ์šฉ +- **๊ฐ์ • ๋ชจ๋ธ**: klue/bert-base ๊ธฐ๋ฐ˜ (์ด๋ฏธ ํ•™์Šต ์™„๋ฃŒ) +- **์ž„๋ฒ ๋”ฉ**: ๊ธฐ์กด skill-embedding ์„œ๋น„์Šค ํ™•์žฅ (ํฌํŠธ 8515) +- **์ €์žฅ**: ๊ธฐ์กด ChromaDB ํ™œ์šฉ (๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์— ๊ฐ์ • ์ถ”๊ฐ€) - **์˜์‚ฌ๊ฒฐ์ •**: ฮต-greedy (ฮต=0.1) - **๊ธฐ์กด ์ฝ”๋“œ**: rb10508_micro์˜ memory/storage.py ์žฌ์‚ฌ์šฉ @@ -45,10 +49,11 @@ def calculate_entropy(probs: List[float]) -> float: - ์ •ํ™•๋„: ์‚ฌ์šฉ์ž ํ‰๊ฐ€ 3.5/5.0 - ๋ฉ”๋ชจ๋ฆฌ: 200MB ์ด๋‚ด -### ๋ฐ์ดํ„ฐ ์ค€๋น„ -- ๊ฐ์ •๋‹น 100๊ฐœ ์ƒ˜ํ”Œ (์ด 500๊ฐœ) -- Gemini๋กœ ์ดˆ๊ธฐ ๋ผ๋ฒจ ์ƒ์„ฑ -- ์ˆ˜๋™ ๊ฒ€์ฆ 20% +### ๋ฐ์ดํ„ฐ ์ค€๋น„ (์™„๋ฃŒ) +- AI Hub ํ•œ๊ตญ์–ด ๋Œ€ํ™” ๋ฐ์ดํ„ฐ์…‹ 38,594๊ฐœ ์ƒ˜ํ”Œ +- 7๊ฐœ ๊ฐ์ • ๊ท ํ˜• ๋ถ„ํฌ +- ํ•™์Šต/๊ฒ€์ฆ/ํ…Œ์ŠคํŠธ ๋ถ„ํ•  ์™„๋ฃŒ +- ํด๋ž˜์Šค ๊ฐ€์ค‘์น˜ ์ ์šฉ ### ๊ฒ€์ฆ ๋ฐฉ๋ฒ• ```bash @@ -63,18 +68,19 @@ curl -w "@curl-format.txt" http://localhost:8503/analyze ``` ### ์‚ฐ์ถœ๋ฌผ -- [ ] skill-embedding ์„œ๋น„์Šค์— ๊ฐ์ • ๋ถ„์„ ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€ -- [ ] 5๊ฐœ ๊ฐ์ • ํ”„๋กœํ† ํƒ€์ž… ์ •์˜ -- [ ] ๊ธฐ๋ณธ ์—”ํŠธ๋กœํ”ผ ๊ณ„์‚ฐ๊ธฐ -- [ ] ๊ธฐ์กด ChromaDB ํ†ตํ•ฉ ์ฝ”๋“œ -- [ ] ์ตœ์†Œ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ (100๊ฐœ) +- [x] 7๊ฐœ ๊ฐ์ • ๋ชจ๋ธ ํ•™์Šต ์™„๋ฃŒ (training_emotion) +- [ ] skill-embedding ์„œ๋น„์Šค์— /analyze_emotion ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€ +- [ ] Temperature Scaling ์ ์šฉ (1.232) +- [ ] ์—”ํŠธ๋กœํ”ผ ๊ณ„์‚ฐ๊ธฐ ๊ตฌํ˜„ +- [ ] ChromaDB ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ†ตํ•ฉ +- [ ] rb10508_micro ์—ฐ๋™ --- -## Phase 2: ์„ฑ๋Šฅ ์ตœ์ ํ™” +## Phase 2: ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐ ํ†ตํ•ฉ ### ๋ชฉํ‘œ -"์‘๋‹ต์‹œ๊ฐ„ 200ms ๋‹ฌ์„ฑ ๋ฐ ์บ์‹ฑ ์‹œ์Šคํ…œ ๊ตฌ์ถ•" +"ONNX ๋ณ€ํ™˜, ์บ์‹ฑ ๊ตฌํ˜„, rb10508_micro ์™„์ „ ํ†ตํ•ฉ" ### ์ตœ์ ํ™” ์ „๋žต ```python @@ -115,77 +121,84 @@ stats.sort_stats('cumulative').print_stats(10) - ์บ์‹œ ์ ์ค‘๋ฅ : 30% ### ์‚ฐ์ถœ๋ฌผ -- [ ] LRU ์บ์‹œ ์‹œ์Šคํ…œ +- [ ] ONNX ๋ชจ๋ธ ๋ณ€ํ™˜ (442MB โ†’ 150MB) +- [ ] LRU ์บ์‹œ ์‹œ์Šคํ…œ (5๋ถ„ TTL) - [ ] ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ API -- [ ] ChromaDB ์ธ๋ฑ์Šค ์ตœ์ ํ™” -- [ ] ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง ๋Œ€์‹œ๋ณด๋“œ -- [ ] ํ”„๋กœํŒŒ์ผ๋ง ๋ฆฌํฌํŠธ +- [ ] ChromaDB ๊ฐ์ • ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ธ๋ฑ์‹ฑ +- [ ] ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง (Grafana) +- [ ] rb10508_micro ๊ฐ์ • ๊ธฐ๋ฐ˜ ์‘๋‹ต ํ†ค ์กฐ์ • --- -## Phase 3: ์‚ฌํšŒ๊ธฐ๋Šฅ ๊ฐ์ • ์ถ”๊ฐ€ +## Phase 3: ๊ฐ์ • ํŒจํ„ด ๋ถ„์„ ๋ฐ ๊ฐœ์ธํ™” ### ๋ชฉํ‘œ -"9๊ฐœ ๊ฐ์ •์œผ๋กœ ํ™•์žฅํ•˜๊ณ  2ํ—ค๋“œ ๊ตฌ์กฐ ๋„์ž…" +"์žฅ๊ธฐ ๊ฐ์ • ํŒจํ„ด ์ถ”์ , ์‚ฌ์šฉ์ž๋ณ„ ๊ฐ์ • ํ”„๋กœํŒŒ์ผ ๊ตฌ์ถ•" -### ํ™•์žฅ ๊ฐ์ • +### ๊ฐ์ • ํŒจํ„ด ๋ถ„์„ ```python -# ์‚ฌํšŒ๊ธฐ๋Šฅ ์ถ”๊ฐ€ -SOCIAL_EMOTIONS = [ - "anxiety", # ๋ถˆ์•ˆ - "envy", # ์งˆํˆฌ - "embarrassment", # ๋‹นํ˜น - "ennui" # ๊ถŒํƒœ -] - -# 2ํ—ค๋“œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ -async def two_head_analysis(text: str, context: dict): - basic_task = analyze_basic(text) # 100ms ๋ชฉํ‘œ - social_task = analyze_social(text, context) # 300ms ๋ชฉํ‘œ +# ์‹œ๊ฐ„๋ณ„ ๊ฐ์ • ์ถ”์  +class EmotionTracker: + def __init__(self, user_id: str): + self.user_id = user_id + self.history = [] # ์‹œ๊ณ„์—ด ๊ฐ์ • ๋ฐ์ดํ„ฐ - basic, social = await asyncio.gather(basic_task, social_task) + def track(self, emotion_result: dict): + """๊ฐ์ • ๊ฒฐ๊ณผ๋ฅผ ์‹œ๊ณ„์—ด๋กœ ์ €์žฅ""" + self.history.append({ + "timestamp": datetime.now(), + "emotions": emotion_result["emotions"], + "dominant": emotion_result["dominant"], + "entropy": emotion_result["entropy"] + }) - # ๋™์  ๊ฐ€์ค‘์น˜ ๊ณ„์‚ฐ - w = calculate_weight(len(text), context) - return w * basic + (1-w) * social + def get_pattern(self, period: str = "day"): + """์ผ/์ฃผ/์›” ๋‹จ์œ„ ๊ฐ์ • ํŒจํ„ด ๋ถ„์„""" + # ์‹œ๊ฐ„๋Œ€๋ณ„ ์ฃผ์š” ๊ฐ์ • + # ๊ฐ์ • ๋ณ€ํ™” ์ถ”์ด + # ์—”ํŠธ๋กœํ”ผ ํŒจํ„ด + return analyze_temporal_pattern(self.history, period) ``` -### ๋ฐ์ดํ„ฐ ํ™•์žฅ -- ์ƒˆ ๊ฐ์ •๋‹น 200๊ฐœ ์ƒ˜ํ”Œ ์ถ”๊ฐ€ -- ์ด 1,300๊ฐœ ๋ผ๋ฒจ ๋ฐ์ดํ„ฐ -- ํฌ๋ผ์šฐ๋“œ์†Œ์‹ฑ ํ™œ์šฉ ๊ฒ€ํ†  +### ๊ฐœ์ธํ™” ์ „๋žต +- ์‚ฌ์šฉ์ž๋ณ„ ๊ฐ์ • ํ”„๋กœํŒŒ์ผ ์ƒ์„ฑ +- ๊ฐ์ • ์‘๋‹ต ํžˆ์Šคํ† ๋ฆฌ ํ•™์Šต +- ๊ฐœ์ธ๋ณ„ ๊ฐ์ • ์ž„๊ณ„๊ฐ’ ์กฐ์ • +- ์—”ํŠธ๋กœํ”ผ ํŠน์ด์  ํ™œ์šฉ (์ฐฝ๋ฐœ์  ์‘๋‹ต) -### Thompson Sampling ๋„์ž… +### ์—”ํŠธ๋กœํ”ผ ๊ธฐ๋ฐ˜ ์˜์‚ฌ๊ฒฐ์ • ```python -class ThompsonSampler: +class EntropyBasedDecision: def __init__(self): - self.alpha = np.ones(9) # ์„ฑ๊ณต ํšŸ์ˆ˜ - self.beta = np.ones(9) # ์‹คํŒจ ํšŸ์ˆ˜ + self.entropy_threshold = 2.5 # ํŠน์ด์  ์ž„๊ณ„๊ฐ’ - def sample(self): - """๋ฒ ํƒ€ ๋ถ„ํฌ์—์„œ ์ƒ˜ํ”Œ๋ง""" - return np.random.beta(self.alpha, self.beta) + def should_be_creative(self, entropy: float) -> bool: + """๋†’์€ ์—”ํŠธ๋กœํ”ผ์ผ ๋•Œ ์ฐฝ์˜์  ์‘๋‹ต""" + return entropy > self.entropy_threshold - def update(self, action, reward): - """๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ํŒŒ๋ผ๋ฏธํ„ฐ ์—…๋ฐ์ดํŠธ""" - if reward > 0: - self.alpha[action] += 1 - else: - self.beta[action] += 1 + 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 ``` ### ์„ฑ๋Šฅ ๋ชฉํ‘œ -- ๊ธฐ๋ณธ์ •์„œ: 100ms -- ์‚ฌํšŒ๊ธฐ๋Šฅ: 300ms -- ํ†ตํ•ฉ ์‘๋‹ต: 350ms -- ์ •ํ™•๋„: 4.0/5.0 +- ํŒจํ„ด ๋ถ„์„: ์ผ 1ํšŒ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ +- ํ”„๋กœํŒŒ์ผ ์—…๋ฐ์ดํŠธ: ์‹ค์‹œ๊ฐ„ +- ๊ฐ์ • ํžˆ์Šคํ† ๋ฆฌ: 30์ผ ๋ณด๊ด€ +- ๊ฐœ์ธํ™” ์ •ํ™•๋„: 70% ์ด์ƒ ### ์‚ฐ์ถœ๋ฌผ -- [ ] 9๊ฐœ ๊ฐ์ • ํ”„๋กœํ† ํƒ€์ž… -- [ ] 2ํ—ค๋“œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ -- [ ] Thompson Sampling ๊ตฌํ˜„ -- [ ] 1,300๊ฐœ ๋ผ๋ฒจ ๋ฐ์ดํ„ฐ -- [ ] A/B ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ +- [ ] ๊ฐ์ • ํŒจํ„ด ๋ถ„์„๊ธฐ +- [ ] ์‚ฌ์šฉ์ž ๊ฐ์ • ํ”„๋กœํŒŒ์ผ DB +- [ ] ์—”ํŠธ๋กœํ”ผ ๊ธฐ๋ฐ˜ ์˜์‚ฌ๊ฒฐ์ • ๋ชจ๋“ˆ +- [ ] ์‹œ๊ณ„์—ด ๊ฐ์ • ์‹œ๊ฐํ™” +- [ ] ๊ฐœ์ธํ™” ์‘๋‹ต ์ „๋žต --- diff --git a/plans/250812_๊ฐ์ •์‹œ์Šคํ…œ_Phase1_์‹คํ–‰๊ณ„ํš.md b/plans/250812_๊ฐ์ •์‹œ์Šคํ…œ_Phase1_์‹คํ–‰๊ณ„ํš.md new file mode 100644 index 0000000..fb367b2 --- /dev/null +++ b/plans/250812_๊ฐ์ •์‹œ์Šคํ…œ_Phase1_์‹คํ–‰๊ณ„ํš.md @@ -0,0 +1,292 @@ +# ๊ฐ์ • ์‹œ์Šคํ…œ Phase 1 ์‹คํ–‰ ๊ณ„ํš + +์ž‘์„ฑ์ผ: 2025๋…„ 8์›” 12์ผ +์ž‘์„ฑ์ž: Claude (51123 ์„œ๋ฒ„) +์ƒํƒœ: ์‹คํ–‰ ์ค€๋น„ ์™„๋ฃŒ + +## 1. ํ˜„ํ™ฉ ๋ถ„์„ ๊ฒฐ๊ณผ + +### 1.1 ๊ธฐ์กด ์ž์‚ฐ +- **ํ•™์Šต ์™„๋ฃŒ ๋ชจ๋ธ**: training_emotion์— 7๊ฐœ ๊ฐ์ • ๋ชจ๋ธ (klue/bert-base) +- **๋ชจ๋ธ ์„ฑ๋Šฅ**: F1 56.3%, Temperature Scaling 1.232 +- **์ธํ”„๋ผ**: skill-embedding ์„œ๋น„์Šค ์šด์˜ ์ค‘ (FastAPI, ONNX Runtime) +- **์—ฌ์œ  ์ž์›**: CPU 0.05%, ๋ฉ”๋ชจ๋ฆฌ 873MB/2GB + +### 1.2 ๊ธฐ์ˆ  ์Šคํƒ ํ™•์ธ +``` +โœ… transformers 4.45.2 ์„ค์น˜๋จ +โœ… FastAPI ๊ตฌ์กฐ ํ™•๋ฆฝ +โœ… ONNX Runtime ์ง€์› +โœ… ChromaDB ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ €์žฅ ๊ฐ€๋Šฅ +``` + +## 2. ๊ฐ์ • ๋ชจ๋ธ ์ƒ์„ธ + +### 2.1 7๊ฐœ ๊ฐ์ • ์ฒด๊ณ„ +```python +emotions = [ + 'fear', # ๊ณตํฌ (์ฆ‰๋ฐœ์  ๊ธฐ๋ณธ์ •์„œ) + 'surprise', # ๋†€๋žŒ (์ฆ‰๋ฐœ์  ๊ธฐ๋ณธ์ •์„œ) + 'anger', # ๋ถ„๋…ธ (์ฆ‰๋ฐœ์  ๊ธฐ๋ณธ์ •์„œ) + 'sadness', # ์Šฌํ”” (๊ฐ์ •์  ๋ฐ˜์‘) + 'happiness', # ํ–‰๋ณต (๊ฐ์ •์  ๋ฐ˜์‘) + 'disgust', # ํ˜์˜ค (๊ฐ์ •์  ๋ฐ˜์‘) + 'neutral' # ์ค‘๋ฆฝ (๊ท ํ˜• ์ƒํƒœ) +] +``` + +### 2.2 ๋กœ๋น™ ์ฒ ํ•™๊ณผ์˜ ์—ฐ๊ฒฐ +- **๊ธฐ๋ณธ์ •์„œ (100ms)**: fear, surprise, anger โ†’ ์ฆ‰๋ฐœ ๋ฐ˜์‘ +- **์‚ฌํšŒ๊ธฐ๋Šฅ (500ms)**: sadness, happiness, disgust โ†’ ์ˆ™๊ณ ๋œ ๋ฐ˜์‘ +- **์—”ํŠธ๋กœํ”ผ ํŠน์ด์ **: ๋†’์€ ์—”ํŠธ๋กœํ”ผ ์‹œ ์ฐฝ๋ฐœ์  ์‘๋‹ต + +## 3. ๊ตฌํ˜„ ์•„ํ‚คํ…์ฒ˜ + +### 3.1 ์„œ๋น„์Šค ํ™•์žฅ (Option A - ์„ ํƒ) +``` +skill-embedding (ํฌํŠธ 8515) +โ”œโ”€โ”€ /embed (๊ธฐ์กด) +โ””โ”€โ”€ /analyze_emotion (์‹ ๊ทœ) + โ”œโ”€โ”€ ์ž…๋ ฅ: text, user_id (optional) + โ”œโ”€โ”€ ์ฒ˜๋ฆฌ: 7๊ฐœ ๊ฐ์ • ๋ถ„์„ + โ””โ”€โ”€ ์ถœ๋ ฅ: emotions, dominant, entropy, confidence +``` + +### 3.2 API ์„ค๊ณ„ +```json +// Request +POST /analyze_emotion +{ + "text": "์˜ค๋Š˜ ํ”„๋กœ์ ํŠธ๊ฐ€ ์‹คํŒจํ–ˆ์–ด์š”", + "user_id": "optional_for_caching" +} + +// Response +{ + "emotions": { + "fear": 0.15, + "surprise": 0.05, + "anger": 0.25, + "sadness": 0.35, + "neutral": 0.10, + "happiness": 0.05, + "disgust": 0.05 + }, + "dominant": "sadness", + "entropy": 2.31, + "confidence": 0.35, + "processing_time_ms": 87 +} +``` + +## 4. ์‹คํ–‰ ๊ณ„ํš + +### Phase 1-A: ๊ธฐ๋ณธ ํ†ตํ•ฉ (Day 1-2) + +#### 51123 ์„œ๋ฒ„ ์ž‘์—… +```bash +# 1. ๋ชจ๋ธ ์ค€๋น„ +sudo mkdir -p /opt/models/emotion +sudo cp -r /path/to/training_emotion/outputs/aihub-7emotions-complete/* \ + /opt/models/emotion/ +sudo chown -R admin:admin /opt/models/emotion +``` + +#### ๋กœ์ปฌ ๊ฐœ๋ฐœ์ž ์ž‘์—… +```python +# 2. emotion_analyzer.py ์ž‘์„ฑ +from transformers import AutoModelForSequenceClassification, AutoTokenizer +import torch +import numpy as np + +class EmotionAnalyzer: + def __init__(self, model_path="/opt/models/emotion"): + self.model = AutoModelForSequenceClassification.from_pretrained(model_path) + self.tokenizer = AutoTokenizer.from_pretrained(model_path) + self.temperature = 1.232 # from calibration + self.emotions = ['fear', 'surprise', 'anger', 'sadness', + 'neutral', 'happiness', 'disgust'] + + async def analyze(self, text: str) -> dict: + inputs = self.tokenizer(text, return_tensors="pt", + max_length=512, truncation=True) + + with torch.no_grad(): + logits = self.model(**inputs).logits + probs = torch.softmax(logits / self.temperature, dim=-1) + + emotion_scores = { + emotion: float(probs[0][i]) + for i, emotion in enumerate(self.emotions) + } + + dominant = max(emotion_scores, key=emotion_scores.get) + entropy = self._calculate_entropy(list(emotion_scores.values())) + + return { + "emotions": emotion_scores, + "dominant": dominant, + "entropy": entropy, + "confidence": emotion_scores[dominant] + } + + def _calculate_entropy(self, probs): + probs = np.array(probs) + probs = probs[probs > 0] + return -np.sum(probs * np.log(probs)) +``` + +#### 51124 ์„œ๋ฒ„ ์ž‘์—… +```yaml +# 3. docker-compose.yml ์ˆ˜์ • +services: + skill-embedding: + volumes: + - /opt/models:/opt/models:ro + environment: + - EMOTION_MODEL_PATH=/opt/models/emotion +``` + +### Phase 1-B: ์ตœ์ ํ™” (Day 3-4) + +#### ONNX ๋ณ€ํ™˜ +```bash +# ๋กœ์ปฌ ๊ฐœ๋ฐœ์ž +python convert_to_onnx.py \ + --model_path /opt/models/emotion \ + --output_path /opt/models/emotion-onnx \ + --optimize +``` + +#### ์บ์‹ฑ ๊ตฌํ˜„ +```python +from functools import lru_cache +import hashlib + +class CachedEmotionAnalyzer(EmotionAnalyzer): + @lru_cache(maxsize=1000) + def _analyze_cached(self, text_hash: str): + # ์‹ค์ œ ๋ถ„์„ ๋กœ์ง + pass + + async def analyze(self, text: str, user_id: str = None): + text_hash = hashlib.md5(text.encode()).hexdigest() + cache_key = f"{user_id}:{text_hash}" if user_id else text_hash + return self._analyze_cached(cache_key) +``` + +### Phase 1-C: ํ†ตํ•ฉ (Day 5) + +#### rb10508_micro ์—ฐ๋™ +```python +# Slack ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์‹œ +async def process_slack_message(text: str, user_id: str): + # 1. ๊ฐ์ • ๋ถ„์„ + emotion_result = await call_emotion_api(text, user_id) + + # 2. ChromaDB ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ + metadata = { + "user_id": user_id, + "timestamp": datetime.now().isoformat(), + "emotions": emotion_result["emotions"], + "dominant_emotion": emotion_result["dominant"], + "emotional_entropy": emotion_result["entropy"] + } + + # 3. ์‘๋‹ต ํ†ค ์กฐ์ • + response_tone = adjust_tone_by_emotion(emotion_result["dominant"]) + + return generate_response(text, tone=response_tone) +``` + +## 5. ์„ฑ๋Šฅ ๋ชฉํ‘œ ๋ฐ ์ธก์ • + +### 5.1 ๋ชฉํ‘œ ์ง€ํ‘œ +- **์‘๋‹ต์‹œ๊ฐ„**: < 200ms (์บ์‹œ ๋ฏธ์Šค), < 50ms (์บ์‹œ ํžˆํŠธ) +- **์ •ํ™•๋„**: ์ฒด๊ฐ ์ •ํ™•๋„ > 70% +- **๋ฉ”๋ชจ๋ฆฌ**: < 1.5GB ์ด ์‚ฌ์šฉ๋Ÿ‰ +- **์ฒ˜๋ฆฌ๋Ÿ‰**: > 10,000 ์š”์ฒญ/์ผ + +### 5.2 ์ธก์ • ๋ฐฉ๋ฒ• +```bash +# ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ +curl -w "@curl-format.txt" \ + -X POST http://localhost:8515/analyze_emotion \ + -H "Content-Type: application/json" \ + -d '{"text":"ํ…Œ์ŠคํŠธ ๋ฉ”์‹œ์ง€"}' + +# ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ +locust -f tests/load_test.py --users 10 --spawn-rate 2 +``` + +## 6. ๋ฆฌ์Šคํฌ ๊ด€๋ฆฌ + +### 6.1 ์ž ์žฌ ์œ„ํ—˜ +| ์œ„ํ—˜ ์š”์†Œ | ์˜ํ–ฅ๋„ | ๋Œ€์‘ ๋ฐฉ์•ˆ | +|---------|-------|----------| +| ๋ชจ๋ธ ํฌ๊ธฐ (442MB) | ์ค‘ | ONNX ๋ณ€ํ™˜์œผ๋กœ 150MB๋กœ ์ถ•์†Œ | +| ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ | ์ค‘ | ๋ชจ๋ธ ๊ณต์œ , ์บ์‹ฑ ์ตœ์ ํ™” | +| ๋‚ฎ์€ ์ •ํ™•๋„ (56.3%) | ๋‚ฎ | confidence ์ž„๊ณ„๊ฐ’ ์„ค์ • | +| Cold start | ๋‚ฎ | ์„œ๋น„์Šค ์‹œ์ž‘ ์‹œ ํ”„๋ฆฌ๋กœ๋“œ | + +### 6.2 ํด๋ฐฑ ์ „๋žต +- confidence < 0.3์ผ ๋•Œ neutral๋กœ ๋ถ„๋ฅ˜ +- ์—”ํŠธ๋กœํ”ผ > 2.8์ผ ๋•Œ "๋ณต์žกํ•œ ๊ฐ์ •" ํ‘œ์‹œ +- ์˜ค๋ฅ˜ ์‹œ ๊ฐ์ • ๋ถ„์„ ์Šคํ‚ตํ•˜๊ณ  ์ง„ํ–‰ + +## 7. ์ž‘์—… ๋ถ„๋‹ด + +### 51123 ์„œ๋ฒ„ (์‹œ์Šคํ…œ ๊ด€๋ฆฌ) +- [ ] ๋ชจ๋ธ ํŒŒ์ผ /opt/models ๋ฐฐ์น˜ +- [ ] ๋ฐฐํฌ ํŒŒ์ดํ”„๋ผ์ธ ์„ค์ • +- [ ] ์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ๋ง +- [ ] ๋ฌธ์„œํ™” + +### ๋กœ์ปฌ ๊ฐœ๋ฐœ์ž (์ฝ”๋“œ ๊ตฌํ˜„) +- [ ] emotion_analyzer.py ์ž‘์„ฑ +- [ ] FastAPI ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€ +- [ ] ONNX ๋ณ€ํ™˜ ์Šคํฌ๋ฆฝํŠธ +- [ ] rb10508_micro ์—ฐ๋™ + +### 51124 ์„œ๋ฒ„ (์„œ๋น„์Šค ์šด์˜) +- [ ] Docker ์ด๋ฏธ์ง€ ๋นŒ๋“œ +- [ ] ์„œ๋น„์Šค ๋ฐฐํฌ +- [ ] ๋กœ๊ทธ ๋ชจ๋‹ˆํ„ฐ๋ง +- [ ] ์„ฑ๋Šฅ ์ธก์ • + +## 8. ๊ฒ€์ฆ ๊ณ„ํš + +### 8.1 ๋‹จ์œ„ ํ…Œ์ŠคํŠธ +```python +def test_emotion_analysis(): + analyzer = EmotionAnalyzer() + result = analyzer.analyze("์ •๋ง ๊ธฐ์œ ํ•˜๋ฃจ์˜€์–ด์š”!") + assert result["dominant"] == "happiness" + assert result["confidence"] > 0.5 +``` + +### 8.2 ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ +- Slack ๋ฉ”์‹œ์ง€ โ†’ ๊ฐ์ • ๋ถ„์„ โ†’ ChromaDB ์ €์žฅ +- ๋‹ค์–‘ํ•œ ๊ฐ์ • ํ…์ŠคํŠธ 100๊ฐœ ํ…Œ์ŠคํŠธ +- ์‘๋‹ต ์‹œ๊ฐ„ ์ธก์ • + +## 9. ๋‹ค์Œ ๋‹จ๊ณ„ (Phase 2 ์ค€๋น„) + +- ๊ฐ์ • ๊ธฐ๋ฐ˜ ๋Œ€ํ™” ์ „๋žต ์ˆ˜๋ฆฝ +- ์žฅ๊ธฐ ๊ฐ์ • ํŒจํ„ด ๋ถ„์„ +- ์‚ฌ์šฉ์ž๋ณ„ ๊ฐ์ • ํ”„๋กœํŒŒ์ผ +- ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ๊ฐ์ • ๋ถ„์„ (์ด๋ฏธ์ง€, ์Œ์„ฑ) + +## 10. ์„ฑ๊ณต ๊ธฐ์ค€ + +โœ… Phase 1 ์™„๋ฃŒ ์กฐ๊ฑด: +1. 7๊ฐœ ๊ฐ์ • ๋ถ„์„ API ์ •์ƒ ์ž‘๋™ +2. ํ‰๊ท  ์‘๋‹ต์‹œ๊ฐ„ < 200ms +3. rb10508_micro ํ†ตํ•ฉ ์™„๋ฃŒ +4. ChromaDB ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ €์žฅ ํ™•์ธ +5. 100๊ฐœ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ํ†ต๊ณผ + +--- + +**์‹œ์ž‘์ผ**: 2025๋…„ 8์›” 13์ผ (์˜ˆ์ •) +**์™„๋ฃŒ์ผ**: 2025๋…„ 8์›” 17์ผ (๋ชฉํ‘œ) \ No newline at end of file