본문 바로가기
AI Agent/Frameworks

LangChain (1) - 패키지 구조와 첫 호출

by AtoN 2024. 1. 18.

LangChain 패키지 구조와 첫 호출

LangChain 코드를 처음 보면 langchain, langchain_core, langchain_openai, langgraph처럼 비슷한 이름의 패키지가 함께 등장합니다. 모델을 한 번 호출하려는데도 무엇을 어디에서 import해야 하는지부터 헷갈리기 쉽습니다.

구조를 이해하는 기준은 공통 인터페이스와 외부 서비스 구현을 분리한다는 것입니다. langchain_core에는 메시지, 프롬프트, Runnable처럼 여러 모델과 integration이 함께 사용하는 기본 추상화가 있고, langchain_openai 같은 provider package에는 OpenAI API에 실제로 연결하는 ChatOpenAI 같은 구현이 들어갑니다. 현재 langchain 패키지는 이 기반 위에서 agent를 만드는 고수준 API를 제공하고, 상태와 반복 실행이 필요한 agent runtime은 LangGraph 위에서 동작합니다.

그래서 가장 작은 모델 호출도 이 구조를 그대로 보여줍니다. ChatOpenAI로 provider 구현을 선택하고, 메시지를 입력한 뒤 invoke()하면 AIMessage를 돌려받습니다. 같은 Runnable 인터페이스에서 여러 입력을 처리하는 batch(), 생성되는 결과를 순차적으로 받는 stream()도 사용할 수 있습니다.

먼저 이 패키지들이 왜 나뉘어 있는지부터 정리하고, ChatOpenAI와 메시지 객체로 가장 작은 호출을 만들어 봅니다. 이어서 invoke, batch, stream이 어떻게 같은 실행 인터페이스로 묶이는지 살펴보고, 이후 prompt와 output parser를 연결하는 Runnable 구성으로 확장합니다.


1장. LangChain의 전체 구조

LangChain 생태계는 하나의 패키지에 모든 기능을 넣는 구조가 아닙니다. 역할에 따라 몇 개의 계층과 integration 패키지로 나뉩니다.

langchain
    애플리케이션과 에이전트를 쉽게 구성하는 상위 API
        │
        ├── langchain-core
        │      메시지, 프롬프트, Runnable 같은 공통 인터페이스
        │
        ├── langchain-<provider>
        │      OpenAI, Anthropic 같은 외부 서비스 연결
        │
        └── LangGraph
               상태와 반복이 필요한 실행 흐름

예를 들어 HumanMessage나 ChatPromptTemplate은 특정 모델 제공자와 관계없이 사용할 수 있으므로 langchain-core에 있습니다.

from langchain_core.messages import HumanMessage
from langchain_core.prompts import ChatPromptTemplate

반면 ChatOpenAI는 OpenAI API에 연결하는 실제 구현이기 때문에 별도의 integration 패키지에 있습니다.

from langchain_openai import ChatOpenAI

LangGraph는 조금 역할이 다릅니다. 단순한 모델 호출이 아니라 에이전트가 도구를 반복해서 호출하거나 실행 상태를 저장하는 것처럼 흐름 자체를 제어해야 할 때 사용합니다. LangChain의 상위 agent API도 이 실행 기반을 사용합니다.

여기서 먼저 기억할 것은 패키지 이름이 아니라 공통 규격, 외부 서비스 연결, 실행 흐름이 서로 분리되어 있다는 구조입니다.


2장. 왜 패키지가 나뉘었나

초기의 LangChain은 프롬프트, 모델, 체인, 에이전트, 메모리, 벡터 DB, 문서 로더 같은 기능과 외부 통합을 한 생태계 안에 빠르게 추가했습니다. 기능이 늘어날수록 문제가 생겼습니다. LangChain의 공통 인터페이스와 OpenAI, Pinecone 같은 외부 서비스의 SDK는 서로 다른 속도로 바뀌기 때문입니다.

그래서 여러 제공자에서 공통으로 사용하는 인터페이스는 langchain-core에 두고, 외부 서비스에 종속되는 구현은 별도의 integration 패키지로 분리하는 방향으로 구조가 정리됐습니다.

langchain-core
    Runnable
    Message
    Prompt
    BaseChatModel
    BaseTool

langchain-openai
langchain-anthropic
langchain-google-genai
langchain-ollama
...

이렇게 나누면 OpenAI API가 변경되어도 공통 인터페이스 전체를 같이 바꿀 필요가 없습니다. 각 integration 패키지가 제공자의 변화에 맞춰 독립적으로 업데이트될 수 있습니다.

langchain-community도 이 변화와 연결됩니다. 이전에는 문서 로더, vector store, tool 같은 수많은 서드파티 통합이 이 패키지에 모여 있었습니다. 이후 주요 통합은 독립 패키지로 이동했고, langchain-community는 2026년에 sunset됐습니다. 신규 개발에서는 가능한 경우 langchain-openai, langchain-anthropic처럼 독립된 integration 패키지를 우선하는 편이 맞습니다.

LangChain v1에서는 상위 API도 정리됐습니다. 기존 chains와 AgentExecutor, 옛 memory API 상당수는 langchain-classic으로 분리됐고, 새 에이전트의 기본 진입점은 create_agent가 맡습니다. create_agent가 만드는 에이전트는 내부적으로 LangGraph의 그래프를 사용합니다.

이 변화까지 포함해 보면 역할은 다음처럼 정리할 수 있습니다.

패키지 역할
langchain 모델과 도구를 이용해 애플리케이션·에이전트를 쉽게 구성하는 상위 API
langchain-core 메시지, 프롬프트, Runnable, 모델·도구 인터페이스 같은 공통 기반
langchain-<provider> 특정 모델 제공자나 외부 서비스와 연결하는 구현
langgraph 상태와 반복이 필요한 실행 흐름을 제어하는 저수준 런타임
langchain-classic 기존 chains와 AgentExecutor 등 레거시 API

3장. 모델을 처음 호출한다

패키지 구조를 잡았으면 실제 모델을 한 번 호출해 보면 됩니다. OpenAI를 사용한다면 LangChain과 OpenAI integration을 함께 설치합니다.

pip install "langchain[openai]"

integration 패키지를 직접 설치해도 됩니다.

pip install langchain-openai

API 키는 코드에 직접 넣기보다 환경변수나 secret manager에서 관리합니다.

export OPENAI_API_KEY="sk-..."

이제 모델 객체를 만듭니다.

from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    model="gpt-5.5",
    temperature=0,
)

가장 기본적인 실행 메서드는 invoke()입니다.

res = model.invoke("안녕하세요")
print(res.content)

여기서 중요한 점은 결과가 일반 문자열이 아니라 AIMessage라는 것입니다.

입력                 ChatModel                 출력
문자열 또는 메시지  ───────────────►          AIMessage

res.content에는 모델이 생성한 내용이 들어 있고, 같은 객체에서 토큰 사용량이나 제공자별 응답 정보도 확인할 수 있습니다.


4장. ChatModel과 Message

LangChain에서 모델을 다룰 때는 ChatModel과 Message를 함께 이해하는 것이 좋습니다. 전통적인 LLM 인터페이스가 문자열을 받아 문자열을 돌려주는 형태라면, ChatModel은 역할이 있는 메시지를 입력과 출력으로 다룹니다.

LLM         문자열                  → 문자열
ChatModel   메시지 또는 메시지 목록 → 메시지

주요 모델 integration도 대부분 ChatModel 형태를 제공합니다.

ChatOpenAI
ChatAnthropic
ChatGoogleGenerativeAI

메시지는 먼저 네 종류만 알아두면 충분합니다.

클래스 역할
SystemMessage 모델의 동작 방식이나 역할을 지정하는 시스템 지시
HumanMessage 사용자의 입력
AIMessage 모델의 응답
ToolMessage 도구 실행 결과를 다시 모델에 전달하는 메시지

시스템 지시와 사용자 입력을 함께 보내면 다음과 같습니다.

from langchain_core.messages import SystemMessage, HumanMessage

res = model.invoke([
    SystemMessage(content="한 문장으로 답하세요."),
    HumanMessage(content="LangChain이 무엇인가요?"),
])

print(res.content)

문자열 하나를 invoke()에 바로 넣을 수도 있지만, 여러 역할이 섞이는 대화나 에이전트에서는 메시지 구조를 사용해야 입력의 의미가 명확해집니다.

다음 편에서는 이 메시지를 매번 직접 만들지 않고 ChatPromptTemplate으로 역할과 변수 자리를 템플릿화합니다.


5장. Runnable과 공통 실행 방식

LangChain에서 모델만 특별한 실행 방식을 사용하는 것은 아닙니다. 프롬프트, 모델, 파서처럼 서로 다른 구성요소가 Runnable이라는 공통 실행 인터페이스를 공유합니다.

입력 하나를 실행할 때는 invoke()를 사용합니다.

model.invoke("안녕하세요")

같은 Runnable에 여러 입력을 처리하려면 batch()를 사용합니다.

model.batch([
    "안녕하세요",
    "반갑습니다",
    "잘 부탁드립니다",
])

응답을 생성되는 대로 받으려면 stream()을 사용합니다.

for chunk in model.stream("긴 이야기를 들려주세요"):
    print(chunk.content, end="", flush=True)

비동기 환경에서는 ainvoke() 같은 비동기 메서드를 사용할 수 있습니다.

await model.ainvoke("안녕하세요")

batch()의 실제 병렬 처리 방식이나 효율은 각 구현과 모델 제공자의 레이트리밋에 영향을 받습니다. stream()도 체인 안의 모든 단계가 스트리밍을 적절히 지원해야 끝까지 효과를 볼 수 있습니다.

이 공통 인터페이스 덕분에 이후 프롬프트, 모델, 출력 파서를 연결한 전체 체인도 하나의 Runnable처럼 실행할 수 있습니다. 4편에서 다룰 prompt | model | parser가 가능한 이유도 여기에 있습니다.


6장. 제공자를 바꾸면 무엇이 달라지나

공통 인터페이스의 장점은 모델 제공자를 바꿔도 기본적인 실행 방식은 유지된다는 점입니다. Anthropic 모델을 사용하더라도 invoke, batch, stream 같은 메서드는 그대로 사용할 수 있습니다.

from langchain_anthropic import ChatAnthropic

model = ChatAnthropic(model="claude-sonnet-4-6")
res = model.invoke("안녕하세요")

여러 제공자의 모델을 같은 초기화 방식으로 다루고 싶다면 init_chat_model()을 사용할 수도 있습니다.

from langchain.chat_models import init_chat_model

model = init_chat_model("openai:gpt-5.5")

제공자를 바꾸면 모델 식별자를 바꿉니다.

model = init_chat_model("anthropic:claude-sonnet-4-6")

다만 공통 인터페이스가 제공자 차이를 전부 없애 주는 것은 아닙니다. 해당 provider integration 패키지는 별도로 설치되어 있어야 하고, 지원하는 파라미터와 기능도 모델마다 다릅니다. reasoning 옵션, multimodal 입력, 토큰 제한처럼 제공자 고유 기능을 사용할 때는 해당 integration 문서를 확인해야 합니다.

즉 LangChain이 통일하는 것은 애플리케이션에서 모델을 다루는 기본 인터페이스이지, 모든 모델의 기능 자체가 아닙니다.


7장. 응답 객체에서 무엇을 읽나

ChatModel의 응답인 AIMessage에는 생성된 텍스트 외에도 실행에 필요한 정보가 함께 들어 있습니다.

res = model.invoke("안녕하세요")

res.content
res.usage_metadata
res.response_metadata
res.id

content는 실제 모델 응답입니다. usage_metadata는 입력·출력 토큰 수처럼 여러 제공자에서 공통으로 다루기 좋은 사용량 정보를 정규화한 필드입니다.

print(res.usage_metadata)

예를 들면 다음과 같은 형태입니다.

{
    "input_tokens": 9,
    "output_tokens": 14,
    "total_tokens": 23
}

이전에는 제공자별 응답 메타데이터를 직접 해석하는 경우가 많았지만, 공통 사용량 정보는 usage_metadata를 기준으로 다루는 편이 모델을 교체하기 쉽습니다.

response_metadata에는 finish reason이나 실제 모델 이름처럼 제공자가 돌려준 세부 정보가 들어갑니다. 이 값의 구조는 제공자마다 다를 수 있으므로 애플리케이션의 핵심 로직을 특정 키에 강하게 의존시키지 않는 편이 좋습니다.

3편에서 다룰 구조화 출력은 이 메타데이터와 다른 문제입니다. 응답 메타데이터를 파싱하는 것이 아니라 모델의 결과를 애플리케이션이 원하는 스키마로 받는 방법을 다룹니다.


8장. 모델 교체와 운영에서 주의할 점

모델 객체는 여러 파일에서 제각각 만들기보다 설정이나 factory 한 곳에서 생성하는 편이 관리하기 쉽습니다. 모델을 교체하거나 timeout, retry 같은 공통 정책을 바꿀 때 수정 범위를 줄일 수 있습니다.

API 키는 소스 코드나 노트북에 직접 넣지 않고 환경변수나 secret manager를 사용합니다. 모델 호출은 네트워크 요청이므로 timeout과 retry도 서비스 요구사항에 맞게 명시적으로 관리하는 편이 안전합니다.

토큰 사용량처럼 제공자와 무관하게 다루려는 값은 usage_metadata 같은 공통 필드를 우선하고, response_metadata처럼 제공자별 구조가 다른 값은 필요한 경우에만 사용합니다.

검색해서 찾은 오래된 예제의 import 경로도 그대로 믿지 않는 편이 좋습니다. langchain.chains, AgentExecutor, langchain_community가 중심인 예제라면 LangChain v1 계열의 패키지 구조에서 어디로 이동했는지 확인해야 합니다.


9장. 정리

LangChain의 패키지 구조는 이름을 전부 외우는 것보다 역할을 나눠 이해하는 것이 중요합니다.

langchain-core
    여러 제공자에서 공통으로 사용하는 인터페이스와 기반 컴포넌트

langchain-<provider>
    실제 모델과 외부 서비스 연결

langgraph
    상태, 반복, 중단과 재개가 필요한 실행 흐름

langchain
    위 구성요소를 쉽게 사용할 수 있게 묶는 상위 API

langchain-classic
    기존 chains와 AgentExecutor 같은 레거시 API

가장 작은 모델 호출은 다음 정도로 시작할 수 있습니다.

from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage

model = ChatOpenAI(model="gpt-5.5", temperature=0)

res = model.invoke([
    SystemMessage(content="한 문장으로 답하세요."),
    HumanMessage(content="LangChain이 무엇인가요?"),
])

print(res.content)
print(res.usage_metadata)

여기까지 이해하면 LangChain에서 모델이 어떤 패키지에 있고, 메시지를 어떻게 전달하며, 왜 invoke, batch, stream 같은 실행 방식이 반복해서 등장하는지 연결됩니다. 다음 편에서는 ChatPromptTemplate을 이용해 모델에 넣을 메시지를 템플릿으로 만드는 방법을 살펴봅니다.


참고자료

'AI Agent > Frameworks' 카테고리의 다른 글

LangGraph (2) - State, Node, Edge  (0) 2024.08.05
LangChain (4) - 조합하기  (0) 2024.05.30
LangGraph (1) - 왜 그래프인가  (0) 2024.05.10
LangChain (3) - 출력 구조  (0) 2024.03.31
LangChain (2) - 프롬프트 넣기  (0) 2024.02.22

댓글