13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] AI 프레임워크 오류

  • [AI] LangChain 오류 디버깅 전략과 실전 트러블슈팅

    [AI] LangChain 오류 디버깅 전략과 실전 트러블슈팅

    LangChain 오류 디버깅 전략과 실전 트러블슈팅

    LangChain 오류를 오래 들여다보면 한 가지 패턴이 보입니다. 실제 장애의 상당수는 “모델이 이상해서”가 아니라, 모델에 도달하기 전후의 계약이 깨져서 납니다. 입력 키가 하나 빠졌거나, 프롬프트 변수명이 바뀌었거나, retriever가 엉뚱한 문서를 가져왔거나, 출력 파서가 모델의 자연어 한 줄을 JSON으로 착각하는 식이죠.

    인프라 엔지니어 관점에서 보면 LangChain은 단순한 체인 문법이 아니라 API 서버, 큐, 환경변수, 네트워크, 관측성까지 이어지는 실행 파이프라인에 가깝습니다. 이 글은 작은 RAG(Retrieval-Augmented Generation, 검색 증강 생성) 앱을 운영하며 정리한 실전 기준입니다. “어제 되던 코드가 오늘 왜 안 되지?” 싶은 순간에 바로 확인할 수 있도록 명령어와 판단 기준을 함께 넣었습니다.

    LangChain 앱에서 입력, 프롬프트, 모델, 파서, 관측 도구가 어떻게 이어지는지 보여주는 개요 이미지입니다.

    1. LangChain 오류는 컴포넌트보다 계약부터 봐야 합니다

    LangChain은 Prompt Template(프롬프트 템플릿), Chat Model(채팅 모델), Retriever(검색기), Output Parser(출력 파서), Tool(도구)을 파이프처럼 이어줍니다. 여기서 중요한 건 각 단계가 서로 “어떤 입력을 받고 어떤 출력을 넘기는지”입니다. 저는 장애를 볼 때 컴포넌트 이름보다 계약 위반 여부를 먼저 봅니다.

    • 입력 계약 위반: 프롬프트가 요구하는 변수와 실제 payload 키가 다릅니다.
    • 타입 계약 위반: 다음 단계는 문자열을 기대하는데 앞 단계가 dict, list, Document 객체를 넘깁니다.
    • 의존성 계약 위반: 예전 예제의 import 경로와 현재 설치된 패키지 구조가 맞지 않습니다.
    • 인증·네트워크 계약 위반: API 키, 조직 설정, 프록시, 방화벽, timeout 설정이 실행 환경과 충돌합니다.
    • 출력 계약 위반: 모델 응답이 JSON, Pydantic 스키마, enum 값 같은 엄격한 형식을 따르지 않습니다.
    • 검색 품질 계약 위반: RAG에서 retriever가 질문과 관련 없는 문서를 가져오고, 모델은 그 문서 안에서만 그럴듯하게 답합니다.

    이 관점이 유용한 이유는 단순합니다. “LLM이 틀렸다”는 말은 범위가 너무 넓거든요. 반면 “prompt 입력 계약이 깨졌다”, “parser 출력 계약이 깨졌다”라고 말하면 바로 재현 코드와 테스트 케이스를 만들 수 있습니다.

    2. 재현 가능한 환경부터 고정하세요

    LangChain 디버깅을 시작할 때 제일 먼저 할 일은 코드 수정이 아니라 환경 스냅샷입니다. 특히 LangChain은 코어 패키지, 통합 패키지, 커뮤니티 패키지가 분리되어 있어 import 경로와 설치 패키지를 같이 봐야 합니다. OpenAI 연동을 쓴다면 공식 문서 기준으로 langchain-openai를 별도로 설치하는 방식이 일반적입니다.

    python -m venv .venv
    source .venv/bin/activate
    python -m pip install -U pip
    python -m pip install -U langchain langchain-core langchain-openai langsmith python-dotenv
    python -m pip check
    python -m pip freeze > requirements.lock.txt
    python -m pip show langchain langchain-core langchain-openai langsmith

    pip check는 의존성 충돌을 빠르게 확인할 때 좋습니다. 여기서 문제가 나오면 LangChain 코드를 보기 전에 패키지 상태부터 정리하는 편이 낫습니다. 운영 서버에서만 깨지는 오류라면 로컬과 서버에서 아래 명령 결과를 나란히 비교하세요.

    python --version
    python -m pip --version
    python -m pip show langchain langchain-core langchain-openai langsmith
    python - <<'PY'
    import importlib.metadata as m
    for name in ['langchain', 'langchain-core', 'langchain-openai', 'langsmith']:
        try:
            print(name, m.version(name))
        except m.PackageNotFoundError:
            print(name, 'NOT INSTALLED')
    PY

    튜토리얼을 따라 했는데 ModuleNotFoundError나 ImportError가 나면, 코드보다 먼저 “그 예제가 어느 시기의 패키지 구조를 기준으로 쓰였는지”를 보세요. 오래된 글에서 langchain.chat_models 계열 import를 복사했다면 현재 프로젝트의 공식 import 경로와 맞지 않을 수 있습니다. 이건 실력 문제가 아니라 패키지 분리의 흔적입니다.

    3. INVALID_PROMPT_INPUT를 일부러 재현해보기

    초보 단계에서 자주 만나는 LangChain 오류는 프롬프트 변수 누락입니다. LangChain의 INVALID_PROMPT_INPUT 오류는 프롬프트 템플릿이 요구하는 입력과 실제 전달한 값이 어긋날 때 발생합니다. 운영에서는 이게 더 교묘합니다. 프론트엔드에서는 question으로 보내는데 백엔드에서는 query로 넘기거나, 큐 메시지에 필드 하나가 빠지는 식이죠.

    from langchain_core.prompts import ChatPromptTemplate
    
    prompt = ChatPromptTemplate.from_messages([
        ('system', 'You are a helpful assistant.'),
        ('human', '{question}에 대해 {tone} 말투로 설명해줘'),
    ])
    
    print('required variables:', prompt.input_variables)
    
    # 일부러 tone을 빼서 오류를 재현합니다.
    messages = prompt.invoke({'question': 'LangChain 오류'})
    print(messages)

    실제로는 모델 호출 전에 payload를 검증해서 “모델 API 비용을 쓰기도 전에 실패”하게 만드는 편이 좋습니다. 오류를 늦게 발견할수록 로그가 지저분해지고 비용도 새기 쉽거든요.

    def validate_prompt_payload(prompt, payload: dict) -> None:
        required = set(prompt.input_variables)
        received = set(payload.keys())
        missing = required - received
        extra = received - required
    
        if missing:
            raise ValueError(f'Missing prompt variables: {sorted(missing)}')
        if extra:
            print(f'[debug] Extra payload keys ignored by prompt: {sorted(extra)}')
    
    payload = {'question': 'LangChain 디버깅', 'tone': '친절한'}
    validate_prompt_payload(prompt, payload)
    messages = prompt.invoke(payload)
    print(messages)

    실무 기준으로는 extra를 무조건 오류로 보지 않습니다. API 요청 전체에는 사용자 ID, trace ID, locale 같은 부가 필드가 들어갈 수 있으니까요. 다만 missing은 즉시 실패시키는 편이 낫습니다. 누락된 값을 빈 문자열로 채우면 당장은 지나가도 모델 응답 품질이 조용히 망가집니다.

    4. LCEL 체인은 예쁘게 쓰되, 디버깅은 끊어서 합니다

    LCEL(LangChain Expression Language)은 prompt | model | parser처럼 읽기 좋은 체인을 만들 수 있어서 생산성이 좋습니다. 문제는 한 줄로 연결한 체인이 실패하면 어느 단계에서 깨졌는지 한눈에 보이지 않는다는 점입니다. 장애 상황에서는 체인을 잠시 해체하세요. 각 연결부에서 실제 값이 무엇인지 보는 게 훨씬 빠릅니다.

    LangChain 디버깅을 위한 LCEL 체인 단계별 구성도

    프롬프트, 모델, 파서 단계를 나누어 어디서 입력과 출력이 바뀌는지 확인하는 구성도입니다.

    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain_openai import ChatOpenAI
    
    prompt = ChatPromptTemplate.from_template('{topic}을 초보자에게 3문장으로 설명해줘.')
    model = ChatOpenAI(model='gpt-4o-mini', temperature=0, timeout=30, max_retries=2)
    parser = StrOutputParser()
    
    inputs = {'topic': 'LangChain 트러블슈팅'}
    
    prompt_value = prompt.invoke(inputs)
    print('[prompt type]', type(prompt_value))
    print('[prompt value]', prompt_value)
    
    model_result = model.invoke(prompt_value)
    print('[model type]', type(model_result))
    print('[model content]', model_result.content)
    
    parsed = parser.invoke(model_result)
    print('[parsed type]', type(parsed))
    print('[parsed]', parsed)

    temperature=0은 디버깅할 때 변동성을 줄이려는 선택입니다. 창의적인 답변을 평가하는 단계가 아니라 오류 재현 단계라면, 같은 입력에 최대한 비슷한 응답이 나오는 편이 원인 분석에 유리합니다. timeout과 max_retries는 운영에서 더 중요합니다. timeout이 없으면 요청이 오래 붙잡혀 API 서버 worker를 묶을 수 있고, retry가 과하면 장애 상황에서 외부 API를 더 세게 두드릴 수 있습니다.

    실패 위치 흔한 에러 신호 근본 원인 먼저 할 일 주의할 트레이드오프
    Prompt 필수 변수 누락, 템플릿 포맷 오류 payload 스키마와 템플릿 변수명 불일치 prompt.input_variables와 요청 body 비교 기본값으로 덮으면 조용한 품질 저하가 생길 수 있음
    Model 인증 실패, timeout, rate limit, 모델명 오류 환경변수, 네트워크, provider 설정 문제 API 키 존재 여부와 실제 모델명을 로그로 확인 retry를 늘리면 안정성은 오르지만 지연과 비용이 늘 수 있음
    Parser JSONDecodeError, validation error 자연어 응답을 엄격한 스키마로 해석 raw output을 저장하고 스키마와 비교 구조화 출력은 안정적이지만 provider 지원 여부를 확인해야 함
    Retriever 답변은 자연스럽지만 근거가 엉뚱함 청크, 임베딩, 검색 쿼리, 필터 조건 문제 검색된 문서 제목·점수·본문 일부 출력 top_k를 늘리면 recall은 오르지만 잡음과 토큰 사용량도 늘 수 있음
    Tool 도구 인자 validation 실패 모델이 tool schema와 다른 인자를 생성 tool call 원문과 schema 필수 필드 확인 스키마를 너무 엄격하게 만들면 재시도가 잦아질 수 있음

    5. 환경변수는 .env와 런타임 값을 분리해서 봅니다

    LLM 앱에서 인증 오류는 의외로 단순한 곳에서 납니다. .env에는 키가 있는데 프로세스에는 로드되지 않았거나, Docker 컨테이너에는 다른 값이 들어갔거나, 스테이징 서버가 운영용 LangSmith 프로젝트로 trace를 보내는 식입니다. 로컬 개발에서는 python-dotenv가 편하지만, 운영에서는 플랫폼의 secret 관리 기능을 우선하세요.

    OPENAI_API_KEY=sk-여기에_실제_키를_넣지_말고_로컬에서만_관리
    LANGSMITH_API_KEY=lsv2_여기에_LANGSMITH_키
    LANGSMITH_TRACING=true
    LANGSMITH_PROJECT=langchain-debug-local
    from dotenv import load_dotenv
    import os
    
    load_dotenv()
    
    required_env = ['OPENAI_API_KEY']
    for key in required_env:
        if not os.getenv(key):
            raise RuntimeError(f'Missing required environment variable: {key}')
    
    print('LANGSMITH_TRACING=', os.getenv('LANGSMITH_TRACING', 'false'))
    print('LANGSMITH_PROJECT=', os.getenv('LANGSMITH_PROJECT', 'not-set'))

    운영 관점에서 LANGSMITH_PROJECT는 꼭 나누는 편이 좋습니다. 개발, 스테이징, 운영 trace가 섞이면 장애 분석 때 “어느 요청이 진짜 고객 요청인지”부터 다시 걸러야 합니다. 로그 비용과 보안 정책도 같이 봐야 합니다. 프롬프트나 검색 문서에 개인정보·계약 정보·내부 장애 내용이 들어갈 수 있다면 trace에 무엇을 남길지 팀 기준을 먼저 정해야 합니다.

    6. LangSmith trace는 print의 대체가 아니라 요청 단위 블랙박스입니다

    로컬에서 한 명이 테스트할 때는 print만으로도 충분합니다. 하지만 API 서버에서 동시에 여러 요청이 들어오면 stdout 로그는 금방 섞입니다. LangSmith 같은 관측성 도구를 붙이면 chain run, model call, parser 실행 흐름을 요청 단위 trace로 따라갈 수 있습니다. 이거 붙여두면 장애 회고 때 진짜 편하더라고요.

    • 입력: 사용자가 보낸 원문과 서버가 체인에 넘긴 값이 같은지 확인합니다.
    • 중간 출력: 프롬프트 완성본, 검색된 문서, 모델 raw response를 분리해서 봅니다.
    • 실패 지점: 전체 latency 중 어디에서 오래 걸렸고 어느 단계에서 예외가 났는지 봅니다.
    export OPENAI_API_KEY='여기에_API_키'
    export LANGSMITH_API_KEY='여기에_LANGSMITH_키'
    export LANGSMITH_TRACING='true'
    export LANGSMITH_PROJECT='langchain-debug-lab'
    python app.py

    print 로그를 완전히 버리라는 뜻은 아닙니다. 개발 초반에는 print + 단계별 invoke가 가장 빠릅니다. 다만 팀 단위 운영으로 넘어가면 trace ID를 HTTP 응답 헤더나 애플리케이션 로그에 같이 남기는 쪽이 좋습니다. 고객 문의, 서버 로그, LangSmith trace를 같은 ID로 묶을 수 있으면 원인 추적 속도가 확 달라집니다.

    상황 추천 도구 이유 쓰지 않아도 되는 경우
    개인 실험, 노트북 코드 print, 단계별 invoke 설정이 적고 즉시 확인 가능 동시 요청이 없고 실패 입력이 단순할 때
    FastAPI/Flask API 서버 구조화 로그 + trace ID 요청별 입력과 예외를 묶기 쉬움 일회성 데모라면 과할 수 있음
    팀 운영 서비스 LangSmith tracing 체인 내부 단계와 모델 호출을 시각적으로 추적 민감 데이터 로깅 정책을 정하지 못했다면 먼저 보류
    회귀 방지 pytest 재현 테스트 한 번 고친 오류가 다시 나는지 확인 모델 품질 평가처럼 정답이 유동적인 경우엔 별도 eval이 필요

    7. JSON 파싱 오류는 프롬프트만으로 해결하기 어렵습니다

    AI 프레임워크 오류 중 사람을 가장 오래 붙잡는 게 출력 파싱입니다. 모델은 자연어를 잘하지만, 애플리케이션은 종종 엄격한 JSON을 원합니다. 쉼표 하나, 따옴표 하나, enum 값 하나만 달라도 parser는 실패합니다. 제가 보는 선택지는 세 가지입니다.

    • 문자열 후처리: 빠르지만 취약합니다. 데모나 내부 도구에만 제한적으로 씁니다.
    • Output Parser: 프롬프트에 형식 지시를 넣고 파서로 검증합니다. 실패 시 raw output 저장이 중요합니다.
    • 구조화 출력: 모델 호출 단계에서 스키마를 강제합니다. 지원 모델과 provider를 확인해야 하지만 운영 안정성은 대체로 이쪽이 낫습니다.
    from typing import Literal
    from pydantic import BaseModel, Field
    from langchain_openai import ChatOpenAI
    
    class Ticket(BaseModel):
        title: str = Field(description='장애 제목')
        severity: Literal['low', 'medium', 'high'] = Field(description='장애 심각도')
        summary: str = Field(description='장애 요약')
    
    model = ChatOpenAI(model='gpt-4o-mini', temperature=0, timeout=30)
    structured_model = model.with_structured_output(Ticket)
    
    result = structured_model.invoke('결제 API가 간헐적으로 실패하고 있습니다. 영향 범위가 넓어 보입니다.')
    print(result)
    print(type(result))

    Literal을 쓴 이유는 단순합니다. severity가 critical, urgent, 높음처럼 제멋대로 늘어나면 downstream 시스템에서 다시 분기 오류가 납니다. 스키마를 좁게 잡으면 validation 실패는 늘 수 있지만, 잘못된 값이 조용히 DB에 저장되는 위험은 줄어듭니다. 운영에서는 둘 중 무엇이 더 비싼 장애인지 판단해야 합니다.

    from pydantic import ValidationError
    
    try:
        ticket = structured_model.invoke('DB 연결이 실패해서 로그인 API가 실패합니다.')
    except ValidationError as exc:
        print('[schema validation failed]')
        print(exc)
        # 운영에서는 원문 입력, raw 응답, trace_id를 함께 저장하는 쪽이 좋습니다.
        raise

    반대로 간단한 블로그 요약, 내부 메모 생성처럼 사람이 읽고 끝나는 작업이라면 엄격한 Pydantic 스키마가 오히려 과할 수 있습니다. 이럴 땐 StrOutputParser로 충분합니다. 기준은 “사람이 읽을 텍스트인가, 시스템이 소비할 데이터인가”입니다. 시스템이 소비한다면 구조화 출력이나 검증 레이어를 빼지 않는 게 맞습니다.

    8. RAG 오류는 모델보다 검색 결과부터 의심하세요

    문서 검색 챗봇을 만들 때 가장 많이 속는 부분이 RAG 품질 문제입니다. 답변은 매끄러운데 사실관계가 묘하게 틀릴 때가 있거든요. 처음엔 모델을 바꿔야 하나 싶지만, trace를 보면 retriever가 질문과 관련 없는 문서를 가져오는 경우가 꽤 많습니다. 모델은 받은 근거 안에서 열심히 답했을 뿐입니다.

    RAG에서 “틀린 답변”은 최소 세 종류로 나눠야 합니다. 첫째, 관련 문서를 못 찾은 검색 실패입니다. 둘째, 관련 문서는 찾았지만 청크가 잘려 핵심 문맥이 빠진 경우입니다. 셋째, 문서는 맞는데 프롬프트가 근거 밖 추론을 허용한 경우입니다. 이 셋은 대응이 다릅니다.

    def debug_documents(docs, limit: int = 3) -> None:
        for i, doc in enumerate(docs[:limit], start=1):
            source = doc.metadata.get('source', 'unknown')
            title = doc.metadata.get('title', 'no-title')
            text = doc.page_content.replace('\n', ' ')[:500]
            print(f'\n[doc {i}] source={source} title={title}')
            print(text)
    
    query = 'FastAPI에서 LangChain 체인이 timeout 날 때 어디를 봐야 하나요?'
    docs = retriever.invoke(query)
    debug_documents(docs)

    위 출력에서 질문과 상관없는 문서가 계속 나온다면 모델 파라미터를 만지기 전에 retriever 설정을 봐야 합니다. top_k를 늘리면 관련 문서를 포함할 가능성은 올라가지만, 무관한 문서도 같이 들어와 토큰 사용량과 혼선을 늘릴 수 있습니다. chunk size를 키우면 문맥은 보존되지만 검색 단위가 둔해질 수 있고, 너무 작게 자르면 답변에 필요한 전후 맥락이 사라질 수 있습니다. 그래서 실패 질문을 모아 회귀 테스트처럼 돌리는 게 낫습니다.

    증상 먼저 의심할 곳 확인 방법 추천 대응
    답변이 유창하지만 근거가 틀림 Retriever 검색된 문서 제목·본문 일부를 출력 쿼리 재작성, metadata filter, top_k 조정
    문서는 맞는데 답이 반쪽 Chunking 원문에서 앞뒤 문맥이 잘렸는지 비교 chunk size, overlap, 문서 분할 기준 재검토
    근거 밖 추측이 섞임 Prompt 프롬프트가 모르면 모른다고 하게 만드는지 확인 근거 기반 답변 규칙과 citation 요구 추가
    요청마다 품질 편차가 큼 모델 설정·검색 후보 같은 질문을 여러 번 실행해 raw context 비교 temperature 낮추기, 검색 결과 고정 테스트 작성

    9. 재시도와 timeout은 비용 증폭 장치이기도 합니다

    외부 모델 API를 쓰면 timeout, rate limit, 일시적인 네트워크 오류를 피할 수 없습니다. 그래서 max_retries를 켜는 건 합리적입니다. 다만 재시도는 공짜가 아닙니다. 실패 요청이 많을 때 retry가 겹치면 지연이 늘고, 일부 실패 모드에서는 비용도 커질 수 있습니다.

    환경 timeout retry 이유
    로컬 디버깅 짧게 낮게 빨리 실패해야 원인 확인이 쉬움
    사용자-facing API 제품 SLA에 맞춤 제한적으로 사용자 대기 시간과 성공률 사이 균형 필요
    배치 작업 상대적으로 길게 조금 더 허용 실시간 응답보다 완료율이 중요할 수 있음
    장애 재현 테스트 짧게 0 또는 낮게 재시도가 원래 오류를 가리지 않게 해야 함
    from langchain_openai import ChatOpenAI
    
    # 장애 재현용: 빨리 실패시키고 원인을 숨기지 않습니다.
    debug_model = ChatOpenAI(model='gpt-4o-mini', temperature=0, timeout=10, max_retries=0)
    
    # 일반 API용: 일시적 실패를 약간 흡수합니다.
    api_model = ChatOpenAI(model='gpt-4o-mini', temperature=0, timeout=30, max_retries=2)

    핵심은 모든 환경에 같은 모델 객체를 쓰지 않는 것입니다. 디버깅 모드에서는 오류를 선명하게 보고, 운영 모드에서는 사용자 경험을 보호해야 합니다. 같은 코드베이스라도 목적이 다르면 timeout과 retry 정책도 달라져야 합니다.

    10. 실패 입력은 테스트로 남겨야 다시 안 밟습니다

    LangChain 트러블슈팅을 하다 보면 그 자리에서 고치고 넘어가기 쉽습니다. 하지만 실패 입력을 테스트로 남기지 않으면 프롬프트를 조금 바꾼 날 같은 문제가 다시 돌아옵니다. 최소한 프롬프트 변수 검증, parser validation, retriever 결과 확인은 작은 테스트로 남겨두세요.

    # tests/test_prompt_contract.py
    import pytest
    from langchain_core.prompts import ChatPromptTemplate
    
    prompt = ChatPromptTemplate.from_template('{question}에 대해 {tone} 말투로 답해줘')
    
    def validate_prompt_payload(prompt, payload: dict) -> None:
        missing = set(prompt.input_variables) - set(payload.keys())
        if missing:
            raise ValueError(f'Missing prompt variables: {sorted(missing)}')
    
    def test_prompt_payload_requires_tone():
        with pytest.raises(ValueError):
            validate_prompt_payload(prompt, {'question': 'LangChain 오류'})
    
    def test_prompt_payload_accepts_required_keys():
        validate_prompt_payload(prompt, {'question': 'LangChain 오류', 'tone': '차분한'})
    python -m pytest -q tests/test_prompt_contract.py -s

    모델 호출까지 포함한 테스트는 더 신중해야 합니다. 외부 API 상태, rate limit, 비용, 모델 응답 변동성이 끼어들기 때문입니다. 계약 테스트는 로컬에서 빠르게 돌리고, 실제 모델 품질 평가는 별도 eval이나 스테이징 파이프라인으로 분리하는 편이 관리하기 쉽습니다.

    LangChain 오류 분석을 위한 LangSmith 추적 대시보드 예시

    요청별 trace, 체인 단계, 오류 발생 지점을 한눈에 확인하는 대시보드 예시입니다.

    11. LangChain 트러블슈팅 순서

    아래 순서는 장애를 볼 때 거의 습관처럼 쓰는 흐름입니다. 중요한 건 모델 호출을 맨 앞에 두지 않는 겁니다. 입력과 검색 결과가 깨진 상태에서 모델을 바꿔봐야 문제만 흐려집니다.

    1. 실패 입력을 고정합니다. 사용자 질문, request body, trace ID, 환경 이름을 한 묶음으로 저장합니다.
    2. 패키지와 import를 확인합니다. pip show, pip check, Python 버전을 비교합니다.
    3. 프롬프트 계약을 봅니다. prompt.input_variables와 payload 키를 비교합니다.
    4. LCEL 체인을 끊습니다. prompt.invoke, model.invoke, parser.invoke를 따로 실행합니다.
    5. RAG라면 검색 결과를 출력합니다. 문서 제목, source, 본문 앞부분을 직접 봅니다.
    6. raw output을 저장합니다. parser가 실패했다면 모델 원문 응답 없이는 원인 분석이 어렵습니다.
    7. timeout과 retry를 확인합니다. 재현 테스트에서는 retry를 낮춰 원래 오류를 가리지 않게 합니다.
    8. 고친 뒤 테스트를 남깁니다. 실패 payload를 최소 재현 케이스로 바꿔 회귀를 막습니다.

    이 순서대로 보면 “LangChain이 이상하다”는 막연한 느낌이 “프롬프트 변수 누락”, “retriever 후보 품질 문제”, “JSON schema validation 실패”처럼 처리 가능한 단위로 바뀝니다. 디버깅은 결국 이름 붙이기 싸움입니다.

    12. 자주 묻는 질문: LLM 앱 개발 문제 해결

    Q. LangChain 오류가 나면 가장 먼저 뭘 봐야 하나요?

    A. 에러 메시지 마지막 줄만 보지 말고 실패 단계를 먼저 나누세요. Prompt, Model, Parser, Retriever 중 어디서 깨졌는지 확인하면 해결 속도가 빨라집니다. 제일 빠른 방법은 LCEL 체인을 끊어서 각 단계의 입력·출력 타입을 출력하는 것입니다.

    Q. 버전 문제는 어떻게 줄이나요?

    A. 설치 직후 requirements.lock.txt를 남기고, 로컬·서버에서 python -m pip show langchain langchain-core langchain-openai langsmith 결과를 비교하세요. 오래된 블로그 예제의 import 경로를 그대로 쓰면 현재 패키지 구조와 맞지 않을 수 있습니다.

    Q. JSON 파싱 오류는 프롬프트를 고치면 충분한가요?

    A. 사람이 읽는 데모라면 프롬프트 보강으로도 버틸 수 있습니다. 하지만 시스템이 JSON을 소비한다면 Pydantic 스키마나 구조화 출력을 우선 검토하세요. 프롬프트만으로 형식을 강제하면 모델 응답 변동에 약합니다.

    Q. LangSmith는 언제 붙이는 게 좋나요?

    A. 개인 실험 단계에서는 필수는 아닙니다. API 서버로 여러 요청을 받거나 팀에서 장애를 같이 봐야 한다면 붙이는 편이 좋습니다. 단, trace에 민감 데이터가 남을 수 있으니 로깅 정책을 먼저 정해야 합니다.

    Q. RAG 답변이 이상하면 모델을 바꾸는 게 먼저인가요?

    A. 보통은 아닙니다. 검색된 문서가 질문과 관련 있는지 먼저 확인하세요. retriever가 엉뚱한 문서를 가져오면 좋은 모델도 그 문서 안에서 그럴듯한 오답을 만들 수 있습니다.

    13. 상황별 추천: 이럴 땐 이렇게 가세요

    LangChain 오류를 줄이는 핵심은 전체 체인을 한 번에 믿지 않는 것입니다. 단계별 계약을 확인하고, 실패 입력을 저장하고, 고친 뒤 테스트로 남기면 같은 문제를 반복해서 밟을 가능성이 확 줄어듭니다. 관련 구현 예제가 더 필요하다면 블로그의 RAG 구축 글, FastAPI 배포 글, LLM 관측성 글을 함께 연결해 내부 링크로 안내하면 좋습니다.

    • 처음 개발 중이라면: print + 단계별 invoke로 충분합니다. 복잡한 도구보다 빠른 재현이 우선입니다.
    • 프롬프트 오류가 잦다면: payload 검증 함수를 모델 호출 전에 두세요. 누락 키를 조기에 실패시키는 게 비용과 시간을 아낍니다.
    • API 서버로 운영한다면: timeout, retry, trace ID, LangSmith tracing을 같이 설계하세요. 관측성 없이 운영하면 장애 때 감으로 뒤지게 됩니다.
    • JSON 결과가 중요하다면: 문자열 파싱보다 구조화 출력 또는 Pydantic 검증을 우선하세요. 시스템이 소비하는 데이터는 느슨하게 두면 나중에 더 비싸게 터집니다.
    • RAG 품질이 문제라면: 모델 교체 전에 retriever 결과와 청크 전략을 확인하세요. 검색이 틀리면 생성도 흔들립니다.
    • 장애 재현 중이라면: retry를 낮추고 temperature를 낮춰 오류를 선명하게 보세요. 운영 안정화 설정과 디버깅 설정은 달라도 됩니다.

    기본값은 단순합니다. 개인 개발은 빠르게 재현하고, 팀 운영은 관측 가능하게 만들고, 시스템 연동은 스키마를 엄격하게 가져가세요. LangChain은 체인을 빠르게 만들게 해주지만, 안정적인 LLM 앱은 결국 각 단계의 입력과 출력을 얼마나 차분하게 검증하느냐에 달려 있습니다.

    LangChain 트러블슈팅과 디버깅 전략 요약 인포그래픽

    입력, 체인, 모델, 파서, 관측성 기준으로 디버깅 우선순위를 요약한 이미지입니다.

    참고한 공식 문서