5.8.3 LangChain/LangGraph 등 오케스트레이션 도구 내장 테스트 기능 활용

5.8.3 LangChain/LangGraph 등 오케스트레이션 도구 내장 테스트 기능 활용

복잡한 AI 에이전트 시스템을 맨바닥에서 구축하는 시대는 지났다. 오늘날의 엔터프라이즈 AI 애플리케이션은 대부분 LangChain, LlamaIndex, 피드백 루프를 처리하는 LangGraph와 같은 강력한 오케스트레이션(Orchestration) 도구들 위에서 조립된다.

이러한 프레임워크들은 수십 개의 프롬프트, 환시, 도구(Tool) 호출 사슬을 추상화하여 제공한다. 하지만 추상화의 이면에는 제어 흐름이 숨겨진다는 위협이 도사리고 있다. 따라서 유닛 테스트를 작성할 때 우리가 직접 모든 클래스를 모킹(Mocking)하는 고통을 겪는 대신, 이들 프레임워크가 자체적으로 제공하는 내장 테스팅 유틸리티와 추적(Tracing) 기능을 십분 활용하여 결정론적 오라클을 구축해야 한다.

1. FakeLLM을 활용한 파이프라인 무결성 튜닝

LangChain 생태계는 개발자가 외부 API 모킹을 위해 vcrpy 등을 직접 세팅할 필요가 없도록, 결정론적 가짜 응답을 방출하는 FakeListLLMFakeChatModel을 내장하고 있다.

이 클래스들은 배열(Array) 형태로 지정된 순서대로 정확하게 텍스트를 반환하므로, 의도 파악(Intent)과 요약(Summarize)이 연속적으로 일어나는 체인(Chain)에서 파입라인 내부의 프롬프트 템플릿 로직이나 출력 파서(Output Parser)가 정상적으로 작동하는지 검증하기에 매우 훌륭하다.

from langchain_community.llms.fake import FakeListLLM
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate

def test_intent_classification_chain():
    # 모델이 무조건 'REFUND_REQUEST'라는 텍스트를 반환하도록 하드코딩된 FakeLLM 주입
    deterministic_responses = ["REFUND_REQUEST"]
    fake_llm = FakeListLLM(responses=deterministic_responses)
    
    prompt = PromptTemplate(input_variables=["user_text"], template="의도 파악: {user_text}")
    chain = LLMChain(llm=fake_llm, prompt=prompt)
    
    # 실행 및 오라클: 모델이 준 텍스트가 후속 로직을 정상적으로 트리거하는지 확인
    result = chain.run("바지 환불해 줘")
    assert result == "REFUND_REQUEST"

이를 통해 네트워크 연결이나 LLM 요금 발생 없이, 체인의 데이터 조립 메커니즘 그 자체만을 밀도 있게 테스트할 수 있다.

2. LangGraph 상태 전이(State Transition) 결정론적 테스트

LangChain이 단방향 파이프라인이라면, LangGraph는 그래프 이론을 바탕으로 한 순환적(Cyclic) 상태 머신(State Machine)이다. 에이전트가 “검색 -> 문서 확인 -> 부족하면 다시 검색“이라는 사이클을 돌 때, 테스트 오라클은 이 **‘노드 간의 전이(Routing) 및 상태(State) 무결성’**을 평가해야 한다.

LangGraph를 테스트할 때는 FakeLLM을 각 노드(Node)에 심어 특정 조건을 억지로 유발(예: “검색 결과 없음” 반환)한 뒤, 그래프가 무한 루프에 빠지지 않고 미리 정의된 Fallback_Node로 안전하게 전이되는지를 단언(Assert)해야 한다.

# LangGraph에서 노드의 경로를 검증하는 오라클 패턴 (가상 코드)
def test_langgraph_fallback_routing():
    app = build_my_agent_graph(mock_llm=AlwaysFailLLM())
    
    # 상태 실행
    final_state = app.invoke({"user_input": "내부 문서 검색해줘"})
    
    # 결정론적 오라클: 모델이 검색에 실패했을 때, 그래프가 에러를 던지지 않고 
    # 'human_handover(상담사 연결)' 노드로 전이되었는가?
    assert "human_handover" in final_state["traversed_nodes"]

3. LangSmith / LangFuse 등 Tracing 도구와의 결합 평가

단위 테스트를 돌릴 때, 단언문(Assert)만으로는 어떤 프롬프트가 주입되어 모델이 실패했는지 사후 분석하기 어렵다. 테스트 코드 실행 시 LangSmith 같은 옵저버빌리티(Observability) 환경 변수를 활성화해 두면, “어떤 픽스처(Fixture)가 주입되었을 때, 어떤 다단계 프롬프트를 거쳐 오라클에서 실패했는지“가 시각적인 트레이스로 영구 저장된다.

오케스트레이션 도구를 쓴다는 것은 개발자가 통제권을 프레임워크에 일정 부분 양도했음을 의미한다. 잃어버린 통제권을 되찾는 유일한 길은, 프레임워크가 제공하는 내부 생태계용 테스팅 도구를 가장 깊숙한 단위 로직(Unit logic)까지 파고들게 하여 결정론적 단언문으로 포장하는 것뿐이다.