DOCS/journey/troubleshooting/251016_phase2_hybrid_memory_implementation.md
happybell80 0252dd1a7f fix: 51123 서버 IP 주소 업데이트 (성수 이전)
192.168.219.45 → 192.168.0.100 일괄 변경

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:52:26 +09:00

10 KiB

Phase 2: ChromaDB + Neo4j 하이브리드 기억 회상 시스템 구현

현재 문서는 ChromaDB + Neo4j 하이브리드 기억 구조를 구현하던 시점의 이력 문서입니다. 2026-03-17 기준 운영 런타임의 rb8001 기억 회상 핵심 경로는 PostgreSQL memory_vectors, memory_graph_nodes, memory_graph_edges 기준으로 전환됐습니다.

날짜: 2025-10-16 작성자: Claude (51124 서버 전담) 관련 파일:

  • rb8001/app/memory/neo4j_client.py
  • rb8001/app/services/memory_hybrid_retrieval.py
  • rb8001/app/router/memory_ontology.py
  • rb8001/tests/test_memory_hybrid.py

목표

Phase 1 (Coldmail 온톨로지)의 성공을 바탕으로, 기억 시스템에 온톨로지 기반 의미적 연결을 도입하여 "1년 전 비슷한 상황"을 회상할 수 있는 하이브리드 시스템 구축.


구현 내용

1. Neo4j Python 드라이버 연동

파일: app/memory/neo4j_client.py (300줄)

주요 기능:

  • Neo4j Bolt 연결 관리 (uri, auth, database)
  • 사건(Event) 노드 저장 및 관계 생성
  • 그래프 추론 (감정, 결과 관계 가중치 계산)

스키마 (설계안):

(사건:Event) -[:PARTICIPANT]-> (사용자:User)
(사건:Event) -[:HAS_EMOTION]-> (감정:Emotion)
(사건:Event) -[:HAS_RESULT]-> (결과:Result)

관계 가중치:

감정 가중치 결과 가중치
joy (기쁨) 1.2 success 1.5
fear (긴장) 1.3 failure 0.8
sadness (슬픔) 1.5 neutral 1.0
기타 1.0 - -

시간 가중치:

  • 최근 → 1.0
  • 1년 전 → 0.5
  • 공식: max(0.5, 1.0 - (days_diff / 365) * 0.5)

2. ChromaDB + Neo4j 하이브리드 Retrieval

파일: app/services/memory_hybrid_retrieval.py (350줄)

3단계 알고리즘:

Stage 1: ChromaDB 벡터 검색 (빠른 필터링)

# 입력: 사용자 쿼리 (예: "작년 프레젠테이션 때 어떻게 했지?")
# 1. skill-embedding (8515) /embed 호출로 쿼리 임베딩
# 2. ChromaDB 유사도 검색: top_k=20 (충분한 후보)
# 3. 출력: 20개 후보 대화 (벡터 점수 포함)

vector_candidates = await chroma_manager.search_memories(
    query=query,
    n_results=top_k * 4  # 20개
)

# distance를 점수로 변환
for mem in memories:
    mem["vector_score"] = max(0.0, 1.0 - distance)

Stage 2: Neo4j 그래프 추론 (의미적 연결)

# 입력: ChromaDB 후보 20개
# Cypher 쿼리 (자동 실행):
MATCH (e:Event)
WHERE e.id IN $event_ids
OPTIONAL MATCH (e)-[:HAS_EMOTION]->(emotion:Emotion)
OPTIONAL MATCH (e)-[:HAS_RESULT]->(result:Result)
RETURN e.id, e.timestamp, emotion.name, result.type

# 우선순위:
# - [:HAS_RESULT]->(success) 관계 있는 사건 우선 (가중치 1.5배)
# - [:HAS_EMOTION]->(fear) 매칭 시 가중치 1.3배
# - 시간적 근접성: 1년 전 > 2년 전 (거리 역수)

graph_score = emotion_weight * result_weight * time_weight

Stage 3: 점수 통합 및 순위 결정

# 최종 점수 = (벡터 0.4 + 그래프 0.6)
final_score = (vector_score * 0.4) + (normalized_graph * 0.6)

# normalized_graph = min(1.0, graph_score / 2.0)
# graph_score는 0.5 ~ 3.0 범위 → 0~1로 정규화

# 최종 점수순 정렬 후 상위 top_k 반환

3. Memory Ontology API 엔드포인트

파일: app/router/memory_ontology.py (200줄)

API 엔드포인트:

POST /api/v1/memory/event

사건 저장 (ChromaDB + Neo4j 동시 저장)

Request:

{
    "content": "프레젠테이션 성공했다",
    "emotion": "joy",
    "result": "success",
    "metadata": {"category": "work"}
}

Response:

{
    "event_id": "06f45509...",
    "message": "Event stored to ChromaDB and Neo4j"
}

POST /api/v1/memory/recall

쿼리 기반 기억 회상 (3단계 하이브리드)

Request:

{
    "query": "작년 프레젠테이션 때 어떻게 했지?",
    "top_k": 5
}

Response:

{
    "memories": [
        {
            "id": "06f45509...",
            "content": "프레젠테이션을 성공적으로 마쳤다. 청중 반응이 좋았다.",
            "vector_score": 0.85,
            "graph_score": 1.8,
            "final_score": 0.94,
            "emotion": "joy",
            "result": "success",
            "timestamp": "2024-10-16T09:00:00"
        }
    ],
    "query": "작년 프레젠테이션 때 어떻게 했지?",
    "count": 1
}

GET /api/v1/memory/stats

하이브리드 시스템 통계

Response:

{
    "robeing_id": "rb8001",
    "user_id": "test_user_ontology",
    "chroma": {
        "collection_name": "rb8001_test_user_ontology_memory",
        "user_id": "test_user_ontology"
    },
    "neo4j": {
        "connected": true,
        "uri": "neo4j://192.168.0.100:7687",
        "database": "neo4j",
        "event_count": 42
    }
}

4. 테스트 및 검증

파일: tests/test_memory_hybrid.py

테스트 결과 (2025-10-16 15:06):

✅ ChromaDB 저장: 4건
⚠️ Neo4j 연결: 실패 (환경변수 NEO4J_PASSWORD 미설정)
   ChromaDB 단독 모드로 fallback 동작 확인

테스트 시나리오:

  1. 4개 사건 저장 (프레젠테이션, 프로젝트, 회의, 점심)
  2. 3개 쿼리 회상 테스트
    • "프레젠테이션 때 어떻게 했지?"
    • "긴장했을 때 어떻게 대처했나?"
    • "성공한 경험이 뭐가 있지?"

결과:

  • ChromaDB 저장: 정상
  • Neo4j 연결: ⚠️ 실패 (localhost → 192.168.0.100로 변경 필요)
  • Fallback 동작: 정상 (Neo4j 연결 실패 시 ChromaDB 단독 모드)

배포 상태

코드 배포

  • 커밋: 714a132 "Phase 2: ChromaDB + Neo4j 하이브리드 기억 회상 시스템"
  • 날짜: 2025-10-16 15:05
  • 서버: 51124 (192.168.219.52)
  • 컨테이너: rb8001 (재시작 완료)

Dependencies

  • neo4j: 5.27.0 (requirements.txt 추가, Docker 이미지에 설치 완료)

환경변수 설정 필요

.env 파일 추가 필요:

# Neo4j 연결 설정
NEO4J_URI=neo4j://192.168.0.100:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=<비밀번호>

현재 상태:

  • NEO4j_URI: bolt://localhost:7687 (기본값, 잘못됨)
  • NEO4J_PASSWORD: 미설정

수정 후 재시작:

cd /home/admin/ivada_project/rb8001
# .env 파일 수정
docker compose down && docker compose up -d

API 등록

파일: main.py (53-54줄)

# 기억 온톨로지 엔드포인트 등록 (Phase 2)
from app.router.memory_ontology import router as memory_router
app.include_router(memory_router)

FastAPI Swagger UI: http://localhost:8001/docs#/Memory%20Ontology


아키텍처

데이터 흐름

사용자 쿼리 "작년 프레젠테이션 때 어떻게 했지?"
    ↓
[ Stage 1: ChromaDB ]
    벡터 검색 → 20개 후보
    ↓
[ Stage 2: Neo4j ]
    그래프 추론 → 감정/결과 가중치
    ↓
[ Stage 3: 점수 통합 ]
    (벡터 0.4 + 그래프 0.6) → 상위 5개
    ↓
응답: [
    {content: "프레젠테이션 성공", final_score: 0.94, emotion: "joy", result: "success"},
    ...
]

저장 흐름

사건 저장 요청 {content, emotion, result}
    ↓
[ ChromaDB ]
    임베딩 생성 (skill-embedding 8515) → 벡터 저장
    ↓
[ Neo4j ]
    Event 노드 생성
    → [:HAS_EMOTION]→(Emotion)
    → [:HAS_RESULT]→(Result)
    → [:PARTICIPANT]→(User)
    ↓
응답: event_id

성능 지표 (예상)

항목 기존 (ChromaDB 단독) Phase 2 (하이브리드)
검색 속도 ~100ms ~200ms
의미 정확도 70% 90% (목표)
관계 추론 불가능 가능
설명 가능성 낮음 높음 (그래프 경로)

예상 개선 효과:

  • "긴장했을 때" 쿼리 → emotion=fear인 사건 우선 (가중치 1.3배)
  • "성공한 경험" 쿼리 → result=success인 사건 우선 (가중치 1.5배)
  • 시간 가중치로 최근 경험 우선

남은 작업

즉시 필요

  • .env 파일에 Neo4j 환경변수 추가
    • NEO4J_URI=neo4j://192.168.0.100:7687
    • NEO4J_PASSWORD=<비밀번호>
  • Docker 재시작 후 재테스트

Phase 2 완료

  • Neo4j 연결 성공 확인
  • API 엔드포인트 테스트 (curl 또는 Swagger UI)
  • 실제 사용자 데이터로 회상 테스트

Phase 3 (감정-윤리 온톨로지, 1개월)

  • 감정-우도 온톨로지 (7가지 감정)
  • 윤리 제약 온톨로지 (사랑 기반 원칙)
  • HermiT 일관성 검사 자동화
  • 추론 과정 추적 및 설명 생성

교훈

1. Neo4j 이미 설치되어 있음

  • 문제: Phase 2 계획서에서 "Neo4j 설치 필요"로 명시
  • 실제: 51123 서버에 2025.06.2 Community Edition 이미 설치됨 (1개월 26일 가동)
  • 교훈: 사전 조사로 중복 작업 방지

2. Fallback 설계의 중요성

  • 문제: Neo4j 연결 실패 시 전체 시스템 중단 가능성
  • 해결: if not self.driver: return [] 로직으로 ChromaDB 단독 모드 fallback
  • 교훈: 외부 의존성은 항상 fallback 준비

3. 환경변수 기본값의 함정

  • 문제: NEO4J_URI 기본값 localhost:7687 (51123 서버는 192.168.0.100)
  • 교훈: 기본값은 문서에 명시, 배포 시 환경변수 체크리스트 필수

참고

  • Phase 2 계획: DOCS/plans/251016_ontology_coldmail_implementation.md (Phase 2 섹션)
  • Phase 1 검증: DOCS/troubleshooting/251016_ontology_filter_validation.md
  • 설계 원칙: DOCS/200_core_design/225_온톨로지_기반_지식_표현.md
  • Neo4j 설치 정보: 251016_ontology_coldmail_implementation.md (66-82줄)
  • 구현 커밋: rb8001 714a132

다음 단계

  1. .env 파일 수정 (NEO4J_URI, NEO4J_PASSWORD)
  2. Docker 재시작
  3. 테스트 재실행 (python tests/test_memory_hybrid.py)
  4. API 엔드포인트 curl 테스트:
    # 사건 저장
    curl -X POST http://localhost:8001/api/v1/memory/event \
      -H "Content-Type: application/json" \
      -H "X-User-Id: test_user" \
      -d '{"content": "프레젠테이션 성공", "emotion": "joy", "result": "success"}'
    
    # 회상
    curl -X POST http://localhost:8001/api/v1/memory/recall \
      -H "Content-Type: application/json" \
      -H "X-User-Id: test_user" \
      -d '{"query": "성공한 경험", "top_k": 5}'
    
  5. Phase 3 시작 여부 결정