diff --git a/journey/ideas/260312_companyx_내부문서_rag_응답_아이디어.md b/journey/ideas/260312_companyx_내부문서_rag_응답_아이디어.md index a33ccdf..3fff88e 100644 --- a/journey/ideas/260312_companyx_내부문서_rag_응답_아이디어.md +++ b/journey/ideas/260312_companyx_내부문서_rag_응답_아이디어.md @@ -4,7 +4,7 @@ tags: [robeing, companyx, rag, ideas, knowledge-grounding] **상태**: 종결 **종결 문서**: [Company X 내부문서 RAG 근거응답 1차 구현 및 부분 검증](../worklog/260312_companyx_내부문서_rag_근거응답_구현및시나리오검증.md) -**후속 진행**: 시나리오/트러블 동시종결 문제 세트로 확장됨. 임베딩은 `Gemini Embedding 2 / 768d`로 전환 확정. +**후속 진행**: 시나리오/트러블 동시종결 문제 세트로 확장됨. 임베딩은 `Gemini Embedding 2 / 768d` 전환 확정, PostgreSQL 저장 전제 확인. 다만 `char_per_token = 4` 한국어 과소 청킹 리스크와 `Gemini 2 PDF 직접 임베딩` 미구현은 남아 있음. ## 배경 - 로빙은 이미 `skill-rag-file`을 통해 문서를 텍스트로 처리하고 검색할 수 있지만, Company X 내부 NAS 문서를 `Company X 업무 근거`로 안정적으로 활용하는 사용자 경험은 아직 고정되지 않았습니다. @@ -72,13 +72,12 @@ tags: [robeing, companyx, rag, ideas, knowledge-grounding] - 사용자가 후속으로 `근거 문서만 다시 정리해줘`를 요청하면 이전 근거를 재구성할 수 있습니다. ## 검증이 필요한 질문 -1. Company X 내부 문서 중 실제 질문 빈도가 높은 문서군은 무엇인가 -2. RAG 입력은 원본 파일 직접 청킹과 중간 정제 포맷 중 무엇이 더 안정적인가 -3. Company X 소속 사용자 식별은 어떤 인증/권한 신호로 고정할 것인가 -4. 답변 근거를 문서 경로, 제목, 문단 수준 중 어디까지 보여주는 것이 적절한가 -5. 문서 원문 인용 허용 범위와 요약 중심 노출 기준은 어떻게 나눌 것인가 +1. NAS 동기화 경로/시점이 `skill-rag-file` 인덱싱에 어떻게 반영되는가 +2. 한국어 토큰 근사(`char_per_token = 4`)로 인한 과소 청킹 리스크를 어떻게 교정할 것인가 +3. `Gemini Embedding 2`에서 PDF 바이트 직접 임베딩을 도입할지, 현재 텍스트 추출 방식을 유지할지 +4. Company X 소속 사용자 식별은 어떤 인증/권한 신호로 고정할 것인가 +5. 답변 근거를 문서 경로/제목/문단/페이지 중 어디까지 노출할 것인가 6. 문서가 서로 충돌할 때 최신성, 공식성, 승인 상태 중 무엇을 우선할 것인가 -7. 어떤 질문 유형부터 `정답률`보다 `근거 일관성`을 먼저 검증해야 하는가 ## 다음 단계 후보 1. 실제 Company X 파일 유형을 분류하는 `research` diff --git a/journey/plans/260315_companyx_rag_답변합성_시나리오동시종결_계획.md b/journey/plans/260315_companyx_rag_답변합성_시나리오동시종결_계획.md index b9ecb8a..db7aaf3 100644 --- a/journey/plans/260315_companyx_rag_답변합성_시나리오동시종결_계획.md +++ b/journey/plans/260315_companyx_rag_답변합성_시나리오동시종결_계획.md @@ -5,13 +5,16 @@ tags: [plans, companyx, rag, answer-composition, scenario, troubleshooting] # Company X RAG 답변합성 시나리오·트러블 동시종결 계획 **작성일**: 2026-03-15 -**상태**: 구현완료_미검증 (Phase 0 부분확정, Phase 1~4 구현완료, Phase 5 미완료) +**상태**: 부분구현_미검증 (Phase 0 부분확정, Phase 1~4 구현완료, Phase 5A 코드전환완료_배포미완료, Phase 5B 미완료) **검토일**: 2026-03-17 **갱신일**: 2026-03-17 **목표**: Company X 내부문서 근거응답 경로를 `대표 질문 특례 처리`에서 `공통 계약 기반 답변합성` 구조로 바꾸되, 구현 전에 `현재 NAS 문서 운영 상태 + 현재 임베딩 전제`를 다시 닫고 대응 troubleshooting 문서와 scenario 문서를 함께 닫습니다. -> **2026-03-17 코드 검토 결과**: 이 계획의 핵심 구현(Phase 1~4)은 이미 코드에 반영되어 있음. -> Phase 5(테스트 고정 및 Slack 실응답 검증)가 미완료 상태. +> **2026-03-17 코드 검토 결과 (갱신)**: +> - Phase 1~3(구조 분리, 질문 유형 계약, 근거 채택 판정): **구현 완료** +> - Phase 4(LLM 전환 + Pydantic 출력 검증): **구현 완료** — `_call_llm_companyx_grounding()` + `CompanyXRAGOutput` Pydantic 검증. `settings.DEFAULT_LLM_MODEL`(gpt-5-mini) SSOT 참조. +> - Phase 5A(인덱싱 파이프라인 전환): **코드 전환 완료, 배포 미완료** — PDF 6페이지 분할 → Base64 → `embed_items()`, `task_type`(RETRIEVAL_DOCUMENT/QUERY) 전달, `metadata` pass-through 구현. skill-embedding + skill-rag-file 양쪽 반영. +> - Phase 5B: **미완료** — A 배포 후 진행. > 상세 내용: [260317_companyx_grounding_코드검토_및_문서현행화](../worklog/260317_companyx_grounding_코드검토_및_문서현행화.md) ## 관련 문서 @@ -28,12 +31,14 @@ tags: [plans, companyx, rag, answer-composition, scenario, troubleshooting] - 닫힘 흐름: 본 플랜 완료 기준 충족 시 시나리오/리서치/트러블 동시 닫힘 (아이디어 문서는 종결 상태 유지) ## 1. 이번 계획의 결정 -- 현재 코드 기준으로 Phase 1~4 핵심 구현은 반영된 상태로 봅니다. -- 남은 핵심은 `테스트/실응답 검증`과 `현재 NAS 문서 동기화 상태 + 현재 Company X 컬렉션 차원 + 현재 Gemini Embedding 2 전환 상태` 재검증입니다. -- **저장 전제**: 2026-03-17 기준 운영 런타임은 `skill-rag-file`, `rb8001`의 핵심 벡터/검색/그래프 경로를 **PostgreSQL 중심으로 전환**한 상태입니다. +- 현재 코드 기준으로 Phase 1~3(구조 분리, 질문 유형 계약, 근거 채택 판정)은 구현 완료 상태입니다. +- **Phase 4(LLM 전환 + Pydantic 출력 검증)는 구현 완료**입니다. `_call_llm_companyx_grounding()` + `CompanyXRAGOutput` Pydantic 검증 도입. +- 남은 핵심은 `Phase 5A 배포` + `Phase 5B 테스트/검증`입니다. +- **저장 전제 (SSOT)**: PostgreSQL `team_document_chunk`가 `skill-rag-file` 문서 청크의 **Single Source of Truth**입니다. `upload.py`, `reindex.py`, `search.py` 모두 `PostgresDocumentVectorStore`를 사용하며, ChromaDB는 레거시 실험 스크립트(`rebuild_chroma_collection.py`)에서만 쓰이고 운영 API와 분리됩니다. 이 계획에서 "DB 전환" 작업은 없으며, 대신 **스키마 최적화 및 인덱스 재생성** 관점에서 관리합니다. - **임베딩 전제 갱신**: 컬렉션 차원 `384`는 더 이상 사용하지 않으며, `768`도 Gemini 2 기준으로 **전량 재임베딩**해야 합니다. - **Go 전환 근거**: 내부 NAS Go 동기화 리서치의 1분 벤치마크는 Go가 Python 대비 **총 사이클 +5~6%** 수준이며, “무조건 큰 폭의 시간 단축”을 보장하지 않습니다. -- **청킹 리스크**: 현재 `skill-rag-file`의 `char_per_token = 4` 근사는 **한국어 과소 청킹** 가능성이 높아, 품질/비용/속도에 영향이 있습니다. +- **인덱싱 파이프라인 전환**: `skill-rag-file`의 텍스트 변환 후 청킹 방식(pdftotext/PyPDF2/OCR/문자단위 분할)을 Gemini Embedding 2 원본 파일 직접 임베딩으로 전환합니다. 이 계획에서 닫습니다. 텍스트 변환 방식으로 임베딩한 뒤 전환하면 전량 재임베딩이 두 번 발생하므로, 파이프라인 전환이 임베딩보다 반드시 선행합니다. +- **임베딩 전담 원칙**: 임베딩 관련 로직은 `skill-embedding`이 전담합니다. `skill-rag-file`에서 Gemini API를 직접 호출하면 모델 관리와 비용 트래킹이 파편화되므로, PDF/이미지 바이트 입력 엔드포인트를 `skill-embedding`에 추가하는 방향으로 확장합니다. - 이 문제는 `검색 인프라 확장`보다 `답변 합성 계약 부재` 문제로 다룹니다. - 질문별 `if` 분기, 질문별 direct answer, 질문별 프롬프트 추가로 닫지 않습니다. - `Company X grounding` 경로에 공통 계약 3개를 먼저 고정합니다. @@ -44,6 +49,7 @@ tags: [plans, companyx, rag, answer-composition, scenario, troubleshooting] ## 2. 범위 - 포함: + - `skill-rag-file` 인덱싱 파이프라인 → Gemini Embedding 2 원본 파일 직접 임베딩 전환 - Company X RAG의 현재 임베딩 경로/차원 재검증 - NAS 최신 문서 동기화본과 검색 컬렉션 반영 상태 확인 - `rb8001/app/services/companyx_grounding_service.py` @@ -54,7 +60,7 @@ tags: [plans, companyx, rag, answer-composition, scenario, troubleshooting] - Company X 전체 문서셋 대규모 확대 - 다른 RAG 경로 공통화 - Prompt DB 전면화 - - `skill-rag-file` 자체 리팩토링 + - `skill-rag-file` 자체 리팩토링 (인덱싱 파이프라인 전환 외) ## 3. 공통 계약 고정안 @@ -94,6 +100,12 @@ tags: [plans, companyx, rag, answer-composition, scenario, troubleshooting] - 플로우: 질문 임베딩 → pgvector 유사도 검색 → 적합 청크 선별 → LLM 컨텍스트 전달 → 답변 생성 - LLM: 현재 rb8001 기준 모델 사용 (gpt-4o-mini 계열) - 근거 부족 시 LLM에게 "문서 없음"을 명시적으로 지시하는 시스템 프롬프트 포함 +- **LLM 출력 형식 강제**: LLM 응답을 프롬프트 지시만으로 믿지 않습니다. `Pydantic` typed validation으로 출력 형식을 검증합니다. + - 근거: [Pydantic AI 도입 기반 LLM 출력 안정화 방향확정 리서치](../research/260313_pydantic_ai_도입_기반_llm_출력_안정화_방향확정_리서치.md) — 프롬프트만으로 형식 일탈을 막지 못함이 실측 확인됨 + - 방식: `Pydantic-only` 우선 (`Pydantic AI`는 의존성 부담으로 2차) + - **핵심**: 텍스트를 생성하는 것이 아니라 `evidence_docs`와 `failure_reason`을 구조화된 JSON으로 받는 것이 목적입니다. Slack UI나 다른 채널로 전달 시 파싱 에러를 원천 차단합니다. + - 출력 스키마: `direct_answer(str)`, `evidence_docs(List[str])`, `failure_reason(Optional[str])` 필드 고정 + - 검증 실패 시 성공처럼 반환하지 않습니다. 명시적 실패 응답으로 처리합니다. - `SKILL.md`의 답변 순서(`direct answer -> evidence documents -> short evidence summary`)를 LLM 프롬프트 계약으로 내립니다. - 질문별 direct answer 하드코딩을 더 늘리지 않습니다. - 질문 유형 판정과 근거 채택 판정은 별도 함수로 분리합니다. @@ -102,6 +114,129 @@ tags: [plans, companyx, rag, answer-composition, scenario, troubleshooting] ## 5. 구현 단계 +> **병렬 실행 구조 — 트랙 A / B / C** +> +> 간섭 없이 3개 트랙으로 분리됩니다. +> +> | 트랙 | 담당 Phase | 건드리는 서비스 | 상태 | +> |------|-----------|----------------|------| +> | A — 인덱싱 파이프라인 | Phase 5A | `skill-embedding` + `skill-rag-file` 인덱싱 경로 (`upload.py`, `reindex.py`) | **코드 완료, 배포 대기** | +> | B — LLM 답변 합성 | Phase 4 (Phase 1~3 구현 완료) | `rb8001/companyx_grounding_service` 전용 | **구현 완료** | +> | C — 테스트·검증 | Phase 5B | A + B 결과물 | **A 배포 후 진행** | +> +> **A와 B가 간섭 없는 이유**: A는 `skill-embedding` API 확장 + `skill-rag-file` 인덱싱 경로(upload/reindex) 수정. B는 `rb8001/companyx_grounding_service`에서 LLM 호출 + Pydantic 검증 추가. 공유 파일 없음. +> +> **A·B 시작 전 1회 합의 필요**: 아래 멀티모달 임베딩 API 규격. 이것만 고정하면 A·B 독립 진행 가능합니다. +> +> **C는 A·B 완료 후**: 선행 의존이 명확하므로 병렬 불가. + +### 트랙 A·B 공유 인터페이스 계약: `skill-embedding` 멀티모달 엔드포인트 + +이 규격은 트랙 A(`skill-embedding` 구현측)와 트랙 B(`rb8001` 소비측)가 독립 개발을 착수하기 위한 합의 기준입니다. + +#### 현재 상태 (2026-03-17 코드 확인) + +기존 `POST /embed` 엔드포인트가 **이미 멀티모달 입력 구조를 갖고 있습니다**. + +- 활성 서비스: `skill-embedding-repo/` (Gemini Embedding 2 기반, `GeminiEmbedder`) +- 레거시 서비스: `skill-embedding/` (ONNX 기반) — 배포 이미지 불일치 의심은 해소됨 +- 기존 `EmbedItem` 모델에 `mime_type` + `data_base64` 필드가 이미 존재: + ```python + class EmbedItem(BaseModel): + text: Optional[str] = None + mime_type: Optional[str] = None # application/pdf, image/jpeg 등 + data_base64: Optional[str] = None # Base64 인코딩 바이너리 + ``` +- `dimensions`: 환경변수 `EMBEDDING_DIM=768` 고정. 요청별 지정 불가이나 768 고정이므로 문제 없음. +- `MAX_BATCH_SIZE`: 100 (환경변수로 조정 가능) + +따라서 **신규 엔드포인트(`/v1/embed/multimodal`)를 만들지 않고, 기존 `POST /embed`를 확장**합니다. + +#### 기존 엔드포인트 확장 — **구현 완료** + +| 항목 | 구현 내용 | +|------|----------| +| `task_type` | `EmbedRequest.task_type` 필드 추가 → `GeminiEmbedder.encode(task_type=...)` → `EmbedContentConfig(task_type=...)` 전달. skill-rag-file: 인덱싱 시 `RETRIEVAL_DOCUMENT`, 검색 시 `RETRIEVAL_QUERY` 전달. | +| `metadata` pass-through | `EmbedItem.metadata` 필드(dict) 추가 → 임베딩 처리에서 제외 → `EmbedResponse.item_metadata`로 그대로 반환. | + +#### 확장 후 Request Payload + +```json +{ + "items": [ + { + "mime_type": "application/pdf", + "data_base64": "BASE64_ENCODED_STRING", + "metadata": { + "file_path": "/mnt/nas/6.Company X/...", + "page_range": [1, 6], + "chunk_index": 0 + } + }, + { + "mime_type": "image/jpeg", + "data_base64": "BASE64_ENCODED_STRING", + "metadata": { + "file_path": "/mnt/nas/6.Company X/...", + "chunk_index": 0 + } + } + ], + "task_type": "RETRIEVAL_DOCUMENT" +} +``` + +| 필드 | 설명 | +|------|------| +| `data_base64` | 파일 바이너리의 Base64 인코딩 문자열. PDF는 6페이지 단위로 분할된 바이너리. | +| `mime_type` | `application/pdf`, `image/png`, `image/jpeg` 등 Gemini가 직접 처리할 수 있는 형식. | +| `metadata` | skill-embedding은 처리하지 않고 결과에 그대로 반환(pass-through). 클라이언트가 결과 매핑에 사용. | +| `task_type` | `RETRIEVAL_DOCUMENT`(인덱싱) 또는 `RETRIEVAL_QUERY`(검색). Gemini Embedding API 최적화 힌트. | + +#### 확장 후 Response Payload + +기존 `EmbedResponse`에 `metadata` 리스트를 추가합니다: +```json +{ + "embeddings": [[0.123, -0.456, ...]], + "item_metadata": [ + { + "file_path": "/mnt/nas/6.Company X/...", + "page_range": [1, 6], + "chunk_index": 0 + } + ], + "model": "gemini-embedding-2-preview", + "backend": "gemini", + "dimensions": 768, + "processing_time": 1.23 +} +``` + +#### 에러 처리 + +기존 FastAPI 에러 응답 구조를 유지하되, batch 부분 실패 시: +```json +{ + "error": { + "code": "UNSUPPORTED_MIME_TYPE | PAYLOAD_TOO_LARGE | EMBEDDING_API_FAILURE", + "message": "상세 사유", + "failed_indices": [0, 2] + } +} +``` +- `failed_indices`: batch 중 실패한 input의 인덱스. 성공한 항목은 정상 반환하고 실패 항목만 별도 표시. + +#### 역할 분리 원칙 +- PDF를 6페이지 단위로 나누는 로직은 `skill-rag-file`이 담당 (파일 시스템 접근 권한 보유) +- `skill-embedding`은 순수하게 '바이너리 → 벡터' 변환만 수행 +- 검색 시 질문(텍스트) 임베딩은 기존 `POST /embed`의 `texts` 필드 그대로 사용 (768d 고정, `task_type=RETRIEVAL_QUERY` 추가) + +#### 확인 완료 항목 +- ~~기존 `/embed` 엔드포인트가 `dimensions=768`을 지원하는지~~ → 환경변수 `EMBEDDING_DIM=768` 고정. 확인 완료. +- ~~skill-embedding 레포 코드(ONNX)와 배포 이미지(Gemini 2) 불일치~~ → `skill-embedding-repo`가 Gemini 2 기반 활성 서비스. 해소. +- Base64 인코딩 시 6페이지 PDF 바이트 크기 → nginx/gunicorn HTTP payload 크기 제한과 맞춰야 함 (구현 시 확인) + ### Phase 0. 운영 전제 재검증 - 확인 항목과 결과: @@ -110,17 +245,19 @@ tags: [plans, companyx, rag, answer-composition, scenario, troubleshooting] | Company X RAG 임베딩 차원 | `384d` 폐기, `Gemini Embedding 2 / 768d` 확정 (0_VALUE 정책) | **확정** | | SKILL.md 전제 | `384d` → `Gemini Embedding 2 / 768d` 갱신 완료 (2026-03-17) | **확정** | | 저장 경로 (rb8001 메모리) | PostgreSQL `memory_vectors` 등 중심 전환 확인 | **확정** | -| 저장 경로 (skill-rag-file 문서 청크) | 현재 ChromaDB(`/app/chroma_db`) 저장 중. 운영 원칙(PostgreSQL 중심)과 불일치. PostgreSQL(pgvector)로 전환 필요 | **미완료** | -| 인덱싱 파이프라인 | 현재 모든 파일을 텍스트 변환 후 청킹. Gemini Embedding 2의 멀티모달(PDF, 이미지, docx 등 직접 임베딩) 능력을 미활용. 원본 파일 직접 임베딩으로 전환 필요 | **미완료** | -| NAS 동기화 경로 | NAS → skill-rag-file 반영 경로/시점 미확인. Phase 5에서 검증 후 Phase 0 표에 반영 | **미완료** | +| 저장 경로 (skill-rag-file 문서 청크) SSOT | `PostgreSQL team_document_chunk`. `upload.py`, `reindex.py`, `search.py` 모두 `PostgresDocumentVectorStore` 사용 확인. "DB 전환" 아님 — **스키마 최적화 및 인덱스 재생성** 관점으로 관리. | **확정** | +| ChromaDB 레거시 격리 | `rebuild_chroma_collection.py` — 실험용/레거시. 운영 API와 완전 분리됨. 긴급하지 않으나 정리 대상. | **레거시 격리** | +| 인덱싱 파이프라인 | 현재 모든 파일을 텍스트 변환 후 청킹. Gemini Embedding 2의 멀티모달(PDF, 이미지, docx 등 직접 임베딩) 능력을 미활용. 원본 파일 직접 임베딩으로 전환. Phase 5A에서 닫음. | **미완료** | +| NAS 동기화 경로 | NAS → skill-rag-file 반영 경로/시점 미확인. **Phase 5A 선행 조건** — 임베딩 실행 전에 NAS 파일이 skill-rag-file에 어떻게 들어오는지(수동 upload vs 자동 동기화) 확인 필요. | **미완료 (5A 선행)** | | 재오픈 질문 20개 재현 | Slack 실응답 재현 미실시 | **미완료** | - 현재 상태: - 임베딩/저장 전제는 확정됨. - - NAS 동기화 경로와 질문 재현은 Phase 5에서 함께 검증. - - 청킹 구조 개선(Gemini 2 PDF 직접 임베딩, 한국어 토큰 근사값)은 **별도 이슈로 분리해 추적**. 현재 텍스트 추출 + 문자 분할 방식으로 운영하되, 리스크로 기록. + - 인덱싱 파이프라인 전환과 Company X 문서 임베딩은 Phase 5A에서 닫음. + - **NAS 동기화 경로는 Phase 5A 선행 조건** — 5A에서 임베딩을 실행하려면 NAS 파일 투입 방식이 먼저 확인돼야 합니다. + - 재오픈 질문 20개 재현은 Phase 5B에서 검증. -### Phase 1. 구조 분리 +### Phase 1. 구조 분리 — **구현 완료** - `companyx_grounding_service`에서 아래 책임을 분리합니다. - 질문 유형 판정 - query candidate 생성 @@ -131,7 +268,7 @@ tags: [plans, companyx, rag, answer-composition, scenario, troubleshooting] - 목표: - 현재 질문별 특례 분기와 generic fallback이 어디서 작동하는지 코드상 경계를 명확히 나눕니다. -### Phase 2. 질문 유형 계약 도입 +### Phase 2. 질문 유형 계약 도입 — **구현 완료** - 최소 4개 유형 분류 함수를 추가합니다. - 재오픈 기준 질문 20개는 각 유형에 명시 매핑돼야 합니다. - 아래 3개는 대표 매핑 예시입니다. @@ -139,7 +276,7 @@ tags: [plans, companyx, rag, answer-composition, scenario, troubleshooting] - `그럼 컴퍼니엑스 내부 규정 상 휴가는 얼마나 쓸 수 있어?` -> 사실 확인형 또는 규정 확인형 성격 - `오늘전통 프로그램을 Company X가 옐로펀치랑 같이 운영한다는 근거 있어?` -> 사실 확인형 -### Phase 3. 근거 채택 판정 추가 +### Phase 3. 근거 채택 판정 추가 — **구현 완료** - 검색 결과를 그대로 상위 3개 노출하지 않습니다. - 질문 유형에 맞지 않는 청크는 버립니다. - 필요 최소 판정: @@ -148,30 +285,101 @@ tags: [plans, companyx, rag, answer-composition, scenario, troubleshooting] - 답변 가능 근거인지 여부 - `휴가` 질문에 `todaytradition` 청크가 잡히는 경우는 `무관한 결과`로 실패 처리해야 합니다. -### Phase 4. LLM 기반 답변 생성으로 전환 +### Phase 4. LLM 기반 답변 생성 + Pydantic 출력 검증 — **구현 완료** - 현재 `_build_direct_answer()` 규칙 문자열 조합을 LLM 호출로 대체합니다. -- 플로우: - 1. 근거 채택 판정을 통과한 청크를 컨텍스트로 조합 +- **전체 흐름 (이 Phase가 책임지는 경계)**: + 1. Phase 3에서 선별된 청크 원문(질문과 무관한 청크는 이미 탈락)을 컨텍스트로 조합 2. 질문 유형별 시스템 프롬프트 + 컨텍스트 + 사용자 질문을 LLM에 전달 - 3. LLM이 `직접 답 + 근거 문서명 + 요약` 형식으로 답변 생성 -- 근거가 없거나 부족할 때는 LLM에게 `문서 없음 / 문서 미확인 / 단정 불가` 중 하나로만 답하도록 프롬프트로 강제합니다. + 3. LLM은 컨텍스트를 보고 질문에 답할 수 있는지 재판단 — 컨텍스트가 있어도 질문에 답하기 부족하면 `failure_reason`을 채움 + 4. LLM 응답을 Pydantic 모델로 검증 (`direct_answer`, `evidence_docs`, `failure_reason`) + 5. 검증 통과 시 구조체를 파싱해 답변 조합 (직접 답 + 근거 문서 목록), Slack/프론트 출력 + 6. 검증 실패 시 성공처럼 반환하지 않고 명시적 실패 처리 +- **역할 경계**: + - Phase 3(RAG): 벡터 유사도 기반 질문 적합도 판정 → 무관한 청크 탈락 + - Phase 4(LLM): 컨텍스트 기반 답변 가능 여부 재판단 → 형식 보장 + - 둘은 다른 판정이며 둘 다 필요합니다. Phase 3을 통과했다고 LLM이 무조건 답할 수 있는 것이 아닙니다. +- **Pydantic 출력 스키마**: + ```python + class CompanyXRAGOutput(BaseModel): + direct_answer: str + evidence_docs: List[str] + failure_reason: Optional[str] = None + ``` + - `failure_reason`이 None이 아니면 성공처럼 반환하지 않습니다. + - 값은 `문서 없음 / 문서 미확인 / 단정 불가` 중 하나로 고정합니다. + - 검증 실패 시에도 성공처럼 반환하지 않습니다. +- **시스템 프롬프트 골격**: + - 질문 유형별로 다른 지시를 포함합니다. + - 근거 부족 시: `"문서에서 직접 답할 수 없으면 failure_reason 필드에 이유를 명시하고 direct_answer는 빈 문자열로 두세요."` + - 무관한 청크 주의: `"제공된 컨텍스트가 질문과 직접 관련이 없다면 근거로 채택하지 마세요."` + - 하드코딩 문장 금지: 질문별 특례 문구 삽입 없이 공통 구조로 답변합니다. +- **LLM 호출 위치**: `companyx_grounding_service` 내부에서 호출합니다. `rb8001`의 기존 gpt-4o-mini 호출 경로(LLM 클라이언트)를 재사용합니다. 별도 서비스로 분리하지 않습니다. +- 프롬프트 지시만으로 형식을 믿지 않습니다. Pydantic typed validation이 출력 형식의 최종 보장입니다. - 질문별 하드코딩 문장을 추가하지 않습니다. -### Phase 5. 테스트 고정 및 검증 +### Phase 5A. 인덱싱 파이프라인 전환 및 Company X 문서 임베딩 +- **전환 후 파이프라인 흐름** (`upload.py` / `reindex.py` 기준): + ``` + text_extractor.extract() → indexing_pipeline.build_index_payloads() + → [PDF: PyPDF2 6페이지 분할 → Base64 | 비PDF: chunk_text fallback] + → embedding_service.embed_items(task_type="RETRIEVAL_DOCUMENT") + → vector_store.replace_document_chunks() + ``` + - PDF: 6페이지 단위 바이너리 Base64 → `embed_items()` (멀티모달 직접 임베딩). `chunk_text`는 검색 호환용 미리보기 텍스트로 유지. + - 비PDF: 기존 텍스트 추출 → 청킹 → `embed_items(text=chunk)` fallback. + - 검색: `embed_text(task_type="RETRIEVAL_QUERY")` — RETRIEVAL_QUERY/DOCUMENT 구분 적용. + - `task_type` + `metadata` pass-through: skill-embedding API에 구현 완료. +- `skill-rag-file` 인덱싱 파이프라인에서 아래를 제거하고 Gemini Embedding 2 직접 임베딩으로 교체합니다. + - pdftotext / PyPDF2 텍스트 추출 경로 제거 + - OCR 경로 제거 + - 문자 단위 청킹(`chunk_text`) 제거 + - 대체: PDF 6페이지 단위 직접 임베딩, 이미지 직접 임베딩 +- **구현 선택 (확정)**: + - **임베딩 전담 원칙 유지**: 임베딩 관련 로직은 `skill-embedding`이 전담합니다. `skill-rag-file`에서 Gemini API를 직접 호출하지 않습니다. + - **API**: 기존 `POST /embed`를 확장 (`task_type` + `metadata` pass-through 추가). 신규 엔드포인트 불필요. 상세는 위 "트랙 A·B 공유 인터페이스 계약" 참조. + - `skill-rag-file`측 구현: `EmbeddingService`에 `embed_file(file_path, file_type)` 메서드를 신설합니다. 내부적으로 파일을 읽어 Base64 인코딩 후 `POST /embed`의 `items` 필드로 전달합니다. + - 파일 형식별 처리 분기 (`skill-rag-file`이 담당): + - PDF: 6페이지 단위로 분할 후 각 분할 바이너리를 Base64 인코딩하여 batch 요청 + - 이미지(PNG/JPEG): 바이트를 Base64 인코딩하여 요청 + - docx 등 Gemini 미지원 형식: 텍스트 추출 유지 → 기존 `POST /embed`의 `texts` 필드 사용 +- **chunk_by_tokens 정리**: `chunk_by_tokens()`가 다른 경로에서 사용되는지 확인 후 미사용이면 제거합니다. 사용처가 있으면 문서에 반영합니다. +- **임베딩 대상 (1·2순위)**: 전체 53,249개 중 시나리오 질문 커버리지가 높은 폴더만 우선 임베딩합니다. + +| 순위 | 폴더 | 파일 수 | 대상 질문 | +|------|------|---------|-----------| +| 1 | `6.Company X/10. MOU&인증` | 91개 | 옐로펀치 공동운영 근거 | +| 1 | `6.Company X/12. 컴퍼니엑스 소개자료` | 1개 | X-COURSE 정의, 슬로건/미션 | +| 1 | `6.Company X/3. 배치프로그램(X-COURSE, X-HISSTORY 등)` | 2,865개 | X-COURSE, 오늘전통, 프로그램 운영 흐름 | +| 2 | `6.Company X/6. 투자` | 13,018개 | 투자사 수, 투자 건수 수치형 질문 | + +- 1순위 합계: 2,957개 (전체의 5.5%). 2순위 포함 시 15,976개 (전체의 30%). +- 파이프라인 전환 완료 후 1순위 → 2순위 순서로 임베딩을 실행합니다. +- 임베딩 완료 후 Phase 0 표의 인덱싱 파이프라인 항목을 **완료**로 갱신합니다. +- 현재 상태: **코드 전환 완료, 배포 미완료** +- 남은 작업: + - skill-embedding + skill-rag-file 배포/재기동 + - NAS 동기화 경로 확인 (선행 조건) + - 1순위/2순위 폴더 실제 임베딩 실행 + - pdftotext/OCR 경로는 텍스트 미리보기 생성용으로 잔존 — PDF 직접 임베딩과 병행. 별도 제거 계획 불필요. + +### Phase 5B. 테스트 고정 및 검증 +- Phase 5A 완료 후 진행합니다. - **자동화 테스트** (코드): - 질문 유형 분류 정확성: 20개 질문 → 4유형 매핑 검증 - 근거 채택 판정: 무관한 청크 반환 시 `grounding_present=false` 확인 - 실패 응답 형식: generic 문장(`관련 근거를 찾았습니다`) 금지 확인 - 성공 응답 형식: `직접 답 + 근거 문서명 + 요약` 구조 확인 + - Pydantic 검증: `CompanyXRAGOutput` 파싱 성공 여부, `failure_reason` 유무에 따른 분기 확인 + - 저장 경로: 임베딩 결과가 `team_document_chunk`(PostgreSQL)에 저장됨을 확인 - **Slack 실응답 검증** (수동, 대표 5개): 1. `오늘전통 프로그램을 Company X가 옐로펀치랑 같이 운영한다는 근거 있어?` → 직접 답 + MOU 근거 2. `컴퍼니엑스의 투자사는 몇개야?` → 수치 답 또는 `단정 불가` 3. `내부 규정 상 휴가는 얼마나 쓸 수 있어?` → 규정 문서 확인 또는 `문서 미확인` 4. `X-COURSE가 뭐야?` → 설명 + 근거 문서 5. `근거 문서명만 다시 정리해줘` → 직전 근거 목록 재정리 +- **Phase 0 갱신**: NAS 동기화 경로/시점 검증을 끝내면 Phase 0 표에 반영 - **종결 worklog**: 테스트 통과 + Slack 5개 검증 완료 시 종결 worklog 작성 -- **Phase 0 갱신**: Phase 5에서 NAS 동기화 경로/시점 검증을 끝내면 Phase 0 표에 반영 -- 테스트는 질문별 예외 성공이 아니라 공통 계약 준수 여부를 검증해야 합니다. +- 테스트는 질문별 예외 성공이 아니라 공통 계약 준수 여부를 검증합니다. - 현재 상태: **미완료** ## 6. 검증 기준 @@ -188,12 +396,15 @@ tags: [plans, companyx, rag, answer-composition, scenario, troubleshooting] - `SKILL.md` 요구사항과 실제 응답 형식이 일치해야 합니다. ## 7. 완료 판정 기준 -1. [260312_companyx_rag_answer_composition_regression.md](../troubleshooting/260312_companyx_rag_answer_composition_regression.md)에서 정의한 재현 질문셋이 더 이상 회귀하지 않습니다. -2. [260312_companyx_내부문서_근거응답_사용자시나리오.md](../scenarios/260312_companyx_내부문서_근거응답_사용자시나리오.md)의 재오픈 기준 질문 20개가 기대 결과를 만족합니다. -3. Company X RAG의 현재 임베딩 경로/차원과 NAS 문서 반영 경로가 `research`에 최신 기준으로 반영됩니다. -4. `companyx_grounding_service`에 질문별 direct answer 특례를 추가하지 않고 공통 계약 구조로 바뀝니다. -5. 테스트가 추가되고 통과합니다. -6. 실행 결과는 `worklog 1건`으로 마감하고, 시나리오/트러블 문서와 양방향 링크를 연결합니다. +1. `skill-rag-file` 인덱싱 파이프라인이 Gemini Embedding 2 원본 파일 직접 임베딩으로 전환됩니다. (pdftotext/PyPDF2/OCR/문자단위 청킹 제거, `EmbeddingService.embed_file()` 구현, `skill-embedding` API 확장 확인) +2. Company X NAS 문서가 전환된 파이프라인으로 임베딩됩니다. +3. [260312_companyx_rag_answer_composition_regression.md](../troubleshooting/260312_companyx_rag_answer_composition_regression.md)에서 정의한 재현 질문셋이 더 이상 회귀하지 않습니다. +4. [260312_companyx_내부문서_근거응답_사용자시나리오.md](../scenarios/260312_companyx_내부문서_근거응답_사용자시나리오.md)의 재오픈 기준 질문 20개가 기대 결과를 만족합니다. +5. Company X RAG의 현재 임베딩 경로/차원과 NAS 문서 반영 경로가 `research`에 최신 기준으로 반영됩니다. +6. `companyx_grounding_service`에 질문별 direct answer 특례를 추가하지 않고 공통 계약 구조로 바뀝니다. +7. LLM 응답이 Pydantic 모델(`direct_answer`, `evidence_docs`, `failure_reason`)로 검증됩니다. 검증 실패 시 성공처럼 반환하는 경로가 없습니다. +8. 테스트가 추가되고 통과합니다. +9. 실행 결과는 `worklog 1건`으로 마감하고, 시나리오/트러블 문서와 양방향 링크를 연결합니다. ## 8. 후속 경계 - 이 계획이 닫혀도 Company X 전체 문서군 확대나 범용 RAG 정책 공통화는 별도 계획으로 다룹니다. diff --git a/journey/research/260315_companyx_rag_답변합성_시나리오동시종결_리서치.md b/journey/research/260315_companyx_rag_답변합성_시나리오동시종결_리서치.md index 4ec59e8..72eedbe 100644 --- a/journey/research/260315_companyx_rag_답변합성_시나리오동시종결_리서치.md +++ b/journey/research/260315_companyx_rag_답변합성_시나리오동시종결_리서치.md @@ -164,10 +164,20 @@ tags: [research, companyx, rag, answer-composition, scenario, troubleshooting] ### 14. 현재 런타임 저장 구조는 PostgreSQL 중심으로 전환돼 있다 - `330_백엔드_PostgreSQL_ChromaDB_Vector_Memory.md` 상단 주석은 **2026-03-17 기준 운영 런타임이 `skill-rag-file`, `rb8001`의 핵심 벡터/검색/그래프 경로를 PostgreSQL 중심으로 전환**했다고 명시합니다. - `rb8001` 기억 회상 핵심 경로는 이미 PostgreSQL `memory_vectors`, `memory_graph_nodes`, `memory_graph_edges` 기준으로 전환됐습니다. -- 단, **`skill-rag-file`의 문서 청크 벡터는 코드 기준으로 여전히 ChromaDB(`/app/chroma_db`)에 저장 중**입니다. 이는 운영 원칙(PostgreSQL 중심)과 불일치하는 상태입니다. +- **`skill-rag-file`의 문서 청크 벡터는 코드(`upload.py`, `reindex.py`, `search.py`) 기준으로 `PostgresDocumentVectorStore`를 사용하며 `team_document_chunk` 테이블에 저장됩니다.** ChromaDB는 `rebuild_chroma_collection.py` 스크립트에서만 사용되며 운영 API 경로와 분리돼 있습니다. +- 확인된 파일별 저장소: + +| 파일 | 사용 저장소 | +|------|------------| +| `upload.py` | `PostgresDocumentVectorStore` (`team_document_chunk`) | +| `reindex.py` | `PostgresDocumentVectorStore` (`team_document_chunk`) | +| `search.py` | `PostgresDocumentVectorStore` (`team_document_chunk`) | +| `rebuild_chroma_collection.py` | ChromaDB (레거시 스크립트, 운영 API 비사용) | + - 의미: - - Company X 문서 임베딩 전환 시 벡터 저장 경로도 ChromaDB → PostgreSQL(pgvector)로 함께 전환해야 합니다. - - 전환 전까지는 skill-rag-file이 ChromaDB에 저장 중임을 전제로 검증해야 합니다. + - `skill-rag-file` 운영 API 경로는 이미 PostgreSQL 중심입니다. "ChromaDB에 저장 중" 표현은 부정확합니다. + - Company X 문서 임베딩 저장 경로는 `team_document_chunk`(pgvector)가 SSOT입니다. + - 레거시 `rebuild_chroma_collection.py`는 운영과 무관하며 정리 대상입니다. ### 15. NAS 전체 PDF 기준 임베딩 시간은 약 5.2시간(추정)이다 - `/mnt/nas/workspace` 전체 PDF 파일 수는 `27,849`개, 200개 샘플 평균 페이지는 `8.05`로 측정됐습니다. @@ -239,7 +249,7 @@ tags: [research, companyx, rag, answer-composition, scenario, troubleshooting] 3. `재정리형 질문`은 이전 응답 재사용 없이 재검색함 (세션 연결 없음). 현재 스코프에서는 재검색 허용으로 운영. 4. Company X 전용 규칙은 현재 독립 모듈(`companyx_grounding_service.py`)로 유지. 범용화는 이 문제 세트 밖. 6. NAS 최신 문서 동기화본이 Company X RAG 검색 컬렉션에 언제, 어떤 방식으로 반영되는지는 아직 미확정. -7. 한국어 과소 청킹 리스크 및 `Gemini 2 PDF 직접 임베딩` 미구현은 별도 이슈로 분리해 추적할지 미확정. +~~7. 한국어 과소 청킹 리스크 및 `Gemini 2 PDF 직접 임베딩` 미구현은 **본 문제 세트에서 즉시 전환** 대상으로 결정할지 미확정.~~ → **확정 (2026-03-17)**: 계획 Phase 5A에서 문자 단위 청킹 제거 + PDF 직접 임베딩 전환으로 결정됨. 이 계획에서 닫음. ## 결론 - 이 이슈의 직접 원인은 `Company X 검색 실패`가 아니라 `질문 적합 근거 선별 없는 답변 합성`입니다. @@ -315,6 +325,9 @@ tags: [research, companyx, rag, answer-composition, scenario, troubleshooting] - Phase 5 테스트 고정 미완료: 재오픈 기준 질문 20개에 대한 자동화 테스트 없음 - Slack 실응답 검증 미완료: 코드는 구현됐으나 실제 Slack에서 재오픈 질문 20개 통과 여부 미확인 - 종결 worklog 미존재: 트러블슈팅 문서가 참조하는 `260315_companyx_정확표기_slack_근거응답_경로종결.md` 파일 없음 -- **청킹-임베딩 전제 불일치 미해결**: `skill-rag-file`의 청킹 로직(`char_per_token = 4`, 영어 기준 고정값)은 Gemini Embedding 2 전환 이후에도 그대로입니다. PDF는 여전히 `pdftotext/PyPDF2`로 텍스트 추출 후 문자 단위 분할하며, Gemini Embedding 2의 PDF 직접 임베딩(6페이지 단위) 경로는 미구현 상태입니다. 임베딩 모델은 교체됐지만 문서를 잘라서 넣는 방식은 구형 그대로인 전제 불일치가 남아 있습니다. +- ~~**청킹-임베딩 전제 불일치 미해결**~~ → **해소 (2026-03-17)**: `IndexingPipelineService` 도입. PDF는 6페이지 단위 바이너리 Base64 → `embed_items(task_type="RETRIEVAL_DOCUMENT")` 직접 임베딩. 비PDF는 텍스트 추출 → 청킹 fallback 유지. `chunk_text`는 검색 호환용 미리보기 텍스트로만 사용. +- ~~**Phase 4 LLM 전환 미구현**~~ → **구현 완료 (2026-03-17)**: `_call_llm_companyx_grounding()` + `CompanyXRAGOutput` Pydantic 검증 도입. `settings.DEFAULT_LLM_MODEL`(gpt-5-mini) SSOT 참조. 규칙 기반 `_build_direct_answer()`는 LLM 실패 시 fallback으로 유지. +- ~~**skill-embedding 배포 이미지와 레포 코드 일치 여부 미확인**~~ → **확인 완료 (2026-03-17)**: 활성 서비스는 `skill-embedding-repo/`(`GeminiEmbedder`, Gemini Embedding 2 기반). 레거시 `skill-embedding/`(ONNX)와는 별도 레포. 해소. +- ~~**Phase 5A 파이프라인 전환 + 인터페이스 계약**~~ → **코드 구현 완료 (2026-03-17)**: `task_type`(RETRIEVAL_DOCUMENT/QUERY) 전달 + `metadata` pass-through 양쪽(skill-embedding, skill-rag-file) 구현. 배포/재기동은 미완료. 상세: [260317_companyx_grounding_코드검토_및_문서현행화](../worklog/260317_companyx_grounding_코드검토_및_문서현행화.md) diff --git a/journey/research/rag/260315_Gemini_Embedding_2_리서치_비용_청킹_도입검토.md b/journey/research/rag/260315_Gemini_Embedding_2_리서치_비용_청킹_도입검토.md index b82814f..4780628 100644 --- a/journey/research/rag/260315_Gemini_Embedding_2_리서치_비용_청킹_도입검토.md +++ b/journey/research/rag/260315_Gemini_Embedding_2_리서치_비용_청킹_도입검토.md @@ -1,7 +1,8 @@ # Gemini Embedding 2 리서치: 비용·청킹·도입 검토 **작성일**: 2026-03-15 -**출처**: Gemini와의 대화 정리 +**갱신일**: 2026-03-17 +**출처**: Gemini와의 대화 정리, [Google Blog (2026-03-10)](https://blog.google/innovation-and-ai/technology/developers-tools/gemini-embedding-2/), [Vertex AI 문서](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/embedding-2), [Gemini API](https://ai.google.dev/gemini-api/docs/embeddings) --- @@ -18,6 +19,7 @@ Google Gemini Embedding 2는 텍스트뿐 아니라 이미지, 오디오, 비디 | 항목 | 내용 | |------|------| | 멀티모달 | 텍스트, 이미지, 오디오, 비디오, PDF를 동일한 벡터로 변환 | +| 인터리브 입력 | 이미지+텍스트 등 여러 모달리티를 한 요청에 동시 전달 가능 | | MRL | 출력 차원 조절 가능 (기본 3,072 → 768/1,536 등) | | 긴 컨텍스트 | 최대 8,192 토큰 입력 | | 다국어 | 한국어 포함 100개 이상 언어 | @@ -29,7 +31,17 @@ Google Gemini Embedding 2는 텍스트뿐 아니라 이미지, 오디오, 비디 | gemini-embedding-2-preview | 멀티모달 검색, RAG, 분류 | 3,072 (조절 가능) | | text-embedding-004 | 텍스트 전용 | 768 | -### 2.3 사용 예시 (Python) +### 2.3 입력 제한 및 포맷 + +| 유형 | 제한 | 포맷 | +|------|------|------| +| 텍스트 | 8,192 토큰 | - | +| PDF | 최대 6페이지/파일, 1파일/요청 | - | +| 이미지 | 최대 6장/요청 | PNG, JPEG | +| 비디오 | 최대 120초 | MP4, MOV | +| 오디오 | 최대 80초 | - | + +### 2.4 사용 예시 (Python) ```python from google import genai diff --git a/journey/scenarios/260312_companyx_내부문서_근거응답_사용자시나리오.md b/journey/scenarios/260312_companyx_내부문서_근거응답_사용자시나리오.md index 6c14c28..4581259 100644 --- a/journey/scenarios/260312_companyx_내부문서_근거응답_사용자시나리오.md +++ b/journey/scenarios/260312_companyx_내부문서_근거응답_사용자시나리오.md @@ -192,9 +192,11 @@ tags: [robeing, companyx, rag, scenarios, user-experience] - `384d local` → `Gemini Embedding 2 / 768d` 전환 확정 (0_VALUE 정책) - NAS Company X 53,249 파일 전량 재임베딩 대상 - SKILL.md 반영 완료 +- NAS 동기화 경로/시점은 아직 미확정이며, 이 전제는 리서치/플랜에서 먼저 닫아야 합니다. +- `char_per_token = 4` 고정 근사로 인한 **한국어 과소 청킹 리스크**가 남아 있습니다. ### 닫힘까지 남은 작업 -1. Gemini Embedding 2 / 768d 기준으로 Company X 대표 문서 재임베딩 완료 확인 +1. Gemini Embedding 2 / 768d 기준으로 Company X 대표 문서 재임베딩 완료 확인 (현재 상태: 미확인) 2. 재오픈 기준 질문 20개 중 대표 5개를 Slack 실응답으로 검증 (전수 20개는 자동화 테스트로 대체) 3. 자동화 테스트 추가 후 통과 4. 종결 worklog 1건 작성