💡 전체 작동 예제가 GitHub에 있습니다: parse-docx-to-markdown-chunks-dotnet
이전 방식은 고통스러웠다
헤딩 기반 Markdown 청킹은 .NET 검색 전처리 경로로, Office 및 PDF 파일을 섹션 크기의 텍스트로 변환한 뒤 임베딩합니다. 이 과정을 생략하면 팀이 전체 Word 파일을 하나의 벡터로 덤프하는 경우가 많습니다. 표지 문단과 의무 조항이 하나의 임베딩을 공유하게 되어 코사인 검색이 잘못된 섹션을 순위에 올립니다. 저는 3페이지 분량의 내부 가이드에서 이 문제를 겪었습니다: 모든 쿼리가 제목 블록을 먼저 반환했는데, 인덱스가 저자들이 이미 작성한 # / ## 구조를 전혀 보지 못했기 때문입니다.
DOCX를 Markdown으로 직접 변환하는 작업은 Open XML 스타일을 탐색하고, 리스트 레벨을 매핑하며, 다음 업로드가 PDF일 때 포맷된 Markdown 경로가 null을 반환하면 여전히 실패합니다. 고정 크기 토큰 윈도우는 이러한 헤딩을 무시합니다. 밤새 처리되는 코퍼스는 페이지 수나 크기에 대한 사전 검사가 없기 때문에 빈 껍데기와 거대한 매뉴얼이 뒤섞이게 됩니다.
전체 파일 임베딩의 실제 비용: 잘못된 섹션 답변, 쓸모없는 업로드에 대한 추출 비용 낭비, 그리고 배치가 나쁠 때 운영자가 열어볼 수 있는
chunk-01.md아티팩트가 없음.
더 나은 방법이 있다
GroupDocs.Parser for .NET은 이 혼란을 네 가지 집중 호출로 대체합니다: GetDocumentInfo, GetFormattedText(FormattedTextMode.Markdown), Markdown이 null일 때 GetText, 그리고 chunk-NN.md를 작성하는 작은 헤딩 워커. 동일한 Parser 타입이 Word DOCX 파싱과 PDF 텍스트 폴백을 모두 처리하므로 하나의 C# 서비스 경로만 유지하면 됩니다.
필요한 것:
- .NET 8 SDK
- GroupDocs.Parser 26.10.0 (temporary license)
- Word DOCX 파일(샘플은 3페이지 / 14820바이트) 및 선택적으로 PDF
dotnet add package GroupDocs.Parser --version 26.10.0
새로운 방식: 검사, 추출, 폴백, 분할
전환 후 동일한 검색 전처리 예시입니다. parse-docx-to-markdown-chunks-dotnet 콘솔 샘플은 정보, Markdown, PDF 텍스트, 그리고 제공된 DOCX에 대한 네 개의 헤딩 청크를 기록합니다.
Step 1 - GetDocumentInfo 로 게이트
전체 추출 비용을 지불하기 전에 파일 유형, 페이지 수, 크기를 확인합니다.
using var parser = new Parser(sourcePath);
var info = parser.GetDocumentInfo();
var sb = new StringBuilder();
sb.AppendLine($"Source: {Path.GetFileName(sourcePath)}");
sb.AppendLine($"File type: {info?.FileType}");
sb.AppendLine($"Pages: {info?.PageCount}");
sb.AppendLine($"Size: {info?.Size} bytes");
File.WriteAllText(outputPath, sb.ToString());
작업 ID 옆에 이러한 사실을 로그합니다. 샘플 DOCX에서는 Microsoft Word Open XML Document, 3페이지, 14820바이트가 표시됩니다.
Step 2 - GetFormattedText 로 Markdown 우선 사용
Word 및 포맷된 추출을 지원하는 형식에서는 분할기를 위해 ATX 헤딩을 유지합니다.
using var parser = new Parser(sourcePath);
using TextReader? reader = parser.GetFormattedText(
new FormattedTextOptions(FormattedTextMode.Markdown));
if (reader == null)
{
return null;
}
string markdown = reader.ReadToEnd();
File.WriteAllText(outputPath, markdown, Encoding.UTF8);
return markdown;
null은 경로를 전환하라는 신호이며, 오류가 아닙니다. UTF-8로 기록해 청크 도구와 git diff가 읽기 쉬운 상태를 유지합니다.
Step 3 - PDF에 대해 GetText 로 폴백
Markdown 모드가 null을 반환하면 일반 텍스트 레이어로 파이프라인에 파일을 유지합니다.
using var parser = new Parser(sourcePath);
using TextReader? reader = parser.GetText();
if (reader == null)
{
return null;
}
string text = reader.ReadToEnd();
File.WriteAllText(outputPath, text, Encoding.UTF8);
return text;
.txt 파일을 기대하고, 인위적인 # 헤딩은 기대하지 마세요. 샘플 호스트는 PDF 분기에서 Result/sample-pdf.txt를 작성합니다.
Step 4 - # 및 ## 로 Markdown 분할
라인을 순회하고 새 ATX # 또는 ##가 나타날 때 플러시하여 chunk-NN.md를 작성합니다.
var chunks = new List<string>();
var current = new StringBuilder();
using (var reader = new StringReader(markdown))
{
string? line;
while ((line = reader.ReadLine()) != null)
{
bool isHeading = line.StartsWith("# ", StringComparison.Ordinal)
|| line.StartsWith("## ", StringComparison.Ordinal);
if (isHeading && current.Length > 0)
{
chunks.Add(current.ToString().TrimEnd());
current.Clear();
}
current.AppendLine(line);
}
}
먼저 오래된 chunk-*.md 파일을 삭제합니다. 더 깊은 ### 라인은 현재 섹션 안에 머무릅니다. 샘플 DOCX는 Result/chunks/ 아래에 네 개의 파일을 생성합니다.
언제 전체 파일 임베딩을 이 경로로 교체해야 할까?
입력에 Word DOCX와 PDF가 혼합되고 인덱스가 섹션 크기의 Markdown을 필요로 할 때 전체 파일 임베딩을 교체합니다. 먼저 GetDocumentInfo를 실행하고, Office 파일에 대해 Markdown을 시도하며, 포맷된 리더가 null일 때 GetText를 호출하고, # / ## 마커가 존재할 때만 분할합니다. 별도의 청크 정책을 추가하기 전까지는 구조화되지 않은 PDF 텍스트를 그대로 두세요. 이 순서는 샘플 콘솔 앱과 일치하며 운영자를 위한 실패 모드를 명확히 합니다.
운영자를 위한 변경 사항
| 이전 | 이후 |
|---|---|
| 파일당 하나의 임베딩 | 헤딩 블록당 하나의 임베딩 |
| 맞춤형 Open XML 마크다운 | GetFormattedText Markdown 모드 |
| PDF가 null 형식 리더에 의해 처리되지 않음 | GetText가 텍스트 아티팩트를 작성 |
| 간단한 사전 검사 없음 | 페이지 수와 크기 게이트 작동 |
모든 Parser와 TextReader를 Dispose하십시오. 야간 대량 처리 전에 라이선스를 적용하거나 temporary license를 사용하십시오. 업로드는 신뢰할 수 없는 입력으로 간주하고 격리된 워커에서 파싱하십시오.
직접 파일에 적용해 보기
parse-docx-to-markdown-chunks-dotnet 저장소를 클론하고, GroupDocs.Parser 26.10.0을 복원한 뒤 documents/sample.docx에 대해 실행합니다. 그런 다음 동일한 네 가지 메서드를 여러분의 매뉴얼 중 하나에 적용하고 생성된 chunk-*.md 파일을 열어 보세요.