--- tags: [research, companyx, rag, answer-composition, scenario, troubleshooting] status: closed closed_date: 2026-03-21 closed_reason: 확정 원인(규칙 기반 하드코딩, 질문별 특례, LLM 미사용)은 260315 계획→260320 흡수→260321 품질개선으로 전부 해소. LLM 합성 + 하이브리드 3중 검색 + Pydantic 검증 동작 확인. --- # Company X RAG 답변합성 시나리오·트러블 동시종결 리서치 ## 상위 원칙 - [Writing Principles](../../book/300_architecture/312_writing-principles.md) - [Company X RAG 답변 합성 회귀](../troubleshooting/260312_companyx_rag_answer_composition_regression.md) - [Company X 내부 문서 근거응답 사용자 시나리오](../scenarios/260312_companyx_내부문서_근거응답_사용자시나리오.md) ## 목적 - `Company X` 근거응답 이슈를 `트러블슈팅 + 시나리오` 한 문제로 보고, 둘을 함께 닫기 위한 원인 기준을 고정합니다. - 현재 실패가 `검색 인프라 문제`인지, `답변 합성 문제`인지, `질문별 하드코딩 구조 문제`인지 단일 경로로 좁힙니다. - 이 문서는 해결안 확장보다 `다음 plan 1건`을 만들 수 있을 정도로 직접 원인을 확정하는 데만 집중합니다. ## 연결 구조 - 같은 레벨 입력: `ideas/260312_companyx_내부문서_rag_응답_아이디어.md`, `scenarios/260312_companyx_내부문서_근거응답_사용자시나리오.md` - 단일 리서치: 본 문서 1개 - 단일 플랜: `plans/260315_companyx_rag_답변합성_시나리오동시종결_계획.md` 1개 - 닫힘 흐름: 플랜 완료 기준 충족 시 시나리오/트러블과 함께 닫힘 선언 (아이디어 문서는 종결 상태 유지) ## 조사 질문 1. 현재 실패의 직접 레이어는 검색인가, 답변 합성인가 2. `컴퍼니엑스의 투자사는 몇개야?`, `내부 규정 상 휴가는 얼마나 쓸 수 있어?`가 왜 깨지는가 3. 현재 Company X 경로는 일반 규칙 기반인가, 질문별 하드코딩 기반인가 4. 트러블 문서와 시나리오 문서를 함께 닫으려면 무엇을 plan에서 고정해야 하는가 ## 범위 / 비범위 - 범위: - `rb8001`의 Company X grounding 진입/검색/응답 합성 경로 - 재오픈 기준 질문 20개에 대한 처리 구조 - 프롬프트/if 분기/질문 적합도 선별 구조 - 비범위: - Company X 전체 문서셋 확대 작업 - `skill-rag-file` 인덱싱 성능 일반론 - 다른 프로젝트나 다른 RAG 경로 전면 리팩토링 - 최종 수정안 구현 자체 ## 사실(Facts) ### 1. Company X 질문은 의도 분류 전에 전용 grounding 경로로 우선 진입한다 - `route_message()`는 사용자 컨텍스트 생성 직후 `try_companyx_grounding()`을 먼저 호출합니다. - 위치: - `rb8001/app/services/message_service.py:81-93` - 의미: - Company X 질문은 일반 intent/skill 경로 전에 별도 처리됩니다. - 따라서 이번 실패는 `일반 의도 분류가 Company X 질문을 놓쳤다`만으로 설명되지 않습니다. ### 2. Company X 경로 진입 조건은 팀 ID + 문자열 marker 기반 휴리스틱이다 - `should_handle_companyx_grounding()`은 `team_id == COMPANYX_TEAM_ID`와 `_looks_like_companyx_grounding_question(message)`로만 판단합니다. - `_looks_like_companyx_grounding_question()`은 `근거`, `내부 문서`, `공동 운영`, `컴퍼니엑스`, `companyx` 같은 marker 포함 여부를 봅니다. - 위치: - `rb8001/app/services/companyx_grounding_service.py:28-60` - 의미: - 질문 유형이나 필요한 답변 방식보다 문자열 포함 여부가 우선입니다. - `컴퍼니엑스`만 들어가도 grounding 경로로 진입할 수 있습니다. ### 3. 검색 질의 확장은 사실상 질문 2종만 별도 하드코딩돼 있다 - `_build_query_candidates()`는 기본적으로 원문 질문 1개를 그대로 사용합니다. - 추가 질의 확장은 아래 경우에만 있습니다. - `오늘전통 + 옐로펀치` - `X-COURSE` - 위치: - `rb8001/app/services/companyx_grounding_service.py:63-91` - 의미: - `투자사 몇 개`, `휴가 규정` 같은 재오픈 질문은 전용 query expansion이 없습니다. - 대표 질문만 상대적으로 유리한 구조입니다. ### 4. 검색 결과는 질문 적합도 재평가 없이 relevance score 순으로만 합쳐진다 - `_search_companyx_documents()`는 후보 질의별 검색 결과를 병합한 뒤 `relevance_score` 내림차순으로만 정렬합니다. - 질문 유형, 문서 종류, 문서-질문 정합성 재평가 단계는 없습니다. - 위치: - `rb8001/app/services/companyx_grounding_service.py:113-127` - 의미: - 검색 hit가 있어도 그 hit가 질문에 맞는 근거인지 다시 검증하지 않습니다. ### 5. 직접 답 생성은 질문별 if 분기 하드코딩이고, 기본값은 generic 문장이다 - `_build_direct_answer()`는 아래 경우만 별도 문장을 가집니다. - `오늘전통 + 옐로펀치` - `X-COURSE` - 그 외 모든 질문은 기본값 `Company X 내부 문서에서 관련 근거를 찾았습니다.`를 반환합니다. - 위치: - `rb8001/app/services/companyx_grounding_service.py:136-153` - 의미: - `투자사는 몇개야?` 같은 수치형 질문은 답을 계산하거나 `단정 불가`를 판단하지 않고 generic 문장으로 끝납니다. - 현재 구조는 질문 유형별 답변 계약이 아니라 일부 질문별 문장 하드코딩입니다. ### 6. 실패 처리는 `검색 결과가 0개일 때`만 작동하고, `검색은 됐지만 무관한 경우`를 다루지 않는다 - `_build_grounded_response()`의 실패 문장은 `if not results`일 때만 반환됩니다. - 결과가 1개라도 있으면 질문 적합성 검증 없이 `근거 문서:` 목록을 붙입니다. - 위치: - `rb8001/app/services/companyx_grounding_service.py:155-171` - 의미: - 휴가 질문에 무관한 `companyx_todaytradition.pdf` 청크가 잡히면, 시스템은 이를 실패가 아니라 성공처럼 출력합니다. - 즉 현재 실패 경계는 `no result`만 있고 `irrelevant result`는 없습니다. ### 7. 현재 Company X 응답은 LLM 기반 합성이 아니라 규칙 기반 문자열 조합이다 - Company X 전용 경로는 검색 결과를 가져온 뒤 `_build_direct_answer()`와 청크 요약 문자열을 이어붙여 응답을 만듭니다. - 위치: - `rb8001/app/services/companyx_grounding_service.py:130-171` - 의미: - 이것은 RAG가 아닙니다. RAG의 정의는 검색된 청크를 컨텍스트로 LLM에 전달해 답변을 생성하는 구조입니다. - 현재 구조는 검색(Retrieval)만 있고 LLM 기반 생성(Generation)이 없습니다. - 따라서 현재 구현 형태는 질문별 문장/분기 하드코딩에 의존하고 있어 유지보수성이 낮고, 질문 유형을 감당하지 못합니다. - **진짜 RAG로 전환**: 검색된 청크를 컨텍스트로 LLM(현재 gpt-4o-mini)에 전달해 답변을 생성하는 구조로 바꿔야 합니다. ### 8. 스킬 문서가 요구하는 계약과 현재 구현 계약은 일치하지 않는다 - `SKILL.md`는 답변 순서를 `direct answer -> evidence documents -> short evidence summary`로 고정합니다. - 또한 `documents are missing, say the evidence is insufficient`를 요구합니다. - 위치: - `DOCS/skills/companyx-rag/SKILL.md` - 현재 구현은 아래 차이가 있습니다. - `직접 답`이 generic 문장으로 대체될 수 있음 - `문서 부족`과 `무관한 문서 반환`을 구분하지 않음 - 증거 문서 적합성 검증 없이 score 상위 청크를 그대로 노출함 - 의미: - 문제의 직접 원인은 스킬 문서 부재가 아니라, 구현이 스킬 계약을 끝까지 지키지 못하는 데 있습니다. ### 9. 대표 성공 질문은 구조적으로 우대돼 있고, 재오픈 질문은 일반화 경로에 놓여 있다 - `오늘전통 + 옐로펀치` 질문은 - 별도 query expansion이 있고 - 별도 direct answer 하드코딩이 있으며 - 대표 근거 문서셋도 이미 worklog에 남아 있습니다. - 관련 문서: - [Company X 내부문서 RAG 근거응답 1차 구현 및 부분 검증](../worklog/260312_companyx_내부문서_rag_근거응답_구현및시나리오검증.md) - 반면 재오픈 질문 다수는 - 전용 query expansion이 없고 - 질문 유형 분기 규칙도 없고 - generic fallback 문장으로 마감됩니다. - 의미: - 현재 성공은 일반화된 성공이 아니라 `대표 질문 최적화 성공`에 가깝습니다. ### 10. 따라서 지금 실패는 `검색 부재`보다 `답변 합성 + 실패 판정 부재 + 질문별 하드코딩 편향`의 결합이다 - 앞선 사실을 합치면 현재 경로는 아래 구조입니다. 1. marker 기반으로 Company X 경로 진입 2. 일부 질문만 유리한 query expansion 3. relevance score만으로 검색 결과 채택 4. 일부 질문만 direct answer 하드코딩 5. 결과가 하나라도 있으면 성공처럼 문서 목록 노출 - 의미: - 이번 이슈의 직접 레이어는 `answer composition`입니다. - 그러나 더 깊게 보면 `질문 유형 계약이 없는 규칙 하드코딩 구조`가 상위 원인입니다. ### 11. 현재 Company X RAG는 `NAS 대량 문서셋 + 임베딩 전환 이슈` 위에 놓여 있다 - `51123` 기준 점검에서 `/mnt/nas/workspace/6.Company X` 아래 실파일은 `53,249`개였고, fullscan 최신 summary는 `2026-03-17 01:23:10+09:00`, hierarchical 최신 summary는 `2026-03-17 11:00:06+09:00`까지 누적돼 있습니다. - 의미: - 지금 Company X RAG는 적은 대표 문서 몇 개만의 문제가 아니라, NAS에서 계속 동기화되는 대량 문서셋 위에서 동작해야 합니다. - 따라서 대표 질문 일부만 통과하는 특례 구조로는 운영 상태를 닫을 수 없습니다. ### 12. Company X RAG의 임베딩 전제가 문서끼리 일치하지 않는다 - `DOCS/skills/companyx-rag/SKILL.md`는 `Default embedding path: local 384d`로 적고 있습니다. - 반면 `260316_임베딩_전체프로젝트_현황_및_SSOT_리서치.md`와 `260316_임베딩_Gemini_Embedding_2_전환_문제오픈.md`는 로빙 전체 기준을 `Gemini Embedding 2`, `768d`, 전수 교체 대상으로 둡니다. - 의미: - 지금 Company X 답변합성 문제는 순수한 응답 문구 문제만이 아닙니다. - 어떤 임베딩 모델/차원/컬렉션 상태를 SSOT로 볼지부터 다시 닫아야 합니다. ### 13. 그래서 2026-03-15 기준 리서치만으로 바로 구현에 들어가면 전제 충돌을 안고 시작한다 - 2026-03-15 리서치는 답변합성 구조 문제를 정확히 짚었지만, 그 이후 열린 `Gemini Embedding 2` 전환 문제와 NAS 대량 문서 운영 상태까지는 반영하지 못했습니다. - 의미: - 지금 필요한 다음 단계는 `새 리서치 전체 재작성`이 아니라, - `현재 NAS 문서량`, `현재 컬렉션 차원`, `현재 재임베딩 여부`, `현재 재오픈 질문셋(20개) 재현 상태`를 다시 확인하는 짧은 갱신 리서치입니다. ### 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`의 문서 청크 벡터는 코드(`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 비사용) | - 의미: - `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`로 측정됐습니다. - 100페이지 임베딩 벤치마크(768d, Gemini 2)에서 총 요청 시간은 `8.2768s`였고, 이를 페이지 단위로 환산하면 `0.0828s/페이지`입니다. - 위 수치를 곱산하면 전체 PDF 예상 페이지는 약 `224,184`페이지, 임베딩 요청 시간은 약 `5.15시간`입니다. - 의미: - 이 값은 **PDF 기준 단순 추정치**이며, 비PDF 파일/추가 전처리/네트워크 부하/실패 재시도는 포함하지 않습니다. ### 16. Go 전환이 “시간 단축”을 보장하지는 않으며, 확인된 이점은 제한적이다 - 내부 NAS Go 동기화 리서치의 1분 벤치마크(23 서버, DSM 목록 조회 루프)에서 Go는 Python 대비 **총 사이클 +5~6%**, 목록 조회는 더 빠르지만 로그인은 차이가 작았습니다. - 해석: - Go 전환의 효과는 **작업 유형에 따라 제한적**이며, “무조건 큰 폭의 시간 단축”이라고 단정할 수 없습니다. - 따라서 임베딩/인덱싱 파이프라인에도 **A/B 실측 없이 Go 전환 효과를 가정하지 않습니다**. - 근거 문서: - https://git.ro-being.com/ivada-infra/DOCS/src/branch/main/journey/research/260313_internal_nas_direct_go_sync_feasibility_research.md ### 17. 현재 텍스트 변환 기반 청킹은 Gemini Embedding 2의 멀티모달 능력을 전혀 활용하지 못하고 있다 - `skill-rag-file`은 모든 파일(PDF, 이미지, docx 등)을 텍스트로 변환한 뒤 문자 단위로 청킹합니다. - Gemini Embedding 2는 텍스트뿐 아니라 **이미지, PDF, 오디오, 비디오를 직접 임베딩**할 수 있는 멀티모달 모델입니다. 텍스트 변환 없이 원본 파일을 그대로 넣을 수 있습니다. - 현재 방식의 문제: - PDF: pdftotext/PyPDF2로 텍스트 추출 시 레이아웃, 표, 이미지 정보 손실 - 이미지 문서: 텍스트 변환 불가하거나 OCR로 품질 저하 - `char_per_token = 4` 한국어 과소 청킹으로 정보 단절 - 의미: - 임베딩 모델을 Gemini Embedding 2로 바꿨으면, 인덱싱 파이프라인도 **원본 파일 직접 임베딩** 방식으로 전환해야 일관성이 있습니다. - 텍스트 변환 파이프라인을 유지하는 것은 Gemini Embedding 2로 전환한 의미를 절반 이상 버리는 것입니다. ## 해석(Interpretation) ### 1. 이 문제는 검색 인프라 장애가 아니라 응답 정책 부재 문제다 - Company X 전용 경로는 실제로 존재하고, 검색 호출도 수행합니다. - 실패가 나는 지점은 `질문에 대한 직접 답을 어떻게 만들고, 어떤 검색 결과를 근거로 채택하며, 언제 모른다고 말할지`가 비어 있다는 점입니다. ### 2. 지금 구조는 시나리오를 닫기 어려운 형태다 - 시나리오는 `설명형`, `사실 확인형`, `수치 확인형`, `재정리형`을 모두 요구합니다. - 하지만 현재 구현은 실질적으로 `대표 사실 확인형 2종`만 특례 처리합니다. - 따라서 새 질문이 들어올수록 `if`와 문장 하드코딩이 늘어날 가능성이 높고, 시나리오를 일반 규칙으로 닫을 수 없습니다. ### 3. 트러블 문서가 지적한 회귀의 직접 원인은 질문 적합도 검증 부재다 - 재오픈 질문들은 검색 결과가 0개라서 실패한 것이 아닙니다. - 결과가 있어도 질문과 무관한 청크를 success처럼 출력했습니다. - 즉 이번 회귀를 닫는 핵심은 `retrieval result exists`가 아니라 `retrieval result is fit for this question`를 판정하는 단계입니다. ### 4. 스킬 문서는 방향이 맞지만, 구현 계약으로 내려오지 않았다 - `SKILL.md`는 이미 직접 답, 근거 문서, 부족 시 명시를 요구합니다. - 문제는 그 계약이 코드에서 재현 가능한 판정 규칙으로 변환되지 않은 것입니다. - 따라서 다음 plan은 새 문구를 더 쓰는 게 아니라, 스킬 계약을 코드가 판정 가능한 구조로 바꾸는 데 집중해야 합니다. ### 5. 다만 지금은 응답 계약 구현 전에 운영 전제를 다시 닫아야 한다 - Company X RAG는 이미 NAS 실문서 대량 적재 상태 위에서 동작하고 있습니다. - 동시에 임베딩 SSOT는 `384d local`과 `Gemini 2 / 768d` 문서가 공존합니다. - 따라서 다음 plan의 첫 단계는 `답변합성 코드 수정`이 아니라 `운영 전제 재검증`이어야 합니다. ### 6. 하드코딩 프롬프트/문장 증식은 이 문제의 해법이 아니다 - 현재도 대표 질문 성공은 이미 하드코딩 특례에 기대고 있습니다. - 이 상태에서 `투자사`, `휴가`, `투자 건수`, `규정`, `복지` 식으로 질문별 문장을 더 추가하면, - 시나리오는 겉보기로만 닫히고 - 트러블은 다른 질문에서 다시 열리며 - 프롬프트와 if 분기 중복만 쌓이게 됩니다. ## 미확정 항목(Unresolved) ### 확정된 항목 (2026-03-17) - ~~5. 컬렉션 차원~~: `384d` 폐기, `Gemini Embedding 2 / 768d` 전량 교체 확정 (0_VALUE 정책). SKILL.md 반영 완료. - ~~1. 질문 적합도 판정~~: `_score_result_relevance()` + `_select_relevant_results()`로 구현됨. rerank는 현재 불필요로 판단 (코드 검토 기준). - ~~2. 수치형 질문 판정~~: `_can_answer_quantitative()` + `_COUNT_MARKER_PATTERN`으로 구현됨. 정밀도 미검증이나 구조는 확정. ### 여전히 미확정 3. `재정리형 질문`은 이전 응답 재사용 없이 재검색함 (세션 연결 없음). 현재 스코프에서는 재검색 허용으로 운영. 4. Company X 전용 규칙은 현재 독립 모듈(`companyx_grounding_service.py`)로 유지. 범용화는 이 문제 세트 밖. ~~6. NAS 최신 문서 동기화본이 Company X RAG 검색 컬렉션에 언제, 어떤 방식으로 반영되는지는 아직 미확정.~~ → **확인 완료 (2026-03-17)**: NAS 마운트 `/mnt/nas` (CIFS). Company X 원본: `/mnt/nas/workspace/6.Company X/` (53,249개). 외부→내부 NAS는 cron(30분 계층형 + 하루 1회 전수조사)으로 동기화. 단, NAS → skill-rag-file 인덱싱은 자동화 없음 — 수동 upload API 호출만 가능. 현재 114개만 인덱싱. 대량 투입 배치 스크립트 필요. ~~7. 한국어 과소 청킹 리스크 및 `Gemini 2 PDF 직접 임베딩` 미구현은 **본 문제 세트에서 즉시 전환** 대상으로 결정할지 미확정.~~ → **확정 (2026-03-17)**: 계획 Phase 5A에서 문자 단위 청킹 제거 + PDF 직접 임베딩 전환으로 결정됨. 이 계획에서 닫음. ## 결론 - 이 이슈의 직접 원인은 `Company X 검색 실패`가 아니라 `질문 적합 근거 선별 없는 답변 합성`입니다. - 더 근본 원인은 `질문 유형 계약 없이 일부 질문만 하드코딩 특례 처리하는 구조`입니다. - **가장 근본 원인**: 현재 구조는 RAG가 아닙니다. 검색된 청크를 LLM 컨텍스트로 전달해 답변을 생성하는 구조가 없고, 규칙 기반 문자열 조합으로 대체하고 있습니다. 이를 진짜 RAG 구조로 전환해야 합니다. - 다만 현재는 `NAS 대량 문서 운영 상태`와 `Gemini Embedding 2 전환 상태`가 겹쳐 있으므로, 다음 plan은 구현 전에 운영 전제 재검증부터 고정해야 합니다. - PostgreSQL 저장 경로 전제는 확정됐으므로, plan에서는 이를 기준으로 재검증/테스트 항목을 정렬해야 합니다. - 그 전제가 닫힌 뒤 구현 단계에서 아래 4가지를 고정해야 합니다. 1. 검색된 청크를 컨텍스트로 LLM에 전달해 답변을 생성하는 구조 도입 2. 질문 유형별 판정 계약 3. 검색 결과의 질문 적합도 판정 계약 4. 근거 부족 시 명시적 실패 계약 ## 다음 plan이 반드시 고정해야 할 항목 1. 현재 Company X RAG의 실제 임베딩 경로와 컬렉션 차원(`384d`/`768d`)을 먼저 재검증할 것 2. NAS 최신 문서 동기화본이 현재 검색 컬렉션에 언제 반영되는지 확인할 것 3. 재오픈 기준 질문 20개를 현재 운영 상태에서 다시 재현해 `검색 실패 / 무관한 hit / 답변 합성 실패`를 분리할 것 4. `success`의 정의를 `검색 hit 존재`가 아니라 `직접 답 + 질문 적합 근거 또는 명시적 부족 안내`로 바꿀 것 5. 질문별 direct answer 하드코딩 추가를 금지하고, 질문 유형별 공통 계약으로 대체할 것 6. `무관한 청크 반환`을 `실패`로 판정하는 경계를 추가할 것 7. 스킬 문서 계약과 코드 계약을 1:1로 맞출 것 8. PostgreSQL 저장 경로 전제를 plan과 테스트 기준에 반영할 것 ## 관련 문서 - [Company X RAG 답변 합성 회귀](../troubleshooting/260312_companyx_rag_answer_composition_regression.md) - [Company X 내부 문서 RAG 응답 아이디어](../ideas/260312_companyx_내부문서_rag_응답_아이디어.md) - [Company X 내부 문서 근거응답 사용자 시나리오](../scenarios/260312_companyx_내부문서_근거응답_사용자시나리오.md) - [Company X 내부문서 RAG 근거응답 현황 리서치](./260312_companyx_내부문서_rag_근거응답_현황_리서치.md) - [Company X RAG 답변합성 시나리오·트러블 동시종결 계획](../plans/260315_companyx_rag_답변합성_시나리오동시종결_계획.md) - [Company X 내부문서 RAG 근거응답 1차 구현 및 부분 검증](../worklog/260312_companyx_내부문서_rag_근거응답_구현및시나리오검증.md) - [Company X RAG 스킬 문서](../../skills/companyx-rag/SKILL.md) --- ## 2026-03-17 코드 검토: 사실 섹션 현행화 > 이 리서치는 2026-03-15 코드 기준으로 작성됐습니다. > 2026-03-17 실제 코드(`rb8001/app/services/companyx_grounding_service.py`)를 직접 검토한 결과, > 아래 사실들이 이미 코드에서 변경되거나 구현된 것으로 확인됐습니다. ### 이미 구현되어 outdated된 사실 | 리서치 사실 | 현재 코드 상태 | |---|---| | Fact 3: query expansion이 2종 하드코딩 | `_build_query_candidates()`가 `_extract_keywords()` + `_DOCUMENT_HINTS_BY_TYPE` 기반 동적 확장으로 변경됨 | | Fact 4: relevance score만으로 결과 채택 | `_score_result_relevance()`, `_select_relevant_results()`로 질문 적합도 재평가 구현됨 | | Fact 5: 직접 답이 2종 하드코딩 + generic 기본값 | `_classify_question_type()` + type-based `_build_direct_answer()`로 4개 유형 처리 구현됨 | | Fact 6: 실패 처리가 results=0일 때만 작동 | `_select_relevant_results()` 필터링 후 relevant=0이면 `_build_failure_answer()` 호출됨. `QUESTION_QUANTITATIVE`는 `_can_answer_quantitative()`로 수치 존재 여부도 판정함 | | Fact 9: 대표 질문만 특례 처리 | 이제 `_classify_question_type()` → `_build_query_candidates()` → `_score_result_relevance()` 공통 경로. 하드코딩 특례 제거됨 | ### 여전히 사실인 항목 - **Fact 1**: Company X 경로는 의도 분류 전에 우선 진입 (`message_service.py:81`) - **Fact 2**: 진입 조건은 여전히 marker 기반 (`_looks_like_companyx_grounding_question`) - **Fact 7**: 응답 합성은 여전히 규칙 기반 문자열 조합 (LLM 합성 아님) ### 현재 미해결 항목 (2026-03-17 갱신) - ~~relevance score만으로 질문 적합도 판정 가능한지~~: `_score_result_relevance()` 구현됨. 현재 구조로 운영. - 수치형 질문의 `단정 가능/불가`를 `_COUNT_MARKER_PATTERN` 패턴만으로 충분한지 미검증 (정밀도 테스트 필요) - 재정리형 질문은 이전 응답 재사용 없이 재검색함 (세션 연결 없음). 현재 스코프에서는 재검색 허용으로 운영. - 임베딩 전제: `384d` → `Gemini Embedding 2 / 768d` 전환 확정 (SKILL.md 반영 완료) ### 현재 코드에서 추가 확인된 구조 - `_DOCUMENT_HINTS_BY_TYPE`: 유형별 문서 힌트 (`quantitative`는 투자/포트폴리오/실적 계열) - `_supports_quantitative_focus()`: 수치형 질문에서 count marker 패턴(`\d+개사`, `총\d+` 등) 존재 여부 판정 - `_select_evidence_results()`: 관련 결과 중 상위 2개만 근거로 선별 (중복 문서 제거) - `_build_failure_answer()`: 유형별 다른 실패 메시지 반환 ### 미완료 항목 (코드 기준) - Phase 5 테스트 고정 미완료: 재오픈 기준 질문 20개에 대한 자동화 테스트 없음 - Slack 실응답 검증 미완료: 코드는 구현됐으나 실제 Slack에서 재오픈 질문 20개 통과 여부 미확인 - 종결 worklog 미존재: 트러블슈팅 문서가 참조하는 `260315_companyx_정확표기_slack_근거응답_경로종결.md` 파일 없음 - ~~**청킹-임베딩 전제 불일치 미해결**~~ → **해소 (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) --- ## 2026-03-19 최신 리서치: Gemini Embedding 2 멀티모달 검색 이슈 및 하이브리드 해결책 #### 1. 문제 발견 (관측 및 검증) * **현상:** PDF 바이너리 직접 임베딩(`pdf_original_bytes`) 시, 텍스트 쿼리에 대한 검색 결과가 빈 배열(`[]`)로 반환되거나 유사도가 극히 낮음. * **근본 원인:** - **페이지 제한:** Gemini Embedding 2는 PDF 직접 임베딩 시 1회 최대 6페이지 제한이 있어 대형 문서의 정보 손실 발생. - **벡터 공간 편차:** 바이너리 기반 벡터(시각적 문맥 포함)와 텍스트 기반 쿼리 벡터 간의 공간적 거리가 멀어 기존 검색 임계값 미달. #### 2. 해결 방안: 하이브리드 임베딩 (Hybrid Indexing) * **전략:** 동일 PDF 문서에 대해 '원본 바이너리 임베딩(시각적 맥락)'과 '텍스트 추출 임베딩(정밀 키워드)'을 병행 수행. * **실측 데이터:** 최신 50개 텍스트 중심 문서(MOU, 투자계획서 등)에 하이브리드 모드 적용 결과, 텍스트 쿼리에 대한 검색 적중률(Recall)이 정상 복구됨을 확인. #### 3. 결론 및 향후 조치 * **인덱싱 파이프라인 수정:** `IndexingPipelineService`를 하이브리드 모드로 상시 전환 (배포 완료). * **재인덱싱:** 기존 Company X의 텍스트 밀집 문서들을 하이브리드 방식으로 재인덱싱하여 RAG 답변 합성의 '근거 데이터' 신뢰성 회복.