
ChatGPT로 처음 LLM을 접하고 여러 LLM오케스트레이션 도구들이 나오면서 한때 거의 모든 프로젝트에서 LangChain을 사용하는 경우가 허다했다.
LangChain외에 다른 오케스트레이션 도구가 없었던 것은 아니지만 워낙 LLM을 다룸에 있어서 디폴트값처럼 여겨지던 것은 사실이다.
ChatGPT의 등장은 AI 개발 환경의 판도를 크게 바꿔놓았다.
이제는 사내 챗봇부터 고객상담, 자동화 업무까지 거의 모든 AI 프로젝트에 LLM이 핵심 컴포넌트로 자리잡았다.
하지만 LLM 하나만으로 실무 시스템을 구성하기엔 여전히 넘어야 할 장벽이 많다. 프롬프트 설계, 입력/출력 파싱, 다양한 외부 데이터 연동, 오류 처리 등 수많은 작업이 남아 있다.
이런 현실적인 요구를 해결하기 위해 다양한 오케스트레이션 프레임워크들이 등장했고, 그 중에서도 LangChain은 빠른 시기에 가장 널리 사용되는 표준처럼 자리잡았다.
실제로 LangChain은 LLM을 여러 도구와 체인 형태로 손쉽게 연결할 수 있게 해줘서, 프로토타이핑 단계에서는 분명 큰 이점을 줬다.
하지만 막상 실무에서 장기적으로 서비스 운영을 하다 보면 “잘 돌아가는 것”과 “잘 유지보수되는 것”은 전혀 다르다는 걸 체감하게 된다.
단순한 예제에서는 문제 없던 구조가, 실제 운영 환경에서는 복잡해지고 점점 디버깅과 확장, 유지보수에 발목을 잡게 되는 것이다.
이 포스팅에서는 이런 배경에서 실제로 느꼈던 LangChain의 한계, 그리고 그 대안으로써 최근 주목받고 있는 Pydantic AI를 비교 분석해보려 한다.
개발자로서 실제 운영·개발 과정에서 마주한 경험을 바탕으로, 어떤 상황에 어떤 선택이 더 맞았는지 솔직하게 이야기해보고자 한다.
LangChain의 가장 큰 문제는 제목에서도 알 수 있듯이 "과도한 추상화"에 있다.
처음에는 각 LLM 호출이나 외부 툴 연결, 입력과 출력을 체인으로 손쉽게 엮을 수 있다는 점이 매력적으로 보인다. 하지만 프로젝트가 조금만 복잡해지면, 오히려 이 추상화가 발목을 잡는다.
실제 현업에서는 새로운 기능을 추가하거나 기존 체인을 조금만 수정하려 해도, 체인 전체 구조를 따라가며 일일이 확인해야 한다.
입력값이 어떻게 변환되는지, 어느 단계에서 어떤 데이터가 주고받아지는지 일관성 있게 추적하는 것도 쉽지 않다.
이처럼 단순한 데모나 프로토타이핑 단계에서는 LangChain의 추상화가 빠른 개발에 도움을 주지만, 실무 환경에선 디버깅과 유지보수의 허들이 된다.
에러가 발생하면 어디서 문제가 생긴 건지 파악하려면 추상화 레이어를 하나씩 뜯어봐야 하고, LangChain 내부 구조를 깊이 이해하지 않으면 원인 분석도 어려워진다.
또, 파이썬 개발자라면 당연히 기대하는 “직관적인 코드 흐름”보다는, LangChain에서 요구하는 스타일에 맞춰 코드를 작성해야 한다는 점도 꽤 답답하다.
간단한 예시를 가져와봤다.
from langchain.chains import SequentialChain, LLMChain
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
# 모델 선언
llm = OpenAI()
# 문서 요약 프롬프트
summary_prompt = PromptTemplate(
input_variables=["text"],
template="다음 문서를 3줄로 요약해줘: {text}"
)
summary_chain = LLMChain(llm=llm, prompt=summary_prompt, output_key="summary")
# 키워드 추출 프롬프트
keyword_prompt = PromptTemplate(
input_variables=["summary"],
template="다음 요약에서 핵심 키워드 5개를 뽑아줘: {summary}"
)
keyword_chain = LLMChain(llm=llm, prompt=keyword_prompt, output_key="keywords")
# 체인 연결
overall_chain = SequentialChain(
chains=[summary_chain, keyword_chain],
input_variables=["text"],
output_variables=["keywords"]
)
result = overall_chain.run({"text": "LangChain은 문제가 많다."})
print(result["keywords"])
간단하게 문서 요약 → 키워드 추출로 이어지는 체인이다.
이 간단한 체인을 위해서도 각 프롬프트를 LLMChain으로 감싸고, 다시 SequentialChain으로 한 번 더 감싸야 한다.
여기에 이어서 각 LLMChain에는 input/output key를 일일이 맞춰줘야 하고, 체인 전체의 입력/출력 변수도 별도로 정의해줘야 한다.
단순히 두 번의 LLM 호출을 연결하는 것뿐인데, 코드 구조는 점점 복잡해지고, 중간 결과를 추적하거나 에러가 발생했을 때 어느 단계에서 문제가 생겼는지 확인하는 것도 쉽지 않다.
실제로 프로젝트가 커질수록
“프롬프트 추가/수정이 필요할 때마다 체인 전체 구조를 다시 검토해야 한다”
“체인 간 데이터 전달 과정에서 변수명이 꼬이거나 누락되는 일이 반복된다”
“디버깅이나 테스트도 LangChain의 추상 구조를 따라가야 하니 부담이 크다”
는 점이 발목을 잡는다.
이처럼 아주 단순한 파이프라인에서도 LangChain의 추상화 레이어가 오히려 개발자의 생산성과 코드의 명확성을 저해하는 상황이 반복된다.
결국 실무에서는,
“이 정도 로직이라면 그냥 파이썬 함수 두 개로 처리하는 게 훨씬 낫지 않을까?”
라는 생각이 들 수밖에 없다.
실제로 LangChain을 쓰지 않고 직접 OpenAI API를 사용해 아래 코드와 같이 간단하게 끝낼 수 있다.
from openai import OpenAI
client = OpenAI()
def get_response(prompt: str):
client = OpenAI()
response = client.responses.create(
input=prompt
)
return response.output_text
def get_summary(text: str) -> str:
prompt = f"다음 문서를 3줄로 요약해줘: {text}"
# 실제 LLM 호출
return get_response(prompt)
def get_keywords(summary: str) -> list[str]:
prompt = f"다음 요약에서 핵심 키워드 5개를 뽑아줘: {summary}"
return get_response(prompt)
summary = get_summary("LangChain은 문제가 많다.")
keywords = get_keywords(summary)
print(keywords)
이 경우 함수 기반으로 코드가 짜여져있어 각 단계를 명확히 분리해 원하는 방식대로 호출/테스트/디버깅이 가능해진다.
즉, 프레임워크가 강제하는 구조가 아니라 파이썬의 설계 방식과 동일하게 유지보수가 가능해진다는 것이다.
결국 중요한건 "프레임워크가 모든 걸 다 해주지 않는다" 라는 것이다.
누군가 100줄 코드로 LangChain 구성하기라는 제목으로 같은 문제점을 지적한 글이 있는데 링크를 남겨둘테니 관심있다면 읽어보는 것이 좋겠다.
위의 예시와는 다르게 개발 자체와는 거리가 좀 있는 문제이긴 하지만 어쨌거나 개발자 입장에서 지나칠 수 없는 문제가 바로 부실한 공식문서이다.
LangChain은 빠르게 성장하는 오픈소스 프로젝트이다 보니,
공식 문서가 자주 바뀌고, 주요 기능이나 API 사용법이 제대로 정리되지 않은 경우가 많다.
내가 참고했던 예제가 어느새 구버전이 되어버리거나,
실제로는 동작하지 않는 코드가 여전히 공식 문서에 남아있는 경우도 심심치 않게 발견된다.
또한 실무에서 자주 마주치는 복잡한 사례나 에러 케이스에 대한 설명은 거의 찾아보기 어렵다.
단순한 예제는 많은데, 실제로 서비스에 붙였을 때 발생하는 문제(예: 입력/출력 타입 불일치, 체인 중간 단계 오류, 외부 데이터 연동 등)에 대한 해결책이나 경험담은 공식 문서나 커뮤니티에서도 찾기 힘들다.
결국, 제대로 된 정보를 얻으려면 깃허브 이슈를 뒤지거나,
커뮤니티 포럼/슬랙 등에서 비슷한 사례를 찾아서 직접 코드를 분석해봐야 하는 상황이 자주 발생한다.
이런 과정은 개발 생산성을 떨어뜨리고, 온보딩된 팀원이나 초보자들에게는 진입장벽으로 작용한다.
실무에서 “구체적인 레퍼런스가 필요한 순간”마다
“이렇게 빠르게 바뀌는 생태계에서 안정적으로 코드를 운영할 수 있을까?”
라는 불안감이 커질 수밖에 없다.
아래는 실제 reddit에서 LangChain에 신나게 데이고 극대노한 개발자의 생생한 증언이다.

이렇게까지 꾹꾹 눌러담은 분노라니 새삼 reddit의 번역기능이 놀라울 따름이다.
다시 개발얘기로 돌아오자면, 첫 번째 문제와 이어지는 디버깅의 문제가 있겠다.
앞서 언급했듯이, LangChain의 핵심은 여러 추상화 레이어를 겹겹이 쌓아서 복잡한 LLM 워크플로를 간편하게 조립하는 것이다.
하지만 실전에서 이 구조가 오히려 “문제가 발생했을 때 원인을 빠르게 파악하는 데 심각한 장애물”로 작용한다.
예를 들어,
체인 내부에서 오류가 발생해도, 스택 트레이스는 LangChain 라이브러리 깊은 곳까지 들어가 있다가 터진다.
내가 작성한 프롬프트에 오타가 있었는지, 중간 단계에서 데이터가 잘못 넘어갔는지 바로 알 수 없다.
체인 사이 데이터 전달이 꼬이면, output_key/input_key 실수, 프롬프트 포맷 에러, 예상치 못한 응답 파싱 오류 등이 한꺼번에 겹쳐서 에러의 근본 원인(프롬프트? 체인 연결? 외부 API?)을 추적하는 데 한참을 허비하게 된다.
단순히 프롬프트 한 줄을 바꿔도
“이게 어느 체인에, 어떤 변수명으로 연결되어 있었지?”
“중간 결과가 정상적으로 전달되고 있는 게 맞나?”
이런 걸 체인 전체 코드를 하나씩 확인하면서 추적해야 하는 일이 반복된다.
뭐, 이미 추상화 얘기에서 비슷한 얘기를 많이 한것 같아 이 부분은 대충 넘어가도록 하겠다.
Python 개발자라면 언젠가 한번은 들어봤을 유효성 검사 라이브러리 Pydantic이 LLM 오케스트레이션 도구를 만들었다.
바로 Pydantic AI다.
솔직히 말하자면 내가 Pydantic AI를 처음 접하고 쓰기 시작한 것은 LangChain을 대신할 라이브러리를 찾고 있었기 때문이 아니라 순전히 호기심 때문이었다.
LangChain이 짜증나긴 하지만 마땅한 대책이 없으니 울며 겨자먹기로 써가던 와중에 정말 우연찮게 Pydantic AI의 존재를 알았고 몇번 시험삼아 써보니 어느 순간 LangChain의 존재는 까마득하게 잊혀져 있었다.
사실 공개된지 얼마 안된 라이브러리기도 하고 LangChain 못지않게 빠르게 개발이 진행되면서 이것저것 많이 바뀌고 있는 상황이긴 하지만 그 모든 것들을 제치고서 사용성이 말도 안되게 좋다는 이유 하나로 이 글을 쓰게되었다.
LangChain의 단점에서 따로 이야기하진 않았지만 LLM을 사용한 개발에 있어서 항상 고민되는 문제가 있다.
바로 입출력 구조가 100% 보장되느냐는 점이다.
일반적인 소프트웨어 개발에서는 함수의 입력과 출력 타입이 명확하게 정의되고, 이 타입에 어긋나는 데이터가 들어오면 곧바로 예외가 발생한다.
하지만 LLM을 사용하는 환경에서는 프롬프트에서 아무리 명확하게 JSON 형식, 혹은 특정 스키마로 답하라고 요구해도 실제로 반환되는 값이 미묘하게 다르거나, 필드가 빠지거나, 예상과는 전혀 다른 형태로 응답하는 경우가 생기지 않는다고는 못하겠다.
LangChain을 사용할 적엔 기본적으로 프롬프트 내부에 답변 형식을 강제하는 프롬프트를 적어야했다.
이 방식은 어디까지나 LLM의 "착실함"에 의존하는 방식이다.
물론 최근 OpenAI나 Perplexity나 구조화된 답변을 받도록 강제하는 Tool이 제공되긴한다만, LangChain에서 이를 사용하는지는 잘 모르겠다.
하여간에 이런 문제는 개발 과정에서 잘 동작하던 체인이 실제 서비스에서는 예외 상황에서 바로 오류를 뱉어내거나 전체 워크플로우가 중단되는 경우를 야기할 수 있다.
Pydantic AI의 가장 큰 장점이 바로 이 지점에서 온다.
Pydantic이 유효성 검사로 이름을 날린 라이브러리라 그런지 Pydantic AI도 입출력 구조화에 진심이다.
LLM을 호출할 때의 입력 데이터는 물론이고 모델이 반환하는 출력도 Pydantic 모델로 엄격히 관리한다.
예를 들어 아래 코드와 같이 입출력을 선언해두면,
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext
from dataclasses import dataclass
@dataclass
class AgentDeps:
text: str
class AgentOutput(BaseModel):
summary: str
agent = Agent(
model = model,
deps_type = AgentDeps,
output_type = AgentOutput
)
이제 입력에 필요한 구조는 AgentDeps를 통해 관리하고 출력은 AgentOutput이 관리하게 된다.
물론 실제 쓰임세는 조금씩 다르긴 하지만 그건 나중에 실제 사용하는 방법을 포스팅할때 얘기하도록 하고 지금은 이 정도로 이해하고 넘어가면 좋겠다.
내부적으로 라이브러리를 뜯어보니 동작은 위에서 언급했던 구조화된 답변을 받도록 강제하는 Tool을 명시적으로 불러오도록 되어있는 것 같다.
뭐 어쨌거나 덕분에 우리는 LLM이 기대한 형식에 맞지 않는 응답을 보내면 곧바로 예외를 통해 감지할 수 있고 중간 데이터 흐름을 명확하게 추적할 수 있게 된다.
Pydantic AI는 복잡한 체인, 에이전트, 툴 등 추상화 레이어를 과하게 쌓지 않고, LLM 호출을 타입이 명확한 파이썬 함수처럼 다룰 수 있게 해준다.
이 구조의 가장 큰 장점은 각 단계를 완전히 독립적인 함수 단위로 분리할 수 있다는 것이다.
예를 들어, "문서 요약 → 키워드 추출 → 결과 정제" 같은 파이프라인을 구현한다고 할 때, 각 단계별 함수가 입력과 출력 타입을 명확히 갖추고 있으니 특정 단계만 따로 수정하거나, 테스트를 따로 돌리거나, 필요하면 순서를 바꾸거나, 중간에 새로운 기능(예: 추가적인 LLM 호출, 외부 API 연동)을 끼워넣는 것도 매우 쉽고, 안전하다.
LangChain은 그럼 이런 것들이 불가능한가?
아니다 분명 이것들은 LangChain을 쓰면서도 충분히 할 수 있는 작업들이다.
다만 작업의 난이도가 다르다.
입출력을 조금 바꿔야하는 경우에도 LangChain은 끄적끄적 프롬프트를 손봐야한다.
중간에 새로운 기능을 추가하려면 기존 체인을 박살내고 다시 연결시켜줘야한다.
이런 작업들은 어려운게 아니라 귀찮고 피곤하다는게 문제다.
실제로 팀 프로젝트를 하다 보면,
"여기 요약 프롬프트만 살짝 바꿔주세요",
"여기에 추가 데이터 전처리 코드를 넣고 싶어요"
같은 요청이 빈번하게 들어온다.
함수형으로 에이전트를 관리하는 방식이라면, 해당 함수만 수정하고, 입출력 타입만 확인하면 되기 때문에 예상치 못한 버그가 터질 일이 거의 없다.
이어지는 장점인데 팀원 온보딩이나 협업도 훨씬 수월하다.
LangChain처럼 체인-에이전트-툴의 추상화된 구조와, 각종 output_key, input_key, 체인 연결 규칙을 처음부터 이해해야 하는 게 아니라
"이 함수는 어떤 입력을 받고, 어떤 출력을 준다"
"이 중간 단계가 문제면, 해당 함수만 보면 된다"
"전체 파이프라인은 파이썬 함수 호출 순서와 거의 같다"
라는 직관적인 개발 방식이 유지된다.
즉, 새로운 팀원이 합류해도 코드 흐름을 한 번에 파악할 수 있고, 각 단계별 책임이 명확하니 서로 충돌 없이 개발을 병렬로 진행하기도 쉽다.
아니다.
다시 한번 말하지만 프레임워크가 모든 걸 다 해주지 않는다.
개인적으로 느끼는 Pydantic AI의 단점은 지원되는 LLM API 서비스가 그닥 많지 않다는 것이다.
물론 이건 다른 라이브러리도 마찬가지겠지만 쓰고자하는 API가 호환이 안된다면 직접 구현하는 수밖에 없다.
가만 생각해보면 OpenAI, Gemini, Ollama 정도만 지원되면 장땡 아닌가 싶기도 하지만 어쨌든 특수한 상황이 없는건 아니기 때문이다.
당장 최근에만 해도 Perplexity를 연동해야하는 상황이 있었는데 Pydantic AI에서는 아직 지원되는 형식이 아니어서 당황했던 적이 있다.
다행인건 OpenAI와 같은 규격의 API를 사용하면 쓸수 있다는 문서가 있어 문제없이 넘어가긴 했다만 세상 모든 LLM이 OpenAI의 API와 호환되는건 아닐거라 생각한다.
그런 부분에 있어서 당분간 Pydantic AI에서는 "OpenAI와 호환되는 서비스를 이용하라" 라고 안내를 하고있다.
이게 LLM 분야 전부가 공유하는 내용인지는 모르겠지만 어쨌든 호환 안되는 모델을 써야한다면 결국 구현해야하는건 나다.
그럼에도 당분간, 어쩌면 한동안은 LLM 오케스트레이션 도구로는 Pydantic AI를 채택할 것 같다.
지금까지의 경험을 돌아보면, LangChain은 프로토타입 단계나 다양한 외부 툴 연동이 필요할 때에는 확실한 강점이 있다.
하지만 실서비스, 특히 구조화된 데이터 흐름과 안정성, 유지보수를 우선시해야 하는 프로젝트라면 Pydantic AI와 같이 “타입 중심”의 심플하고 명확한 설계를 제공하는 프레임워크가 훨씬 큰 만족을 주는 듯하다.
으로 더 많은 LLM API가 표준화되고, Pydantic AI도 더 다양한 모델과 연동이 쉬워진다면 LLM을 다루는 개발자들에게 점점 더 매력적인 선택지가 될 것이라 생각한다.
여담이지만 해외에서는 많이들 Pydantic AI를 사용하는 것 같은데 아직 한국에서는 잘 알려지지 않은 것 같다.
혹시라도 나중에 Pydantic AI가 한국에서도 유명해지면 당장 이 글을 보여주면서 내가 선구자 중에 한명이었다! 라고 자랑할 수 있었으면 좋겠다.