# rb8001: Slack 문서분석 흐름 개선 + LangGraph 파이프라인 적용

## 개요
- 서비스: `rb8001` (포트 8001), `skill-rag-file` (포트 8508)
- 이슈 묶음: 파일 분석 미반영, 안내문 반복, 이전 문서 혼입, 윤리문구 노출 및 모델 중복 초기화(선행 이슈 포함)
- 일시(KST): 2025-10-21 22:40–23:10

## 증상
- 새 PDF 첨부 후에도 “텍스트를 붙여넣어주세요/분석해 드릴게요” 같은 안내 문구가 반복 노출됨.
- 문서분석 재질의 시 이전 문서(AIdol deck) 내용이 혼입되어 이어받아 응답됨.
- 파일 분석 진행 표시가 없어 사용자 체감상 “멈춤”처럼 보임.

## 원인
1) 파일 처리 비동기 흐름
- Slack 이벤트 수신 후 즉시 LLM 호출, 파일 업로드/추출/인덱싱은 백그라운드로 처리 → 최초 응답이 파일 내용 반영 전 생성.

2) 이벤트 필터 부족
- Slack `message_changed` 등 비텍스트/봇 시스템 이벤트까지 처리 루프로 유입 → 빈 텍스트 기반 LLM 호출로 안내문 반복.

3) 문서 컨텍스트 혼입
- 스레드 기준의 ‘현재 문서’ 고정이 없어 RAG 검색 시 최근/이전 문서 결과가 섞여 들어와 응답 문맥 오염.

4) (선행 이슈) 윤리 설명 노출/모델 중복 초기화
- 사용자 본문에 윤리 경고가 포함되고, Gemini 모델이 중복 초기화되던 문제(별도 문서에 정리)도 함께 개선.

## 조치 (코드)
- LangGraph 파이프라인 도입: 업로드→추출→임베딩/인덱싱→검색을 동기 대기(최대 45초) 후 상위 스니펫을 컨텍스트에 주입.
  - 파일: `rb8001/app/pipelines/langgraph_document.py`
- Slack 진행 표시: context 블록(작은 회색 텍스트)로 “파일 분석 중…” → “로빙 중…” 업데이트, 최종 응답 시 삭제.
  - 파일: `rb8001/app/router/slack_handler.py`
- 이벤트 필터 강화: `message_changed` 등 비텍스트/봇 이벤트 무시, empty 이벤트 무시, 내부 message 정규화.
  - 파일: `rb8001/app/router/slack_handler.py`
- 스레드별 문서 고정: `thread_ts → {team_id, document_ids}` 캐시 저장, 후속 질의 RAG 검색 결과를 해당 문서로 제한.
  - 파일: `rb8001/app/router/thread_doc_cache.py`, `rb8001/app/router/slack_handler.py`
- PDF 추출 품질 개선: 품질 휴리스틱(len, garbage_ratio, unique_chars)로 저품질 텍스트 시 강제 OCR(pytesseract) 후 재청킹/인덱싱, 메타 기록(ocr_used, quality)
  - 파일: `skill-rag-file/app/api/upload.py`, `skill-rag-file/app/services/text_extractor.py`
- 텍스트 직접 조회: `/api/text/{document_id}`로 전체 본문+메타 제공 → rb8001 LangGraph가 업로드 직후 doc_id 고정 후 본문 직접 분석(스니펫은 보조)
  - 파일: `skill-rag-file/app/api/text.py`, `skill-rag-file/app/main.py`
- LangGraph 보강: 업로드 직후 `/api/reindex`(force_ocr)→`/api/text/{doc}`→검색 순으로 대기 처리, 분석은 full‑text 우선
  - 파일: `rb8001/app/pipelines/langgraph_document.py`, `rb8001/app/router/slack_handler.py`
- (선행) 윤리 노출/모델 고정/메모리 위생:
  - 윤리 설명 사용자 비노출, 공정성 오탐 감소, 정적 대안 제거.
  - `gemini-2.5-flash-lite` 단일 모델 고정 및 중복 초기화 제거.
  - 정책성 고정 문구 메모리 저장/컨텍스트/검색에서 필터링.
  - 파일: `app/router/router.py`, `app/services/ethics_constraints_ontology.py`,
    `app/llm/llm_service.py`, `app/skills/startup_news_skill.py`,
    `app/state/conversation_repository.py`, `app/router/message_router.py`, `app/llm/gemini_handler.py`

## 검증 절차
1) PDF 첨부 DM 테스트
- 기대 로그 흐름: 
  - “파일 분석 중…” → “로빙 중…” 진행 메시지 표시
- LangGraph 업로드/인덱싱 완료 후 `[Graph] Injected … snippet(s)` 로그
- PDF 저품질 케이스: 업로드 로그에 `ocr_used: true`, `quality.garbage_ratio` 개선 확인
  - Router result의 content가 문서 스니펫 기반 분석으로 생성

2) 비텍스트/변경 이벤트 무시 확인
- `message_changed` 전송 시 `non_text_subtype_ignored` 기록, LLM 호출 없음.

3) 스레드 문서 고정 확인
- 같은 thread_ts 내 후속 질문에서 RAG 검색 결과가 고정된 `document_id`로 필터됨.

4) 윤리/모델/메모리 위생
- 윤리 설명 본문 비노출, `Gemini API model initialized: gemini-2.5-flash-lite` 1회만 출력, 고정 문구(“중립적이고 …”) 메모리 저장/주입/검색 제외.

## 결과 (요약)
- 새로 첨부한 PDF의 내용이 최초 응답부터 반영.
- 안내 문구 반복/비텍스트 이벤트 유입 해소.
- 동일 스레드에서는 동일 문서에 고정된 RAG 검색으로 문맥 혼입 방지.

## 로그 발췌 (예시)
```
[Graph] uploaded Service_Process_ReverseMountain.pdf -> 062bf6... (15 chunks)
[Graph] Injected 2 snippet(s) from RAG search
Non-text message subtype ignored: message_changed
```

## 교훈 ✍️
- 파일 기반 RAG는 업로드/인덱싱 완료 시점까지 대기하지 않으면 초기 응답이 빈약해진다 → 사용자 경험 저하.
- Slack 이벤트는 `message_changed`/봇/빈 본문 등의 예외 케이스를 강하게 필터링해야 루프/중복 안내를 방지할 수 있다.
- 스레드 단위 문서 고정은 RAG 문맥 혼입을 실질적으로 줄인다.
- 내부 진단/윤리 설명은 사용자 본문과 분리하고, 정책성 고정 문구는 메모리 위생 대상이다.

## 관련 커밋
- `rb8001@397afab` langgraph: sync upload/index/search + thread pinning
- `rb8001@705eef4` slack: ignore non-text/bot events
- `rb8001@23385c2` progress indicators
- `rb8001@d4039f4` memory hygiene filters
- `rb8001@c9ac22f` ethics fairness tuning
- `rb8001@daad1bd` single gemini + hide ethics text

---
문서 규칙: `DOCS/300_architecture/312_writing-principles.md` 준수
