create_tool_calling_agent는 더 이상 동작하지 않는다.

기존에 Project를 진행하면서 create_tool_calling_agent를 활용해 ReAct 프레임워크로 동작하는 Agent를 만들었다.
그런데 LangChain이 v0 계열에서 v1으로 업데이트되면서 약간의 변화가 생겼고,
기존 함수가 Deprecated되어 코드가 제대로 동작하지 않게 되었다.

기존 코드를 그대로 사용하기 위해서는 langchain-classic이라는 패키지를 새롭게 다운로드 받으라고 안내되어 있다.
하지만 이미 Deprecated된 코드이기 때문에, LangChain의 변화 양상에 맞게 코드를 정리해보는 게 필요하다고 생각했다.

때문에 LangChain-v1에 대한 블로그에 대해 쭉 살펴봤다. (https://docs.langchain.com/oss/python/migrate/langchain-v1)
결론부터 정리하면, 기존의 create_tool_calling_agent 함수의 Static한 한계로 인해, create_react_agent라는 Dynamic한 방식으로 흐름이 바뀌다가, v1으로 업데이트되면서 이 2개의 함수는 Deprecated되고, 두 함수의 역할이 create_agent라는 함수에 모두 통합되었다.
여기서는 create_agent 함수에 대해 구체적으로 정리해보려고 한다,
create_tool_calling_agent의 위치와 한계


create_tool_calling_agent는 v0 계열에서 “도구 호출(tool calling)에 특화된 에이전트”를 만들기 위한 헬퍼 함수였다.
LLM이 함수/툴을 호출하고, 그 결과를 다시 모델 입력으로 연결하는 기본 루프를 제공하는 것이 목적이었다.
하지만 이 방식은 다음과 같은 한계를 가졌다.
- 프롬프트 커스터마이징이 제한적이었다.
- 사전 처리(pre)·사후 처리(post) 로직을 확장하기가 어려웠다.
- 상태(state), 모델 선택, 에러 처리, structured output 등을 각각 다른 방식으로 얹어야 했다.
- LangGraph 기반 로직과 LangChain 기반 로직이 이원화되어 있었다.
결과적으로 “툴 호출은 되지만, 에이전트 전체 동작을 유연하게 제어하기는 어려운” 구조였다.
create_react_agent의 등장과 역할
create_react_agent는 langgraph.prebuilt에 있던 함수로, ReAct(Reason + Act) 패턴을 쉽게 구현하기 위해 제공되었다.
이 함수는 단순한 툴 호출을 넘어 다음을 가능하게 했다.
- Thought → Action → Observation의 반복 루프
- 동적 모델 선택
- 동적 프롬프트
- 커스텀 상태 관리
- LangGraph 기반의 실행 흐름 제어
즉, create_tool_calling_agent보다 훨씬 “에이전트다운” 기능을 제공했다.
그러나 문제는 위치와 복잡성이었다.
- 해당 함수의 위치가 langgraph.prebuilt에 있어 LangChain 사용자 입장에서 진입 장벽이 있었다.
- 또한, 훨씬 많은 기능을 제공했지만, 확장 방식이 일관되지 않았다.
- hook, config, callable model 등 여러 패턴이 혼재되어 있었다.
create_agent의 도입 배경


LangChain v1에서는 위 두 흐름을 모두 정리하여 create_agent 하나로 통합했다.
create_agent는 기존 create_react_agent의 개념을 계승하면서도, 구조를 단순화하고 확장 포인트를 명확히 한 함수다.
핵심 설계 철학은 다음과 같다.
- “에이전트의 모든 확장은 middleware로 한다”
- “상태는 TypedDict 기반으로 명시적으로 관리한다”
- “모델, 프롬프트, 툴, 에러 처리, human-in-the-loop을 하나의 루프에서 통합한다”
- “LangChain 중심 API로 통일한다”
이로 인해 create_agent는 단순한 헬퍼가 아니라, LangChain v1의 표준 에이전트 생성 API가 되었다.
create_react_agent → create_agent의 구체적 변화
문서 기준으로 중요한 차이점은 다음과 같다.
- 위치 : 기존 - create_react_agent: langgraph.prebuilt → 최신 - create_agent: langchain.agents
- 프롬프트
- prompt → system_prompt로 명확화
- 동적 프롬프트는 decorator + middleware 패턴으로 통합
- hook 구조 : 기존 - pre-model / post-model hook → 최신 - middleware.before_model / after_model
- 상태 관리 : Pydantic, dataclass 제거 & TypedDict 기반 AgentState만 허용
- 모델 선택 : callable model 제거 & middleware.wrap_model_call로 통합
- 툴 에러 처리 : 내부 처리 → wrap_tool_call middleware
- structured output : 별도 노드 제거 & ToolStrategy / ProviderStrategy로 통합
즉, create_react_agent에서 “가능은 했지만 분산되어 있던 기능”을,
create_agent에서는 “일관된 미들웨어 기반 확장 모델”로 재설계했다.
create_tool_calling_agent는 어떻게 되었나
v1 기준에서 create_tool_calling_agent는 사실상 독립적인 추천 API에서 제외되었다.
create_agent가 내부적으로 툴 호출, ReAct 루프, structured output까지 모두 포괄하기 때문이다.
정리하면 다음과 같다.
- 단순 툴 호출 에이전트 → create_agent 사용
- ReAct 패턴 에이전트 → create_agent 사용
- 동적 모델·프롬프트·상태·에러 처리 → create_agent + middleware
기존 함수들은 개념적으로 흡수되었고, API 표면에서는 create_agent 하나만 남긴 셈이다.
create_agent 사용 방법
create_agent는 LangChain v1에서 유일하게 권장되는 표준 에이전트 생성 API다.
기존의 create_tool_calling_agent, create_react_agent를 모두 흡수한 통합 인터페이스이며, 에이전트의 동작은 다음 요소들의 조합으로 정의된다.
- 모델 (model)
- 도구 (tools)
- 미들웨어 (middleware)
- 상태 스키마 (state_schema)
- 정적 컨텍스트 (context_schema)
핵심 철학은 “에이전트의 모든 확장은 middleware로 한다”이다.
1. 가장 기본적인 사용법
여기서 가장 단순한 형태는 모델과 도구만 넘기는 방식이다.
from langchain.agents import create_agent
from langchain.tools import tool
@ tool
def check_weather(city: str) -> str:
return f"{city} is sunny"
agent = create_agent(
model = "gpt-5-mini"
tools = [check_weather]
)
result = agent.invoke({
"messages": [{"role": "user", "content": "서울 날씨 알려줘"}]
}]
이 경우 내부적으로는 다음 루프가 자동으로 구성된다.
- 사용자 메시지 입력
- 모델 호출
- 필요 시 툴 호출
- 툴 결과를 다시 모델 입력으로 반영
- 최종 응답 반환
2. system_prompt 사용법
v1에서는 prompt라는 이름이 system_prompt로 명확히 분리되었다.
agent = create_agent(
model="gpt-4o-mini",
tools=[check_weather],
system_prompt="너는 간결하게 답변하는 어시스턴트다."
)
주의할 점은 SystemMessage 객체를 직접 넘기지 않고 문자열만 사용해야 한다는 것이다.
3. 동적 프롬프트 (Dynamic prompt)
대화 상태나 사용자 속성에 따라 System prompt를 바꾸고 싶을 때는 dynamic_prompt middleware를 사용한다.
from dataclasses import dataclass
from langchain.agents.middleware import dynamic_prompt, ModelRequest
@dataclass
class Context:
user_level: str
@dynamic_prompt
def my_prompt(request: ModelRequest) -> str:
if request.runtime.context.user_level == "expert":
return "전문적인 기술 설명을 제공해라."
return "쉽게 설명해라."
agent = create_agent(
model = "gpt-5-mini",
tools = [check_weather],
middleware = [my_prompt],
context_schema = Context
)
agent.invoke(
{"messages" : [{"role": "user", "context": "asnyc가 뭐야?"}]},
context = Context(user_level = "expert")
)
이 방식은 v0의 callable model, 동적 prompt 로직을 대체한다.
4. Agent의 확장 (Middleware)
LangChain v1에서 Agent의 동작 확장은 middleware를 통해 이루어진다.
middleware는 에이전트 실행 루프의 특정 지점에 개입하여, 모델 호출 전·후 처리, 툴 실행 제어, 사용자 개입(Human-in-the-Loop) 등을 구조적으로 구현할 수 있도록 한다.
이를 통해 프롬프트, 모델, 상태, 에러 처리 로직을 에이전트 핵심 로직과 분리한 채 재사용 가능하게 설계할 수 있다.
middleware는 단순한 콜백이 아니라, 에이전트 실행 흐름에 직접 영향을 주는 제어 계층으로 동작한다.
middleware는 다음과 같은 시점에 개입할 수 있다.
- 모델 호출 이전: 입력 메시지 수정, 요약, 필터링, 가드레일 적용
- 모델 호출 이후: 출력 검증, 후처리, 사용자 승인 요구
- 툴 실행 시점: 툴 호출 가로채기, 에러 처리, 로깅
- 모델 선택 시점: 동적 모델 전환
이 구조를 통해 에이전트의 “행동 정책”을 코드로 명확하게 분리할 수 있다.
아래 예시는 사용자가 이메일을 실제로 전송하기 전에 반드시 사람의 승인을 받도록 하는 middleware 구성 예시다.
send_email 툴이 호출되려는 순간 실행이 중단되며, 사용자의 승인 또는 거절이 있어야 다음 단계로 진행된다.
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
agent = create_agent(
model="claude-sonnet-4-5-20250929",
tools=[read_email, send_email],
middleware=[
HumanInTheLoopMiddleware(
interrupt_on={
"send_email": {
"description": "Please review this email before sending",
"allowed_decisions": ["approve", "reject"]
}
}
)
]
)
'Agentic AI 구축 > LangChain & LangGraph' 카테고리의 다른 글
| [LangChain 함수 3] Streaming을 통해 결과 반환반기 (stream) (0) | 2026.01.10 |
|---|---|
| [LangChain 함수 2] Agent의 Model과 Message 이해하기 (0) | 2026.01.10 |
| [LangGraph 기초 5] Interrupt가 필요한 이유 (Human-in-the-Loop) (0) | 2026.01.03 |
| [LangGraph 기초 4] Memory와 Checkpoint의 개념 (0) | 2026.01.03 |
| [LangGraph 기초 3] Conditional Edge의 개념 (Command 인자) (0) | 2026.01.03 |