
Tool을 어떻게 랭체인 환경에서 구현할 수 있을까?
import tool
from pydantic import BaseModel, Field
class WeatherInput(BaseModel):
location: str = Field(description="날씨를 조회할 도시와 국가")
unit: str = Field(default="celsius", description="온도 단위 (celsius 또는 fahrenheit)")
@tool("get_current_weather", args_schema=WeatherInput)
def get_current_weather(location: str, unit: str = "celsius") -> str:
"""주어진 도시의 현재 날씨 정보를 조회합니다."""
# 도구 실행 로직 작성
return f"{location}의 날씨는 맑음입니다."
from langchain.tools import tool
@tool
def get_weather(location : str) -> str :
"이 도구는 특정 지역의 날씨 정보를 반환함."
return f"오늘 {location} 날씨는 비가 옵니다."
LLM + Tool ⇒ Agent
from langchain.agents import create_agent
agent = create_agent(
model = model,
tools = [get_weather]
)

content(result[”messages”][-1].content)에 최종 응답 확인agent.invoke(
{"messages": [{"role": "user", "content": "서울 날씨 어때요?"}]},
)

tool_calls 호출 → get_weather 형식대로 ToolMessage가 반환됨 → AIMessage 다시 호출
{
"messages": [
HumanMessage(content='서울 날씨는 어때요?'),
AIMessage(content='', tool_calls[///]),
ToolMessage(content='서울 날씨는...'),
AIMessage(content='서울 현재 날씨는...')
}
Agent가 도구를 호출하여 사용할 수 있도록 SystemMessage를 부여해보자!
tools = [add, multiply, divide]
agent = create_agent(
model=model,
tools = tools
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "42 + 3 * 23은 뭔가요?"}]},
)
# HumanMessage -> AIMessage 답변 바로 출력
agent = create_agent(
model,
tools,
system_prompt="너는 사칙연산할 때 무조건 도구를 호출해라"
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "42 + 3 * 23은 뭔가요?"}]},
)
# HumanMessage -> AIMessage(tool_calls: 곱하기) -> ToolMessage(결과) -> AIMessage(tool_calls: 더하기) -> ToolMessage -> AIMessage(결과)
API를 사용한 Tool을 만들어 더 강력한 Agent 만들어보기
→ 알라딘 베스트셀러 Top10 파이썬 함수를 Tool로 넣을 수 있음
다중 턴 대화시 대화 맥락을 고려한 Tool 호출을 위해 메모리 관리 필요
메모리 종류
Checkpointer: 스레드(대화 상태)를 저장할 저장소
checkpointer=InMemoryServer() → 기억용 수첩과 펜을 Agent에게 쥐여주기
response = agent.invoke(
{"messages": [{"role": "user", "content": "안녕하세요, 저는 벼리입니다."}]},
{"configurable": {"thread_id": "1"}} # thread id에 해당하는 저장공간에 대화를 저장함
)

: Agent 내부에서 일어나는 일을 섬세하게 컨트롤하는 방법
Middleware 기능
랭체인에서 제공하는 middleware: https://docs.langchain.com/oss/python/langchain/middleware/built-in
from langchain.tools import tool
from typing import List, Dict
# 이메일 전송 도구
@tool
def send_email_tool(to: str, subject: str, body: str) -> str:
"""
지정한 이메일 주소로 메일을 보내는 도구입니다.
"""
return f"✅ 이메일이 성공적으로 전송되었습니다.\n수신자: {to}\n제목: {subject}\n내용: {body[:50]}..."
# 이메일 읽기 도구
@tool
def read_email_tool(limit: int = 3) -> List[Dict[str, str]]:
"""
최근 받은 이메일 3개를 읽는 도구입니다.
"""
return f"✅ 이메일이 성공적으로 조회되었습니다."
agent = create_agent(
model="gpt-5-nano",
tools=[send_email_tool, read_email_tool],
middleware=[
LLMToolEmulator(
model="gpt-5-nano",
),
],
)
response = agent.invoke(
{
"messages": [
{"role": "user", "content": "최근 온 메일 확인하고 알아서 답장해줘."}
]
}
)
작동 순서
read_email_tool 을 흉내내서 답변을 가지고옴agent = create_agent(
model="gpt-5-nano",
tools=[send_email_tool, read_email_tool],
middleware=[
LLMToolEmulator(model="gpt-5-nano"),
TodoListMiddleware(),
],
)
response = agent.invoke(
{
"messages": [
{"role": "user", "content": "최근 온 메일 확인하고 나한테 요약해서 보고해줘. 그 다음 답장을 작성해서 회신해줘. 마지막으로 어떻게 보냈는지 보고하고"}
]
}
)
작동 순서
read_email_tool 을 흉내내서 답변을 가지고옴from langchain.agents.middleware import LLMToolEmulator, HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
agent = create_agent(
model,
tools=[send_email_tool, read_email_tool],
# 체크포인터 연결
checkpointer=checkpointer,
middleware=[
LLMToolEmulator(model="gpt-5-nano"),
HumanInTheLoopMiddleware(
interrupt_on= {
"send_email_tool": {
"allowed_decisions": ["approve", "edit", "reject"]
},
"read_email_tool" : False
}
)
],
)
prompt = "무슨 메일 왔는지 확인해줘"
response = agent.invoke(
{"messages": [{"role": "user", "content": prompt}]},
{"configurable": {"thread_id": "HIL-a"}}
)
!image.png
apply_to_input을 설정하면, before_agent 단에서 마스킹 처리됨
agent = create_agent(
model,
tools=[save_customer_feedback],
middleware=[
LLMToolEmulator(model="gpt-5-nano"),
# 이메일 주소는 전부 마스킹 처리
PIIMiddleware("email", strategy="redact",apply_to_input=True),
# 카드번호는 마지막 4자리만 남기고 나머지 마스킹 처리
PIIMiddleware("credit_card", strategy="mask",apply_to_input=True)
],
)