
72% → 75% → 92%: 재현 가능한 RAG 검증
요약
RAG 시스템의 검색 정밀도를 높이기 위해 단순 Q&A 변환이 아닌, 5가지 프롬프트 설계 규칙을 적용한 Q&A 팩트 생성 방식을 제안합니다. 실험 결과, 규칙 기반의 팩트 생성을 통해 검색 정확도를 기존 Markdown 대비 20%p 향상시킨 재현 가능한 결과를 확인했습니다.
핵심 포인트
- 단순 Q&A 변환은 검색 성능 향상에 미미한 영향(3%p)을 미침
- 5가지 프롬프트 규칙 적용 시 검색 정밀도가 92%까지 대폭 향상됨
- 팩트 자체의 완결성과 검색 엔진용 토큰(식별자 등)의 일치성이 핵심
- 하이브리드 검색 인프라가 키워드 매칭 효과를 극대화함

문서를 Q&A 쌍으로 변환하면 검색 정밀도가 향상될 것이라 기대했지만, 효과는 거의 없었습니다.
동일한 소스 문서로부터 3개의 지식 베이스(Knowledge Base)를 구축하고, 각각에 동일한 12개의 질문을 3회씩 던졌습니다.
- 가공되지 않은 Markdown 청크 (Markdown Chunk):
72% - 범용 프롬프트를 통한 Q&A 팩트 (Q&A Fact):
75% - 검색을 의식한 프롬프트를 통한 Q&A 팩트:
92%
이 '75%'라는 숫자에 놀랐습니다. 단순한 Q&A화에 따른 검색 향상은 고작 3포인트에 불과하며, 이는 향상이라기보다 오차 범위에 가깝습니다. 가공되지 않은 Markdown 대비 +20포인트의 향상은 전혀 다른 요소, 즉 '팩트 생성 시 적용된 5가지 프롬프트 설계 규칙'으로부터 비롯되었습니다. 이 규칙들은 '각 팩트는 그 자체만으로 완벽하게 답변을 성립시켜야 한다', '검색 엔진이 필요로 하는 토큰(서비스명, 파라미터명, 식별자)은 소스에서 인덱싱되는 팩트로 토씨 하나 틀리지 않고 그대로 이어져야 한다'라는 두 가지 생각으로 집약됩니다.
본고는 그 검색 정밀도 향상이 실제로 어디에서 얻어졌는지를 보여주는, 재현 가능한 기록입니다.
모든 프롬프트, 팩트, 챗봇의 응답을 포함한 완전한 리포지토리는 이곳에서 공개하고 있습니다: github.com/HidekiMori/rag-accordion-demo (MIT 라이선스).
1. 셋업 (Setup)
소스 문서로는 5개의 API 서비스(StructFlow, RefineLoop, RenderOCR, CastDoc, ExtractDoc)에 대해 설명한 821행의 Markdown 파일인 'LDX hub 개발자 포털'을 사용했습니다. 이 문서에는 설명문, 파라미터 표, JSON 예시, 그리고 횡단적인 '에러(Errors)' 섹션이 혼재되어 있습니다. 대규모 검색 테스트를 수행하기에 충분한 길이이며, 파이프라인의 각 파트가 어떻게 작동하는지 테스트하기에 충분한 구조를 가지고 있습니다.
이 문서로부터 다음과 같은 3개의 지식 베이스를 구축했습니다.
mdx_direct: 가공되지 않은 Markdown.\n\n으로 청크화(1청크당 1024 토큰, 50 토큰의 오버랩).naive_facts: '이 문서에서 Q&A 쌍을 추출하라'는 범용 프롬프트에 의해 생성된 Q&A 팩트. 1개의 JSONL 행당 1개의 팩트로, 총 78개의 팩트.best_facts: 5가지 명시적인 규칙을 설정한 Stage 2 프롬프트에 의해 생성된 Q&A 팩트. 총 82개의 팩트.
이 세 가지를 모두 지식 베이스로서 Dify Cloud에 로드했습니다. 임베딩 모델(OpenAI text-embedding-3-large, 차원수는 기본값), 검색 설정(하이브리드 검색, 가중치 스코어: 시맨틱 0.7 / 키워드 0.3, Top K=3), 챗봇용 LLM(OpenAI GPT-5.5), 시스템 프롬프트는 모두 공통입니다. 각 테스트 간에 다른 변수는 어떤 지식 베이스를 어태치(Attach)했는가 하는 점뿐입니다.
Dify Cloud의 벡터 스토리지(Vector Storage)는 TiDB Cloud Starter 위에서 구동되고 있습니다(Dify 통합에 관한 PingCAP의 도입 사례 참조). 이번에 TiDB에 대해 직접 SQL을 작성한 것은 아닙니다. 여기서 중요한 것은 TiDB 상의 하이브리드 검색이야말로, 후술할 규칙 5의 키워드 측 효과를 결과로 나타나게 한 인프라라는 사실입니다.
Q&A 팩트는 2단계의 StructFlow 파이프라인에 의해 생성되었습니다. StructFlow는 '입력 + 지시 → 구조화된 출력'을 수행하는 범용적인 기능이며, 구조화 도구 그 자체는 아닙니다. 출력의 형태를 결정하는 것은 '지시' 부분입니다. 따라서 동일한 기능이 Stage 1에서는 53개의 독립된 섹션을 생성하고, Stage 2에서는 Q&A 팩트를 생성합니다. 두 스테이지 모두 Google Gemini 3.5 Flash (Temperature 0)로 실행됩니다. Stage 1은 sections 배열을 출력하고, Stage 2는 facts 배열을 출력합니다. 각 배열은 스테이지 사이에 1레코드 1행의 JSONL로 플래튼(Flatten)됩니다. 제5장에서 이 구성을 '아코디언(accordion)'이라고 부르고 있습니다. Stage 1의 프롬프트는 naive_facts와 best_facts
둘 다 동일하며, Stage 2의 프롬프트만 다릅니다.
명칭에 대한 보충 설명입니다. 본 기사에서 "StructFlow"라는 이름은 두 가지 역할로 등장합니다. 하나는 테스트용 코퍼스(LDX hub 개발자 포털)에 문서화되어 있는 "서비스"로서의 역할이고, 다른 하나는 해당 코퍼스에서 Q&A 팩트(fact)를 구축한 "엔진"으로서의 역할입니다. 동일한 도구가 두 가지 컨텍스트에서 사용되고 있습니다. Q&A 팩트의 예시나 테스트용 질문에 "StructFlow"가 등장하는 경우에는 코퍼스 내의 서비스를 가리키며, 파이프라인 설명이나 아코디언 도식에서 등장하는 경우에는 엔진을 가리킵니다.
테스트용 질문은 12개를 준비했습니다. 각각은 특정 검색 과제(직접 검색, 파라미터 리스트에 파묻힌 기본값, 여러 서비스 비교, 코드 블록 내에 숨겨진 엔티티, 캐주얼한 표현, 특정 서비스에 스코프된 횡단적 관심사, 엔드 투 엔드(end-to-end) 워크플로우 통합)를 탐색하도록 설계되었습니다. 각 지식 베이스(knowledge base)에 대해 3회의 독립적인 테스트를 실시했습니다. 12개의 질문 × 3개의 지식 베이스 × 3회의 실행 = 108회의 챗봇 응답을 평가하여 ✅ / ⚠️ / ❌로 채점했습니다.
채점은 실행 전에 준비한 질문별 루브릭(rubric, 평가 기준)에 기반하여 수동으로 진행되었습니다. 기대되는 모든 정보를 포함한 완전한 답변은 ✅, 부분적이거나 불완전한 답변(예: Q12에서 부분적인 실패 경고가 누락된 경우 등)은 ⚠️, 소스에 답이 명확히 존재함에도 틀린 답변을 하거나 "문서에 없습니다"라고 거부한 경우는 ❌로 처리했습니다. 전체 퍼센티지 계산에서는 ✅를 정답으로 간주하고, ⚠️와 ❌를 모두 오답으로 취급하여 부분 점수는 전혀 부여하지 않았습니다. 질문별 경향과 총 108회 분량의 챗봇 실제 응답 로그는 리포지토리의 results/에 커밋되어 있으므로, 루브릭에 동의하지 않는다면 누구나 재채점할 수 있습니다.
2. 결과 하이라이트
| 지식 베이스 | 정확도 | mdx_direct와의 비교 |
|---|---|---|
mdx_direct | 72% | — (베이스라인) |
naive_facts | 75% | +3 pt (운용상 오차 범위) |
best_facts | 92% | +20 pt (naive 대비 +17 pt) |
단순한 Q&A화로 인한 +3포인트는 실질적으로 오차입니다. 질문별 내역을 보면 그 이유를 알 수 있습니다.
| 질문 | mdx_direct | naive_facts | 인스턴스 변화 |
|---|---|---|---|
Q2 (max_revisions의 기본값) | 0/3 | 3/3 | +3 (진정한 개선) |
| Q6 (LDX hub는 무료인가?) | 0/3 | 3/3 | +3 (진정한 개선) |
| Q4 (RenderOCR vs CastDoc) | 3/3 | 0/3 | −3 (진정한 악화) |
Q12 (completed가 StructFlow에서 의미하는 것) | 3/3 | 0/3 | −3 (진정한 악화) |
| Q10 (스캔된 계약서의 워크플로우) | 2/3 | 3/3 | +1 (mdx 실행 시의 변동성) |
진정한 의미에서의 개선과 악화(fix/break)인 4개의 쌍은 인스턴스 단위로 보면 완전히 상쇄됩니다 (+3 +3 -3 -3 = 0). +3포인트라는 차분 전체(naive 27/36 vs mdx 26/36)는 Q10 단독으로 인해 발생한 것입니다. Q10에서는 mdx_direct가 1회 실행에서 ⚠️로 떨어졌던 반면(해당 실행에서는 ExtractDoc → StructFlow 체인이 무너졌습니다), naive_facts는 3회 실행 모두에서 ✅를 유지했습니다. 이 "+1"의 인스턴스는 가공되지 않은 Markdown 측의 실행 시 변동성(run variance)에 의한 것이며, Q&A 변환에 따른 검색 정확도 향상이 아닙니다. 단순한 Q&A화는 단지 실패하는 지점을 섞어 놓았을 뿐이며(Q2/Q6가 개선되고, Q4/Q12가 악화됨), 차감하면 제로가 됩니다. 즉, 헤드라인의 +3포인트는 Q10에서의 단순한 노이즈입니다.
best_facts는 위 4개의 ✅를 모두 유지한 상태에서, 추가로 6개의 질문을 부분 점수에서 만점으로 끌어올렸습니다. Stage 1의 섹션 구분, LLM, 검색 시스템은 모두 동일합니다. naive_facts
와 best_facts의 차이는 Stage 2의 프롬프트뿐입니다.
이 구조적인 차이는 3개의 지식 베이스(Knowledge Base)에 대한 3회의 실행 모두에서 예외 없이 재현되었습니다. 이는 통계적인 노이즈가 아닙니다.
그렇다고 해서 이 논문이 벤치마크를 목적으로 하는 것은 아닙니다. 현실적인 하나의 코퍼스(Corpus)를 대상으로, 검색이 실패하는 특정 패턴을 탐색하기 위해 의도적으로 선별한 질문 세트를 사용한 제어된 엔지니어링 검증입니다. 아래에서 설명할 5가지 규칙이 서로 다른 문서 구조, 검색 백엔드(Search Backend), 청킹(Chunking) 전략 하에서도 통용될지는 여러분의 코퍼스를 사용하여 리포지토리에서 직접 확인해야 할 성격의 것이며, 이 단일 검증만으로 단언할 수는 없습니다.
3. 5가지 규칙
이 규칙들은 검색을 의도한 Stage 2 프롬프트에 포함된 규칙들입니다. 각 규칙은 특정 실패 모드(Failure Mode)를 방지하는 역할을 합니다.
규칙 1 — 자기 완결적 답변
모든 팩트(Fact)는 다른 팩트를 읽지 않고도 단독으로 질문에 완전히 답할 수 있어야 합니다.
하이브리드 검색(Hybrid Search)은 상위 3개의 청크(Chunk)를 반환합니다. 만약 답이 여러 팩트에 걸쳐 분할되어 있다면(예: "이것이 기본값입니다" + "파라미터는 max_revisions입니다" + "RefineLoop는 이러한 설정을 수용합니다" 등), 검색 엔진은 이 3개를 모두 찾아내야 합니다. 하지만 항상 그렇게 되는 것은 아닙니다. 각 팩트가 전체 내용을 한 곳에 보유하고 있다면, 검색 엔진은 그 하나의 엔트리(Entry)에 도달하는 것만으로 충분합니다.
실제 예시: best_facts에서의 "작업 상태(Job Status)에는 어떤 것들이 있습니까?"라는 엔트리에는 모든 값(queued, processing, completed, failed)과 각각의 의미가 하나의 팩트 안에 명시되어 있습니다.
규칙 2 — 개발자 친화적인 질문 어구
실제 사용자가 쿼리(Query)로 입력할 법한 자연스러운 어구를 사용합니다("~하려면 어떻게 해야 하나요?", "~의 기본값은 무엇인가요?", "X는 어떤 포맷을 지원하나요?" 등).
임베딩 모델(Embeddings)은 의미적 유사성(Semantic Similarity)을 높게 평가합니다. 생성된 질문이 실제 사용자 쿼리와 유사한 어구로 되어 있다면, 벡터 공간(Vector Space) 상에서 가까운 위치에 배치됩니다. 선언적인 요약("~의 설정에 대해 설명하기" 등)은 검색 시 실제로 전송되는 쿼리와는 거리가 멀어지게 됩니다.
이 규칙은 간과되기 쉽습니다. 하지만 생성 시 추가 비용이 들지 않으면서도 모든 검색에서 보상을 가져다줍니다.
증거에 관한 보충: 이번 검증에서 규칙 2의 효과를 단독으로 분리하여 입증한 것은 아닙니다. naive_facts와 best_facts 모두 이미 "질문 형태의 질문"을 생성하고 있기 때문에, 이 규칙이 여기서 직접 테스트되고 있는 것은 아닙니다. 그럼에도 이 규칙을 포함한 이유는, "요약"이나 "중요 포인트"를 요구하는 프롬프트에서 이 실패 모드(질문 형식이 아닌 선언적인 요약이 되어버리는 현상)가 빈번하게 발생하며, 일단 발생하면 검색 정확도가 저하되기 때문입니다.
규칙 3 — 기술적 식별자의 완전한 보존
API 이름, 엔드포인트(Endpoint) 경로, 파라미터(Parameter) 이름, Enum 값, 코드 스니펫(Code Snippet) 등은 토씨 하나 틀리지 않고 그대로(Verbatim) 유지합니다. 의역하지 않습니다.
하이브리드 검색에는 벡터(Vector)와 키워드(Keyword)라는 두 가지 채널이 있습니다. 키워드 채널은 토큰의 완전 일치에 의존합니다. max_revisions, results[].status, ki/ocr 등의 문자열은 완전히 일치하는 형태로 나타날 때만 기여합니다. 만약 의역된다면(예: "리비전 수 파라미터"), 키워드 채널로부터의 기여는 완전히 상실됩니다.
벡터 채널이 이를 보완해 줄 수도 있지만, 많은 경우 그렇지 않습니다. 결과적으로 합산 점수가 컷오프(Cutoff) 값을 밑돌게 되어, 해당 팩트가 상위 3개 안에 들지 못하게 됩니다.
규칙 4 — 횡단적 정보에서의 서비스 특화 팩트화
어떤 섹션이 특정 서비스와 관련된 에러, 상태 또는 동작에 대해 설명하고 있는 경우, 질문과 답변 모두에 "해당 서비스 이름"을 포함한 팩트를 생성합니다.
소스 문서에서는 횡단적인 정보가 그것이 영향을 미치는 각 서비스의 설명으로부터 떨어져 독립된 섹션에 배치되는 경우가 자주 있습니다. 이번 소스 문서에는 ## Errors (에러)라는 섹션이 있으며, 그곳에는 다음과 같이 기재되어 있습니다.
StructFlow의 작업(job)은 일부 개별 레코드가 failed (실패)로 표시된 상태에서도 status: completed (완료)가 될 수 있습니다. 부분적인 실패를 감지하려면 항상 summary.failed_count와 각 results[].status를 확인하십시오.
섹션의 헤더는 단순한 "Errors"이며, "StructFlow errors"가 아닙니다. 범용적인 Q&A 추출 프롬프트(prompt)로는 이러한 콘텐츠를 일관되게 처리할 수 없습니다. 이번 naive_facts 실행에서는 부분적인 실패에 관한 콘텐츠가 완전히 스킵되었습니다 (grep "partial failure" data/facts_naive.txt를 실행해도 아무것도 반환되지 않습니다).
이 상태에서는 검색 파이프라인이 "StructFlow 작업에서 completed는 무엇을 의미합니까?"라는 질문에 답할 수 없습니다. 왜냐하면 인덱싱(indexing)된 팩트(fact) 중에 completed, StructFlow, 그리고 "부분적인 실패"를 하나의 행으로 연결하는 것이 존재하지 않기 때문입니다. 이것이 Q12이며, naive_facts는 3번의 실행 모두에서 정답 수 0이라는 결과로 끝났습니다.
검색을 의식한 버전에서는 Stage 2에서 강제적으로 서비스로 스코프(scope)된 변형을 생성하게 합니다. "완료된 StructFlow 작업에서 부분적인 실패를 어떻게 확인해야 합니까?"라는 질문에 대해, 답변 텍스트에서 StructFlow를 명확하게 언급하고 모든 식별자를 토씨 하나 틀리지 않고 유지한 팩트입니다. 이를 통해 Q12는 모든 실행에서 깔끔하게 해결되었습니다.
규칙 5 — 의도적인 키워드 설계
각 팩트에는 서비스 이름, 파라미터(parameter), 컨셉(concept) 등 3~7개의 짧은 토큰으로 구성된 keywords 필드를 갖게 합니다. 이 토큰들은 하이브리드 검색(hybrid search)의 키워드 채널에 대한 직접적인 탄약이 됩니다.
명시적인 지시가 없는 경우, LLM은 모호하고 설명적인 키워드를 생성하는 경향이 있습니다. wait 파라미터에 관한 범용 팩트에서는 키워드가 ["wait", "connection", "timeout", "polling"]이 되어버렸습니다. LDX hub 고유의 것은 wait뿐이며, 나머지는 범용적인 네트워크 용어입니다. 검색을 의식한 버전에서는 ["StructFlow", "curl", "job_id", "wait"]와 같이 서비스 이름이나 리소스 타입(resource type)을 포함하여 유지하도록 했습니다.
이것이 이번 검증에서 Q4를 해결한 규칙입니다. 범용 프롬프트에서도 RenderOCR의 주요 기능에 관한 팩트는 생성되었지만, 그 키워드는 ["OCR", "scanned PDF", "Office files", "layout preservation", "languages"]였으며, 리터럴(literal)로서의 RenderOCR 토큰이 포함되어 있지 않았습니다. 사용자가 "RenderOCR vs CastDoc?"라고 입력했을 때, 키워드 채널의 기여도가 본래보다 대폭 낮아졌고, 벡터 채널(vector channel) 단독으로는 이 팩트를 안정적으로 상위 3위 안에 올릴 수 없었습니다. 검색을 의식한 버전에서는 RenderOCR과 관련된 모든 팩트의 키워드에 RenderOCR을 포함하고 있습니다. 결과적으로 이 팩트는 확실하게 검색되게 되었습니다.
각 규칙의 예시나 실패 모드(failure mode)를 포함한 완전한 프롬프트는 prompt_engineering.md에 기재되어 있습니다.
4. 어떤 프롬프트로도 해결할 수 없었던 한 가지 질문
Q8("OCR에는 어떤 엔진이 사용 가능합니까?")은 3개의 지식 베이스(knowledge base) 모두에서 실패한 유일한 질문입니다. 소스에서는 "KI OCR"에 대해 157행의 설명문 불렛 포인트(Powered by KI OCR, a battle-tested enterprise OCR engine)에서 단 한 번 언급되었으며, 엔진 ID인 ki/ocr
644행의 GET /renderocr/engines JSON 응답 예시 안에 파묻혀 있습니다. 두 위치 모두 쿼리에 대해 안정적으로 히트되지 않습니다. 설명문의 불렛 포인트(bullet points)는 RenderOCR의 다른 기능적 특징들에 의해 압도당하며, JSON 코드 블록은 단순한 청킹 (Chunking)이나 Q&A 생성 모두에 있어 소화하기 어려운 형태입니다.
어떤 프롬프트도 이를 구제할 수 없었습니다. 이번 Q&A 변환에서 두 형태 모두 검색 가능한 "OCR 엔진 이름이라는 사실 (Fact)"로 격상되지 못했습니다. Stage 2의 최적화 프롬프트는 식별자 보존(규칙 3)에 대해서는 이해하고 있지만, 코드 블록에서 엔티티 (Entity)를 추출하여 설명적인 문맥의 사실로 격상시키기 위한 명확한 지시는 주어지지 않았습니다.
이것이 여기서 제시한 5가지 규칙으로 대응할 수 있는 솔직한 한계입니다. 결손된 데이터는 어찌할 도리가 없습니다. 향후 과제로는 명시적인 코드 블록으로부터의 엔티티 추출이나, JSON에 내장된 식별자를 독자적인 사실로 격상시키는 Stage 2의 추가 패스 (Pass) 등이 고려될 수 있습니다.
5. 아코디언 패턴 (Accordion Pattern)
임의의 구조화된 소스로부터 Q&A 사실(Fact)마다 하나의 JSONL 행을 생성하는 메커니즘은 다음과 같습니다.
문서
→ Stage 1 (세그멘터): { sections: [...] }
→ 플래트닝 (Flattening): JSONL로 1개 섹션당 1행
...

Dify로 구현된 동일한 형태의 파이프라인입니다. Stage 1 (S1) 및 Stage 2 (S2)의 StructFlow 노드는 모두 Google Gemini 3.5 Flash로 동작합니다. Flatten (플래트닝) 노드는 각 스테이지의 배열 출력을 1레코드 1행의 JSONL로 변환합니다. Save 노드는 확인용 중간 파일을 내보냅니다. 완전한 YAML은 리포지토리의 workflows/dify-accordion.yml에 있습니다.
"아코디언"이라는 이름은 그 형태에서 유래되었습니다. "1개 문서 → N개 섹션 → M개 사실"이라는 흐름 속에서, 각 스테이지 다음에 배열 출력을 1레코드 1행의 JSONL로 전개하는 플래트닝 단계를 끼워 넣기 때문입니다. 두 스테이지는 StructFlow (LDX hub의 구조화 추출 서비스)의 작업으로 실행됩니다. 섹션의 경계를 정의할 수 있는 텍스트라면 HTML, PDF의 장 (Chapter), DOCX의 헤딩 스타일 등 어떤 텍스트라도 동일한 메커니즘이 작동합니다.
이 데모가 보여주는 것은 이 메커니즘 자체는 "중립적"이라는 점입니다. 범용적인 Stage 2 프롬프트와 결합했을 때, 아코디언이 생성하는 사실의 성능은 단순한 청킹과 거의 동등합니다. 단순한 Q&A화에 대한 "+17포인트 향상"은 메커니즘에 의한 것이 아닙니다. Stage 2 프롬프트라는 "지렛대 (Lever)"에 의한 것입니다.
이 부분이 저에게 가장 놀라운 지점이었습니다. 세그멘테이션을 한 뒤에 추출한다는 2단계 설계 메커니즘 자체가 더 큰 역할을 할 것이라고 예상했기 때문입니다. 실제로는 그렇지 않았으며, Stage 2의 프롬프트가 지렛대 역할을 하고 있었습니다.
6. 실무적 의미
RAG에 관한 많은 조언은 어떤 임베딩 모델을 사용할지, 어떤 유사도 함수를 사용할지, 청크 크기를 어떻게 할지, Top-K 윈도우를 어느 정도로 할지와 같은 "검색 측면"에 초점을 맞추고 있습니다. 이러한 결정들도 중요하지만, 이는 모두 "이미 인덱싱된 사실"에 대해 작용하는 것입니다. 그 사실의 형태(각 사실이 무엇을 말하고, 어떤 토큰을 가지며, 질문을 어떻게 표현하는지)는 사실이 생성될 때 결정됩니다. 그리고 그 결정의 중요성이 바로 이번 검증을 통해 확인한 것입니다.
자신의 문서에 대해 Q&A 형식의 RAG를 구축하려는 분들을 위한 세 가지 중요한 포인트는 다음과 같습니다.
- Q&A화가 자동으로 원본 청크 (Chunk)보다 우수할 것이라고 오해하지 말 것. 우수한 경우도 있지만, 이는 검색에 실제로 필요한 정보가 변환 시점에 유지되어 있을 때에만 한정됩니다. 범용적인 "Q&A 쌍을 추출하라"는 프롬프트는 적절하게 청킹 (Chunking)된 Markdown에 대해 거의 대등한 수준에 불과합니다.
- 키워드 설계가 키워드 채널 (Keyword Channel)의 향상을 견인한다. 앞서 언급한 규칙 5입니다. 키워드 필드에 서비스 이름을 넣는 것이 Q4를 해결한 요인이었습니다. 식별자 저장 (규칙 3) 또한 동일한 채널을 타겟팅하고 있으며, 모델이 재표현 (Paraphrasing)을 수행할 때 중요한 역할 (Load-bearing)을 하지만, 이번 Temperature 0 설정에서는 모델이 자발적으로 식별자를 저장했기 때문에 견인책이라기보다는 보험으로서 기능했습니다. 두 규칙 모두 생성 시 추가 비용이 들지 않으며, Stage 2 프롬프트에 포함시키기도 쉽습니다.
- 횡단적인 섹션은 각각의 서비스에 대해 재앵커링 (Re-anchoring, 재결합)할 필요가 있다. 규칙 4입니다. 4개의 제품에 대해 언급하고 있는
## Errors섹션은 하나의 중립적인 사실 (Fact)이 아니라, 4개의 제품 각각에 스코프 (Scope)된 사실을 생성해야 합니다. 그렇지 않으면 서비스 이름과 에러를 동일한 행으로 연결하는 인덱싱된 사실이 존재하지 않게 되어, 쿼리 실행 시 검색 시스템이 그 간극을 메울 수 없게 됩니다.
이러한 규칙은 약품명, 법령 코드, 모델 번호, CSS 클래스 라이브러리 등 특징적인 식별자를 가진 모든 도메인에 일반화할 수 있습니다. 검색 시스템은 이미 문자열을 매칭하는 방법을 알고 있습니다. 생성 시 프롬프트의 역할은 단지 그 "문자열이 문자열 그대로 유지되도록" 보장하는 것뿐입니다.
7. 미결 과제 — 프롬프트가 다음 병목 구간이 된다
5가지 규칙은 "무엇이 작동하는가"를 설명하지만, "그것을 어떻게 적용하는가"까지는 설명하지 않습니다.
본 검증에서 사용한 Stage 2 최적화 프롬프트는 LDX hub에 강력하게 특화되어 있습니다. max_revisions가 파라미터라는 점, RenderOCR과 CastDoc이 별개의 서비스라는 점, ## Errors가 횡단적인 섹션이라는 점, StructFlow의 작업이 failed 레코드를 포함한 채 completed 상태가 된다는 점 등이 포함되어 있습니다. 이러한 결정은 모두 해당 도메인을 이미 이해하고 있는 인간에 의해 프롬프트에 수동으로 작성된 것입니다.
하나의 도메인에 대해서는 이것으로 충분하지만, 이 방식으로는 범용화할 수 없습니다.
예를 들어, 약품 라벨링, 법령 코드, 또는 CSS 클래스 라이브러리를 위해 이 패턴을 채택하려는 팀은 자신들만의 서비스 이름, 고유한 횡단적 관심사, 저장해야 할 고유한 식별자를 갖춘 "자신들만의 Stage 2 프롬프트"를 작성해야 합니다. 그 노하우는 LLM 안에 있는 것이 아니라, 프롬프트 작성자의 머릿속에 있습니다.
이 문제를 해결하기 위한 두 가지 접근 방식이 있지만, 어느 쪽이 옳은지는 아직 검증되지 않았습니다.
첫 번째는 방법론을 규칙화하는 것입니다. 체크리스트를 작성하고 (리포지토리의 prompt_engineering.md에는 이미 준비되어 있습니다), 이 패턴을 사용하는 엔지니어가 자신의 도메인에 맞춰 이를 채워 넣을 것이라고 신뢰하는 것입니다. 이는 문서를 발판 (Scaffolding)으로 삼는 접근 방식입니다. 유능한 테크니컬 라이터 (Technical Writer)가 있는 팀에는 효과적이지만, 그렇지 않은 팀에는 큰 효과가 없습니다.
두 번째는 프롬프트 자체를 StructFlow의 출력물로 만드는 것입니다. 소스 문서를 입력받아 해당 도메인용 Stage 2 프롬프트를 생성하는 "Stage 0"를 추가합니다. 아코디언은 한 단계 더 확장됩니다: "1개 문서 → 1개 프롬프트 (Stage 0) → N개 섹션 (Stage 1) → M개 사실 (Stage 2)". 이 Stage 0 프롬프트는 시스템 내에서 유일하게 도메인에 의존하지 않는 프롬프트가 되며, 모든 소스에서 재사용 가능해집니다.
저는 Stage 0가 현실적인 가능성이라고 생각하지만, 이 부분은 아직 가설 단계이며 검증하지 않았습니다. 아코디언 메커니즘 (Accordion mechanism)은 하나의 StructFlow 단계가 하나의 입력을 많은 구조화된 출력으로 변환할 수 있음을 이미 증명했습니다. "다음 StructFlow 단계를 위한 프롬프트"가 그 출력 중 하나가 되지 못할 원리적인 이유는 없습니다. 이것이 검색 품질 측면에서 수동으로 작성된 도메인 특화 프롬프트 (domain-specific prompt)를 실제로 능가할지는 아직 해결되지 않은 문제입니다. 이것이 제가 다음에 테스트하고 싶은 부분입니다.
8. 마치며
리포지토리 (Repository)는 MIT 라이선스로 공개되어 있으며, Dify의 워크플로우 YAML 파일도 포함되어 있습니다. 이를 Dify에 임포트(Import)하고, 여러분의 문서를 넣어 다시 실행해 보세요. 이 글 전체의 가장 짧은 요약은 두 가지 Stage 2 프롬프트(stage2_best.md와 stage2_naive.md)의 차이점을 비교해 보는 것입니다. 이 글의 나머지 모든 부분은 그 차이점이 무엇을 하고 있는지 파헤치는 내용입니다.
Dify의 지식 (Knowledge) 기능을 통해 이용하는 TiDB Cloud는, 벡터 데이터베이스 (Vector database)를 처음부터 구축할 필요 없이 이번 검증을 재현 가능하게 만들어 준 숨은 인프라스트럭처 (Infrastructure)입니다.
이 글의 영어 버전: 72% -> 75% -> 92%: a reproducible RAG validation (dev.to)
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기