Pydantic 유효성 검사 오류로 인해 청크가 누락되는 문제
요약
Pydantic 유효성 검사 오류로 인해 데이터 추출 파이프라인에서 청크 누락 문제가 발생합니다. LLM이 스키마와 불일치하는 구조적 JSON을 반환할 때, Pydantic은 어떤 필드가 실패했는지 맥락 없이 단순한 오류 메시지만 제공하여 디버깅이 어렵습니다. 이를 해결하기 위해 TracePilot과 같은 도구를 사용하여 실제 실패한 LLM 출력을 추적하고 분석해야 합니다.
핵심 포인트
- Pydantic 유효성 검사만으로는 LLM의 구조적 오류를 파악하기 어렵다.
- LLM은 스키마와 불일치하는 '구조적으로는 유효하지만' 잘못된 JSON을 반환할 수 있다.
- 단순한 재시도나 사전 검증 코드는 확장성이 떨어지는 임시방편이다.
- 실패한 LLM의 실제 출력을 추적(Trace)하는 것이 근본적인 해결책이다.
Pydantic 유효성 검사 오류로 인해 청크가 누락되는 문제
추출 파이프라인이 조용히 실패합니다. 50개의 청크 중 하나가 누락됩니다. 나중에 사용자가 해당 데이터에 대해 질문했을 때 이를 알아차립니다.
발생한 상황은 다음과 같습니다:
graphiti-service: Error ingesting chunk <doc>#chunk-22:
1 validation error for ExtractedEntitiesFreeform
그게 전부입니다. 필드 이름도 없고, 타입 불일치도 없습니다. 그냥 "1 validation error"만 있습니다. 청크는 사라지고, 문서는 불완전합니다. 어떤 엔티티 필드가 문제를 일으켰는지 알아내는 건 행운을 빕니다.
왜 이런 일이 발생할까요
당신은 Claude Sonnet (또는 GPT-4o)을 호출하여 텍스트 청크에서 구조화된 엔티티를 추출합니다. 프롬프트에는 "이 스키마와 일치하는 유효한 JSON을 반환하라"고 명시되어 있습니다. LLM은 겉보기에는 올바르게 보이지만 실제로는 그렇지 않은 무언가를 반환합니다.
일반적인 실패 모드는 다음과 같습니다:
필드 이름 이탈(Field name drift):
# 당신의 스키마는 다음을 예상합니다:
class ExtractedEntitiesFreeform(BaseModel):
entities: list[Entity]
...
타입 불일치(Type mismatch):
# 스키마가 말하는 것:
class Entity(BaseModel):
name: str
...
중첩 구조 붕괴(Nested structure collapse):
# 예상되는 형태:
{
"entities": [
...
LLM은 도움이 되려고 노력하고 있습니다. 하지만 그렇지 않습니다. Pydantic 모델과 일치하지 않는, 구조적으로 유효한 JSON을 생성하는 것입니다. 그리고 Pydantic은 어떤 필드가 실패했는지에 대한 맥락 없이 한 줄짜리 오류를 보여줍니다.
수동 해결 방법
두 가지 옵션이 있습니다:
옵션 1: 더 나은 오류 메시지와 함께 재시도하기
from pydantic import ValidationError
def extract_entities(text: str) -> ExtractedEntitiesFreeform | None:
...
e.errors()는 오류 목록을 제공하지만, LLM이 완전히 잘못된 구조(예를 들어 엔티티를 다른 키 아래에 중첩)를 반환한 경우 일반적인 "필드 필요(field required)" 오류가 발생합니다.
옵션 2: 파싱 전에 유효성 검사하기
def validate_llm_response(raw: dict) -> ExtractedEntitiesFreeform | None:
"""LLM의 일반적인 특이점을 사전 검증하고 정규화합니다."""
...
이 방법은 작동합니다. 하지만 스키마가 변경될 때마다 방어적인 코드를 작성하고 있습니다. 새로운 필드가 추가될 때마다 또 다른 정규화 규칙이 생깁니다. 이는 취약하며 확장성이 떨어집니다.
진짜 문제점
당신은 눈가림식 디버깅을 하고 있습니다. 당신에게는 다음 세 가지 정보만 있습니다:
- 입력 텍스트 (chunk 22)
- LLM 응답 (잘린 오류 메시지)
- 실패한 유효성 검사 (필드 컨텍스트 없음)
당신이 가지고 있지 않은 것은 정확히 실패했던 LLM 출력입니다. Claude가 실제로 무엇을 반환했는지 볼 수 없습니다. 고정된 프롬프트로 추출 과정을 재현할 수도 없습니다. 당신은 추측하고 있을 뿐입니다.
TracePilot의 등장
단 한 줄의 변경만으로 충분합니다:
from tracepilot import TracePilot
tp = TracePilot(api_key="tp_live_YOUR_KEY")
...
이제 chunk 22가 실패하면, TracePilot 대시보드를 열어봅니다. 당신은 다음을 볼 수 있습니다:
- Claude가 반환한 정확한 JSON
- 유효성 검사에 실패한 필드
- 전체 오류 추적(error trace)
Fork & Rerun 버튼을 클릭합니다. 프롬프트를 수정하여
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기