에이전트
에이전트는 언어 모델과 도구(tools)를 결합하여 작업에 대해 추론하고, 사용할 도구를 결정하며, 반복적으로 솔루션을 향해 작업하는 시스템을 만듭니다.
create_agent는 프로덕션에 바로 사용할 수 있는 에이전트 구현을 제공합니다.
LLM 에이전트는 목표를 달성하기 위해 루프 내에서 도구를 실행합니다. 에이전트는 중지 조건이 충족될 때까지 실행됩니다. 즉, 모델이 최종 출력을 내보내거나 반복 제한에 도달할 때까지 실행됩니다.

create_agent는 LangGraph를 사용하여 그래프 기반 에이전트 런타임을 구축합니다. 그래프는 에이전트가 정보를 처리하는 방법을 정의하는 노드(단계)와 엣지(연결)로 구성됩니다. 에이전트는 이 그래프를 통해 이동하며, 모델 노드(모델을 호출), 도구 노드(도구를 실행) 또는 미들웨어와 같은 노드를 실행합니다.
Graph API에 대해 자세히 알아보세요.
설정
%pip -Uq install langchain==1.0.3from dotenv import load_dotenv
load_dotenv("../.env", override=True)True
from langchain_teddynote import logging
logging.langsmith("LangChain-V1-Tutorial")LangSmith 추적을 시작합니다.
[프로젝트명]
LangChain-V1-Tutorial
from collections.abc import Iterator
def pretty_print(response: dict):
"""LangGraph `invoke` 메시지를 보기 좋게 출력합니다."""
for msg in response["messages"]:
msg.pretty_print()
def stream_print(response: Iterator[dict]):
"""LangGraph `stream` 메시지를 보기 좋게 출력합니다."""
for event in response:
for value in event.values():
value["messages"][-1].pretty_print()에이전트 핵심 구성 요소
-
모델(Model): 모델은 에이전트의 추론 엔진입니다. 정적 및 동적 모델 선택을 모두 지원하며 여러 방법으로 지정할 수 있습니다.
-
정적 모델(Static model): 정적 모델은 에이전트를 생성할 때 한 번 구성되며 실행 전체에서 변경되지 않습니다. 이것이 가장 일반적이고 간단한 접근 방식입니다.
-
동적 모델(Dynamic model): 동적 모델은 현재 상태(State)와 컨텍스트(Context)를 기반으로 런타임(Runtime)에 선택됩니다. 이를 통해 정교한 라우팅 로직과 비용 최적화가 가능합니다.
-
정적 모델
기본 에이전트 생성하기: create_agent
create_agent는 LangChain 1.0에서 에이전트를 구축하는 표준 방법입니다.
변경 사항 (LangChain/LangGraph v1.0)
- Deprecated:
langgraph.prebuilt.create_react_agent - 새로운 표준:
langchain.agents.create_agent
주요 차이점
create_agent는 단순히 이름만 바뀐 것이 아니라 많은 개선이 포함되었습니다:
- Middleware 지원:
- Human-in-the-loop (도구 호출 승인)
- 메시지 요약 (컨텍스트 길이 관리)
- PII 삭제 (개인정보 보호)
- 커스텀 미들웨어 작성 가능
- 향상된 구조화된 출력: 도구 호출과 구조화된 출력을 하나의 루프에서 처리하여 지연시간과 비용 절감
- 표준화된 콘텐츠 블록: 모든 프로바이더에서 일관된
.content_blocks속성 제공
사용 예시
❌ Deprecated (langgraph.prebuilt)
from langgraph.prebuilt import create_react_agent✅ 새로운 방식 (langchain.agents)
from langchain.agents import create_agent
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[weather_tool],
system_prompt="Help the user..."
)정적 모델을 사용하려면 create_agent에 모델 식별자 문자열을 전달하면 됩니다. 예를 들어 OpenAI의 GPT-5를 사용하려면 "openai:gpt-5"와 같은 형태로 입력해주세요.
💡 팁: 모델 이름만 입력해도 자동으로 제공자를 추론합니다. 예를 들어
"gpt-5"만 입력하면 자동으로"openai:gpt-5"로 인식됩니다. 지원되는 모델의 전체 목록은 init_chat_model 문서에서 확인할 수 있습니다.
from langchain.agents import create_agent
agent = create_agent("openai:gpt-4.1-nano")agent
from langchain_core.messages import HumanMessage
# 에이전트 실행
response = agent.invoke({"messages": [HumanMessage("안녕하세요?")]})pretty_print(response)================================[1m Human Message [0m=================================
안녕하세요?
==================================[1m Ai Message [0m==================================
안녕하세요! 무엇을 도와드릴까요?
모델 구성을 더 세밀하게 제어하려면 공급자 패키지를 사용하여 모델 인스턴스를 직접 초기화하세요. 이 예제에서는 ChatOpenAI를 사용합니다. 사용 가능한 다른 채팅 모델 클래스는 채팅 모델을 참조하세요.
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
model = ChatOpenAI(
model="gpt-5",
temperature=0.1,
max_completion_tokens=1000,
timeout=30,
# ... (other params)
)
agent = create_agent(model, tools=[])모델 인스턴스를 사용하면 구성을 완전히 제어할 수 있습니다. temperature, max_tokens, timeouts, base_url과 같은 특정 매개변수 및 기타 공급자별 설정을 지정해야 할 때 사용하세요. 모델에서 사용 가능한 매개변수와 메서드를 보려면 전체 공급자(Provider)를 참조하세요.
동적 모델
동적 모델은 현재 상태(State)와 컨텍스트(Context)를 기반으로 런타임(Runtime)에 선택됩니다. 이를 통해 정교한 라우팅 로직과 비용 최적화가 가능합니다.
동적 모델을 사용하려면 요청에서 모델을 수정하는 @wrap_model_call 데코레이터를 사용하여 미들웨어를 생성합니다:
from collections.abc import Callable
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain.agents.middleware import ModelRequest, ModelResponse, wrap_model_call
# 기본 모델
basic_model = ChatOpenAI(model="gpt-4.1-mini")
# 고급 모델
advanced_model = ChatOpenAI(model="gpt-4.1")
@wrap_model_call
def dynamic_model_selection(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
"""대화 복잡도에 따라 모델을 동적으로 선택합니다."""
message_count = len(request.state["messages"])
# 긴 대화에는 고급 모델을 사용
model = advanced_model if message_count > 10 else basic_model
# 선택된 모델 출력
print(f"메시지 수: {message_count}, 선택된 모델: {model.model_name}")
request.model = model
return handler(request)
agent = create_agent(
model=basic_model, # 기본 모델
middleware=[dynamic_model_selection],
tools=[],
)# 테스트 1: 짧은 대화 (10개 이하 메시지)
answer = agent.invoke({"messages": [HumanMessage("안녕하세요.")]})
answer["messages"][-1].pretty_print()메시지 수: 1, 선택된 모델: gpt-4.1-mini
==================================[1m Ai Message [0m==================================
안녕하세요! 어떻게 도와드릴까요?
# 테스트 2: 긴 대화 (10개 초과 메시지)
long_conversation = [HumanMessage("안녕") for i in range(11)]
answer = agent.invoke({"messages": long_conversation})
answer["messages"][-1].pretty_print()메시지 수: 11, 선택된 모델: gpt-4.1
==================================[1m Ai Message [0m==================================
안녕하세요! 여러 번 인사해주셔서 정말 반갑습니다 😊
저에게 궁금한 점이 있으시면 언제든지 말씀해 주세요!
⚠️ 주의: 사전 바인딩된 모델(이미
bind_tools가 호출된 모델)은 구조화된 출력을 사용할 때 지원되지 않습니다. 구조화된 출력과 함께 동적 모델 선택이 필요한 경우 미들웨어에 전달되는 모델이 사전 바인딩되지 않았는지 확인하세요.
💡 팁: 모델 구성 세부 정보는 모델을 참조하세요. 동적 모델 선택 패턴은 미들웨어의 동적 모델을 참조하세요.
도구
도구는 에이전트에게 작업을 수행할 수 있는 능력을 제공합니다. 에이전트는 다음을 용이하게 함으로써 단순한 모델 전용 도구 바인딩을 넘어섭니다:
- 순차적인 여러 도구 호출(단일 프롬프트에 의해 트리거됨)
- 적절한 경우 병렬 도구 호출
- 이전 결과를 기반으로 한 동적 도구 선택
- 도구 재시도 로직 및 오류 처리
- 도구 호출 간 상태 지속성
자세한 내용은 도구를 참조하세요.
도구를 사용하는 에이전트
create_agent를 사용하여 질문에 답하고 도구를 호출할 수 있는 간단한 에이전트를 만들어봅니다.
이 예제에서는 검색 함수와 날씨 조회 함수를 도구로 등록합니다. 실제 API를 호출하지는 않고 더미 데이터를 반환하지만, 에이전트가 도구를 언제, 어떻게 사용하는지 학습하기에 충분합니다.
간단한 시스템 프롬프트를 통해 에이전트의 역할과 동작 방식을 정의하고, 모델이 적절한 도구를 선택하여 사용자 요청에 응답하는 과정을 확인할 수 있습니다.
from langchain.agents import create_agent
from langchain.tools import tool
# 도구 정의
@tool
def web_search(query: str) -> str:
"""웹에서 정보를 검색합니다."""
return f"{query}에 대한 결과가 없습니다."
@tool
def get_weather(city: str) -> str:
"""도시의 날씨를 가져옵니다."""
return f"{city} 날씨: 맑음, 27°C"
# 에이전트에 도구 목록을 전달합니다.
agent = create_agent(
"openai:gpt-4.1-nano",
tools=[web_search, get_weather],
system_prompt="당신은 친절한 어시스턴트입니다",
)agent
# 에이전트 실행
response = agent.invoke({"messages": [HumanMessage("서울 날씨가 어때?")]})
pretty_print(response)================================[1m Human Message [0m=================================
서울 날씨가 어때?
==================================[1m Ai Message [0m==================================
Tool Calls:
get_weather (call_Op5F9VnOPYsTMJ6ZzExtz1sl)
Call ID: call_Op5F9VnOPYsTMJ6ZzExtz1sl
Args:
city: 서울
=================================[1m Tool Message [0m=================================
Name: get_weather
서울는 항상 맑습니다!
==================================[1m Ai Message [0m==================================
서울의 현재 날씨는 맑습니다. 오늘도 좋은 하루 보내시길 바랍니다!
도구 오류 처리
도구 오류 처리 방식을 사용자 정의하려면 @wrap_tool_call 데코레이터를 사용하여 미들웨어를 생성합니다.
from langchain_core.messages import ToolMessage
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call
@wrap_tool_call
def handle_tool_errors(request, handler):
"""사용자 정의 메시지로 도구 실행 오류를 처리합니다."""
try:
return handler(request)
except Exception as e:
# 모델에 사용자 정의 오류 메시지를 반환합니다
return ToolMessage(
content=f"도구 오류: 입력을 확인하고 다시 시도해주세요. (상세: {str(e)})",
tool_call_id=request.tool_call["id"],
)
@tool
def failing_tool(input_text: str) -> str:
"""항상 실패하는 테스트용 도구입니다."""
raise ValueError("이 도구는 의도적으로 실패하도록 설계되었습니다.")
agent = create_agent(
model="openai:gpt-4.1-nano",
tools=[failing_tool],
middleware=[handle_tool_errors],
)response = agent.invoke(
{"messages": [HumanMessage("failing_tool 도구를 사용하여 도구 호출에 실패하시오.")]}
)
pretty_print(response)================================[1m Human Message [0m=================================
failing_tool 도구를 사용하여 도구 호출에 실패하시오.
==================================[1m Ai Message [0m==================================
Tool Calls:
failing_tool (call_scOrCE2I7wVo51pOa6fgfWCc)
Call ID: call_scOrCE2I7wVo51pOa6fgfWCc
Args:
input_text: 도구 호출에 실패하시오.
=================================[1m Tool Message [0m=================================
도구 오류: 입력을 확인하고 다시 시도해주세요. (상세: 이 도구는 의도적으로 실패하도록 설계되었습니다.)
==================================[1m Ai Message [0m==================================
도구 호출에 실패하였습니다. 추가로 도와드릴 것이 있나요?
도구가 실패하면 에이전트는 사용자 정의 오류 메시지와 함께 ToolMessage를 반환합니다.
Context Schema
LangGraph v1.0에서 추가된 context_schema 기능은 에이전트의 도구 함수에 런타임 컨텍스트를 타입 안전하게 전달할 수 있게 해줍니다.
주요 개념
- 컨텍스트 스키마 정의: 도구 함수에 전달할 컨텍스트 데이터의 구조를 정의합니다.
- 도구 함수에서 컨텍스트 접근:
ToolRuntime[T]타입 힌트를 통해 컨텍스트에 타입 안전하게 접근합니다. - 에이전트 생성 시 스키마 등록:
context_schema인자를 통해 스키마를 등록합니다. 에이전트에 컨텍스트 스키마를 등록하면 모든 도구가 해당 타입의 컨텍스트를 받을 수 있습니다.
왜 사용하나요?
- 사용자별 격리: 각 요청마다 다른 사용자 컨텍스트(user_id, 권한 등)를 주입하여 멀티테넌트 환경에서 안전하게 작동
- 타입 안전성: TypeScript/Python의 타입 시스템을 활용하여 컴파일 타임에 오류 감지
- 프롬프트 오염 방지: 민감한 정보(user_id, API 키 등)를 LLM에 노출하지 않고 도구 실행 시에만 사용
from dataclasses import dataclass
from langchain.tools import ToolRuntime
# 1. 컨텍스트 스키마 정의
@dataclass
class UserContext:
user_id: str
# 2. 도구 함수에서 컨텍스트 접근
def get_account_info(runtime: ToolRuntime[UserContext]) -> str:
"""현재 사용자의 계정 정보를 가져옵니다."""
user_id = runtime.context.user_id
# 실제 구현에서는 데이터베이스나 외부 API를 통해 사용자 정보를 조회합니다.
# 여기서는 데모 목적으로 하드코딩된 값을 반환합니다.
if user_id == "kim":
return "계정 소유자: 김첨지\n\n잔액: 5,000원"
if user_id == "hong":
return "계정 소유자: 홍길동\n\n잔액: 1,200원"
return "사용자를 찾을 수 없습니다."
# 3. 에이전트 생성 시 스키마 등록
agent = create_agent(
"openai:gpt-4.1-nano",
tools=[get_account_info],
context_schema=UserContext,
system_prompt="당신은 재정 어시스턴트입니다",
)agent
response = agent.invoke(
{"messages": [HumanMessage("내 계좌의 현재 잔고를 알려주세요.")]},
context=UserContext(user_id="kim"),
)
pretty_print(response)================================[1m Human Message [0m=================================
내 계좌의 현재 잔고를 알려주세요.
==================================[1m Ai Message [0m==================================
Tool Calls:
get_account_info (call_rJAyRPm4Z1qm8hiKrXzvZ5I3)
Call ID: call_rJAyRPm4Z1qm8hiKrXzvZ5I3
Args:
=================================[1m Tool Message [0m=================================
Name: get_account_info
계정 소유자: 김첨지
잔액: 5,000원
==================================[1m Ai Message [0m==================================
현재 계좌 잔고는 5,000원입니다.
response = agent.invoke(
{"messages": [HumanMessage("내 계좌의 현재 잔고를 알려주세요.")]},
context=UserContext(user_id="hong"),
)
pretty_print(response)================================[1m Human Message [0m=================================
내 계좌의 현재 잔고를 알려주세요.
==================================[1m Ai Message [0m==================================
Tool Calls:
get_account_info (call_q5R1HaZqK49WIIdJgnzJto1T)
Call ID: call_q5R1HaZqK49WIIdJgnzJto1T
Args:
=================================[1m Tool Message [0m=================================
Name: get_account_info
계정 소유자: 홍길동
잔액: 1,200원
==================================[1m Ai Message [0m==================================
현재 계좌의 잔고는 1,200원입니다.
response = agent.invoke(
{"messages": [HumanMessage("내 계좌의 현재 잔고를 알려주세요.")]},
context=UserContext(user_id="park"),
)
pretty_print(response)================================[1m Human Message [0m=================================
내 계좌의 현재 잔고를 알려주세요.
==================================[1m Ai Message [0m==================================
Tool Calls:
get_account_info (call_9T3XLmlZa6MM7BiAvgoZiauo)
Call ID: call_9T3XLmlZa6MM7BiAvgoZiauo
Args:
=================================[1m Tool Message [0m=================================
Name: get_account_info
사용자를 찾을 수 없습니다.
==================================[1m Ai Message [0m==================================
죄송합니다. 계좌 정보에 접근하는 데 문제가 있거나 계정을 찾을 수 없습니다. 계좌 잔고를 확인하시려면 은행 또는 금융 기관의 공식 앱 또는 웹사이트를 이용하시거나 고객 서비스에 문의하시기 바랍니다. 도움이 필요하시면 말씀해 주세요.
시스템 프롬프트
프롬프트를 제공하여 에이전트가 작업에 접근하는 방식을 형성할 수 있습니다. system_prompt 매개변수는 문자열로 제공할 수 있습니다.
만약 system_prompt가 제공되지 않으면 에이전트는 메시지에서 직접 작업을 추론합니다.
from langchain.agents import create_agent
agent = create_agent(
"openai:gpt-4.1-mini",
system_prompt="당신은 유용한 어시스턴트입니다. 간결하고 정확하게 답변하세요.",
)동적 시스템 프롬프트
런타임 컨텍스트나 에이전트 상태에 따라 시스템 프롬프트를 수정해야 하는 고급 사용 사례의 경우 미들웨어를 사용할 수 있습니다.
@dynamic_prompt 데코레이터는 모델 요청을 기반으로 시스템 프롬프트를 동적으로 생성하는 미들웨어를 생성합니다:
from typing import TypedDict
from langchain.agents import create_agent
from langchain.agents.middleware import ModelRequest, dynamic_prompt
class Context(TypedDict):
user_role: str
@dynamic_prompt
def user_role_prompt(request: ModelRequest) -> str:
"""사용자 역할에 따라 시스템 프롬프트를 생성합니다."""
user_role = request.runtime.context.get("user_role", "user")
base_prompt = "당신은 유용한 어시스턴트입니다."
if user_role == "expert":
return f"{base_prompt} 상세한 기술적 답변을 제공하세요."
if user_role == "beginner":
return f"{base_prompt} 개념을 쉽게 설명하고 전문 용어를 피하세요."
return base_prompt
agent = create_agent(
model="openai:gpt-4.1-mini",
tools=[],
middleware=[user_role_prompt],
context_schema=Context,
)# 시스템 프롬프트는 컨텍스트에 따라 동적으로 설정됩니다
result = agent.invoke(
{"messages": [{"role": "user", "content": "머신러닝을 설명해줘"}]},
context={"user_role": "beginner"},
)
print(result["messages"][-1].content)물론이죠! 머신러닝은 컴퓨터가 사람이 직접 프로그램하지 않아도 스스로 학습하고 문제를 해결할 수 있게 하는 기술이에요.
예를 들어, 컴퓨터에게 고양이 사진과 강아지 사진을 많이 보여주면, 컴퓨터가 그 사진들을 보고 '이런 특징이 있으면 고양이구나', '저런 특징이 있으면 강아지구나' 하고 배워요. 그리고 나중에 새로운 사진을 보여주면, 배운 내용을 바탕으로 고양이인지 강아지인지 맞출 수 있게 되는 거죠.
쉽게 말해 머신러닝은 '컴퓨터가 경험(데이터)을 통해 배워서 스스로 똑똑해지는 방법'이라고 생각하면 돼요.
# 시스템 프롬프트는 컨텍스트에 따라 동적으로 설정됩니다
result = agent.invoke(
{"messages": [{"role": "user", "content": "머신러닝을 설명해줘"}]},
context={"user_role": "expert"},
)
print(result["messages"][-1].content)물론입니다! 머신러닝(Machine Learning)은 인공지능(AI)의 한 분야로, 컴퓨터가 명시적으로 프로그래밍되지 않아도 데이터를 통해 학습하고 스스로 성능을 향상시킬 수 있도록 만드는 기술입니다.
### 머신러닝의 기본 개념
- **학습(Training)**: 컴퓨터에게 데이터(입력)와 그에 대응하는 정답(출력)을 제공하여, 데이터 내의 패턴이나 규칙을 찾도록 하는 과정입니다.
- **모델(Model)**: 학습 과정을 통해 만들어진 수학적 함수나 알고리즘으로, 새로운 데이터에 대해 예측이나 분류 등을 수행합니다.
- **일반화(Generalization)**: 학습 데이터에 튜닝된 모델이 새로운 데이터에도 잘 적용되는 능력입니다.
### 머신러닝의 주요 유형
1. **지도학습(Supervised Learning)**
입력과 정답이 모두 주어진 데이터를 통해 모델을 학습시키는 방법입니다.
- 예: 이메일 스팸 필터링, 이미지 분류
- 알고리즘: 선형 회귀, 로지스틱 회귀, 서포트 벡터 머신(SVM), 신경망 등
2. **비지도학습(Unsupervised Learning)**
정답 없이 입력 데이터만 가지고 숨겨진 패턴이나 군집을 찾는 방법입니다.
- 예: 고객 세분화, 차원 축소
- 알고리즘: K-평균 클러스터링, PCA(주성분분석), 연관 규칙 학습
3. **강화학습(Reinforcement Learning)**
에이전트가 환경과 상호작용하며 보상을 최대화하는 방향으로 학습하는 방법입니다.
- 예: 게임 플레이, 로봇 제어
- 알고리즘: Q-러닝, 딥 강화학습
### 머신러닝의 활용 예시
- 음성 인식 및 자연어 처리
- 이미지 및 얼굴 인식
- 추천 시스템(넷플릭스, 유튜브 추천)
- 자율주행차
- 금융 사기 탐지
### 요약
머신러닝은 데이터를 통해 자동으로 학습하는 알고리즘을 개발하는 기술로, 복잡한 문제를 해결하고 다양한 분야에서 혁신을 이끌고 있습니다. 데이터가 충분하고 품질이 좋을수록 더 정확한 모델을 만들 수 있습니다.
필요하다면 특정 알고리즘이나 구현 방법에 대해서도 자세히 설명해 드릴 수 있습니다!
고급 개념
구조화된 출력
경우에 따라 에이전트가 특정 형식으로 출력을 반환하도록 할 수 있습니다. LangChain은 response_format 매개변수를 통해 구조화된 출력을 위한 전략을 제공합니다.
ToolStrategy
ToolStrategy는 도구 호출을 사용하여 구조화된 출력을 생성합니다. 도구 호출을 지원하는 모든 모델에서 작동합니다.
ToolStrategy는 도구의 비구조화된 텍스트 출력을 LLM이 해석하여 지정된 Pydantic 스키마로 변환하도록 하는 전략입니다.
from pydantic import BaseModel
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
class Weather(BaseModel):
temperature: float
condition: str
def weather_tool(city: str) -> str:
"""Get the weather for a city."""
return f"it's sunny and 70 degrees in {city}"
# 방법 2: 명시적 -> 도구 결과를 참고하여 LLM이 Weather 객체를 만듦
agent = create_agent(
"openai:gpt-4o-mini",
tools=[weather_tool],
response_format=ToolStrategy(Weather), # "도구 결과를 Weather로 변환"
)
# 위 방법 1과 방법 2의 출력 결과는 같을 수 있다. 하지만 의도와 명시성의 차이가 있다.
result = agent.invoke(
{"messages": [{"role": "user", "content": "What's the weather in SF?"}]}
)
print(repr(result["structured_response"]))
# results in `Weather(temperature=70.0, condition='sunny')`Weather(temperature=70.0, condition='sunny')
ProviderStrategy
ProviderStrategy는 모델 공급자의 기본 구조화된 출력 생성을 사용합니다. 더 안정적이지만 기본 구조화된 출력을 지원하는 공급자(예: OpenAI)에서만 작동합니다.
from pydantic import BaseModel, Field
from langchain.agents.structured_output import ProviderStrategy
# 1. 응답 스키마 정의
class ResponseFormat(BaseModel):
"""에이전트의 응답 스키마입니다."""
email_sender: str = Field(description="이메일 발신자")
email_sender_address: str = Field(description="발신자의 주소")
# 2. 에이전트에 응답 형식 적용
agent = create_agent(
"openai:gpt-4.1-nano",
system_prompt="이메일에서 유용한 정보를 추출합니다.",
response_format=ProviderStrategy(ResponseFormat),
)sample_input = """From: 박지민 ([email protected])
Subject: 2025년 1분기 프로젝트 킥오프 미팅 안내
안녕하세요,
다음 주 월요일(11월 4일) 오후 2시에 신규 프로젝트 킥오프 미팅을 진행하고자 합니다.
회의실 A에서 진행될 예정이며, 프로젝트 범위 및 일정에 대해 논의할 예정입니다.
참석 가능 여부를 금요일까지 회신 부탁드립니다.
감사합니다.
박지민
프로젝트 매니저
테크솔루션즈
서울시 강남구 테헤란로 123
"""
response = agent.invoke({"messages": [("user", sample_input)]})
# 3. 구조화된 응답 접근
structured_response = response["structured_response"]
assert isinstance(structured_response, ResponseFormat)
structured_response.model_dump()
print(repr(structured_response))ResponseFormat(email_sender='박지민', email_sender_address='[email protected]')
⚠️ 참고:
langchain 1.0부터 단순히 스키마를 전달하는 것(예:response_format=ContactInfo)은 더 이상 지원되지 않습니다.ToolStrategy또는ProviderStrategy를 명시적으로 사용해야 합니다.
💡 팁: 구조화된 출력에 대해 자세히 알아보려면 구조화된 출력을 참조하세요.
메모리
에이전트는 메시지 상태를 통해 대화 기록을 자동으로 유지합니다. 대화 중에 추가 정보를 기억하도록 사용자 정의 상태 스키마를 사용하도록 에이전트를 구성할 수도 있습니다.
상태에 저장된 정보는 에이전트의 단기 메모리로 생각할 수 있습니다.
사용자 정의 상태 스키마는 TypedDict로 AgentState를 확장해야 합니다.
사용자 정의 상태를 정의하는 두 가지 방법이 있습니다:
- 미들웨어를 통해 (권장)
create_agent의state_schema를 통해
미들웨어를 통한 상태 정의
특정 미들웨어 후크 및 해당 미들웨어에 연결된 도구에서 사용자 정의 상태에 액세스해야 할 때 미들웨어를 사용하여 사용자 정의 상태를 정의하세요.
from langchain_core.tools import tool
from langchain.agents import AgentState
from langchain.agents.middleware import AgentMiddleware
@tool
def web_search(query: str) -> str:
"""웹에서 정보를 검색합니다."""
return f"{query}에 대한 결과가 없습니다."
@tool
def get_weather(city: str) -> str:
"""도시의 날씨를 가져옵니다."""
return f"{city} 날씨: 맑음, 27°C"
tools = [web_search, get_weather]
class CustomState(AgentState):
user_preferences: dict
class CustomMiddleware(AgentMiddleware):
state_schema = CustomState
tools = tools
def before_model(self, state: CustomState, runtime):
"""모델 호출 전 사용자 선호도를 로깅합니다."""
user_prefs = state.get("user_preferences", {})
print(f"사용자 선호도: {user_prefs}")
# 필요시 상태를 수정하여 반환
# return {"user_preferences": updated_prefs}
# 상태를 수정하지 않으면 None 반환
return None
agent = create_agent(
"openai:gpt-4.1-mini",
tools=tools,
middleware=[CustomMiddleware()],
)# 에이전트는 이제 메시지 외에 추가 상태를 추적할 수 있습니다
result = agent.invoke(
{
"messages": [{"role": "user", "content": "기술적인 설명을 선호합니다"}],
"user_preferences": {"style": "technical", "verbosity": "detailed"},
}
)사용자 선호도: {'style': 'technical', 'verbosity': 'detailed'}
⚠️ 참고: 미들웨어를 통해 사용자 정의 상태를 정의하는 것이
create_agent의state_schema를 통해 정의하는 것보다 선호됩니다. 이는 상태 확장을 관련 미들웨어 및 도구에 개념적으로 범위를 지정할 수 있기 때문입니다.
state_schema는create_agent에서 하위 호환성을 위해 여전히 지원됩니다.
state_schema를 통한 상태 정의
도구에서만 사용되는 사용자 정의 상태를 정의하는 바로 가기로 state_schema 매개변수를 사용하세요.
from langchain.agents import AgentState
class CustomState(AgentState):
user_preferences: dict
agent = create_agent(
"openai:gpt-4.1-mini",
tools=[web_search, get_weather],
state_schema=CustomState,
)
result = agent.invoke(
{
"messages": [{"role": "user", "content": "기술적인 설명을 선호합니다"}],
"user_preferences": {"style": "technical", "verbosity": "detailed"},
}
)⚠️ 참고:
langchain 1.0부터 사용자 정의 상태 스키마는 반드시TypedDict타입이어야 합니다. Pydantic 모델과 데이터 클래스는 더 이상 지원되지 않습니다. 자세한 내용은 v1 마이그레이션 가이드를 참조하세요.
💡 팁: 메모리에 대해 자세히 알아보려면 메모리를 참조하세요. 세션 간에 지속되는 장기 메모리 구현에 대한 정보는 장기 메모리를 참조하세요.
state_schema 와 context_schema 차이점
state_schema (동적 상태):
- 실행 중 변경되는 데이터
- 예시: 메시지 히스토리, 중간 계산 결과, 카운터
- TypedDict 사용, AgentState 확장
- 자동으로 관리됨
class CustomState(AgentState):
message_count: int
agent = create_agent(
model="...",
state_schema=CustomState
)context_schema (정적 컨텍스트):
- 실행 중 변경되지 않는 데이터
- 예시: 사용자 ID, 세션 정보, API 키
- dataclass 사용 가능
- invoke() 시 명시적으로 전달
@dataclass
class Context:
user_id: str
agent = create_agent(
model="...",
context_schema=Context
)
agent.invoke(
{"messages": [...]},
context=Context(user_id="123")
)요약
- state_schema: 대화 중 변하는 상태
- context_schema: 대화 중 변하지 않는 설정/메타데이터
InMemorySaver: 대화 기록을 메모리에 저장하기
LangGraph의 InMemorySaver는 에이전트의 대화 상태를 메모리에 저장하여 이전 대화 내용을 기억할 수 있게 해줍니다.
주요 개념
- 체크포인터 생성: 메모리 기반 체크포인터
InMemorySaver를 생성합니다. 애플리케이션이 종료되면 데이터가 사라집니다. - 에이전트에 체크포인터 연결:
checkpointer파라미터로 체크포인터를 전달하여 대화 상태 저장 기능을 활성화합니다. - 스레드 ID로 세션 관리:
thread_id를 통해 대화 세션을 구분하며, 같은thread_id를 사용하면 이전 대화 내용을 기억합니다.
왜 사용하나요?
- 대화 연속성: 이전 메시지를 기억하여 문맥을 유지한 대화 가능
- 세션 관리: thread_id로 사용자별, 대화별로 독립적인 세션 관리
- 간편한 테스트: 별도 DB 없이 메모리만으로 빠르게 테스트 가능 (프로덕션에서는 영구 저장소 사용 권장:
SqliteSaver,PostgresSaver,MongoDBSaver, …)
from langgraph.checkpoint.memory import InMemorySaver
# 1. 체크포인터 생성
memory = InMemorySaver()
# 2. 에이전트에 체크포인터 연결
agent = create_agent(
"openai:gpt-4.1-mini",
checkpointer=memory,
)from langchain_core.runnables import RunnableConfig
# 3. 스레드 ID로 세션 관리
config = RunnableConfig(configurable={"thread_id": "1"})
response = agent.stream(
{"messages": [("user", "안녕하세요. 제 이름은 샘알트만입니다.")]},
config=config,
)
stream_print(response)==================================[1m Ai Message [0m==================================
안녕하세요, 샘알트만님! 만나서 반갑습니다. 어떻게 도와드릴까요?
response = agent.stream(
{"messages": [HumanMessage("제 이름이 무엇인지 기억하나요?")]},
config=config,
)
stream_print(response)==================================[1m Ai Message [0m==================================
네, 샘알트만님이라고 말씀해 주셨어요. 도움이 필요하시면 언제든지 말씀해 주세요!
thread_id 가 변경되면, 이전 대화를 기억하지 못합니다.
config = RunnableConfig(configurable={"thread_id": "999"})
response = agent.stream(
{"messages": [HumanMessage("제 이름이 무엇인지 기억하나요?")]},
config=config,
)
stream_print(response)==================================[1m Ai Message [0m==================================
죄송하지만 사용자의 이름을 기억하지 못합니다. 필요하시면 다시 알려주세요!
미들웨어
미들웨어는 실행의 여러 단계에서 에이전트 동작을 사용자 정의하기 위한 강력한 확장성을 제공합니다. 미들웨어를 사용하여 다음을 수행할 수 있습니다:
- 모델이 호출되기 전에 상태 처리(예: 메시지 자르기, 컨텍스트 주입)
- 모델의 응답 수정 또는 검증(예: 가드레일, 콘텐츠 필터링)
- 사용자 정의 로직으로 도구 실행 오류 처리
- 상태 또는 컨텍스트에 따른 동적 모델 선택 구현
- 사용자 정의 로깅, 모니터링 또는 분석 추가
미들웨어는 에이전트의 실행 그래프에 원활하게 통합되어 핵심 에이전트 로직을 변경하지 않고도 주요 지점에서 데이터 흐름을 가로채고 수정할 수 있습니다.
💡 팁:
@before_model,@after_model및@wrap_tool_call과 같은 데코레이터를 포함한 포괄적인 미들웨어 문서는 미들웨어를 참조하세요.