주요 컨텐츠로 이동

코드 기반 지식 도우미 구축

MLflow를 사용한 청킹 전략 평가

Building a Knowledge Assistant over Code

발행일: 2026년 3월 23일

공학2 min read

작성자: Daniel Liden

Summary

  • 코드에 대한 RAG는 고유한 청킹 과제가 있습니다. 함수 본문 중간에서 분할하거나 구조적 컨텍스트를 삭제하면 올바른 파일을 찾더라도 검색 품질이 저하됩니다.
  • Databricks Knowledge Assistant에 사용된 세 가지 청킹 전략을 체계적으로 비교하기 위해 내장 및 사용자 지정 LLM 심사관을 갖춘 MLflow의 GenAI 평가 프레임워크를 사용했습니다.
  • 평가 프로세스 자체가 주요 교훈이었습니다. 구조화된 평가 데이터셋, 추적 가능한 결과, 실제로 중요한 것에 맞춰진 사용자 지정 LLM 심사관이 RAG 반복을 실용적으로 만드는 요소입니다.

개발자가 새로운 프로젝트에 참여하거나 익숙하지 않은 코드베이스에서 작업해야 할 때, Databricks Knowledge Assistant와 같은 지식 도우미는 코드에 대한 자연어 질문에 답하여 개발자가 빠르게 적응하도록 돕습니다. 하지만 답변의 품질은 소스 코드와 주변 컨텍스트가 어떻게 준비되고 추가되었는지에 크게 좌우됩니다. 한 가지 중요한 요소는 청킹(chunking)입니다. 즉, 인덱싱 및 검색을 위해 소스 파일을 어떻게 조각으로 나눌 것인가 하는 문제입니다. 코드는 이 과정을 복잡하게 만듭니다. 함수 본문을 중간에 자르거나 클래스 컨텍스트를 제거하면, 능력이 뛰어난 도우미조차도 코드에 대한 질문에 답하는 데 어려움을 겪을 것입니다.

저희는 Casper’s Kitchens 데모 GitHub 리포지토리에 세 가지 지식 도우미를 구축했습니다. 각 도우미는 단순한 고정 크기 기준선부터 코드를 구문 구성 요소로 구문 분석하는 구조 인식 접근 방식까지, 서로 다른 청킹 전략을 사용했습니다. 이 리포지토리는 Databricks에서 유령 주방 비즈니스를 시뮬레이션하며, Lakeflow 파이프라인, DSPy 에이전트, Databricks Asset Bundles(DAB)를 포함한 광범위한 기능을 사용하고, 마크다운 파일과 노트북 셀에 문서를 포함합니다. 파일 간 종속성, 혼합 파일 형식, 도메인별 패턴은 능력이 뛰어난 지식 도우미가 큰 도움이 될 만한 프로젝트 유형을 만듭니다.

이 게시물에서는 코드 작업이 일반적인 비즈니스 문서 작업과 어떻게 다른지, 각 청킹 전략을 Databricks Knowledge Assistant로 어떻게 배포했는지, 그리고 MLflow의 평가 프레임워크를 사용하여 어떻게 비교했는지 살펴봅니다. 모든 코드는 여기에서 찾을 수 있습니다.

MLflow evaluation framework
Casper’s Kitchens 리포지토리를 배포하는 방법에 대해 Knowledge Assistant에게 질문하기

지식 도우미 작동 방식 (그리고 코드가 다른 이유)

내부적으로 지식 도우미는 다양한 형태의 검색 증강 생성(RAG)을 사용합니다. 관련 소스 데이터 조각을 검색하고, 종종 벡터 검색 인덱스에서 가져와, 사용자 쿼리에 대한 답변 생성을 위한 컨텍스트로 대규모 언어 모델에 전달합니다.

Databricks Knowledge Assistant는 쿼리 분해, 컨텍스트 기반 재순위 지정, 문서 메타데이터에 대한 추론을 통합하는 Instructed Retriever를 포함한 정교한 검색 기술로 이 기반을 구축합니다. 이러한 기능은 실제 코드베이스의 복잡성을 처리하는 데 큰 도움이 되며, 기본 청크가 의미론적 경계를 잘 보존할 때 가장 잘 작동합니다.

지식 도우미는 일반적으로 선형적으로 흐르는 비즈니스 문서 모음 위에서 구성되고 평가됩니다. 코드는 중첩된 계층 구조를 가집니다. 파일은 클래스를 포함하고, 클래스는 메서드를 포함하며, 메서드는 로직 블록을 포함합니다. 코드에서 의미론적 단위는 종종 단락이 아니라 완전한 함수입니다.

이는 다음과 같은 특정 과제를 야기합니다:

  • 의미론적 경계: 함수 본문을 중간에 나누면 함수가 무엇을 하는지 이해하는 데 필요한 컨텍스트를 잃게 됩니다. deletion_order = ['experiments', 'jobs'...를 포함하는 청크는 UCState.clear_all() 내에 이 변수가 있다는 것을 보여주지 않으면 유용성이 떨어집니다.
  • 파일 간 종속성: 코드는 다른 코드를 참조합니다. 한 함수를 이해하려면 종종 해당 클래스, 가져온 모듈 또는 관련 함수의 컨텍스트가 필요합니다.
  • 혼합 파일 형식: 저희 코드베이스에는 .py 파일, .ipynb 노트북(코드/마크다운 셀이 있는 JSON), .md 문서, .yaml 구성 파일이 있으며, 각 파일은 다른 구문 분석 접근 방식이 필요합니다.

Databricks Knowledge Assistant를 사용하면 자체 벡터 인덱스를 사용할 수 있으므로 청크를 원하는 대로 준비하고 Knowledge Assistant를 결과로 가리키기만 하면 됩니다. 이를 통해 RAG를 위해 코드베이스를 준비하는 다양한 접근 방식을 비교하고 최상의 접근 방식을 선택할 수 있었습니다.

청킹 전략

청킹 전략이 실제로 어떻게 다른지 알아보기 위해 “리소스 정리(resource cleanup)는 어떤 순서로 발생하는가?”라고 질문했을 때 어떤 일이 발생하는지 생각해 봅시다. 이 답변은 실험, 작업 및 파이프라인을 추적하는 유틸리티 클래스에 있습니다. 이 클래스의 로직은 초기화, 삭제 순서 목록, 정리 메서드에 걸쳐 있습니다. 각 메서드가 어떻게 작동하고 리소스 정리 클래스인 UCState에 대한 검색 컨텍스트에 어떤 영향을 미치는지 살펴보겠습니다.

단순 기준선: 고정 크기 문자 청크

가장 간단한 접근 방식은 소스 파일을 고정된 문자 간격으로 겹쳐서 분할하고 코드를 일반 텍스트로 취급하는 것입니다. 오늘날 프로덕션 준비 RAG 시스템에 선택할 방식은 아닙니다. 구문과 의미론적 경계를 무시하므로 코드 쿼리가 중요하게 생각하는 방식으로 실패합니다. 하지만 구현이 매우 간단하고, 문서가 많은 리포지토리의 경우 종종 “충분히 좋으며”, 첫 번째 단계로 흔히 사용되므로 유용한 기준선입니다.

다음은 저희 코드베이스에서 deletion_order를 검색했을 때 단순 청킹이 생성한 결과입니다:

변수 이름이 두 개로 잘렸고(eletion 대신 deletion), 청크에는 메서드 이름이 포함되지 않습니다. 누군가 “UCState 삭제 순서”를 검색하면 이 청크는 잘 일치하지 않을 것입니다. 또한 메서드의 deletion_order 목록도 잘렸습니다.

언어 인식: LangChain 휴리스틱 분할기

LangChain의 RecursiveCharacterTextSplitter.from_language()는 언어별 구분 기호(Python의 경우 \nclass\ndef와 같은)를 사용하여 논리적 경계에서 분할하는 것을 선호합니다. 함수를 그대로 유지하려고 하지만 엄격한 크기 제한을 적용합니다. 개념적으로 이는 임의의 문자 수 대신 의미론적 경계(예: defclass)에서 분할을 우선시함으로써 단순 청킹을 개선하므로, 청크는 완전한 로직 단위를 포함할 가능성이 더 높습니다.

다음은 동일한 검색에 대해 이 접근 방식이 생성한 결과입니다:

청크는 더 자연스러운 경계에서 시작하지만, 어떤 파일이나 함수에 속하는지 보여주는 컨텍스트가 여전히 부족하고, for 루프 시작 직후에 잘립니다.

AST 기반: 메타데이터 헤더가 있는 Tree-Sitter

AST(Abstract Syntax Tree) 기반 청킹은 실제 코드 구조를 이해하기 위해 Tree-sitter와 같은 파서를 사용합니다. AST는 코드의 구문 구조, 즉 언어의 문법 규칙에 따라 코드가 어떻게 구성되는지를 파악하는 코드의 트리 표현입니다. 문자 경계에서 분할하거나 휴리스틱 패턴을 사용하는 대신, AST 기반 청킹 전략은 코드를 구문 트리로 파싱하고 함수, 클래스 또는 문 블록과 같은 의미론적 경계에서 청킹합니다. 또한 필요한 경우 크기 제한을 초과하여 함수 중간에 분할하는 대신 완전한 단위를 유지할 수 있습니다.

AST 기반 분할을 처리하기 위해 ASTChunk Python 라이브러리를 사용했습니다. 이 라이브러리에는 각 청크 앞에 파일 경로 및 클래스/함수 계층 구조를 보여주는 메타데이터 헤더를 추가하는 청크 확장 옵션이 포함되어 있습니다. 이 컨텍스트는 임베딩의 일부가 되어, 쿼리 용어가 청크 본문에 나타나지 않더라도 검색이 관련 코드에 쿼리를 일치시키는 데 도움이 됩니다.

다음은 쿼리에 대해 이 접근 방식이 생성한 청크입니다.

헤더는 이 코드가 어디에 있는지 정확히 알려줍니다: utils/uc_state/state_manager.pyclass UCState:def clear_all(...). 임베딩될 때 이 청크는 “UCState,” “clear_all,” 또는 “deletion order”에 대한 쿼리와 더 강력한 의미론적 연결을 갖게 됩니다.

이 단계에서 Knowledge Assistant에 어떤 메서드가 가장 잘 작동할지에 대한 몇 가지 직관이 있었지만, 확실히 알기 위해서는 체계적인 평가를 수행해야 했습니다.

MLflow를 사용한 평가 설정

MLflow의 GenAI 평가 프레임워크는 LLM, 에이전트 및 검색 시스템을 비교하기 위한 완전한 도구 키트를 제공합니다. 평가 데이터셋, 예측 함수 및 LLM 심사자를 제공하면 파이프라인을 통해 각 질문을 실행하고 결과를 채점합니다. 다음은 세 가지 청킹 방법을 비교하기 위해 이를 사용한 방법입니다.

평가 데이터셋

광범위한 개념적 주제부터 코드에 대한 자세한 쿼리까지 다양한 범주에 걸쳐 46개의 질문을 만들었습니다.

범주개수예시
특정 값 찾기7"UCState.clear_all()의 정확한 삭제 순서는 무엇인가요?"
전체 정의 검색8"ComplaintResponse 모델의 모든 필드와 유효성 검사기를 나열하세요."
시스템 흐름 이해6"불만 처리 파이프라인이 생성부터 Lakebase 동기화까지 어떻게 엔드투엔드로 작동하나요?"
앱 구현 비교13"complaints-manager와 refund-manager 간에 parse_agent_response는 어떻게 다른가요?"
프레임워크 및 패턴 비교12"각 에이전트는 어떤 ML 프레임워크를 사용하나요? 오류 처리 및 스트리밍 패턴은 어떻게 다른가요?"

코드베이스에 다른 컨텍스트에 구조적으로 유사한 코드가 있는 경우(예: 함수 이름이 겹치는 두 앱, 병렬 데이터베이스 스키마 또는 미묘하게 다른 구성 파일)와 같이 모호화 질문에 데이터셋을 의도적으로 가중치를 두었습니다. 이러한 질문은 청킹의 약점을 가장 명확하게 드러냅니다. 청크에 코드 위치에 대한 메타데이터가 부족하면 검색 시스템이 다른 컨텍스트에 존재하는 유사한 클래스와 함수를 구분하는 데 어려움을 겪을 것입니다.

LLM 심사자

품질의 다른 측면을 포착하는 세 가지 주요 LLM 심사자를 사용했습니다.

  • RetrievalSufficiency (내장): 검색된 청크에 질문에 답하기에 충분한 정보가 포함되어 있습니까? 이는 검색 품질을 생성과 독립적으로 측정하기 때문에 청킹 전략을 비교하는 핵심 지표입니다.
  • RetrievalGroundedness (내장): 응답이 검색된 컨텍스트에 기반하고 있습니까, 아니면 청크에 없는 정보를 도입합니까?
  • answer_correctness (사용자 지정): 이 사용자 지정 채점기는 각 답변을 정확, 부분적으로 정확 또는 부정확으로 순위를 매겨 엄격한 예/아니오 정확도 채점기보다 약간 더 미묘합니다. 단편화되거나 불완전한 *컨텍스트*의 가능성을 고려할 때, 세부 정보가 누락되거나 사소한 부정확성이 있는 답변을 찾아내고 싶습니다.

평가 실행

비교를 공정하게 유지하기 위해 모든 전략은 동일한 대상 청크 크기(1,000자), 오버랩(200자) 및 임베딩 모델(databricks-gte-large-en)을 사용했습니다. 실제로는 최종 청크 크기가 다릅니다(예: AST 기반 청킹은 전체 의미론적 단위를 보존하기 위해 확장될 수 있으며, 매우 작은 파일은 자연스럽게 작은 청크를 생성합니다).

각 청킹 전략에 대해 Delta 테이블에 청크를 작성하고, 관리되는 임베딩(Databricks Knowledge Assistant에서 요구하는 databricks-gte-large-en 임베딩 모델 사용)으로 Vector Search 인덱스를 생성하고, Knowledge Assistant 엔드포인트에 인덱스를 연결했습니다. 전체 설정은 문서에 나와 있습니다.

각 청킹 전략의 Knowledge Assistant 엔드포인트에 직접 쿼리하여 평가했습니다. MLflow의 to_predict_fn()은 서빙 엔드포인트를 예측 함수로 래핑하며, Knowledge Assistant는 검색 범위를 포함한 전체 MLflow 추적을 생성하므로 내장 심사자는 검색된 청크와 최종 응답 모두를 검사할 수 있습니다.

LLM 심사자는 Databricks Model Serving을 통해 LLM 심사자를 호출합니다. databricks-claude-opus-4-6을 사용했습니다.

평가 실행이 완료되면 MLflow의 실험 UI를 통해 세 가지 전략의 결과를 나란히 비교할 수 있습니다.

Databricks Model Serving
MLflow 평가 UI에서 청킹 접근 방식 비교
기술 가이드 eBook

ETL 시작하기

결과 및 교훈

46개의 모든 질문을 각 Knowledge Assistant를 통해 실행하고 세 가지 심사자로 결과를 채점했습니다. 다음은 우리가 발견한 내용입니다.

판단 기준Naive언어 인식 분할기AST
검색 적절성85%87%89%
검색 근거성76%72%76%
답변 정확성 (사용자 정의)59% 완전 정확 (37% 부분 정확)61% 완전 정확 (37% 부분 정확)70% 완전 정확 (28% 부분 정확)

세 가지 전략 모두 85% 이상의 검색 적절성을 달성했습니다. 이는 코드 분할 방식에 관계없이 Knowledge Assistant의 검색 기법이 관련 문맥을 찾는다는 것을 의미합니다. 검색 수준에서의 차이는 미미합니다.

사용자 정의 정확성 결과는 더 흥미로운 이야기를 들려줍니다. AST 기반 분할은 Naive 방식의 59%, 언어 인식 방식의 61%에 비해 70%의 경우 완전한 답변을 생성했습니다. 세 가지 전략 모두 거의 모든 경우에 부분적으로라도 정확한 답변을 생성했습니다. 더 나은 청크는 지식 도우미가 질문에 더 완전하게 답하는 데 도움이 됩니다.

이점은 특정 질문 유형에 집중되어 있습니다. AST 기반 분할은 구조적으로 유사한 코드가 여러 모듈에 걸쳐 존재하여 모호성 질문에서 뛰어났는데, 이는 포함된 메타데이터(파일 경로, 클래스, 함수 이름)가 필요한 컨텍스트를 제공했기 때문입니다. 세 가지 전략 모두 값 조회 및 전체 정의 검색에서는 비슷했습니다.

MLflow 추적을 사용하면 개별 질문을 자세히 살펴보고 어떤 청크가 검색되었고 답변이 어디서 달라졌는지 정확히 확인할 수 있습니다.

MLflow traces
MLflow 추적을 통해 평가 결과를 조사할 수 있습니다.

이 조사를 통해 몇 가지 질문이 해결되지 않은 채로 남았습니다. AST 기반 분할을 사용하여 본 개선 사항이 주로 평균 청크 크기가 더 큰 결과였을까요? LLM 심사관을 지원하는 모델 선택에 결과가 얼마나 의존적이었을까요? 평가 질문에서 실제 사용자가 질문할 주요 범주를 놓쳤을까요?

배운 점

Databricks Knowledge Assistant는 즉시 사용 가능하며 매우 뛰어납니다. 세 가지 전략 모두에서 검색 적절성이 높았으며 거의 모든 질문에 대해 부분적으로라도 정확한 답변을 얻었습니다.

데이터 준비는 여전히 중요합니다. AST 기반 분할은 이 평가에서 근거성과 정확성을 개선했으며, 특히 유사한 코드를 모호하게 하는 질문에 효과적이었습니다. 검색 및 답변 품질의 사소한 개선조차도 하루에 수십 개의 질문을 하는 개발자 팀 전체에 걸쳐 누적됩니다.

사용자 정의 LLM 심사관은 우리가 정말 중요하게 생각하는 것을 측정하는 데 도움이 됩니다. MLflow의 make_judge() API를 사용하면 사용 사례별 LLM 심사관을 쉽게 구축할 수 있습니다. 당사의 사용자 정의 answer_correctness 심사관은 단순한 통과/실패 정확성 심사관보다 정확성에 대한 더 미묘한 관점을 제공할 수 있었습니다.

MLflow 추적은 평가 루프를 단순화합니다. 개별 질문을 조사하여 어떤 청크가 검색되었고 답변이 잘못된 부분을 정확히 확인할 수 있습니다. 추적이 유지되므로 엔드포인트를 다시 쿼리하지 않고도 다른 심사관으로 다시 점수를 매길 수 있습니다.

참고 자료

직접 시도해 보세요

이 데모는 Casper’s Kitchens repo에서 따라 할 수 있습니다. 자체 코드베이스에 대한 청킹 전략을 평가하든 다른 RAG 개선 사항을 탐색하든 이 평가 프레임워크는 접근 방식을 비교할 수 있는 재현 가능한 방법을 제공합니다.

  1. 질문과 예상 답변으로 평가 데이터 세트를 만듭니다.
  2. 청킹 전략을 구현합니다(또는 당사의 전략을 시작점으로 사용합니다).
  3. MLflow LLM 심사관을 설정합니다—내장 옵션으로 시작하고 격차를 발견하면 사용자 정의 옵션을 추가합니다.
  4. 각 전략에 대해 새 인덱스로 평가를 실행합니다.

(이 글은 AI의 도움을 받아 번역되었습니다. 원문이 궁금하시다면 여기를 클릭해 주세요)

게시물을 놓치지 마세요

관심 있는 카테고리를 구독하고 최신 게시물을 받은편지함으로 받아보세요