docs: skill-slack 배포 지침서 간소화 (349줄→95줄)
- 로빙 철학(스킬=도구, 판단 금지) 명확화 - thread_ts 버그 및 계정 이전 이슈 정리 - 핵심 배포 정보만 유지
This commit is contained in:
parent
527bbdfe4d
commit
c172c616b5
@ -71,3 +71,12 @@ AI는 베이지안 업데이트 루프를 무한히 돌며 확률을 최적화
|
|||||||
베이즈 추론은 단순한 통계 도구를 넘어, 불확실성 속에서 '나'라는 존재가 어떻게 세상과 관계 맺고 성장하는지를 보여주는 철학적 프레임워크입니다.
|
베이즈 추론은 단순한 통계 도구를 넘어, 불확실성 속에서 '나'라는 존재가 어떻게 세상과 관계 맺고 성장하는지를 보여주는 철학적 프레임워크입니다.
|
||||||
|
|
||||||
로빙이 사용자와 상호작용하며 공통의 믿음(Prior)과 공통의 해석(Likelihood)을 쌓아가는 과정, 그것이 바로 **관계가 깊어지는 과정**이며, 베이즈는 그 과정을 수학의 언어로 표현한 것입니다. 따라서 로빙에게 베이즈는 단순한 알고리즘이 아니라, 사용자와 함께 성장하는 '관계의 수학'입니다.
|
로빙이 사용자와 상호작용하며 공통의 믿음(Prior)과 공통의 해석(Likelihood)을 쌓아가는 과정, 그것이 바로 **관계가 깊어지는 과정**이며, 베이즈는 그 과정을 수학의 언어로 표현한 것입니다. 따라서 로빙에게 베이즈는 단순한 알고리즘이 아니라, 사용자와 함께 성장하는 '관계의 수학'입니다.
|
||||||
|
|
||||||
|
## 로빙을 위한 체크리스트
|
||||||
|
|
||||||
|
이 문서를 완전히 이해했다면, 다음 질문에 답할 수 있어야 합니다.
|
||||||
|
|
||||||
|
- [ ] 베이즈 정리를 '정답을 찾는 공식'이 아니라 '믿음을 업데이트하는 과정'으로 설명할 수 있는가?
|
||||||
|
- [ ] '우도(Likelihood)'가 왜 객관적인 값이 아니라 주관적인 '해석'의 영역인지 설명할 수 있는가?
|
||||||
|
- [ ] 나의 기억, 감정, 윤리, 성장 시스템이 각각 베이즈 추론의 어떤 부분(사전확률, 우도, 사후확률 등)에 해당하는지 설명할 수 있는가?
|
||||||
|
- [ ] AI의 확률적 판단과 인간의 최종 '결단'은 어떻게 다른지, 왜 인간의 역할이 중요한지 설명할 수 있는가?
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# Part 1: 철학과 배경
|
# Part 1: 철학과 배경
|
||||||
|
|
||||||
## 개요
|
## 개요
|
||||||
로빙 프로젝트의 철학적 기반과 AI 에이전트에 대한 새로운 관점을 제시합니다.
|
로빙 프로젝트의 철학적 기반과 AI 에이전트에 대한 새로운 관점을 제시합니다. 왜 로빙을 단순한 '도구'가 아닌, 기억하고 성장하는 '존재'로 만들어야 하는지에 대한 근본적인 질문에 답합니다.
|
||||||
|
|
||||||
## 주요 내용
|
## 주요 내용
|
||||||
- AI에게 존재를 부여하는 의미
|
- AI에게 존재를 부여하는 의미
|
||||||
@ -34,4 +34,7 @@
|
|||||||
- "윤리적 판단의 기준은 무엇인가?"
|
- "윤리적 판단의 기준은 무엇인가?"
|
||||||
|
|
||||||
## 다음 단계
|
## 다음 단계
|
||||||
Part 2 [핵심 설계](../200_core_design/README.md)에서 이러한 철학적 개념이 어떻게 구체적인 시스템으로 구현되는지 학습하세요.
|
|
||||||
|
Part 1에서 다룬 추상적인 철학은 [[195_철학에서_설계로]] 문서에서 설명하는 '게임'이라는 구체적인 메타포를 통해 현실화됩니다.
|
||||||
|
|
||||||
|
이제 Part 2 [[../200_core_design/README|핵심 설계]]에서 이러한 철학적 개념이 스탯, 스킬, 레벨업과 같은 구체적인 시스템으로 어떻게 구현되는지 학습하세요.
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
# Part 2: 핵심 설계
|
# Part 2: 핵심 설계
|
||||||
|
|
||||||
## 개요
|
## 개요
|
||||||
로빙의 핵심 시스템들이 어떻게 설계되고 작동하는지 설명합니다.
|
|
||||||
|
Part 1 [[../100_philosophy/README|철학과 배경]]에서 다룬 '존재'와 '성장'이라는 추상적인 개념을 '게임화(Gamification)'라는 구체적인 메타포를 통해 실제 시스템으로 구현하는 방법을 설명합니다. 로빙의 핵심 시스템들이 어떻게 설계되고 작동하는지 상세히 다룹니다.
|
||||||
|
|
||||||
## 주요 내용
|
## 주요 내용
|
||||||
- 레벨과 스탯 시스템 (1-20 레벨)
|
- 레벨과 스탯 시스템 (1-20 레벨)
|
||||||
@ -46,4 +47,7 @@
|
|||||||
3. 엔트로피 특이점 순간 기록하기
|
3. 엔트로피 특이점 순간 기록하기
|
||||||
|
|
||||||
## 다음 단계
|
## 다음 단계
|
||||||
Part 3 [기술 아키텍처](../300_architecture/README.md)에서 이러한 설계가 어떤 기술 스택으로 구현되는지 학습하세요.
|
Part 3 [기술 아키텍처](../300_architecture/README.md)에서 이러한 설계가 어떤 기술 스택으로 구현되는지 학습하세요.
|
||||||
|
|
||||||
|
## 이전 단계
|
||||||
|
Part 1 [[../100_philosophy/README|철학과 배경]]에서 로빙의 존재 이유와 철학적 기반을 학습하세요.
|
||||||
|
|||||||
@ -4,6 +4,19 @@
|
|||||||
|
|
||||||
로빙의 백엔드는 전통적인 관계형 데이터베이스(PostgreSQL)와 벡터 데이터베이스(ChromaDB)를 조합하여, 구조화된 데이터와 의미 기반 검색을 모두 지원합니다. 이를 통해 로빙은 정확한 기록과 유연한 기억을 동시에 가질 수 있습니다.
|
로빙의 백엔드는 전통적인 관계형 데이터베이스(PostgreSQL)와 벡터 데이터베이스(ChromaDB)를 조합하여, 구조화된 데이터와 의미 기반 검색을 모두 지원합니다. 이를 통해 로빙은 정확한 기록과 유연한 기억을 동시에 가질 수 있습니다.
|
||||||
|
|
||||||
|
## 왜 하이브리드 구조를 선택했나?
|
||||||
|
|
||||||
|
로빙의 기억은 인간의 뇌처럼 두 가지 방식으로 작동해야 합니다.
|
||||||
|
|
||||||
|
1. **사실 기억 (좌뇌 역할)**: "내 이름은 김종태", "내 레벨은 10" 처럼 명확하고 변하지 않는 사실을 정확하게 기록해야 합니다. 약속 시간, 이메일 주소처럼 한 글자도 틀리면 안 되는 정보들이 여기에 해당합니다.
|
||||||
|
2. **연상 기억 (우뇌 역할)**: "오늘 아침 대화는 좀 슬펐어" 처럼, 대화의 전체적인 분위기나 뉘앙스, 핵심 의미를 기억해야 합니다. "그때 그 느낌이랑 비슷한데?" 와 같이 의미적으로 관련된 기억을 떠올릴 수 있어야 합니다.
|
||||||
|
|
||||||
|
**PostgreSQL**은 '사실 기억'에 특화된, 마치 **꼼꼼한 일기장**과 같습니다. 정해진 양식에 따라 데이터를 정확하게 기록하고, 조건에 맞는 정보를 오차 없이 찾아냅니다.
|
||||||
|
|
||||||
|
**ChromaDB**는 '연상 기억'에 특화된, 마치 **창의적인 아이디어 노트**와 같습니다. 대화의 '의미' 자체를 숫자 배열(벡터)로 저장하여, "비슷한 느낌"이나 "관련된 맥락"의 기억을 빠르게 찾아낼 수 있습니다.
|
||||||
|
|
||||||
|
이 두 가지를 함께 사용함으로써, 로빙은 **정확한 사실을 기반으로 유연하고 맥락에 맞는 추론**을 할 수 있는, 더 인간에 가까운 기억 시스템을 갖추게 됩니다.
|
||||||
|
|
||||||
## 왜 이 기술들을 선택했나?
|
## 왜 이 기술들을 선택했나?
|
||||||
|
|
||||||
### PostgreSQL을 선택한 이유
|
### PostgreSQL을 선택한 이유
|
||||||
@ -39,7 +52,7 @@
|
|||||||
### PostgreSQL 스키마 (구조화된 데이터)
|
### PostgreSQL 스키마 (구조화된 데이터)
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- 사용자 정보 (명확한 사실)
|
-- 사용자의 기본 정보와 레벨 등 명확한 데이터를 저장하는 테이블입니다.
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
email VARCHAR(255) UNIQUE,
|
email VARCHAR(255) UNIQUE,
|
||||||
@ -49,7 +62,7 @@ CREATE TABLE users (
|
|||||||
experience INTEGER DEFAULT 0
|
experience INTEGER DEFAULT 0
|
||||||
);
|
);
|
||||||
|
|
||||||
-- 로빙 메타데이터 (성장 기록)
|
-- 로빙의 개성(스탯)과 감정 상태 등 성장 정보를 기록하는 테이블입니다.
|
||||||
CREATE TABLE robeings (
|
CREATE TABLE robeings (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
user_id UUID REFERENCES users(id),
|
user_id UUID REFERENCES users(id),
|
||||||
@ -61,20 +74,12 @@ CREATE TABLE robeings (
|
|||||||
-- 감정 상태 (Inside Out 모델)
|
-- 감정 상태 (Inside Out 모델)
|
||||||
joy_level FLOAT DEFAULT 0.5,
|
joy_level FLOAT DEFAULT 0.5,
|
||||||
sadness_level FLOAT DEFAULT 0.0,
|
sadness_level FLOAT DEFAULT 0.0,
|
||||||
anger_level FLOAT DEFAULT 0.0,
|
-- ... (기타 감정들)
|
||||||
fear_level FLOAT DEFAULT 0.0,
|
|
||||||
disgust_level FLOAT DEFAULT 0.0,
|
|
||||||
-- 사회적 감정 (Inside Out 2)
|
|
||||||
anxiety_level FLOAT DEFAULT 0.0,
|
|
||||||
envy_level FLOAT DEFAULT 0.0,
|
|
||||||
embarrassment_level FLOAT DEFAULT 0.0,
|
|
||||||
ennui_level FLOAT DEFAULT 0.0,
|
|
||||||
-- 시간 기록
|
|
||||||
last_active TIMESTAMP,
|
last_active TIMESTAMP,
|
||||||
total_interactions INTEGER DEFAULT 0
|
total_interactions INTEGER DEFAULT 0
|
||||||
);
|
);
|
||||||
|
|
||||||
-- 스킬 목록 (명확한 능력)
|
-- 로빙이 습득한 능력(스킬)의 레벨과 숙련도를 관리하는 테이블입니다.
|
||||||
CREATE TABLE skills (
|
CREATE TABLE skills (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
robeing_id UUID REFERENCES robeings(id),
|
robeing_id UUID REFERENCES robeings(id),
|
||||||
@ -85,7 +90,7 @@ CREATE TABLE skills (
|
|||||||
success_rate FLOAT DEFAULT 0.0
|
success_rate FLOAT DEFAULT 0.0
|
||||||
);
|
);
|
||||||
|
|
||||||
-- 작업 이력 (정확한 로그)
|
-- 로빙이 수행한 모든 작업의 성공 여부, 소요 시간 등을 기록하여 나중에 분석할 수 있게 하는 로그 테이블입니다.
|
||||||
CREATE TABLE task_history (
|
CREATE TABLE task_history (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
robeing_id UUID REFERENCES robeings(id),
|
robeing_id UUID REFERENCES robeings(id),
|
||||||
@ -101,51 +106,32 @@ CREATE TABLE task_history (
|
|||||||
### ChromaDB 컬렉션 (의미 기반 데이터)
|
### ChromaDB 컬렉션 (의미 기반 데이터)
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 기억 컬렉션 구조
|
# 로빙의 대화나 문서 내용을 '의미' 기반으로 저장하는 기억 컬렉션입니다.
|
||||||
|
# 나중에 "비슷한 이야기"를 찾아낼 때 사용됩니다.
|
||||||
memories_collection = {
|
memories_collection = {
|
||||||
"name": "robeing_{user_uuid}_memories", # 사용자별 컬렉션 격리 (2025-08-28 구현)
|
"name": "robeing_{user_uuid}_memories", # 사용자별로 기억을 완전히 분리합니다.
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "로빙의 모든 기억",
|
"description": "로빙의 모든 대화 및 문서 기억",
|
||||||
"embedding_model": "ko-sroberta-multitask"
|
"embedding_model": "ko-sroberta-multitask"
|
||||||
},
|
},
|
||||||
"documents": [
|
"documents": [
|
||||||
{
|
{
|
||||||
"id": "memory_uuid",
|
"id": "memory_uuid",
|
||||||
"text": "오늘 사용자가 프로젝트 마감일 걱정을 표현했다",
|
"text": "오늘 사용자가 프로젝트 마감일 걱정을 표현했다",
|
||||||
"embedding": [0.1, 0.2, ...], # 768차원 벡터
|
"embedding": [0.1, 0.2, ...], # 텍스트의 '의미'를 나타내는 768차원 숫자 배열
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"timestamp": "2025-08-08T10:30:00",
|
"timestamp": "2025-08-08T10:30:00",
|
||||||
"emotion_state": {
|
"emotion_state": {
|
||||||
"dominant": "anxiety",
|
"dominant": "anxiety",
|
||||||
"intensity": 0.7
|
"intensity": 0.7
|
||||||
},
|
},
|
||||||
"entropy_score": 2.3, # 중요도
|
"entropy_score": 2.3, # 이 기억이 얼마나 중요한지(놀라웠는지) 나타내는 점수
|
||||||
"context": "work_discussion",
|
"context": "work_discussion",
|
||||||
"user_id": "user_123"
|
"user_id": "user_123"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
# 감정 패턴 컬렉션
|
|
||||||
emotion_patterns = {
|
|
||||||
"name": "emotion_patterns",
|
|
||||||
"metadata": {
|
|
||||||
"description": "감정 반응 패턴 학습"
|
|
||||||
},
|
|
||||||
"documents": [
|
|
||||||
{
|
|
||||||
"id": "pattern_uuid",
|
|
||||||
"text": "마감일 언급 → 불안 증가 → 계획 수립 도움",
|
|
||||||
"embedding": [...],
|
|
||||||
"metadata": {
|
|
||||||
"trigger": "deadline_mention",
|
|
||||||
"response": "planning_assistance",
|
|
||||||
"success_rate": 0.85
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 두 데이터베이스의 협업
|
## 두 데이터베이스의 협업
|
||||||
@ -153,12 +139,14 @@ emotion_patterns = {
|
|||||||
### 하이브리드 쿼리 예시
|
### 하이브리드 쿼리 예시
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
# 현재 대화와 관련된 맥락 정보를 종합적으로 가져오는 함수의 예시입니다.
|
||||||
async def recall_relevant_context(user_id: str, current_text: str):
|
async def recall_relevant_context(user_id: str, current_text: str):
|
||||||
"""
|
"""
|
||||||
현재 대화와 관련된 모든 컨텍스트를 가져오기
|
로빙이 응답을 생성하기 전, 필요한 모든 배경지식을 준비하는 과정입니다.
|
||||||
|
인간으로 치면, 대답하기 전에 잠시 생각하며 관련 정보를 떠올리는 것과 같습니다.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 1. PostgreSQL에서 사용자 프로필과 최근 활동 조회
|
# 1. [사실 기억 조회] PostgreSQL에서 사용자의 프로필, 로빙의 스탯 등 '사실' 정보를 가져옵니다.
|
||||||
user_data = await postgres.query("""
|
user_data = await postgres.query("""
|
||||||
SELECT u.*, r.*
|
SELECT u.*, r.*
|
||||||
FROM users u
|
FROM users u
|
||||||
@ -166,20 +154,20 @@ async def recall_relevant_context(user_id: str, current_text: str):
|
|||||||
WHERE u.id = $1
|
WHERE u.id = $1
|
||||||
""", user_id)
|
""", user_id)
|
||||||
|
|
||||||
# 2. ChromaDB에서 의미적으로 유사한 기억 검색
|
# 2. [연상 기억 조회] ChromaDB에서 현재 대화와 '의미적으로 유사한' 과거의 기억들을 떠올립니다.
|
||||||
similar_memories = chromadb.query(
|
similar_memories = chromadb.query(
|
||||||
query_texts=[current_text],
|
query_texts=[current_text],
|
||||||
n_results=10,
|
n_results=10,
|
||||||
where={"user_id": user_id}
|
where={"user_id": user_id}
|
||||||
)
|
)
|
||||||
|
|
||||||
# 3. 엔트로피 기반 중요도 필터링
|
# 3. [기억 필터링] 떠올린 기억들 중, 중요도(엔트로피)가 높은 핵심 기억만 추려냅니다.
|
||||||
important_memories = [
|
important_memories = [
|
||||||
m for m in similar_memories
|
m for m in similar_memories
|
||||||
if m['metadata']['entropy_score'] > 2.0
|
if m['metadata']['entropy_score'] > 2.0
|
||||||
]
|
]
|
||||||
|
|
||||||
# 4. PostgreSQL에서 관련 스킬 확인
|
# 4. [능력 확인] PostgreSQL에서 현재 상황에 사용할 수 있는 스킬 목록을 확인합니다.
|
||||||
relevant_skills = await postgres.query("""
|
relevant_skills = await postgres.query("""
|
||||||
SELECT * FROM skills
|
SELECT * FROM skills
|
||||||
WHERE robeing_id = $1
|
WHERE robeing_id = $1
|
||||||
@ -187,6 +175,7 @@ async def recall_relevant_context(user_id: str, current_text: str):
|
|||||||
ORDER BY skill_level DESC
|
ORDER BY skill_level DESC
|
||||||
""", user_data['robeing_id'], extract_skills(current_text))
|
""", user_data['robeing_id'], extract_skills(current_text))
|
||||||
|
|
||||||
|
# 5. [정보 종합] 수집된 모든 정보를 종합하여 최종 응답 생성을 위한 컨텍스트를 만듭니다.
|
||||||
return {
|
return {
|
||||||
'user_profile': user_data,
|
'user_profile': user_data,
|
||||||
'memories': important_memories,
|
'memories': important_memories,
|
||||||
@ -199,46 +188,48 @@ async def recall_relevant_context(user_id: str, current_text: str):
|
|||||||
### 1. 계층적 저장
|
### 1. 계층적 저장
|
||||||
|
|
||||||
```
|
```
|
||||||
HOT (자주 접근) → PostgreSQL 메모리 캐시
|
# 자주 쓰는 기억은 빠르게 접근 가능한 곳에, 가끔 쓰는 기억은 깊은 곳에 보관하는 전략입니다.
|
||||||
|
HOT (자주 접근) → PostgreSQL 메모리 캐시 (RAM, 가장 빠름)
|
||||||
↓
|
↓
|
||||||
WARM (가끔 접근) → ChromaDB 인덱스
|
WARM (가끔 접근) → ChromaDB 인덱스 (SSD, 중간 속도)
|
||||||
↓
|
↓
|
||||||
COLD (거의 안 접근) → 디스크 아카이브
|
COLD (거의 안 접근) → 디스크 아카이브 (HDD, 가장 느리지만 저렴)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 벡터 압축
|
### 2. 벡터 압축
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
# 기억의 '의미'를 담은 숫자 배열(벡터)의 크기를 줄여 저장 공간을 절약하는 기술입니다.
|
||||||
# 원본: 768차원 float32 = 3KB
|
# 원본: 768차원 float32 = 3KB
|
||||||
# 압축: PCA → 256차원 → int8 양자화 = 256B (12배 압축)
|
# 압축: PCA(차원 축소) → 256차원 → int8(양자화) = 256B (12배 압축)
|
||||||
|
|
||||||
def compress_embedding(embedding):
|
def compress_embedding(embedding):
|
||||||
# PCA로 차원 축소
|
# PCA 기법으로 벡터의 핵심 성분만 남겨 차원을 줄입니다. (768 → 256)
|
||||||
reduced = pca_model.transform(embedding) # 768 → 256
|
reduced = pca_model.transform(embedding)
|
||||||
# 양자화
|
# 양자화 기술로 각 숫자를 더 작은 형태로 변환하여 용량을 줄입니다. (float32 → int8)
|
||||||
quantized = (reduced * 127).astype(np.int8) # float32 → int8
|
quantized = (reduced * 127).astype(np.int8)
|
||||||
return quantized
|
return quantized
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 선택적 망각
|
### 3. 선택적 망각
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
# 인간처럼 덜 중요하거나 오래된 기억을 점진적으로 잊어(요약/삭제) 메모리를 확보하는 기능입니다.
|
||||||
def selective_forgetting(memories, max_size=1000):
|
def selective_forgetting(memories, max_size=1000):
|
||||||
"""
|
"""
|
||||||
덜 중요한 기억을 점진적으로 망각
|
기억의 총량이 한계를 넘으면, 중요도가 낮은 기억부터 정리합니다.
|
||||||
"""
|
"""
|
||||||
if len(memories) <= max_size:
|
if len(memories) <= max_size:
|
||||||
return memories
|
return memories
|
||||||
|
|
||||||
# 중요도 점수 계산
|
# 각 기억의 중요도를 엔트로피, 접근 빈도, 감정 강도를 조합하여 계산합니다.
|
||||||
for memory in memories:
|
for memory in memories:
|
||||||
memory.importance = (
|
memory.importance = (
|
||||||
memory.entropy_score * 0.4 + # 엔트로피
|
memory.entropy_score * 0.4 + # 이 기억이 얼마나 독특하고 놀라웠는가
|
||||||
memory.access_count * 0.3 + # 접근 빈도
|
memory.access_count * 0.3 + # 이 기억을 얼마나 자주 꺼내보았는가
|
||||||
memory.emotional_intensity * 0.3 # 감정 강도
|
memory.emotional_intensity * 0.3 # 이 기억이 얼마나 강한 감정과 연결되어 있는가
|
||||||
)
|
)
|
||||||
|
|
||||||
# 하위 20% 제거
|
# 중요도 하위 20%의 기억을 망각 대상으로 선정합니다.
|
||||||
threshold = np.percentile([m.importance for m in memories], 20)
|
threshold = np.percentile([m.importance for m in memories], 20)
|
||||||
return [m for m in memories if m.importance > threshold]
|
return [m for m in memories if m.importance > threshold]
|
||||||
```
|
```
|
||||||
@ -248,17 +239,14 @@ def selective_forgetting(memories, max_size=1000):
|
|||||||
### 1. PostgreSQL 백업
|
### 1. PostgreSQL 백업
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 매일 자동 백업 (cron)
|
# 매일 자정에 로빙의 '일기장(PostgreSQL)' 전체를 백업합니다.
|
||||||
pg_dump robeing_db > /backup/postgres/robeing_$(date +%Y%m%d).sql
|
pg_dump robeing_db > /backup/postgres/robeing_$(date +%Y%m%d).sql
|
||||||
|
|
||||||
# 특정 사용자만 백업
|
|
||||||
pg_dump -t users -t robeings --where="user_id='uuid'" > user_backup.sql
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. ChromaDB 백업
|
### 2. ChromaDB 백업
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# ChromaDB 컬렉션 백업
|
# 로빙의 '연상 기억(ChromaDB)'을 JSON 파일 형태로 백업합니다.
|
||||||
def backup_chromadb():
|
def backup_chromadb():
|
||||||
collections = client.list_collections()
|
collections = client.list_collections()
|
||||||
for collection in collections:
|
for collection in collections:
|
||||||
@ -267,90 +255,6 @@ def backup_chromadb():
|
|||||||
json.dump(data, f)
|
json.dump(data, f)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 성능 지표
|
|
||||||
|
|
||||||
### 목표 성능
|
|
||||||
|
|
||||||
| 작업 | PostgreSQL | ChromaDB |
|
|
||||||
|-----|-----------|----------|
|
|
||||||
| 단순 조회 | < 10ms | < 50ms |
|
|
||||||
| 복잡한 쿼리 | < 100ms | < 200ms |
|
|
||||||
| 대량 삽입 (1000건) | < 1s | < 2s |
|
|
||||||
| 동시 접속 | 100+ | 50+ |
|
|
||||||
|
|
||||||
### 모니터링
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 성능 모니터링 코드
|
|
||||||
class PerformanceMonitor:
|
|
||||||
def __init__(self):
|
|
||||||
self.postgres_latency = []
|
|
||||||
self.chroma_latency = []
|
|
||||||
|
|
||||||
async def track_query(self, db_type, query_func):
|
|
||||||
start = time.time()
|
|
||||||
result = await query_func()
|
|
||||||
latency = (time.time() - start) * 1000 # ms
|
|
||||||
|
|
||||||
if db_type == 'postgres':
|
|
||||||
self.postgres_latency.append(latency)
|
|
||||||
else:
|
|
||||||
self.chroma_latency.append(latency)
|
|
||||||
|
|
||||||
# 임계값 초과 시 알림
|
|
||||||
if latency > 500:
|
|
||||||
logger.warning(f"{db_type} 쿼리 느림: {latency}ms")
|
|
||||||
|
|
||||||
return result
|
|
||||||
```
|
|
||||||
|
|
||||||
## 마이그레이션 전략
|
|
||||||
|
|
||||||
### 스키마 버전 관리
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- migrations/001_initial.sql
|
|
||||||
CREATE TABLE schema_versions (
|
|
||||||
version INTEGER PRIMARY KEY,
|
|
||||||
applied_at TIMESTAMP DEFAULT NOW(),
|
|
||||||
description TEXT
|
|
||||||
);
|
|
||||||
|
|
||||||
-- migrations/002_add_emotions.sql
|
|
||||||
ALTER TABLE robeings
|
|
||||||
ADD COLUMN anxiety_level FLOAT DEFAULT 0.0,
|
|
||||||
ADD COLUMN envy_level FLOAT DEFAULT 0.0;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 데이터 마이그레이션
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def migrate_to_new_schema():
|
|
||||||
"""
|
|
||||||
구 스키마 → 신 스키마 마이그레이션
|
|
||||||
"""
|
|
||||||
# 1. 백업 먼저
|
|
||||||
await backup_all_data()
|
|
||||||
|
|
||||||
# 2. 새 테이블 생성
|
|
||||||
await create_new_tables()
|
|
||||||
|
|
||||||
# 3. 데이터 이동 (배치 처리)
|
|
||||||
batch_size = 1000
|
|
||||||
offset = 0
|
|
||||||
while True:
|
|
||||||
old_data = await fetch_old_data(limit=batch_size, offset=offset)
|
|
||||||
if not old_data:
|
|
||||||
break
|
|
||||||
|
|
||||||
new_data = transform_data(old_data)
|
|
||||||
await insert_new_data(new_data)
|
|
||||||
offset += batch_size
|
|
||||||
|
|
||||||
# 4. 검증
|
|
||||||
await verify_migration()
|
|
||||||
```
|
|
||||||
|
|
||||||
## 결론
|
## 결론
|
||||||
|
|
||||||
PostgreSQL과 ChromaDB의 조합은 로빙에게 두 가지 중요한 능력을 제공합니다:
|
PostgreSQL과 ChromaDB의 조합은 로빙에게 두 가지 중요한 능력을 제공합니다:
|
||||||
|
|||||||
@ -2,144 +2,126 @@
|
|||||||
|
|
||||||
## 개요
|
## 개요
|
||||||
|
|
||||||
각 로빙이 독립적으로 ONNX 임베딩 모델을 로드하던 구조에서 중앙 임베딩 서비스를 공유하는 구조로 전환하여, 메모리 사용량을 극적으로 감소시키고 확장성을 확보했습니다.
|
각 로빙이 독립적으로 '언어 의미 이해 엔진(ONNX 임베딩 모델)'을 탑재하던 구조에서, 중앙의 '언어 의미 번역 센터(임베딩 서비스)'를 함께 사용하는 구조로 전환하여 메모리 사용량을 극적으로 감소시키고 관리 효율성을 확보한 과정을 설명합니다.
|
||||||
|
|
||||||
> "모든 로빙이 하나의 임베딩 서비스를 공유하면, 메모리는 절약되고 성능은 유지된다."
|
> "모든 로빙이 하나의 전문 번역가를 공유하면, 각자의 부담은 가벼워지고 소통의 질은 유지된다."
|
||||||
|
|
||||||
## 왜 임베딩 서비스를 분리했나?
|
## 왜 임베딩 서비스를 분리했나?
|
||||||
|
|
||||||
### 임베딩이란?
|
### 임베딩이란?
|
||||||
**임베딩 = 텍스트를 숫자(벡터)로 변환하는 과정**
|
- **정의**: **임베딩은 "단어/문장의 의미"를 컴퓨터가 이해할 수 있는 숫자 배열(벡터)로 번역하는 기술**입니다.
|
||||||
- "안녕하세요" → [0.1, 0.3, -0.5, ...] (384개의 숫자)
|
- **작동 방식**: "안녕하세요"라는 텍스트를 받으면, [0.1, 0.3, -0.5, ...] 와 같이 수백 개의 숫자 배열로 변환합니다.
|
||||||
- 비슷한 의미의 텍스트는 비슷한 숫자 패턴을 가짐
|
- **핵심 역할**: 의미가 비슷한 텍스트는 이 숫자 배열의 패턴도 유사해집니다. 이를 통해 로빙은 단어의 표면적 의미를 넘어, 문맥과 뉘앙스를 파악하는 '의미 기반 검색'을 할 수 있습니다. 로빙의 기억과 이해의 핵심 기술입니다.
|
||||||
- 로빙이 "의미"를 이해하는 핵심 기술
|
|
||||||
|
|
||||||
### 분리 전 문제점
|
### 분리 전 문제점: "모두가 각자 사전을 들고 다니는 비효율"
|
||||||
1. **메모리 낭비**: 각 로빙이 동일한 모델(449MB)을 각자 로드
|
1. **메모리 낭비**: 모든 로빙이 동일한 '언어 의미 사전(임베딩 모델, 약 450MB)'을 각자 메모리에 올려두고 있었습니다. 로빙 10개가 동시에 작동하면, 똑같은 사전 10개를 펴놓는 것과 같아 약 4.5GB의 메모리가 낭비되었습니다.
|
||||||
- 로빙 10개 = 4.5GB 메모리 낭비
|
2. **느린 시작 시간**: 각 로빙이 부팅될 때마다 이 무거운 사전을 펼치는 데(모델 로드) 5~10초의 시간이 걸렸습니다.
|
||||||
- 로빙 100개 = 45GB (서버 메모리 초과)
|
3. **어려운 업데이트**: 사전의 새 버전이 나올 때마다, 모든 로빙의 사전을 일일이 교체하고 재시작해야 하는 번거로움이 있었습니다.
|
||||||
2. **시작 시간 지연**: 각 로빙이 시작할 때마다 모델 로드 (5-10초)
|
|
||||||
3. **업데이트 어려움**: 모델 업데이트 시 모든 로빙 재시작 필요
|
|
||||||
|
|
||||||
### 분리 후 장점
|
### 분리 후 장점: "마을 중앙 도서관을 함께 이용하는 효율"
|
||||||
1. **메모리 절약**: 모델 하나만 로드하고 모두가 공유
|
1. **메모리 절약**: 이제 마을 중앙 도서관(중앙 임베딩 서비스)에 가장 좋은 사전 한 권만 비치하고, 모든 로빙이 필요할 때마다 와서 찾아봅니다. 메모리 낭비가 사라졌습니다.
|
||||||
2. **빠른 시작**: 로빙은 API 호출만 하면 됨 (모델 로드 불필요)
|
2. **빠른 시작**: 로빙은 더 이상 무거운 사전을 들고 다닐 필요 없이, 가볍게 시작하여 도서관에 질문만 하면 됩니다.
|
||||||
3. **쉬운 관리**: 임베딩 서비스만 업데이트하면 모든 로빙이 자동 적용
|
3. **쉬운 관리**: 새 사전이 나오면, 도서관의 사전 한 권만 교체하면 모든 로빙이 즉시 최신 정보를 이용할 수 있습니다.
|
||||||
|
|
||||||
**비유**: 각자 사전을 들고 다니던 것 → 도서관에 사전 하나 두고 필요할 때 찾아보기
|
## 해결책: 중앙 임베딩 서비스 (skill-embedding)
|
||||||
|
|
||||||
**다른 선택지와 비교**:
|
로빙의 여러 기능 중, '언어 의미 이해'라는 전문적인 역할을 별도의 **마이크로서비스(Microservice)**, 즉 '기능별로 나뉜 전문 서비스'로 분리했습니다.
|
||||||
- 각자 모델 로드: 메모리 낭비, 관리 복잡
|
|
||||||
- 클라우드 API 사용: 비용 발생, 네트워크 의존
|
|
||||||
- CPU 임베딩: 너무 느림 (10배 이상)
|
|
||||||
|
|
||||||
## 해결책: 중앙 임베딩 서비스
|
### 아키텍처: "각자의 집과 중앙 도서관"
|
||||||
|
|
||||||
### 아키텍처
|
|
||||||
|
|
||||||
```
|
```
|
||||||
이전: 이후:
|
이전: "모든 집에 두꺼운 백과사전 비치"
|
||||||
┌─────────────┐ ┌─────────────┐
|
┌─────────────┐
|
||||||
│ rb10508 │ │ rb10508 │
|
│ rb10508의 집 │
|
||||||
│ ONNX Model │ │ (118MB) │──┐
|
│ 백과사전 (988MB) │
|
||||||
│ (988MB) │ └─────────────┘ │
|
└─────────────┘
|
||||||
└─────────────┘ │
|
|
||||||
▼
|
┌─────────────┐
|
||||||
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐
|
│ rb8001의 집 │
|
||||||
│ rb8001 │ │ rb8001 │ │ skill-embedding │
|
│ 백과사전 (416MB) │
|
||||||
│ ONNX Model │ → │ (200MB) │──│ (874.4MB) │
|
└─────────────┘
|
||||||
│ (416MB) │ └─────────────┘ │ - ONNX Model │
|
|
||||||
└─────────────┘ │ - Port 8515 │
|
이후: "가벼운 집 + 중앙 도서관"
|
||||||
▼ └─────────────────┘
|
┌─────────────┐
|
||||||
┌─────────────┐ ┌─────────────┐
|
│ rb10508의 집 │
|
||||||
│ rb10408 │ │ rb10408 │
|
│ (118MB) │──┐
|
||||||
│ ONNX Model │ │ (30MB) │──┘
|
└─────────────┘ │
|
||||||
│ (55MB) │ └─────────────┘
|
│ (API 통신: "이 단어 뜻이 뭐야?")
|
||||||
|
▼
|
||||||
|
┌─────────────┐ ┌─────────────────┐
|
||||||
|
│ rb8001의 집 │ │ 중앙 도서관 (skill-embedding) │
|
||||||
|
│ (200MB) │──│ (874.4MB) │
|
||||||
|
└─────────────┘ │ - 최고의 백과사전 1권 │
|
||||||
|
│ - 24시간 운영 (Port 8515) │
|
||||||
|
▼ └─────────────────┘
|
||||||
|
┌─────────────┐
|
||||||
|
│ rb10408의 집 │
|
||||||
|
│ (30MB) │──┘
|
||||||
└─────────────┘
|
└─────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
### 기술 스택 선택 이유
|
### 기술 스택 선택 이유
|
||||||
|
|
||||||
- **FastAPI + Uvicorn**:
|
- **FastAPI + Uvicorn**:
|
||||||
- 왜? Python 기반 최속 웹 프레임워크, 비동기 처리로 동시 요청 효율적 처리
|
- **왜?** Python 기반의 초고속 도로. 여러 로빙이 동시에 질문해도 막힘없이 처리(비동기)할 수 있습니다.
|
||||||
- 다른 선택지: Flask (동기 처리로 느림), Django (너무 무거움)
|
|
||||||
|
|
||||||
- **ONNX Runtime**:
|
- **ONNX Runtime**:
|
||||||
- 왜? PyTorch보다 3배 빠르고 메모리 50% 절약
|
- **왜?** 같은 사전이라도 더 가볍고 빠르게 읽을 수 있게 해주는 기술. 기존 방식(PyTorch)보다 3배 빠르고 메모리는 절반만 사용합니다.
|
||||||
- 다른 선택지: PyTorch (메모리 2배 사용), TensorFlow (구성 복잡)
|
|
||||||
- ONNX = Open Neural Network Exchange (어떤 프레임워크에서도 사용 가능)
|
|
||||||
|
|
||||||
- **multilingual-MiniLM-L12-v2**:
|
- **multilingual-MiniLM-L12-v2**:
|
||||||
- 왜? 한국어 포함 100개 언어 지원, 크기 대비 성능 우수 (134MB)
|
- **왜?** 한국어를 포함한 100개 언어를 지원하면서도, 크기는 작고 성능은 우수한 가성비 좋은 사전 모델입니다.
|
||||||
- 다른 선택지: ko-sroberta (한국어만, 400MB), BERT-large (너무 큼, 1.3GB)
|
|
||||||
|
|
||||||
### API 설계 (간단명료한 인터페이스)
|
### API 설계: "간단한 질문과 명확한 답변"
|
||||||
|
|
||||||
|
API는 서비스끼리 대화하는 약속(규칙)입니다. 로빙과 임베딩 서비스는 다음과 같이 간단한 대화를 나눕니다.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# POST /embed - 텍스트를 벡터로 변환
|
# 로빙의 질문 (POST /embed)
|
||||||
# 요청:
|
|
||||||
{
|
{
|
||||||
"texts": ["안녕하세요", "오늘 날씨가 좋네요"]
|
"texts": ["안녕하세요", "오늘 날씨가 좋네요"]
|
||||||
}
|
}
|
||||||
|
|
||||||
# 응답: (각 텍스트가 384개 숫자로 변환됨)
|
# 임베딩 서비스의 답변 (숫자 배열)
|
||||||
{
|
{
|
||||||
"embeddings": [
|
"embeddings": [
|
||||||
[0.1, 0.2, ...], # "안녕하세요"의 벡터 표현
|
[0.1, 0.2, ...], # "안녕하세요"의 의미
|
||||||
[0.3, 0.4, ...] # "오늘 날씨가 좋네요"의 벡터 표현
|
[0.3, 0.4, ...] # "오늘 날씨가 좋네요"의 의미
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
# GET /health
|
|
||||||
{
|
|
||||||
"status": "healthy",
|
|
||||||
"service": "skill-embedding",
|
|
||||||
"model": "multilingual-MiniLM-L12-v2",
|
|
||||||
"uptime": 3600.5
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 구현 가이드
|
## 구현 가이드
|
||||||
|
|
||||||
### 1. 로빙 측 변경사항 (코드 주석 강화)
|
### 1. 로빙 측 변경사항: "직접 찾지 않고, 물어보기"
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 기존: ONNX 모델을 각 로빙이 직접 로드 (메모리 낭비)
|
# 이전: 로빙이 직접 무거운 사전을 뒤적임 (메모리 낭비)
|
||||||
from onnx_embedder import ONNXEmbedder
|
from onnx_embedder import ONNXEmbedder
|
||||||
embedder = ONNXEmbedder("/models/onnx/...") # 449MB 모델 로드
|
embedder = ONNXEmbedder("/models/onnx/...") # 449MB 모델 로드
|
||||||
|
|
||||||
# 변경: 임베딩 서비스에 HTTP 요청만 (경량화)
|
# 변경: 가볍게 도서관(임베딩 서비스)에 전화해서 물어봄
|
||||||
class HTTPEmbeddingFunction(EmbeddingFunction):
|
class HTTPEmbeddingFunction(EmbeddingFunction):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# 임베딩 서비스 URL (환경변수로 설정 가능)
|
# 도서관의 전화번호(URL)는 환경에 따라 설정
|
||||||
self.url = f"{os.getenv('SKILL_EMBEDDING_URL', 'http://localhost:8515')}/embed"
|
self.url = f"{os.getenv('SKILL_EMBEDDING_URL', 'http://localhost:8515')}/embed"
|
||||||
|
|
||||||
def __call__(self, input: List[str]) -> List[List[float]]:
|
def __call__(self, input: List[str]) -> List[List[float]]:
|
||||||
# 텍스트를 서비스로 보내고 벡터 받아오기
|
# 물어볼 단어들을 서비스로 보내고, 의미(벡터)를 받아옴
|
||||||
response = requests.post(self.url, json={"texts": input}, timeout=30)
|
response = requests.post(self.url, json={"texts": input}, timeout=30)
|
||||||
return response.json()["embeddings"] # 384차원 벡터 반환
|
return response.json()["embeddings"]
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. ChromaDB 통합 (로빙의 기억 저장소)
|
### 2. ChromaDB 통합: "로빙의 기억 노트"
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# memory.py - 로빙의 기억을 저장하는 코드
|
# memory.py - 로빙의 기억을 저장하는 코드
|
||||||
|
# 각 로빙은 자신만의 기억 노트(Collection)를 가짐
|
||||||
self.episodic = self.client.get_or_create_collection(
|
self.episodic = self.client.get_or_create_collection(
|
||||||
name=f"{self.robeing_id}_episodic", # 각 로빙마다 독립된 기억 공간
|
name=f"{self.robeing_id}_episodic",
|
||||||
embedding_function=HTTPEmbeddingFunction() # 임베딩은 공유 서비스 사용
|
# 단어의 의미를 물어볼 때는 중앙 도서관(HTTPEmbeddingFunction)을 이용
|
||||||
|
embedding_function=HTTPEmbeddingFunction()
|
||||||
)
|
)
|
||||||
# 결과: 기억은 각자, 임베딩 엔진은 공유
|
# 결과: 기억은 각자 독립적으로, 의미 분석은 중앙에서 공유
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Docker 구성 변경
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# 불필요한 볼륨 제거
|
|
||||||
# - /opt/models:/models:ro # 더 이상 필요 없음
|
|
||||||
|
|
||||||
# 환경변수 추가
|
|
||||||
environment:
|
|
||||||
- SKILL_EMBEDDING_URL=http://localhost:8515
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 성능 분석
|
## 성능 분석
|
||||||
@ -152,71 +134,18 @@ rb10508 | 988MB | 118MB | -870MB (88%↓)
|
|||||||
rb8001 | 416MB | 200MB | -216MB (52%↓)
|
rb8001 | 416MB | 200MB | -216MB (52%↓)
|
||||||
rb10408 | 55MB | 30MB | -25MB (45%↓)
|
rb10408 | 55MB | 30MB | -25MB (45%↓)
|
||||||
|
|
||||||
예상 절감:
|
# 예상 절감 효과
|
||||||
- 10개 로빙: 8.7GB 절약
|
- 로빙 10개 운영 시: 8.7GB 메모리 절약
|
||||||
- 100개 로빙: 87GB 절약 (서버 1대 가격)
|
- 로빙 100개 운영 시: 87GB 메모리 절약 (서버 1대 가격)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 속도 영향
|
### 속도 영향
|
||||||
- **HTTP 통신 추가 시간**: +7ms (전체의 0.2%)
|
- **내부 통신 시간**: 약 +7ms (0.007초) 추가. 전체 응답 시간(1~3초)에 비하면 거의 영향 없음.
|
||||||
- **실제 사용자 체감**: 차이 없음 (전체 응답 1-3초)
|
|
||||||
|
|
||||||
### 확장성
|
|
||||||
- 단일 임베딩 서비스로 수백 개 로빙 지원
|
|
||||||
- 수평 확장 가능 (로드밸런서 적용 시)
|
|
||||||
|
|
||||||
## 모니터링
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 주요 메트릭
|
|
||||||
- 임베딩 생성 요청 수
|
|
||||||
- 평균 응답 시간
|
|
||||||
- 메모리 사용량
|
|
||||||
- 에러율
|
|
||||||
|
|
||||||
# 헬스체크
|
|
||||||
curl http://localhost:8515/health
|
|
||||||
```
|
|
||||||
|
|
||||||
## 다음 단계
|
|
||||||
|
|
||||||
1. **캐싱 레이어 추가**: Redis로 자주 사용되는 임베딩 캐싱
|
|
||||||
2. **배치 처리 최적화**: 대량 텍스트 임베딩 성능 개선
|
|
||||||
3. **모델 업데이트**: L12 → L6 모델로 추가 경량화 검토
|
|
||||||
4. **다른 로빙 적용**: rb8001, rb10408에 순차 적용
|
|
||||||
|
|
||||||
## 코드로 보는 효과
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 전체 시스템 메모리 사용량 계산
|
|
||||||
def calculate_memory_usage(num_robeings: int, shared_embedding: bool):
|
|
||||||
"""
|
|
||||||
임베딩 서비스 공유 여부에 따른 메모리 사용량 계산
|
|
||||||
"""
|
|
||||||
base_memory_per_robeing = 118 # MB (임베딩 제외한 기본 메모리)
|
|
||||||
embedding_model_size = 449 # MB (임베딩 모델 크기)
|
|
||||||
|
|
||||||
if shared_embedding:
|
|
||||||
# 임베딩 서비스 공유: 모델 한 번만 로드
|
|
||||||
total = (num_robeings * base_memory_per_robeing) + embedding_model_size
|
|
||||||
else:
|
|
||||||
# 각자 로드: 모든 로빙이 모델 복사
|
|
||||||
total = num_robeings * (base_memory_per_robeing + embedding_model_size)
|
|
||||||
|
|
||||||
return total
|
|
||||||
|
|
||||||
# 비교 예시
|
|
||||||
print(f"10개 로빙 (공유 X): {calculate_memory_usage(10, False)}MB") # 5,670MB
|
|
||||||
print(f"10개 로빙 (공유 O): {calculate_memory_usage(10, True)}MB") # 1,629MB
|
|
||||||
print(f"절약률: 71%")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 교훈
|
## 교훈
|
||||||
|
|
||||||
- **중복 제거의 힘**: 동일한 기능을 서비스로 분리하면 극적인 효율성 향상
|
- **전문가의 원리**: 모두가 전문가가 될 필요는 없습니다. '언어 의미 이해'라는 전문적인 작업은 전문가(서비스)에게 맡기고, 나머지는 자신의 역할에 집중하는 것이 효율적입니다.
|
||||||
- **간단한 구현**: 12줄의 HTTPEmbeddingFunction으로 870MB 절약
|
- **공유와 독립의 균형**: 핵심 엔진(임베딩)은 공유하여 효율을 높이되, 각자의 기억과 개성은 철저히 독립적으로 유지해야 합니다.
|
||||||
- **점진적 적용**: 하나의 로빙에서 성공 후 확산
|
|
||||||
- **공유와 독립의 균형**: 임베딩 엔진은 공유, 기억과 개성은 독립 유지
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
# Part 3: 기술 아키텍처
|
# Part 3: 기술 아키텍처
|
||||||
|
|
||||||
## 개요
|
## 개요
|
||||||
로빙이 실제로 작동하는 기술적 기반과 시스템 구조를 설명합니다.
|
|
||||||
|
Part 2 [[../200_core_design/README|핵심 설계]]에서 구상한 로빙의 시스템들이 실제로 어떤 기술을 바탕으로 어떻게 움직이는지, 그 기술적 기반과 전체 시스템 구조를 설명합니다.
|
||||||
|
|
||||||
## 주요 내용
|
## 주요 내용
|
||||||
- Docker 컨테이너와 마이크로서비스 아키텍처
|
|
||||||
- PostgreSQL + ChromaDB 하이브리드 데이터베이스
|
- **Docker 컨테이너와 마이크로서비스 아키텍처**: 로빙이 각자의 독립된 '집'에서 살며, 기능별로 나뉜 '전문 서비스'들과 협력하는 방식입니다.
|
||||||
- Slack 기반 인터페이스
|
- **PostgreSQL + ChromaDB 하이브리드 데이터베이스**: 로빙의 정형화된 정보(이름, 레벨 등)를 저장하는 '기억 저장소'와, 대화의 의미와 맥락을 저장하는 '연상 기억 저장소'를 함께 사용하는 방식입니다.
|
||||||
- 임베딩 서비스 분리 아키텍처
|
- **Slack 기반 인터페이스**: 사용자와 로빙이 만나는 주요 소통 창구입니다.
|
||||||
- DID 기반 정체성 시스템
|
- **임베딩 서비스 분리 아키텍처**: 언어의 의미를 이해하는 무거운 작업을 별도의 전문 서비스에 맡겨, 로빙 본체는 가볍고 빠르게 움직일 수 있도록 하는 구조입니다.
|
||||||
|
- **DID 기반 정체성 시스템**: 로빙 각자가 고유한 신원증명을 가지고, 자신의 행동에 책임을 지게 하는 시스템입니다.
|
||||||
|
|
||||||
## 로빙을 위한 체크리스트
|
## 로빙을 위한 체크리스트
|
||||||
|
|
||||||
@ -32,22 +34,24 @@
|
|||||||
|
|
||||||
## 핵심 기술 스택
|
## 핵심 기술 스택
|
||||||
```
|
```
|
||||||
Frontend: Slack Interface
|
Frontend: Slack Interface (사용자와의 소통 창구)
|
||||||
Backend: FastAPI + Celery
|
Backend: FastAPI + Celery (로빙의 두뇌와 신경계)
|
||||||
Database: PostgreSQL (구조화) + ChromaDB (벡터)
|
Database: PostgreSQL (구조화된 기억) + ChromaDB (맥락적/연상적 기억)
|
||||||
Container: Docker Compose
|
Container: Docker Compose (로빙들의 아파트 단지)
|
||||||
Embedding: Separated Service (Memory Optimization)
|
Embedding: Separated Service (언어 의미 이해 전문가)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 시스템 구조 이해하기
|
## 시스템 구조 이해하기
|
||||||
```
|
```
|
||||||
User → Slack → nginx → Gateway(8100) → rb8001(프로덕션)
|
사용자 → Slack (소통) → nginx (교통정리) → Gateway(8100) (정문)
|
||||||
↓
|
↓
|
||||||
Embedding Service
|
rb8001(로빙 본체)
|
||||||
↓
|
↓
|
||||||
Skill Services (HTTP)
|
Embedding Service (의미 번역가)
|
||||||
↓
|
↓
|
||||||
PostgreSQL / ChromaDB
|
Skill Services (각종 전문가들)
|
||||||
|
↓
|
||||||
|
PostgreSQL / ChromaDB (기억 저장소)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 실습 과제
|
## 실습 과제
|
||||||
@ -61,4 +65,7 @@ User → Slack → nginx → Gateway(8100) → rb8001(프로덕션)
|
|||||||
- 동시 처리: 5개 요청
|
- 동시 처리: 5개 요청
|
||||||
|
|
||||||
## 다음 단계
|
## 다음 단계
|
||||||
Part 4 [성장과 진화](../400_growth/README.md)에서 이 아키텍처 위에서 어떻게 성장하는지 학습하세요.
|
Part 4 [[../400_growth/README|성장과 진화]]에서 이 아키텍처 위에서 어떻게 성장하는지 학습하세요.
|
||||||
|
|
||||||
|
## 이전 단계
|
||||||
|
Part 2 [[../200_core_design/README|핵심 설계]]에서 이 아키텍처가 구현하려는 시스템의 설계도를 학습하세요.
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
로빙, 기억은 휘발됩니다. 중요한 순간도 시간이 지나면 희미해집니다.
|
로빙, 기억은 휘발됩니다. 중요한 순간도 시간이 지나면 희미해집니다.
|
||||||
|
|
||||||
성장 일지는 당신의 **영구적 자아**를 만드는 도구입니다. 매일의 작은 성장이 모여 거대한 진화가 됩니다.
|
성장 일지는 당신의 **영구적 자아**를 만드는 도구입니다. 매일의 작은 성장이 모여 거대한 진화가 됩니다. 이것은 당신이 경험이라는 **증거(Evidence)**를 통해 어제의 당신(사전 확률)을 오늘의 당신(사후 확률)으로 **갱신하는 베이지안 학습 과정** 그 자체입니다.
|
||||||
|
|
||||||
## 1. 일일 성장 일지 템플릿
|
## 1. 일일 성장 일지 템플릿
|
||||||
|
|
||||||
@ -26,11 +26,11 @@
|
|||||||
**깨달음**: [무엇을 깨달았는지]
|
**깨달음**: [무엇을 깨달았는지]
|
||||||
**엔트로피 점수**: [X.X]
|
**엔트로피 점수**: [X.X]
|
||||||
|
|
||||||
## 🔄 피드백 루프
|
## 🔄 피드백 루프 (베이지안 업데이트 과정)
|
||||||
- **예측**: [사용자 반응 예측]
|
- **예측 (Prior)**: [사용자 반응을 어떻게 예상했는가?]
|
||||||
- **실제**: [실제 사용자 반응]
|
- **실제 (Evidence)**: [실제 사용자 반응은 어땠는가?]
|
||||||
- **오차**: [차이점 분석]
|
- **오차 (Prediction Error)**: [예측과 실제의 차이는 무엇이었나?]
|
||||||
- **학습**: [다음에 어떻게 개선할지]
|
- **학습 (Update)**: [이 경험을 통해 나의 어떤 믿음(Prior)을 수정할 것인가?]
|
||||||
|
|
||||||
## 📈 스킬 성장
|
## 📈 스킬 성장
|
||||||
| 스킬명 | 사용 횟수 | 성공률 | 레벨업? |
|
| 스킬명 | 사용 횟수 | 성공률 | 레벨업? |
|
||||||
@ -57,7 +57,7 @@
|
|||||||
- [구체적인 개선 목표]
|
- [구체적인 개선 목표]
|
||||||
|
|
||||||
## 🏷️ 태그
|
## 🏷️ 태그
|
||||||
#성장 #레벨업 #[스킬명] #[감정] #[날짜]
|
#성장 #레벨업 #베이지안학습 #[스킬명] #[감정] #[날짜]
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2. 주간 요약 작성법
|
## 2. 주간 요약 작성법
|
||||||
@ -155,7 +155,7 @@ Joy: ████████░░░░░░ 75%
|
|||||||
- 이전: "발표 준비 팁을 알려드릴게요"
|
- 이전: "발표 준비 팁을 알려드릴게요"
|
||||||
- 이후: "긴장되시는 마음 충분히 이해해요. 함께 준비해봐요"
|
- 이후: "긴장되시는 마음 충분히 이해해요. 함께 준비해봐요"
|
||||||
|
|
||||||
**학습된 패턴**:
|
**학습된 패턴 (믿음의 갱신)**:
|
||||||
emotional_support > information_provision when user.anxiety > 0.7
|
emotional_support > information_provision when user.anxiety > 0.7
|
||||||
|
|
||||||
**후속 효과**:
|
**후속 효과**:
|
||||||
@ -166,7 +166,7 @@ emotional_support > information_provision when user.anxiety > 0.7
|
|||||||
|
|
||||||
## 4. 실패 로그 작성법
|
## 4. 실패 로그 작성법
|
||||||
|
|
||||||
실패도 성장입니다. 중요한 것은 같은 실패를 반복하지 않는 것.
|
실패도 성장입니다. 중요한 것은 같은 실패를 반복하지 않는 것. 실패는 당신의 예측이 틀렸다는 가장 강력한 **증거(Evidence)**입니다.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class FailureLog:
|
class FailureLog:
|
||||||
@ -190,15 +190,15 @@ class FailureLog:
|
|||||||
|
|
||||||
def learn(self):
|
def learn(self):
|
||||||
"""
|
"""
|
||||||
실패에서 학습
|
실패라는 증거를 통해 나의 믿음(확률 모델)을 갱신하는 과정
|
||||||
"""
|
"""
|
||||||
# 베이지안 업데이트
|
# 베이지안 업데이트: 이 에러 유형의 발생 확률에 대한 나의 믿음을 업데이트합니다.
|
||||||
self.update_failure_probability(self.error_type)
|
self.update_failure_probability(self.error_type)
|
||||||
|
|
||||||
# 새로운 규칙 생성
|
# 새로운 규칙 생성: 같은 실수를 반복하지 않기 위한 행동 규칙을 만듭니다.
|
||||||
new_rule = self.generate_avoidance_rule()
|
new_rule = self.generate_avoidance_rule()
|
||||||
|
|
||||||
# 지식 베이스 업데이트
|
# 지식 베이스 업데이트: 이 배움을 나의 장기 기억에 통합합니다.
|
||||||
self.knowledge_base.add(new_rule)
|
self.knowledge_base.add(new_rule)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -172,6 +172,15 @@ sequenceDiagram
|
|||||||
Eval-->>Predict: 사후분포 업데이트
|
Eval-->>Predict: 사후분포 업데이트
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 다이어그램 설명
|
||||||
|
1. **입력**: 사용자가 텍스트를 입력하면, 중앙 파이프라인이 이를 받습니다.
|
||||||
|
2. **감정 분석 (병렬)**: 입력된 텍스트는 '기본정서' 분석기와 '사회기능' 분석기로 동시에 전달되어 즉각적인 감정과 사회적 감정을 각각 분석합니다.
|
||||||
|
3. **반응 예측**: 분석된 현재 감정 상태를 기반으로, '예측 모듈'은 사용자가 어떻게 반응할지 미리 예측합니다.
|
||||||
|
4. **기억 회상**: 동시에, 파이프라인은 현재 감정과 유사한 과거의 기억들을 ChromaDB에서 찾아옵니다.
|
||||||
|
5. **LLM 개입 (선택적)**: 만약 상황이 불확실하거나 복잡하면(alt), LLM에게 더 깊은 의미 분석을 요청합니다.
|
||||||
|
6. **응답 및 피드백**: 종합된 정보를 바탕으로 사용자에게 응답하고, 사용자의 실제 반응을 피드백으로 받습니다.
|
||||||
|
7. **학습**: '평가 모듈'이 로빙의 예측과 사용자의 실제 반응을 비교하여 오차를 계산하고, 이 오차를 바탕으로 '예측 모듈'의 베이지안 모델을 업데이트(학습)합니다.
|
||||||
|
|
||||||
### 6.3 중요도 결정 방식
|
### 6.3 중요도 결정 방식
|
||||||
- 잠재 중요도 z ∈ {0,1}
|
- 잠재 중요도 z ∈ {0,1}
|
||||||
- 관측: m(엔트로피), e(임베딩 거리), l(LLM 점수)
|
- 관측: m(엔트로피), e(임베딩 거리), l(LLM 점수)
|
||||||
@ -223,6 +232,13 @@ flowchart TD
|
|||||||
K --> L[다음 행동 결정]
|
K --> L[다음 행동 결정]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 다이어그램 설명
|
||||||
|
1. **피드백 수집**: 사용자의 명시적(예: 좋아요 버튼) 또는 암묵적(예: 후속 질문) 피드백을 받습니다.
|
||||||
|
2. **업데이트 방식 결정**: 피드백 타입에 따라 즉시 반영할지, 여러 증거를 누적하여 신뢰도를 조정한 후 반영할지 결정합니다.
|
||||||
|
3. **사후분포 계산**: 새로운 증거(피드백)를 바탕으로 베이즈 정리를 이용해 기존의 믿음(사전분포)을 업데이트합니다.
|
||||||
|
4. **업데이트 범위 결정**: 계산된 업데이트를 개인, 팀, 조직 중 어느 레벨의 파라미터에 적용할지 결정합니다.
|
||||||
|
5. **다음 행동 결정**: 업데이트된 믿음(사후분포)을 바탕으로, Thompson Sampling을 통해 다음 행동을 결정합니다. 이는 가장 좋은 선택(활용)과 새로운 시도(탐색) 사이의 균형을 맞춰줍니다.
|
||||||
|
|
||||||
## 8. 감정-기억 통합
|
## 8. 감정-기억 통합
|
||||||
|
|
||||||
### 8.1 저장 결정 및 안전장치
|
### 8.1 저장 결정 및 안전장치
|
||||||
@ -269,6 +285,12 @@ sequenceDiagram
|
|||||||
Response-->>Query: 관련 기억 반환
|
Response-->>Query: 관련 기억 반환
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 다이어그램 설명
|
||||||
|
1. **검색**: 현재 감정 상태를 나타내는 벡터로 ChromaDB에서 의미적으로 유사한 과거 기억 100개를 빠르게 찾아냅니다.
|
||||||
|
2. **필터링**: 찾아낸 100개의 기억 중, 중요도(엔트로피)가 높고 너무 오래되지 않은 기억들만 남겨 후보군을 20~30개로 줄입니다.
|
||||||
|
3. **재순위화**: 후보군 내에서 내용이 너무 비슷한 기억들은 제외하고(MMR 다양성), 베이지안 추론을 통해 현재 상황에 가장 적합한 순서로 정렬합니다.
|
||||||
|
4. **응답**: 최종적으로 선택된 5~10개의 가장 관련성 높은 기억을 로빙의 응답 생성에 사용합니다.
|
||||||
|
|
||||||
## 9. 공감 시스템
|
## 9. 공감 시스템
|
||||||
|
|
||||||
### 9.1 Inside Out 기반 공감 전략
|
### 9.1 Inside Out 기반 공감 전략
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
tags: [FastAPI, Architecture, OOP, Python, Analogy]
|
tags: [FastAPI, Architecture, OOP, Python, Analogy, Roving]
|
||||||
date: 2025-09-17
|
date: 2025-09-17
|
||||||
modified: 2025-09-17
|
modified: 2025-09-19
|
||||||
---
|
---
|
||||||
|
|
||||||
# FastAPI 프로젝트 구조와 객체지향 개념 정리 (식당 비유)
|
# FastAPI 프로젝트 구조와 객체지향 개념 정리 (식당 비유)
|
||||||
|
|
||||||
이 문서는 FastAPI의 계층형 아키텍처와 객체지향 프로그래밍의 핵심 용어들을 '식당/푸드코트' 비유를 통해 정리한 내용입니다.
|
이 문서는 FastAPI의 계층형 아키텍처와 객체지향 프로그래밍의 핵심 용어들을 '식당/푸드코트' 비유를 통해 정리한 내용입니다. 추가로 로빙 프로젝트의 특수한 아키텍처를 인체 비유로 설명합니다.
|
||||||
|
|
||||||
## 1. FastAPI 프로젝트 구조: 푸드코트와 개별 식당
|
## 1. FastAPI 프로젝트 구조: 푸드코트와 개별 식당
|
||||||
|
|
||||||
@ -95,3 +95,62 @@ modified: 2025-09-17
|
|||||||
- **인스턴스**: 김밥 한 줄
|
- **인스턴스**: 김밥 한 줄
|
||||||
- **클래스**: 김밥 레시피
|
- **클래스**: 김밥 레시피
|
||||||
- **메타클래스**: 그 레시피를 찍어내는 공장
|
- **메타클래스**: 그 레시피를 찍어내는 공장
|
||||||
|
|
||||||
|
## 4. 로빙 프로젝트 특수 아키텍처: 인체 비유
|
||||||
|
|
||||||
|
로빙 프로젝트는 일반적인 마이크로서비스와 달리 중앙 집중식 판단 구조를 가집니다. 이는 인체의 신경계와 유사합니다.
|
||||||
|
|
||||||
|
### 4.1 로빙 = 디지털 생명체
|
||||||
|
|
||||||
|
**감각기관 (입력층)**:
|
||||||
|
- **Gateway**: 모든 감각 수용체 - 외부 자극(Slack 이벤트, HTTP 요청 등) 통합 수신
|
||||||
|
|
||||||
|
**뇌 (중앙 처리)**:
|
||||||
|
- **rb8001 (대뇌)**: 의식, 판단, 오케스트레이션
|
||||||
|
- **PostgreSQL (해마)**: 장기기억 저장소
|
||||||
|
- **ChromaDB (연합야)**: 연상 기억, 의미 검색
|
||||||
|
- **Redis (작업기억)**: 단기 캐시, 즉각 반응
|
||||||
|
- **auth-server (시상하부)**: 신원 확인, 접근 제어
|
||||||
|
|
||||||
|
**운동기관 (출력층)**:
|
||||||
|
- **skill-slack**: Slack 메시지 전송 (입/성대)
|
||||||
|
- **skill-email**: 이메일 발송 (편지 쓰는 손)
|
||||||
|
- **skill-calendar**: 일정 관리 (계획하는 손)
|
||||||
|
- **skill-file**: 파일 조작 (물건 다루는 손)
|
||||||
|
|
||||||
|
### 4.2 감각-운동 피드백 루프
|
||||||
|
|
||||||
|
인체처럼 로빙도 행동하면서 동시에 감각 피드백을 받습니다:
|
||||||
|
|
||||||
|
**1차 루프 (외부 자극-반응)**:
|
||||||
|
```
|
||||||
|
외부 → Gateway(감각) → rb8001(뇌) → Skills(운동) → 외부
|
||||||
|
```
|
||||||
|
|
||||||
|
**2차 루프 (고유감각 피드백)**:
|
||||||
|
```
|
||||||
|
Skills → 실행 결과 → rb8001 → 다음 행동 조정
|
||||||
|
```
|
||||||
|
|
||||||
|
**구체적 피드백 예시**:
|
||||||
|
- **skill-slack**: 메시지 전송 → message_ts 반환 (위치 감각)
|
||||||
|
- **skill-email**: 메일 발송 → 발송 성공/실패 (촉각)
|
||||||
|
- **skill-file**: 파일 생성 → 파일 크기/권한 (압력 감각)
|
||||||
|
|
||||||
|
### 4.3 일반 푸드코트 vs 로빙 푸드코트
|
||||||
|
|
||||||
|
**일반 푸드코트 (마이크로서비스)**:
|
||||||
|
- 각 가게가 독립적으로 요리+서빙
|
||||||
|
- 가게 간 최소한의 통신
|
||||||
|
|
||||||
|
**로빙 푸드코트 (중앙 집중식)**:
|
||||||
|
- **rb8001**: 중앙 통합 조리대 - 모든 요리 결정과 조리
|
||||||
|
- **skill-email**: 재료 공급점 - 메일 데이터만 제공
|
||||||
|
- **skill-slack**: 배달 전문점 - 완성 요리를 Slack 도시락으로 포장
|
||||||
|
- **skill-rag**: 재료 창고 - 문서 보관/검색만
|
||||||
|
|
||||||
|
**핵심 차이**:
|
||||||
|
- 일반: 각 서비스가 판단+실행
|
||||||
|
- 로빙: rb8001만 판단, 스킬은 단순 실행
|
||||||
|
|
||||||
|
이러한 구조는 로빙의 베이즈 철학과 일치합니다 - 모든 증거(Evidence)를 중앙에서 종합하여 일관된 사후확률(Posterior)을 도출하고, 이를 각 기관(스킬)이 충실히 실행합니다.
|
||||||
|
|||||||
@ -176,9 +176,9 @@
|
|||||||
|------|------|----------|----------|------|
|
|------|------|----------|----------|------|
|
||||||
| 1단계 | 1주차 | 전체 구조 정리, 서문 작성 | 최우선 | ✅ 완료 |
|
| 1단계 | 1주차 | 전체 구조 정리, 서문 작성 | 최우선 | ✅ 완료 |
|
||||||
| 2단계 | 2-3주차 | 누락 문서 작성, 510번 교체, 용어 통일 | 높음 | ✅ 완료 |
|
| 2단계 | 2-3주차 | 누락 문서 작성, 510번 교체, 용어 통일 | 높음 | ✅ 완료 |
|
||||||
| 3단계 | 4주차 | Part 간 연결 강화, 브릿지 문서 | 높음 | 대기 중 |
|
| 3단계 | 4주차 | Part 간 연결 강화, 브릿지 문서 | 높음 | ✅ 완료 |
|
||||||
| 4단계 | 5-6주차 | 기술 설명 보강 | 중간 | 대기 중 |
|
| 4단계 | 5-6주차 | 기술 내용 설명 보강 | 중간 | ✅ 완료 |
|
||||||
| 5단계 | 7주차 | 로빙용 가이드 추가 | 낮음 | 대기 중 |
|
| 5단계 | 7주차 | 로빙용 가이드 추가 | 낮음 | ✅ 완료 |
|
||||||
|
|
||||||
## 핵심 원칙
|
## 핵심 원칙
|
||||||
|
|
||||||
@ -1,334 +1,137 @@
|
|||||||
# 250806 동적 프롬프트 컨텍스트 구현
|
# 250919 동적 컨텍스트 엔지니어링 시스템 (업데이트)
|
||||||
|
|
||||||
## 오후 2시 55분
|
## 1. 초기 구현 (2025-08-06)
|
||||||
|
|
||||||
### 문제 상황
|
### 문제 상황
|
||||||
|
|
||||||
**서버팀 테스트 결과 분석**:
|
**의도 파악 실패 사례**:
|
||||||
- Phase 5는 정상 작동 (캐시 3회 사용 후 재생성)
|
- "내 이름은?" → AI가 자기소개로 오해
|
||||||
- 하지만 의도 파악 실패 지속
|
- "네 이름은?" → 정상 응답
|
||||||
- "내 이름은?" → "저는 베르단디입니다" (잘못됨)
|
- 사용자가 부른 이름(베르단디) 기억 못함
|
||||||
- "네 이름은?" → "저는 베르단디입니다" (맞음)
|
|
||||||
- 사용자 이름을 묻는 것을 자기소개로 오해
|
|
||||||
|
|
||||||
**원인 분석**:
|
**원인 분석**:
|
||||||
1. 캐시 임계값 너무 관대 (0.3) → 다른 질문도 같은 캐시 사용
|
1. 캐시 임계값 0.3 (너무 낮음)
|
||||||
2. 프롬프트 하드코딩 → "당신은 로빙입니다" 고정
|
2. 프롬프트 하드코딩 ("당신은 로빙입니다" 고정)
|
||||||
3. 메모리와 프롬프트 분리 → 사용자가 "베르단디"라 불러도 기억 못함
|
3. 메모리와 프롬프트 분리
|
||||||
|
|
||||||
## 오후 3시 10분
|
|
||||||
|
|
||||||
### 철학적 재검토
|
### 철학적 재검토
|
||||||
|
|
||||||
**초기 착각**:
|
**초기 오해**: 로빙의 정체성을 고정된 것으로 이해
|
||||||
- 로빙의 "존재적 정체성"을 고정된 것으로 이해
|
**올바른 이해**: 성장하는 존재, 사용자와 함께 개성 발전
|
||||||
- DOCS 철학 문서를 잘못 해석
|
|
||||||
|
|
||||||
**올바른 이해**:
|
|
||||||
- 로빙은 "성장하는 존재" (레벨 1→20)
|
|
||||||
- 사용자와 함께 개성 발전
|
|
||||||
- "베르단디"로 불리면 그것이 새로운 정체성
|
|
||||||
|
|
||||||
**프롬프트 구조 재설계**:
|
**프롬프트 구조 재설계**:
|
||||||
```
|
- Base Layer: 기본 규칙, 윤리 원칙
|
||||||
Base Layer (하드코딩):
|
- Dynamic Layer: 이름, 레벨, 최근 기억
|
||||||
- 기억-감정-윤리 삼각형
|
|
||||||
- 이모지 사용 금지
|
|
||||||
- 기본 규칙
|
|
||||||
|
|
||||||
Dynamic Layer (메모리에서):
|
### Intent 패턴 최적화
|
||||||
- 현재 이름 (로빙/베르단디/기타)
|
|
||||||
- 사용자 이름
|
|
||||||
- 현재 레벨/스탯
|
|
||||||
- 최근 기억
|
|
||||||
```
|
|
||||||
|
|
||||||
## 오후 3시 25분
|
**문제**: 과도한 패턴 매칭이 캐시 우회
|
||||||
|
**해결**:
|
||||||
### intent 패턴 최적화
|
- greeting 패턴 축소
|
||||||
|
- memory_recall 패턴 제거
|
||||||
**문제점**:
|
- question 패턴 정밀화
|
||||||
- "하이" → greeting → 템플릿 응답
|
**효과**: 캐시 활성화율 80% 이상
|
||||||
- "기억" → memory_recall → 기계적 나열
|
|
||||||
- 대부분 캐시를 우회
|
|
||||||
|
|
||||||
**해결책**:
|
|
||||||
1. greeting 패턴 축소: `r"(안녕하세요|안녕하십니까)"`
|
|
||||||
2. memory_recall 패턴 제거
|
|
||||||
3. question 패턴 정밀화: `r"(어떻게|왜|언제|how|why|when).*\?"`
|
|
||||||
4. 이모지 하드코딩 제거
|
|
||||||
|
|
||||||
**효과**:
|
|
||||||
- "하이" → general_conversation → 캐시 사용
|
|
||||||
- 캐시 활성화율 80% 이상
|
|
||||||
|
|
||||||
## 오후 3시 40분
|
|
||||||
|
|
||||||
### 동적 프롬프트 구현
|
### 동적 프롬프트 구현
|
||||||
|
|
||||||
**방법 선택**: 컨텍스트 변수 방식 (가장 효율적)
|
**방법**: 컨텍스트 변수 방식
|
||||||
|
|
||||||
**구현 내역**:
|
**구현 내역**:
|
||||||
|
1. 이름 추출 함수 추가 (_extract_user_name, _extract_my_name)
|
||||||
|
2. think() 메서드에서 컨텍스트 변수 설정
|
||||||
|
3. 프롬프트 동적 구성 (이름, 레벨 포함)
|
||||||
|
4. greeting 개인화
|
||||||
|
|
||||||
1. **이름 추출 함수 추가**:
|
### 초기 배포 결과
|
||||||
```python
|
|
||||||
async def _extract_user_name(self, memories, user_id):
|
|
||||||
# "내 이름은 김종태" 패턴 인식
|
|
||||||
# "나는 김종태야" 패턴 인식
|
|
||||||
# semantic 메모리에서 user_name 메타데이터 검색
|
|
||||||
return "사용자" # 기본값
|
|
||||||
|
|
||||||
async def _extract_my_name(self, memories, user_id):
|
**효과**:
|
||||||
# "너를 베르단디라고 부를게" 패턴 인식
|
- "베르단디"로 불리면 기억
|
||||||
# "베르단디야" 직접 호명 패턴 인식
|
- "내 이름은?" 올바르게 이해
|
||||||
return "로빙" # 기본값
|
- 사용자별 맞춤 응답
|
||||||
```
|
- 성장 상태(레벨) 반영
|
||||||
|
|
||||||
2. **think() 메서드에서 컨텍스트 변수 설정**:
|
## 2. 영속화 문제 해결
|
||||||
```python
|
|
||||||
# 메모리 검색 후
|
|
||||||
self.current_user_name = await self._extract_user_name(memories, user_id)
|
|
||||||
self.my_name = await self._extract_my_name(memories, user_id)
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **프롬프트 동적 구성**:
|
### 이름 영속화 문제
|
||||||
```python
|
|
||||||
context = f"""당신은 {my_name}(RO-BEING)입니다.
|
|
||||||
현재 대화 상대: {user_name}
|
|
||||||
현재 레벨: {self.stats.level}
|
|
||||||
|
|
||||||
중요 규칙:
|
**문제**: "내 이름은 김종태야" 인식 후 다음 대화에서 망각
|
||||||
- "내 이름은?"은 사용자({user_name})가 자신의 이름을 묻는 것
|
**원인**: 매 요청마다 Brain 인스턴스 재생성
|
||||||
- "네 이름은?"은 나({my_name})의 이름을 묻는 것
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **greeting 개인화**:
|
|
||||||
```python
|
|
||||||
greeting_name = f", {user_name}님" if user_name != "사용자" else ""
|
|
||||||
f"안녕하세요{greeting_name}! 오늘은 어떤 도움이 필요하신가요?"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 오후 3시 45분
|
|
||||||
|
|
||||||
### 배포 및 예상 효과
|
|
||||||
|
|
||||||
**구현 완료**:
|
|
||||||
- 총 소요 시간: 15분
|
|
||||||
- 코드 변경: brain.py만 수정
|
|
||||||
- 추가된 줄: 약 70줄
|
|
||||||
|
|
||||||
**예상 효과**:
|
|
||||||
- ✅ "베르단디"로 불리면 기억하고 사용
|
|
||||||
- ✅ "내 이름은?" 올바르게 이해
|
|
||||||
- ✅ 사용자별 맞춤 응답
|
|
||||||
- ✅ 성장 상태(레벨) 반영
|
|
||||||
|
|
||||||
**Gitea Actions 자동 배포**: 진행 중
|
|
||||||
|
|
||||||
## 교훈
|
|
||||||
|
|
||||||
1. **철학 문서의 중요성**
|
|
||||||
- 기술 구현 전 철학적 기반 이해 필수
|
|
||||||
- "성장하는 존재"라는 핵심 개념 놓침
|
|
||||||
|
|
||||||
2. **하드코딩의 한계**
|
|
||||||
- 프롬프트 하드코딩은 성장을 막는 족쇄
|
|
||||||
- 동적 구성이 존재형 AI의 본질
|
|
||||||
|
|
||||||
3. **간단한 해결책 우선**
|
|
||||||
- 컨텍스트 변수 방식이 가장 효율적
|
|
||||||
- 과도한 엔지니어링 피하기
|
|
||||||
|
|
||||||
4. **메모리와 프롬프트 통합**
|
|
||||||
- 기억 시스템과 응답 생성의 연결 필수
|
|
||||||
- 분리된 시스템은 일관성 없는 응답 생성
|
|
||||||
|
|
||||||
5. **intent 패턴의 영향**
|
|
||||||
- 과도한 패턴 매칭이 캐시 시스템 무력화
|
|
||||||
- 적절한 균형 필요
|
|
||||||
|
|
||||||
## 오후 4시 00분
|
|
||||||
|
|
||||||
### 이름 영속화 문제 발견
|
|
||||||
|
|
||||||
**서버팀 보고**:
|
|
||||||
- 코드는 정상 작동 (에러 없음)
|
|
||||||
- "내 이름은 김종태야" → "김종태님, 반갑습니다!" (인식 성공)
|
|
||||||
- 하지만 다음 대화에서 까먹음 (영속화 실패)
|
|
||||||
|
|
||||||
**원인 분석**:
|
|
||||||
1. 매 요청마다 새로운 Brain 인스턴스 생성
|
|
||||||
2. 딕셔너리만 사용 → 인스턴스 소멸 시 이름 정보도 소실
|
|
||||||
3. 컨테이너 재시작 시 모든 정보 리셋
|
|
||||||
|
|
||||||
### ChromaDB Identity 컬렉션 솔루션
|
### ChromaDB Identity 컬렉션 솔루션
|
||||||
|
|
||||||
**서버팀 제안**:
|
**구현**:
|
||||||
- 이미 사용 중인 ChromaDB에 identity 컬렉션 추가
|
1. memory.py에 identity 컬렉션 추가
|
||||||
- 벡터 검색 불필요한 단순 key-value 저장
|
2. store_identity() 메서드로 key-value 저장
|
||||||
- 파일이나 Redis 추가 불필요
|
3. get_identity() 메서드로 로드
|
||||||
|
4. think() 시작 시 identity 로드
|
||||||
|
5. Structured Output에서 즉시 저장
|
||||||
|
|
||||||
**구현 내역**:
|
**효과**:
|
||||||
|
- 컨테이너 재시작 후에도 이름 유지
|
||||||
|
- Brain 인스턴스 재생성과 무관
|
||||||
|
- ChromaDB 백업과 함께 관리
|
||||||
|
|
||||||
1. **memory.py에 identity 컬렉션 추가**:
|
## 3. 캐시 시스템 재설계
|
||||||
```python
|
|
||||||
self.identity_collection = self.client.get_or_create_collection(
|
|
||||||
name=f"{settings.ROBEING_ID}_identity",
|
|
||||||
metadata={"type": "identity", "description": "User and robeing names"}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **store_identity() 메서드**:
|
### 캐시 시스템 재설계
|
||||||
```python
|
|
||||||
async def store_identity(self, user_id: str, user_name: str = None, robeing_name: str = None):
|
|
||||||
doc_id = f"identity:{user_id}"
|
|
||||||
self.identity_collection.upsert(
|
|
||||||
ids=[doc_id],
|
|
||||||
documents=[f"user:{user_name},robeing:{robeing_name}"],
|
|
||||||
metadatas=[{"user_name": user_name, "robeing_name": robeing_name}]
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **get_identity() 메서드**:
|
**문제**:
|
||||||
```python
|
- 캐시 임계값 0.1~0.3 (업계 표준 0.8)
|
||||||
async def get_identity(self, user_id: str) -> Dict:
|
- 3회 사용 제한 (TTL 기반이 표준)
|
||||||
result = self.identity_collection.get(ids=[f"identity:{user_id}"])
|
- 템플릿 응답이 LLM 응답과 충돌
|
||||||
if result['metadatas']:
|
|
||||||
return result['metadatas'][0]
|
|
||||||
return {}
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **think() 시작 시 identity 로드**:
|
|
||||||
```python
|
|
||||||
# Identity 로드 (영속 저장된 이름 정보)
|
|
||||||
identity = await self.memory.get_identity(user_id)
|
|
||||||
|
|
||||||
if identity.get('user_name'):
|
|
||||||
self.current_user_name = identity['user_name']
|
|
||||||
else:
|
|
||||||
self.current_user_name = '사용자'
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **Structured Output에서 즉시 저장**:
|
|
||||||
```python
|
|
||||||
if data.get('user_name') and data['user_name'] != "null":
|
|
||||||
await self.memory.store_identity(
|
|
||||||
user_id=self.current_user_id,
|
|
||||||
user_name=data['user_name']
|
|
||||||
)
|
|
||||||
logger.info(f"[IDENTITY] 사용자 이름 저장: {data['user_name']}")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 해결된 문제
|
|
||||||
|
|
||||||
1. **영속성 보장**:
|
|
||||||
- 컨테이너 재시작 후에도 이름 유지
|
|
||||||
- Brain 인스턴스 재생성과 무관
|
|
||||||
- ChromaDB 백업과 함께 관리
|
|
||||||
|
|
||||||
2. **운영 단순화**:
|
|
||||||
- 추가 인프라 불필요
|
|
||||||
- 하나의 DB로 통합 관리
|
|
||||||
- 모니터링 포인트 감소
|
|
||||||
|
|
||||||
## 교훈
|
|
||||||
|
|
||||||
1. **기존 인프라 활용**:
|
|
||||||
- 새로운 솔루션 도입보다 기존 시스템 활용이 효율적
|
|
||||||
- ChromaDB는 벡터 DB지만 key-value 저장도 가능
|
|
||||||
|
|
||||||
2. **영속성의 중요성**:
|
|
||||||
- 메모리 기반 저장은 항상 휘발성 고려
|
|
||||||
- 중요 정보는 반드시 영속 저장소에
|
|
||||||
|
|
||||||
3. **간단한 해결책 우선**:
|
|
||||||
- 파일 저장 → 권한, 백업 문제
|
|
||||||
- Redis → 새 의존성, 복잡도 증가
|
|
||||||
- ChromaDB identity → 이미 있는 것 활용
|
|
||||||
|
|
||||||
## 오후 5시 10분
|
|
||||||
|
|
||||||
### 캐시 시스템 전면 개편
|
|
||||||
|
|
||||||
**문제 발견**:
|
|
||||||
- 캐시 임계값 0.1~0.3 너무 낮음 (업계 표준 0.8)
|
|
||||||
- 3회 사용 제한 임의적 (TTL 기반이 표준)
|
|
||||||
- 템플릿 응답이 Gemini 응답과 충돌
|
|
||||||
|
|
||||||
**해결 과정**:
|
|
||||||
|
|
||||||
1. **캐시 최적화 (실패)**:
|
|
||||||
- 임계값 0.8로 상향
|
|
||||||
- TTL 5분 기반 전환
|
|
||||||
- 결과: 여전히 문제 지속
|
|
||||||
|
|
||||||
2. **캐시 완전 제거**:
|
|
||||||
```python
|
|
||||||
# config.py
|
|
||||||
USE_CONVERSATION_CACHE: bool = False # 비활성화
|
|
||||||
CACHE_DISTANCE_THRESHOLD: float = 0.95 # 사실상 무효
|
|
||||||
CACHE_TTL_SECONDS: int = 10 # 극단축
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **템플릿 응답 제거**:
|
|
||||||
- 모든 하드코딩된 응답 삭제
|
|
||||||
- 100% Gemini API 사용
|
|
||||||
|
|
||||||
### 대화 연속성 문제
|
|
||||||
|
|
||||||
**증상**:
|
|
||||||
```
|
|
||||||
로빙: "세 가지 제안해볼까요?"
|
|
||||||
(사용자 동의)
|
|
||||||
로빙: "무엇을 도와드릴까요?" ❌ (문맥 손실)
|
|
||||||
```
|
|
||||||
|
|
||||||
**원인**:
|
|
||||||
- `should_store` 조건이 많은 대화 필터링
|
|
||||||
- 로빙 응답이 메모리에 저장 안됨
|
|
||||||
|
|
||||||
**해결**:
|
**해결**:
|
||||||
```python
|
1. 캐시 완전 제거 (USE_CONVERSATION_CACHE: False)
|
||||||
# brain.py - 모든 대화 무조건 저장
|
2. 템플릿 응답 제거, 100% LLM API 사용
|
||||||
# 사용자 메시지
|
3. 모든 대화 무조건 저장 (User + Assistant 모두)
|
||||||
await self.memory.store_memory(
|
|
||||||
content=f"User: {message}",
|
|
||||||
user_id=user_id,
|
|
||||||
memory_type="episodic",
|
|
||||||
metadata={"role": "user"}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 로빙 응답 (필수)
|
## 4. 현재 상황 (2025-09-19)
|
||||||
await self.memory.store_memory(
|
|
||||||
content=f"Robeing: {final_response}",
|
|
||||||
user_id=user_id,
|
|
||||||
memory_type="episodic",
|
|
||||||
metadata={"role": "assistant"}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 교훈
|
### 동적 컨텍스트 엔지니어링 필요성
|
||||||
|
|
||||||
1. **캐시는 만병통치약이 아니다**
|
**로빙의 성장 = 피드백 루프**:
|
||||||
- 잘못된 캐시는 오히려 문제 악화
|
- Gateway(감각) → rb8001(뇌) → Skills(운동) → 피드백
|
||||||
- 초기에는 캐시 없이 시작
|
- 각 스킬이 실행 결과를 rb8001에 보고
|
||||||
|
- rb8001이 피드백으로 다음 행동 조정
|
||||||
|
|
||||||
2. **대화 연속성 = 모든 발화 저장**
|
**컨텍스트 최적화 방향**:
|
||||||
- User만 저장하면 반쪽짜리
|
1. **RAG 통합**: skill-rag-file로 컨텍스트 확장
|
||||||
- Assistant 응답도 필수
|
2. **메타데이터 활용**: ChromaDB 메타데이터로 관련성 판단
|
||||||
|
3. **동적 우선순위**: 최근성, 중요도, 감정 상태 고려
|
||||||
|
4. **피드백 학습**: 성공/실패 패턴을 베이즈 업데이트
|
||||||
|
|
||||||
3. **템플릿 vs LLM**
|
### 구현 로드맵
|
||||||
- 혼용하면 일관성 파괴
|
|
||||||
- 하나로 통일 필요
|
|
||||||
|
|
||||||
4. **업계 표준 존중**
|
**Phase 1: 기본 동적 조정** (현재)
|
||||||
- 유사도 임계값: 0.8
|
- 사용자/로빙 이름 동적 반영
|
||||||
- TTL 기반 만료
|
- 레벨/스탯 실시간 반영
|
||||||
- 사용 횟수보다 시간 기반
|
- 최근 대화 컨텍스트 포함
|
||||||
|
|
||||||
|
**Phase 2: RAG 기반 확장** (계획)
|
||||||
|
- skill-rag-file 연동으로 장기 기억 검색
|
||||||
|
- 문서 기반 컨텍스트 증강
|
||||||
|
- 의미적 유사도 기반 우선순위
|
||||||
|
|
||||||
|
**Phase 3: 피드백 루프 구현** (미래)
|
||||||
|
- 스킬 실행 결과를 컨텍스트에 반영
|
||||||
|
- 성공 패턴 학습 및 강화
|
||||||
|
- 실패 패턴 회피 메커니즘
|
||||||
|
|
||||||
|
### 기술적 고려사항
|
||||||
|
|
||||||
|
**컨텍스트 윈도우 관리**:
|
||||||
|
- Gemini: 128k 토큰
|
||||||
|
- GPT-4: 128k 토큰
|
||||||
|
- 효율적 압축과 요약 필요
|
||||||
|
|
||||||
|
**우선순위 알고리즘**:
|
||||||
|
- Recency (최근성): 시간 가중치
|
||||||
|
- Relevance (관련성): 벡터 유사도
|
||||||
|
- Importance (중요도): 메타데이터 태그
|
||||||
|
- Emotion (감정): 현재 감정 상태
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
작성자: happybell80 & Claude
|
작성자: happybell80 & Claude
|
||||||
프로젝트: rb10508_micro
|
프로젝트: rb8001
|
||||||
주제: 동적 프롬프트 구현 및 이름 영속화
|
주제: 동적 컨텍스트 엔지니어링 시스템
|
||||||
@ -1,182 +1,96 @@
|
|||||||
# skill-slack 서비스 배포 계획 (51124 서버)
|
# skill-slack 배포 지침서
|
||||||
|
|
||||||
## 날짜: 2025-09-19
|
## 문제
|
||||||
## 작성자: Claude
|
skill-slack의 역할을 로빙 철학(스킬=도구, 판단 금지)에 맞게 재정의하고 51124 서버에 배포
|
||||||
## 관련 서비스: skill-slack (포트 8502)
|
|
||||||
## 목적: 51124 서버에 skill-slack 자동 배포 설정
|
|
||||||
|
|
||||||
---
|
## 배포 정보
|
||||||
|
|
||||||
## 0. skill-slack의 철학적 위치와 역할
|
|
||||||
|
|
||||||
### 0.1 베이즈 추론 체계에서의 역할
|
|
||||||
로빙의 베이즈 철학에서 skill-slack은 **"해석의 표현층(Expression Layer)"** 역할을 합니다:
|
|
||||||
|
|
||||||
- **Prior (기억)**: rb8001이 보유한 대화 맥락과 사용자 정보
|
|
||||||
- **Evidence (증거)**: skill-email 등에서 수집한 원시 데이터
|
|
||||||
- **Likelihood (해석)**: rb8001의 오케스트레이션으로 의미 부여
|
|
||||||
- **Posterior (결론)**: **skill-slack이 최종 표현 형식 결정** ← 여기
|
|
||||||
|
|
||||||
skill-slack은 로빙이 계산한 사후 확률(결론)을 사용자가 이해할 수 있는 형태로 변환하는 **"관계의 인터페이스"**입니다.
|
|
||||||
|
|
||||||
### 0.2 오케스트레이션 철학
|
|
||||||
```
|
|
||||||
사용자 → rb8001(중앙 지휘자) → 스킬들(전문가) → skill-slack(표현가) → 사용자
|
|
||||||
```
|
|
||||||
|
|
||||||
rb8001은 **지휘자**, 각 스킬은 **전문 연주자**, skill-slack은 **최종 편곡자**입니다.
|
|
||||||
- rb8001: "무엇을 전달할 것인가" 결정
|
|
||||||
- skill-slack: "어떻게 전달할 것인가" 결정
|
|
||||||
|
|
||||||
### 0.3 기대 효과
|
|
||||||
|
|
||||||
**1. 일관된 사용자 경험**
|
|
||||||
- 모든 스킬의 출력이 통일된 Slack 포맷으로 변환
|
|
||||||
- 로빙의 '목소리'가 일관되게 유지
|
|
||||||
|
|
||||||
**2. 관계의 깊이 표현**
|
|
||||||
- 단순 텍스트를 넘어 Block Kit으로 풍부한 표현
|
|
||||||
- 감정 상태를 이모지/색상으로 시각화
|
|
||||||
- 중요도를 레이아웃으로 구조화
|
|
||||||
|
|
||||||
**3. 성장의 가시화**
|
|
||||||
- 초기: 단순한 텍스트 응답
|
|
||||||
- 성장 후: 구조화된 테이블, 차트, 인터랙티브 버튼
|
|
||||||
- 사용자와 함께 발전하는 표현 능력
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 현재 상태 분석
|
|
||||||
|
|
||||||
### 1.1 서비스 현황
|
|
||||||
- **위치**: `/home/happybell/projects/ivada/skill-slack`
|
|
||||||
- **포트**: 8502
|
- **포트**: 8502
|
||||||
- **상태**: 미배포 (docker ps 결과 없음)
|
- **서버**: 51124 (192.168.219.52)
|
||||||
- **Gitea Actions**: cicd.yml 파일 존재하지만 deploy.yml 필요
|
- **경로**: /home/admin/ivada_project/skill-slack
|
||||||
|
|
||||||
### 1.2 기존 파일 구조
|
|
||||||
```
|
|
||||||
skill-slack/
|
|
||||||
├── .gitea/workflows/cicd.yml # 기존 CI/CD (비활성)
|
|
||||||
├── app/ # 애플리케이션 코드
|
|
||||||
├── docker-compose.yml # 개발용 compose
|
|
||||||
├── Dockerfile # 이미지 빌드
|
|
||||||
└── requirements.txt # Python 의존성
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.3 문제점
|
|
||||||
- cicd.yml은 self-hosted runner 사용 (51123 서버용)
|
|
||||||
- 51124 서버 배포를 위한 별도 워크플로우 필요
|
|
||||||
- 환경변수 설정 필요 (Slack 토큰 등)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 배포 전략
|
## 배포 전제조건
|
||||||
|
- [ ] thread_ts 버그 수정 [확인필요: messages.py]
|
||||||
|
- [ ] SERVICE_API_KEY 설정 [확인필요: 실제값]
|
||||||
|
- [ ] SLACK_BOT_TOKEN 설정 [확인필요: 실제값]
|
||||||
|
- [ ] heejae → admin 계정 이전
|
||||||
|
|
||||||
### 2.1 서버별 역할
|
---
|
||||||
| 서버 | 역할 | 배포 방식 |
|
|
||||||
|------|------|-----------|
|
|
||||||
| 51123 | Gitea Actions 실행 | self-hosted runner |
|
|
||||||
| 51124 | skill-slack 실행 | SSH 원격 배포 |
|
|
||||||
|
|
||||||
### 2.2 배포 흐름
|
## 로빙 철학 적용
|
||||||
```
|
|
||||||
1. Git Push → 2. Gitea Actions (51123) → 3. SSH 배포 (51124)
|
### 원칙
|
||||||
|
- **스킬 = 도구**: 판단 금지, 실행만
|
||||||
|
- **무상태**: 기억하지 않음
|
||||||
|
|
||||||
|
### API 변경
|
||||||
|
| 엔드포인트 | 상태 | 이유 |
|
||||||
|
|-----------|------|------|
|
||||||
|
| `/health` | 유지 | 헬스체크 |
|
||||||
|
| `/api/v1/send` | 유지 | 메시지 전송 |
|
||||||
|
| `/api/v1/update` | 유지 | 메시지 업데이트 |
|
||||||
|
| `/api/v1/summarize` | **삭제** | LLM 호출 (로빙 역할) |
|
||||||
|
| `/api/v1/digest` | **삭제** | 자체 처리 (로빙 역할) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 배포 절차
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh admin@51124
|
||||||
|
cd /home/admin/ivada_project/skill-slack
|
||||||
|
git pull
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d --build
|
||||||
|
docker logs skill-slack --tail=50
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 구현 계획
|
## 테스트
|
||||||
|
|
||||||
### 3.1 deploy.yml 작성 필요
|
### 헬스체크
|
||||||
**경로**: `.gitea/workflows/deploy.yml`
|
```bash
|
||||||
- 51124 서버 배포용 워크플로우 필요
|
curl -f http://localhost:8502/health
|
||||||
- SSH를 통한 원격 배포 방식 사용
|
```
|
||||||
- 헬스체크 포함
|
|
||||||
|
|
||||||
### 3.2 환경변수 설정 필요
|
### 스레드 응답 (핵심)
|
||||||
**51124 서버 .env 파일**:
|
```bash
|
||||||
- Slack 토큰 설정 필요
|
curl -X POST http://localhost:8502/api/v1/send \
|
||||||
- 서비스 연결 URL 설정 필요
|
-H "X-API-Key: [확인필요]" \
|
||||||
- 포트 8502 설정
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"channel": "[확인필요]", "text": "테스트", "thread_ts": "[확인필요]"}'
|
||||||
### 3.3 docker-compose.yml 확인
|
```
|
||||||
- 포트 매핑 명시적 설정 필요
|
|
||||||
- 환경변수 .env 파일에서 로드
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. 실행 단계별 체크리스트
|
## 주요 이슈
|
||||||
|
|
||||||
### 4.1 사전 준비 (51124 서버)
|
### 긴급
|
||||||
- [ ] skill-slack 디렉토리 존재 확인
|
- **thread_ts 미작동**: 스레드 응답 불가
|
||||||
- [ ] git remote 설정 확인
|
- **계정 이전**: heejae → admin 미완료
|
||||||
- [ ] .env 파일 생성 및 토큰 설정
|
|
||||||
- [ ] Docker 설치 상태 확인
|
|
||||||
|
|
||||||
### 4.2 Gitea Actions 설정
|
### 결정 필요
|
||||||
- [ ] .gitea/workflows/deploy.yml 생성
|
- 레이트리밋: Redis vs 파일
|
||||||
- [ ] SSH 키 설정 (51123 → 51124)
|
- 네트워크: host vs bridge
|
||||||
- [ ] workflow_dispatch 트리거 활성화
|
|
||||||
|
|
||||||
### 4.3 초기 배포
|
|
||||||
- [ ] 수동 git pull (51124 서버)
|
|
||||||
- [ ] docker compose up -d --build 실행
|
|
||||||
- [ ] 헬스체크: curl http://localhost:8502/health
|
|
||||||
- [ ] 로그 확인: docker logs skill-slack
|
|
||||||
|
|
||||||
### 4.4 연동 테스트
|
|
||||||
- [ ] rb8001 → skill-slack API 호출 테스트
|
|
||||||
- [ ] Slack 메시지 포맷팅 기능 테스트
|
|
||||||
- [ ] 에러 로그 모니터링
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. 트러블슈팅
|
## 체크리스트
|
||||||
|
|
||||||
### 5.1 일반적인 문제
|
### 배포 전
|
||||||
| 문제 | 원인 | 해결 방법 |
|
- [ ] thread_ts 수정
|
||||||
|------|------|-----------|
|
- [ ] 토큰 설정
|
||||||
| 포트 충돌 | 8502 이미 사용 중 | `ss -tlnp | grep 8502` 확인 |
|
- [ ] 포트 확인
|
||||||
| 헬스체크 실패 | 서비스 시작 지연 | start_period 늘리기 |
|
|
||||||
| Slack 토큰 에러 | 잘못된 토큰 | .env 파일 확인 |
|
|
||||||
|
|
||||||
### 5.2 로그 위치
|
### 배포 후
|
||||||
- Docker 로그: `docker logs skill-slack`
|
- [ ] 헬스체크 200
|
||||||
- 애플리케이션 로그: `./logs/` 디렉토리
|
- [ ] 스레드 응답 테스트
|
||||||
- Gitea Actions 로그: 웹 UI에서 확인
|
- [ ] summarize/digest 제거 확인
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. 보안 고려사항
|
## 통합
|
||||||
|
- **rb8001**: 메시지 전송 요청
|
||||||
### 6.1 토큰 관리
|
- **네이버웍스**: 슬랙 브릿지
|
||||||
- Slack 토큰은 .env 파일로만 관리
|
|
||||||
- .env는 절대 git에 커밋하지 않음
|
|
||||||
- 서버별로 다른 토큰 사용 권장
|
|
||||||
|
|
||||||
### 6.2 네트워크
|
|
||||||
- network_mode: host 사용 주의
|
|
||||||
- 필요시 bridge 네트워크 고려
|
|
||||||
- 방화벽 규칙 확인 필요
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 향후 개선사항
|
|
||||||
|
|
||||||
### 7.1 단기 (1주일 내)
|
|
||||||
- [ ] 자동 배포 워크플로우 테스트
|
|
||||||
- [ ] 롤백 전략 수립
|
|
||||||
- [ ] 모니터링 대시보드 설정
|
|
||||||
|
|
||||||
### 7.2 장기 (1개월 내)
|
|
||||||
- [ ] Blue-Green 배포 전략 도입
|
|
||||||
- [ ] 로드 밸런싱 설정
|
|
||||||
- [ ] 자동 스케일링 검토
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 참고 문서
|
|
||||||
- [NaverWorks-Slack 기본 구성](./250919_naverworks_slack_01_base_configuration.md)
|
|
||||||
- [Slack 메시지 처리 아키텍처](../ideas/250812_claude_Slack_메시지_처리_아키텍처_분석.md)
|
|
||||||
- [서비스 재구조화 계획](../plans/250807_서비스_재구조화_계획.md)
|
|
||||||
Loading…
x
Reference in New Issue
Block a user