-
Context7 분석 (1) 문서 특화 청킹 전략IT 2026. 3. 31. 21:00
Context7 분석 시리즈
(1) 문서 특화 청킹 전략 ← 현재 글
(2) 코드 스니펫 vs 정보 스니펫
(3) 서버 사이드 리랭킹
(4) 5단계 품질 파이프라인
(5) 다층 품질 스코어링AI가 짜준 코드, 왜 자꾸 안 돌아갈까?
ChatGPT나 Claude에게 "Next.js 미들웨어 만들어줘"라고 부탁해 본 적 있으신가요? 그럴듯한 코드가 나오지만, 실제로 돌리면
import경로가 다르거나, 아예 존재하지 않는 함수를 호출하는 경우가 꽤 있습니다. 이게 바로 환각(Hallucination)입니다.LLM은 학습 데이터가 수개월~수년 전 시점에 고정되어 있기 때문에, 라이브러리가 업데이트되면 옛날 API를 기반으로 코드를 생성합니다. React 19에서 바뀐 훅 패턴, Next.js 15의 새로운 라우팅 규칙 같은 건 학습 데이터에 없으니, 모델이 "그럴듯하게 지어내는" 수밖에 없는 거죠.
Context7은 바로 이 문제를 해결하기 위해 만들어진 서비스입니다. 쿼리가 들어오는 그 순간, 해당 라이브러리의 최신 공식 문서와 코드 예제를 AI의 컨텍스트 창에 직접 주입(inject)해서, 모델이 지어내는 대신 참조하게 만드는 방식입니다.
이 시리즈에서는 Context7의 핵심 설계를 5편에 걸쳐 낱낱이 분석합니다. 1편에서는 전체 아키텍처 개요와, 문서를 어떻게 쪼개는지(청킹 전략)를 다룹니다.
누가, 왜 만들었나
Context7은 서버리스 Redis/Vector/Kafka 플랫폼으로 유명한 Upstash에서 만들었습니다. 2025년 3월에 공개되었고, 현재 GitHub 스타 5만 개를 넘긴 인기 프로젝트입니다. GitHub 저장소는 github.com/upstash/context7입니다.
Upstash가 이걸 만든 이유는 명확합니다. 자사의 Upstash Vector(벡터 검색)와 Upstash Redis(캐싱)를 활용한 레퍼런스 제품이면서, 동시에 모든 개발자가 매일 겪는 "AI가 잘못된 코드를 준다"는 pain point를 정면으로 공략한 것이죠.
Context7의 핵심 아키텍처
Context7은 크게 3개 레이어로 구성됩니다. 여기서 반드시 알아둬야 할 점이 있습니다.
GitHub 저장소에는 "껍데기"만 있다
github.com/upstash/context7의 README에도 명시되어 있습니다: "This repository hosts the MCP server's source code. The supporting components -- API backend, parsing engine, and crawling engine -- are private and not part of this repository."
즉, 이 저장소에 있는 것은 Context7 클라우드 API를 호출하는 클라이언트(프록시)일 뿐, 문서를 파싱하거나 청킹하거나 인덱싱하는 코드는 한 줄도 없습니다. 실제 저장소 구조를 보면:
패키지 실제 하는 일 핵심 코드 packages/mcpMCP 프로토콜을 지원하는 프록시 서버 GET context7.com/api/v2/context호출 (~400줄)packages/sdkTypeScript SDK 클라이언트 동일 API를 감싼 래퍼 packages/clictx7CLI 도구인증 설정 + API 호출 packages/tools-ai-sdkVercel AI SDK 연동 동일 API를 AI SDK 도구로 래핑 MCP 서버의 핵심인
api.ts파일을 보면,searchLibraries()는GET /v2/libs/search를,fetchLibraryContext()는GET /v2/context를 호출할 뿐입니다. 로컬에서 문서를 처리하는 로직은 제로입니다."그러면 이 repo를 클론해서 내 MCP/RAG를 만들 수 있나?"
사실상 불가능합니다. 이 저장소를 포크해서 API 엔드포인트 URL만 자기 서버로 바꿀 수는 있지만, 그 서버에서 돌아갈 백엔드 전체를 직접 만들어야 합니다. 즉:
- 문서 크롤링/수집 엔진
- 마크다운 파싱 & 청킹 엔진 (이번 글에서 다루는 내용)
- 임베딩 생성 + 벡터 DB 저장
- 리랭킹 모델
/v2/libs/search와/v2/context에 해당하는 API 엔드포인트
Context7의 GitHub 저장소에서 얻을 수 있는 것은 MCP 프로토콜 연동 보일러플레이트뿐입니다. 다만, 이 보일러플레이트 자체는 MCP 서버를 직접 만들 때 훌륭한 참고 자료입니다. 구체적으로 세 가지를 배울 수 있습니다:
기술 풀려는 핵심 문제 Context7 코드에서 배울 수 있는 것 MCP SDK
(@modelcontextprotocol/sdk)"AI 에이전트가 내 도구를 호출할 수 있게 하려면 어떤 규격을 따라야 하나?"
→ MCP는 AI 에이전트와 외부 도구 사이의 표준 통신 규격. 이 SDK가 없으면 도구 등록, 파라미터 스키마 정의, 요청/응답 처리를 모두 직접 구현해야 함도구(tool) 정의 방법, 파라미터 스키마 선언, 에러 처리 패턴 stdio / HTTP 트랜스포트 "MCP 서버를 어떤 방식으로 실행하고 연결하나?"
→ stdio는 로컬 프로세스로 실행(Cursor, Claude Code가 직접 프로세스를 띄움). HTTP는 원격 서버로 실행(여러 클라이언트가 네트워크를 통해 접속). 용도에 따라 둘 중 하나를 선택해야 함stdio 모드와 HTTP(StreamableHTTP) 모드를 하나의 코드베이스에서 동시 지원하는 구조 OAuth 인증 "HTTP 모드에서 아무나 내 MCP 서버를 호출하면 안 되는데, 어떻게 인증하나?"
→ stdio는 로컬이라 인증이 불필요하지만, HTTP로 공개 서버를 띄우면 사용자 인증이 필수. OAuth 2.0(RFC 9728)을 통해 토큰 기반 접근 제어를 구현MCP 서버에 OAuth 보호 리소스 메타데이터를 적용하는 실전 구현 예제 RAG 엔진(문서 파싱, 청킹, 임베딩, 리랭킹) 자체는 완전히 별도로 만들어야 하지만, 그 엔진 위에 "AI 에이전트가 호출할 수 있는 MCP 서버 껍데기"를 씌우는 방법은 이 저장소에서 충분히 배울 수 있습니다.
참고로 Context7은 별도의 엔터프라이즈 온프레미스 버전(비공개 Docker 이미지, 유료 라이선스)도 제공합니다. 이 버전에는 파싱/인덱싱 파이프라인이 포함되어 있지만, 오픈소스 저장소와는 별개의 클로즈드 소스 제품입니다.
아키텍처 구조
이런 배경을 알고 아래 아키텍처를 보면, 왜 "얇은 프록시"라고 하는지 명확해집니다:
Two-Step 쿼리 흐름
Context7 MCP 서버는 AI 에이전트에게 두 개의 도구(tool)를 노출합니다:
resolve-library-id: 라이브러리 이름으로 검색 → 라이브러리 ID, 신뢰도 점수, 스니펫 수 반환query-docs: 라이브러리 ID + 질문 → 관련 코드 스니펫과 정보 스니펫 반환
"사용자가 직접 1번을 호출하고, 결과를 보고 2번을 호출하는 건가?" 아닙니다. 사용자는 그냥 평소처럼 질문만 합니다. "Next.js 미들웨어 인증 구현해줘"라고 쓰면, AI 에이전트(Claude, Cursor 등)가 자동으로 1번과 2번을 순서대로 호출합니다. MCP 프로토콜에서 도구 호출은 LLM이 스스로 판단해서 수행하는 것이므로, 사용자 입장에서는 뒤에서 무슨 일이 일어나는지 신경 쓸 필요가 없습니다.
사용자: "Next.js 미들웨어 인증 구현해줘" [AI 에이전트가 자동 수행] 1단계: resolve-library-id("nextjs") 호출 ← {id: "/vercel/next.js", trustScore: 10, snippets: 2847} 2단계: query-docs("/vercel/next.js", "미들웨어 인증 구현") 호출 ← { codeSnippets: [...], infoSnippets: [...] } [AI가 받은 스니펫을 참조하여 코드 생성]왜 하나의 도구로 합치지 않고 두 단계로 나눴을까요? 같은 이름의 라이브러리가 여러 개일 수 있기 때문입니다. 예를 들어 "express"를 검색하면 공식
/expressjs/express외에도 포크나 동명의 다른 프로젝트가 나올 수 있습니다. 1단계에서 후보 목록과 신뢰도 점수를 먼저 받으면, AI 에이전트가 가장 적합한 라이브러리를 선택한 뒤 2단계를 호출할 수 있습니다.여기서 코드 스니펫과 정보 스니펫이 무엇이고 어떻게 인덱싱되는지는 2편에서 자세히 다룹니다. 이번 1편에서는 이 스니펫들이 만들어지는 첫 번째 단계, 즉 문서를 어떻게 쪼개는가(청킹)에 집중합니다.
문서 특화 청킹 전략
Context7의 가장 중요한 기술적 결정 중 하나는 문서 구조를 이해하는 청킹(Chunking)입니다. 일반적인 RAG 시스템은 텍스트를 고정 길이로 자르지만, Context7은 문서의 의미 단위를 존중합니다.
일반 RAG vs Context7의 청킹
차이를 먼저 비교해 보겠습니다. 일반적인 RAG 시스템의 "고정 길이 청킹"은 단순합니다. 텍스트를 500자(또는 500토큰)마다 기계적으로 잘라서 벡터 데이터베이스에 넣는 방식이죠. 빠르고 단순하지만, 치명적인 문제가 있습니다:
- 함수 정의가 중간에 잘려서
function fetchUser(id: string)의 앞부분만 남고, 반환 타입과 구현부는 다음 청크로 넘어가버림 - "미들웨어란 무엇인가?" 설명이 3개 청크에 흩어져서 어떤 청크도 온전한 답을 못 줌
- 코드 예제와 그 코드를 설명하는 문단이 다른 청크에 배치되어 맥락이 끊김
Context7은 이런 문제를 문서의 구조를 이해한 뒤 의미 단위로 쪼개는 방식으로 해결합니다.
청킹 규칙
규칙 내용 왜 이렇게 하나? 기본 분할 단위 H2 제목 기준 문서의 자연스러운 의미 단위. H1은 페이지 전체 제목이고, H3는 너무 세분화됨 목표 크기 ~800 토큰 LLM 컨텍스트 효율과 검색 정확도의 균형점 하드 리밋 1,200 토큰 이보다 커지면 검색 노이즈가 증가 오버사이즈 처리 1순위: 코드 블록이 끝나는 지점에서 분할
2순위: 코드 블록이 없으면 문단 사이에서 분할코드 예제가 중간에 잘리면 함수 정의가 불완전해져서 쓸모없어짐 TOC 감지 링크 비율 50% 초과 시 제거 목차는 검색 노이즈만 유발 중복 제거 콘텐츠 해싱으로 동일 섹션 제거 같은 내용이 여러 문서에 복붙된 경우 대비 먼저 이해해야 할 것: 마크다운을 어떻게 "읽는가"
위 규칙 중 "H2에서 끊자", "코드 블록 뒤에서 끊자"라는 판단을 내리려면, 먼저 마크다운 문서의 구조를 파악해야 합니다. "여기가 제목이고 저기가 코드 블록이다"를 알아야 하니까요. Context7은 이를 위해
unified와remark-parse두 라이브러리를 사용합니다.unified는 텍스트를 처리하는 범용 파이프라인 프레임워크입니다. 비유하자면, unified는 "공장의 컨베이어 벨트"입니다. 벨트 자체는 무엇을 만드는지 모르지만, 그 위에 올려놓는 기계(플러그인)에 따라 마크다운도, HTML도, 자연어도 처리할 수 있습니다. "파싱 → 변환 → 출력"이라는 3단계 파이프라인을 조립할 수 있게 해주는 틀이죠.
remark-parse는 그 컨베이어 벨트에 올려놓는 마크다운 전용 파서 플러그인입니다. 마크다운 텍스트를 읽어서 AST(Abstract Syntax Tree, 추상 구문 트리)로 변환합니다. AST란 문서를 트리 형태로 표현한 것으로, 이런 모습입니다:
Root ├── Heading (depth: 2, text: "미들웨어 인증") ├── Paragraph (text: "미들웨어는 요청이...") ├── Code (lang: "typescript", value: "export function...") └── Paragraph (text: "이 코드에서 주의할 점은...")Context7은 이 트리 구조를 보고 "여기가 H2 제목(
Heading depth:2)이니까 여기서 끊자", "여기는Code노드니까 통째로 보존하자"라는 지능적인 분할 판단을 내립니다. 단순히 "800토큰마다 자르기"가 아니라, 문서의 구조(트리)를 먼저 파악한 뒤 의미 단위로 쪼개는 방식입니다.오버사이즈 처리를 구체적으로
AST 파싱이 왜 필요한지, 오버사이즈 처리 예시를 통해 더 명확히 이해해 봅시다.
아래처럼 하나의 H2 섹션이 1,500 토큰짜리라고 해 봅시다. 1,200 토큰 하드 리밋을 넘었으니 어딘가에서 잘라야 합니다.
## 미들웨어 인증 (이 섹션 전체 = 1,500 토큰) 미들웨어는 요청이 완료되기 전에 실행됩니다... (설명 400토큰) ```typescript export function middleware(request: NextRequest) { const token = request.cookies.get('auth-token') if (!token) { return NextResponse.redirect(new URL('/login', request.url)) } return NextResponse.next() } ``` (코드 블록 600토큰) 이 코드에서 주의할 점은... (후속 설명 500토큰)remark-parse가 이 섹션을 AST로 파싱하면,
Heading→Paragraph→Code→Paragraph노드가 순서대로 나열됩니다. Context7은 이 AST를 훑으면서Code노드가 끝나는 바로 뒤를 분할 지점으로 선택합니다:- 청크 A (1,000토큰): Heading + Paragraph + Code → 코드가 온전히 보존됨
- 청크 B (500토큰): Paragraph (후속 설명)
만약
Code노드가 없고Paragraph노드만 연속된 순수 텍스트 섹션이라면,Paragraph노드 사이의 자연스러운 경계에서 분할합니다. 절대Code노드 중간에서 자르지 않는다는 것이 핵심 원칙입니다. 함수 정의가 반으로 잘린 코드 스니펫은 LLM에게 도움이 되기는커녕 혼란만 줍니다.이렇게 보면 왜 remark-parse로 AST를 먼저 만드는지 이해됩니다. 단순 텍스트 상태에서는 "여기가 코드 블록이 끝나는 곳"을 정확히 판단하기 어렵지만, AST에서는
Code노드의 시작과 끝이 명확하게 구분되기 때문입니다.YAML 프론트매터 활용
YAML 프론트매터(frontmatter)는 마크다운 파일 맨 위에
---로 감싸서 넣는 메타데이터 블록입니다.왜 "front matter"라고 부르나?
이 이름은 출판 용어에서 왔습니다. 종이 책에서 "front matter"란 본문(1장) 시작 전에 오는 페이지들 — 표지, 저작권, 목차, 서문 — 을 뜻합니다. 즉, 본론(body) 앞(front)에 오는 부가 정보(matter)라는 뜻이죠. 2008년 정적 사이트 생성기 Jekyll이 이 개념을 차용해서, 마크다운 파일의 본문 앞에 놓는 메타데이터 블록을 "front matter"라고 불렀습니다. 형식으로 YAML을 채택했기 때문에 "YAML front matter"가 된 것입니다.
마크다운 표준인가?
공식 표준은 아닙니다. 마크다운의 공식 규격인 CommonMark 스펙에는 프론트매터가 포함되어 있지 않습니다. 순수한 CommonMark 파서는
---블록을 수평선(<hr>)으로 해석할 수도 있습니다.하지만 현실적으로는 사실상의 표준(de facto standard)이 되었습니다. 거의 모든 정적 사이트 생성기(Jekyll, Hugo, Gatsby, Next.js, Docusaurus, Astro), 노트 앱(Obsidian, Notion export), 코드 에디터(VS Code), 심지어 GitHub까지 마크다운 파일의 YAML 프론트매터를 인식하고 별도로 렌더링합니다. 따라서 개발자 문서에서 프론트매터를 사용하는 것은 마크다운 양식에 어긋나는 것이 아니라, 생태계가 폭넓게 수용한 확장 관행입니다.
왜 생겨났나?
초기 웹에서는 콘텐츠(글 내용)와 메타정보(제목, 작성일, 카테고리)가 따로 관리되었습니다. 글 내용은 HTML 파일에, 메타정보는 별도 설정 파일이나 데이터베이스에 저장했죠. 파일이 흩어지고, "이 글의 제목이 뭐지?"를 찾으려면 여러 곳을 뒤져야 합니다. "하나의 파일에 메타정보와 본문을 함께 담자"는 아이디어로 프론트매터가 탄생한 것입니다.
Context7은 프론트매터를 어떻게 활용하나?
여기서 오해하기 쉬운 부분이 있습니다. Context7이 문서에 프론트매터를 기입하는 것이 아닙니다. 프론트매터는 라이브러리 개발자(또는 문서 작성자)가 이미 작성해 놓은 것입니다. Next.js, React, Django 등 대부분의 오픈소스 프로젝트는 문서를 마크다운으로 관리하면서 프론트매터에 제목, 설명, 카테고리 등을 넣어둡니다. 예를 들어:
--- title: "Next.js Middleware" description: "HTTP 요청을 가로채 인증, 리다이렉트 등을 처리하는 미들웨어 가이드" sidebar_label: "Middleware" --- # Middleware Middleware allows you to run code before a request is completed...Context7의 Parse 단계에서 이 마크다운 파일을 처리할 때, 본문을 청킹하기 전에 먼저 프론트매터를 분리해서 읽습니다. 그리고 여기서 추출한
title과description을 해당 문서에서 나온 모든 청크의 검색용 메타데이터로 붙입니다.이것이 왜 중요할까요? 청크만 놓고 보면 맥락이 빠질 수 있기 때문입니다. 예를 들어 위 문서를 청킹하면 "미들웨어는 요청이 완료되기 전에 실행됩니다..."라는 청크가 나올 수 있는데, 이 청크만 봐서는 "이게 Next.js 미들웨어에 대한 것인지, Express 미들웨어에 대한 것인지" 알 수 없습니다. 프론트매터에서 추출한
title: "Next.js Middleware"가 메타데이터로 붙어 있으면, 벡터 검색 시 "이 청크는 Next.js 미들웨어에 관한 문서에서 나온 것"이라는 문맥이 함께 전달되어 검색 정확도가 올라갑니다.정리하면: 프론트매터는 라이브러리 개발자가 이미 써놓은 것이고, Context7은 그것을 읽어서 청크에 메타데이터로 부착하는 역할입니다.
정리 & 다음 편 예고
Context7의 청킹 전략 핵심을 요약하면:
- H2 제목 기준으로 문서의 의미 단위를 존중하며 분할
- ~800 토큰 목표, 1,200 토큰 하드 리밋으로 LLM 효율과 검색 정확도를 균형
- 오버사이즈 시 코드 블록 경계를 최우선으로 존중 (코드 중간에서 절대 자르지 않음)
- unified + remark-parse로 마크다운을 AST 트리로 파싱한 뒤 구조적으로 분할
- YAML 프론트매터에서 검색용 메타데이터 추출
다음 2편에서는 이렇게 만들어진 청크가 코드 스니펫과 정보 스니펫이라는 두 가지 타입으로 어떻게 분류되고 인덱싱되는지를 다룹니다.
Context7 분석 시리즈
(1) 문서 특화 청킹 전략 ← 현재 글
(2) 코드 스니펫 vs 정보 스니펫
(3) 서버 사이드 리랭킹
(4) 5단계 품질 파이프라인
(5) 다층 품질 스코어링
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
로컬 VLM으로 가족사진 3만 장 분석하기 — 열흘간의 대장정 (0) 2026.04.03 Context7 분석 (5) 다층 품질 스코어링 (1) 2026.04.02 Context7 분석 (4) 5단계 품질 파이프라인 (0) 2026.04.01 Context7 분석 (3) 서버 사이드 리랭킹 (0) 2026.04.01 Context7 분석 (2) 코드 스니펫 vs 정보 스니펫 (0) 2026.03.31 모니터 없는 서버에서 브라우저를 띄우는 법 — Xvfb와 Playwright의 만남 (0) 2026.03.30 3,200개 청크에 맥락을 심다 — Contextual Retrieval 최적화 삽질기 (0) 2026.03.29 로컬 RAG 시스템의 두뇌 삼형제 — Ollama 모델 3종 역할 분담기 (0) 2026.03.28 RAG 성능 측정의 핵심: RAGAS Ground Truth 준비 완벽 가이드 (0) 2026.03.27 RAGAS로 RAG 시스템 평가하기 — 지표별 의미와 Python 실전 사용법 (0) 2026.03.27