랭체인 Agent, Tools, 구조화된 출력 정리

2026. 2. 9. 00:31·온라인강의

이번에는 지난번 '랭체인 1.0 설치 및 랭체인 기본 지식'에 이서 랭체인을 활용한 Agent, Tools, 구조화된 출력를 정리하고자 합니다. 

 

관련해서 수강한 강의는  인프런에서 오영재 강사님의 'LangChain version 1.0 을 활용한 생성형 AI 서비스 구축' 2번째 정리입니다. 


 

15. Agent 개요

•  LLM을 핵심 엔진으로 사용하여 주어진 목표를 달성하기 위해 독립적으로작업(추론, 판단, 실행, 피드백)을 수행하는 인공지능 시스템

• 주로 LLM(대규모 언어 모델)의 능력을 기반으로 동작하며, 사용자의명령을이해하고, 판단하며, 실행

• LLM은 두뇌, Agent는 이 두뇌를 활용해 행동을 실행하는 작업자 역할

 

Agent핵심 기술 스택

• LLM (예: GPT, Claude): 언어 이해 및 생성

• LangChain / LangGraph: 에이전트 워크플로우 및 상태 관리

• Vector Database (예: Chroma, Pinecone): 정보 저장 및 검색

• Memory: 상태 유지 및 맥락 관리

• API 통합 (예: Tavily, SerpAPI): 외부 도구와 연결

 

Agent Tools Overview

• LLM이 정의된 함수/도구를 호출해 결과를 얻도록 유도

• @tool 데코레이터로 Python 함수를 도구 등록

• Agent 생성 + create_agent의 tools 파라미터로 모델에 연결

• 함수 실행 결과는 ToolMessage로 agent가 모델에 재 전달

• 모델이 도구 호출 결과를 반영해 최종 답변 작성

• 동적 모델 선택 • 도구 오류 처리

• 동적 prompt 제공

• 구조화된 출력 (Structured Output) 요청

 

16. Agent, Tools, 구조화된 출력 Overview - Part1

1) @tool을 활용한 기본 tool 설정

랭체인에서 가장 간단하게 도구를 만드는 방법은 @tool 데코레이터를 사용하는 것입니다. @tool은 **파이썬의 데코레이터(decorator)**이며, LangChain 프레임워크에서 해당 함수를 LLM(대형언어모델)이 사용할 수 있는 “도구(tool)”로 등록하는 역할을 합니다.

Type hints 는 필수입니다. 이들은 도구의 입력 스키마(input schema) 를 정의하기 때문입니다.
독스트링(docstring) 은 모델이 도구의 목적을 이해할 수 있도록 간결하면서도 유용한 정보를 포함해야 합니다.

 

1️⃣ @tool의 정식 명칭

구분설명
명칭 데코레이터(Decorator)
파이썬 개념 함수 정의 위에 붙어서 기존 함수의 동작을 확장하거나 수정하는 문법
형태 @something
실제 의미 search_db = tool(search_db) 와 동일

즉,

 
@tool
def search_db(...):

는 내부적으로 아래와 같은 의미입니다.

 

def search_db(...):

    ...

search_db = tool(search_db)

 

2️⃣ 데코레이터(Decorator)란 무엇인가

**데코레이터는 기존 함수를 수정하지 않고 기능을 덧붙이는 래퍼(wrapper)**입니다.

쉽게 말하면:

기존 함수데코레이터 적용 후
일반 파이썬 함수 추가 기능이 붙은 함수
단순 실행 실행 전후 처리 가능
메타정보 없음 설명, 타입정보 등 추가 가능

예시 개념:

 
@log
def func():
    pass

→ 실행할 때 자동으로 로그가 남도록 기능이 추가됨.

 

3️⃣ LangChain에서 @tool이 하는 일 (핵심)

LangChain에서 @tool은 단순한 데코레이터가 아니라 LLM Agent가 호출 가능한 외부 기능으로 변환하는 역할을 합니다.

✅ 주요 기능

기능설명
Tool 등록 해당 함수를 Agent가 사용할 수 있는 도구로 등록
설명 자동 추출 docstring(함수 설명)을 LLM이 읽을 수 있게 변환
입력 스키마 생성 함수 인자 타입을 기반으로 입력 형식 정의
LLM 호출 가능 모델이 판단해서 함수 실행 가능
함수 → API화 LLM 입장에서 하나의 기능 API처럼 동작
from langchain.tools import tool

@tool
def search_db(query: str, limit: int = 10) -> str:
    """검색어(query)에 해당하는 고객 데이터베이스 레코드를 조회합니다.

    Args:
        query: 검색할 키워드 또는 문장
        limit: 반환할 최대 결과 개수
    """
    return f"'{query}'에 대한 검색 결과 {limit}개를 찾았습니다."

search_db

 

5️⃣ 왜 docstring이 중요한가

여기 부분이 실제로 매우 중요합니다.

 
"""검색어(query)에 해당하는 고객 데이터베이스 레코드를 조회합니다."""

이 설명은:

  • 사람이 보는 주석이 아니라
  • LLM이 tool 선택을 할 때 사용하는 설명

입니다.

Agent는 다음과 같이 이해합니다.

"검색 관련 질문이면 search_db를 사용해야 한다"

 

위와 같은 형태의 랭체인 코드는 결국 각 LLM사의 Function calling 함수로 변환해서 수행됩니다. 

아래는 openai의 Function calling 으로 변환시킨 코드입니다.

tools = [
    {
        "type": "function",
        "function": {
            "name": "search_db",
            "description": "검색어(query)에 해당하는 고객 데이터베이스 레코드를 조회합니다.",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "검색할 키워드 또는 문장"
                    },
                    "limit": {
                        "type": "integer",
                        "description": "반환할 최대 결과 개수",
                        "default": 10
                    }
                },
                "required": ["query"]
            }
        }
    }
]

 

2) Pydantic모델을 통한 고급 스키마 정의를 통한 툴 설정

 

from pydantic import BaseModel, Field
from langchain_core.tools import tool
import requests

# 입력 데이터 구조 정의 (Pydantic 사용)
class WeatherInput(BaseModel):
    """날씨 질의에 사용할 입력 스키마"""
    latitude: float = Field(description="질의할 지역의 위도를 입력합니다.")
    longitude: float = Field(description="질의할 지역의 경도를 입력합니다.")

# 현재의 온도 가져오기
@tool(args_schema=WeatherInput)
def get_weather(latitude, longitude):
    """
    제공된 좌표의 현재 기온을 섭씨(Celsius) 단위로 가져옵니다.
    """
    print('get_weather 도구 호출됨')
    response = requests.get(f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&current=temperature_2m")
    data = response.json()
    return data['current']['temperature_2m']

# 서울의 위도, 경도
get_weather.invoke({'latitude': 37.56667, 'longitude': 126.97806})

 

get_weather는 러너블이기 때문에 .invoke를 해야 출력이 됨

 

3)ReAct Agent

LangChain에서 말하는 ReAct Agent는 단순한 이름이 아니라, LLM이 “생각(Reasoning)”과 “행동(Action)”을 반복하면서 문제를 해결하도록 만든 에이전트 구조를 의미합니다. 과거에는 react agent(reasoning & act )라고 했지만, 랭체인 1.0부터는 그냥 agent라고 불립니다.

from langchain.agents import create_agent

# ReAct 에이전트 생성
agent = create_agent(
    model=model,
    tools=[tavily, search_db, calc, get_weather]  # Agent가 사용할 도구 목록
)

agent

# 날씨 조회 예제
result = agent.invoke(
    {"messages": [
        {'role': 'system', "content": "당신은 도움이 되는 어시스턴트입니다. 주어진 도구를 이용해 답변하세요."},
        {"role": "user", "content": "지금 서울 기온이 몇도인가요?"}
    ]}
)

result['messages'][-1].pretty_print()

 

messages 라는 키를 갖는 딕셔너리를 만들어서 invoke해야 하는데, 왜냐하면 모든 노드(함수)들의 상태(state)를 갖는데 이 state의 키가 messages 임

 

 

17. Agent, Tools, 구조화된 출력 Overview - Part2

4) 동적 모델(Dynamic Model)

from langchain_openai import ChatOpenAI
from langchain_google_genai import ChatGoogleGenerativeAI

from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse

# basic_model = ChatOpenAI(model="gpt-5-nano")
# advanced_model = ChatOpenAI(model="gpt-5-mini")

basic_model = ChatGoogleGenerativeAI(model="gemini-2.5-pro")
advanced_model = ChatGoogleGenerativeAI(model="gemini-2.5-flash")

@wrap_model_call
def dynamic_model_selection(request: ModelRequest, handler) -> ModelResponse:
    """대화의 복잡도에 따라 사용할 모델을 동적으로 선택"""

    # 현재 대화에서 주고받은 메시지 개수를 계산
    message_count = len(request.state["messages"])

    # 메시지가 10개를 초과하면 복잡한 대화로 간주 → 고급 모델 사용
    if message_count > 10:
        # 긴 대화일 경우 고급(Advanced) 모델 사용
        model = advanced_model
    else:
        # 짧은 대화일 경우 기본(Basic) 모델 사용
        model = basic_model

    # 선택된 모델을 request에 설정
    request.model = model

    # handler를 호출하여 요청 처리 계속 진행
    return handler(request)

agent_dynamic = create_agent(
    model=basic_model,  # Default model
    tools=[search_db, calc, get_weather],
    middleware=[dynamic_model_selection]
)

agent_dynamic

 

이 코드는 LangChain Agent에서 실행 시점(runtime)에 사용할 LLM 모델을 자동으로 바꾸는 구조, 즉 **동적 모델 선택(Dynamic Model Selection)**을 구현한 예시입니다. 핵심은 대화 상태(state)와 컨텍스트(context)를 보고 비용과 성능을 동시에 최적화하는 것입니다.

아래는 구조 → 동작 흐름 → 실제 의미 순서로 정리합니다.


✅ 1️⃣ 동적 모델(Dynamic Model)이란 무엇인가

동적 모델은 다음 의미입니다.

구분설명
정적 모델 (Static model) 항상 동일한 모델 사용
동적 모델 (Dynamic model) 상황에 따라 모델을 변경

즉,

  • 간단한 질문 → 빠르고 저렴한 모델
  • 복잡한 대화 → 성능이 좋은 모델

을 자동으로 선택합니다.


✅ 2️⃣ 이 코드의 전체 구조 (한 줄 요약)

 
사용자 요청 → Agent → Middleware → 모델 선택 → 모델 실행

Agent가 바로 모델을 호출하는 것이 아니라, 중간에 Middleware가 끼어들어 모델을 바꿉니다.


✅ 3️⃣ 주요 구성 요소 설명

✅ (1) 기본 모델 / 고급 모델 정의

 
basic_model = ChatGoogleGenerativeAI(model="gemini-2.5-pro")
advanced_model = ChatGoogleGenerativeAI(model="gemini-2.5-flash")
모델역할
basic_model 기본 처리용 (비용 절약)
advanced_model 복잡한 요청 처리용

실무에서는 보통:

  • nano / flash → 빠르고 저렴
  • mini / pro → 느리지만 정확

구조로 나눕니다.


✅ (2) @wrap_model_call 데코레이터

 
@wrap_model_call

이것은 모델 호출 직전에 실행되는 미들웨어를 만드는 데코레이터입니다.

쉽게 말하면:

역할설명
위치 LLM 호출 직전
기능 요청 수정 가능
변경 가능 항목 모델, 파라미터, 메시지 등

즉,

👉 "모델을 실행하기 전에 마지막으로 개입할 수 있는 지점"

입니다.


✅ (3) ModelRequest 객체

 
request: ModelRequest

여기에는 현재 Agent 상태가 들어 있습니다.

대표적으로:

항목의미
request.state["messages"] 지금까지의 대화 전체
request.model 현재 사용할 모델
request.tools 사용 가능한 tool

✅ (4) 실제 모델 선택 로직

 
message_count = len(request.state["messages"])

현재까지 대화 메시지 개수를 확인합니다.

 
if message_count > 10: model = advanced_model else: model = basic_model

의미:

조건선택 모델
메시지 ≤ 10 기본 모델
메시지 > 10 고급 모델

즉,

  • 대화가 길어질수록 문맥 이해가 어려워짐
  • 더 성능 좋은 모델로 자동 전환

✅ (5) handler(request)

 
return handler(request)

이 부분이 중요합니다.

개념설명
handler 다음 처리 단계
역할 실제 모델 실행

middleware는 가로채서 수정만 하고 다시 흐름을 넘겨주는 역할입니다.


✅ 4️⃣ 전체 실행 흐름 (실제 동작 순서)

 
1️⃣ 사용자 질문 입력 ↓ 2️⃣ Agent 실행 시작 ↓ 3️⃣ dynamic_model_selection 실행 ↓ 4️⃣ 메시지 개수 확인 ↓ 5️⃣ 모델 선택 (basic or advanced) ↓ 6️⃣ 선택된 모델로 LLM 호출 ↓ 7️⃣ 결과 반환

✅ 5️⃣ 왜 이런 구조를 쓰는가 (실무적 이유)

LLM 운영에서 가장 큰 문제는 다음입니다.

문제설명
비용 증가 항상 고급 모델 사용 시 비용 폭증
성능 부족 항상 저가 모델 사용 시 품질 저하

동적 모델은 이를 해결합니다.

상황선택
단순 질문 저비용 모델
긴 분석 대화 고성능 모델

즉 성능 대비 비용 최적화(cost-performance optimization) 구조입니다.


✅ 핵심 요약

항목의미
Dynamic Model 실행 시점에 모델을 변경
@wrap_model_call 모델 호출 전 개입하는 미들웨어
ModelRequest 현재 Agent 상태 정보
핵심 목적 비용 절감 + 성능 유지
동작 방식 상태 기반 모델 라우팅

이 코드는 LangChain Agent에서 실행 시점(runtime)에 사용할 LLM 모델을 자동으로 바꾸는 구조, 즉 **동적 모델 선택(Dynamic Model Selection)**을 구현한 예시입니다. 핵심은 대화 상태(state)와 컨텍스트(context)를 보고 비용과 성능을 동시에 최적화하는 것입니다.

아래는 구조 → 동작 흐름 → 실제 의미 순서로 정리합니다.


5) 동적 시스템 프롬프트

실행 시점의 컨텍스트(runtime context) 나 에이전트 상태(agent state) 에 따라 시스템 프롬프트를 동적으로 변경해야 하는 고급 사용 사례에서는 미들웨어(middleware) 를 사용할 수 있습니다.
@dynamic_prompt 데코레이터를 사용하면, 모델 요청(model request) 에 따라 시스템 프롬프트를 동적으로 생성하는 미들웨어를 만들 수 있습니다.

ModelRequest 안에는 모델 호출에 필요한 모든 정보가 들어 있습니다. (예: 모델 이름, 입력 메시지들, 현재까지의 내부 상태, 도구 사용 여부, 런타임(runtime) 객체 등)

request.runtime은 LangChain v1의 Agents SDK / Middleware 시스템에서 “현재 실행 중인 에이전트 호출의 런타임 상태(runtime state)” 를 담고 있는 객체입니다.

즉, agent.invoke()가 실행되는 순간의 컨텍스트(context), 도구 호출 정보, 메시지 히스토리 등을 담고 있으며, 미들웨어(dynamic_prompt 등)가 이를 읽어서 동적으로 프롬프트나 행동을 바꾸는 데 사용합니다.

from typing import TypedDict

from langchain.agents import create_agent
from langchain.agents.middleware import dynamic_prompt, ModelRequest

class Context(TypedDict):
    user_role: str

@dynamic_prompt
def user_role_prompt(request: ModelRequest) -> str:
    """사용자 역할(user role)에 따라 시스템 프롬프트를 생성"""

    # 실행 컨텍스트(runtime context)에서 사용자 역할 정보를 가져옴
    # 기본값은 "user"
    user_role = request.runtime.context.get("user_role", "user")

    # 기본 프롬프트 정의
    base_prompt = "당신은 도움이 되는 어시스턴트입니다."

    # 사용자 역할에 따라 프롬프트를 다르게 설정
    if user_role == "expert":
        # 전문가(expert)인 경우: 기술적으로 자세한 답변을 제공
        return f"{base_prompt} 기술적으로 자세하고 전문적인 답변을 제공하세요."
    elif user_role == "beginner":
        # 초보자(beginner)인 경우: 쉬운 설명과 비전문 용어 사용
        return f"{base_prompt} 개념을 쉽게 설명하고 전문 용어 사용을 피하세요."

    return base_prompt

agent_dynamic_prompt = create_agent(
    model=model,
    tools=[search_db, calc, get_weather],
    middleware=[user_role_prompt],
    context_schema=Context
)

# 실행 컨텍스트(context)에 따라 시스템 프롬프트가 동적으로 설정됨
result = agent_dynamic_prompt.invoke(
    {
        "messages": [
            {"role": "user", "content": "기계 학습(machine learning)을 한 문장으로 설명해줘."}
        ]
    },
    context={"user_role": "expert"}  # 사용자 역할을 '전문가'로 지정
)

result['messages'][-1].pretty_print()

 

6) 구조화된 출력 (Structured output)

특정 상황에서는 에이전트가 정해진 형식의 출력 결과를 반환하도록 하고 싶을 때가 있습니다.
이때 LangChain은 response_format 매개변수를 통해 구조화된 출력을 생성하는 여러 가지 방법을 제공합니다.

from pydantic import BaseModel
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy

class ContactInfo(BaseModel):
    name: str
    email: str
    phone: str

agent_structured = create_agent(
    model=model,
    tools=[search_db, calc, get_weather],
    response_format=ToolStrategy(ContactInfo)
)

result = agent_structured.invoke({
    "messages": [{"role": "user", "content": "다음에서 연락처 정보를 추출하세요: John Doe, john@example.com, (555) 123-4567"}]
})

result["structured_response"]

final_result=dict(result["structured_response"])

final_result['name']

 

'온라인강의' 카테고리의 다른 글

에이전트 시스템 설계 및 UX디자인  (0) 2026.02.12
랭체인 1.0 단기 메모리(Short-term-Memory)  (0) 2026.02.11
랭체인 1.0 설치 및 랭체인 기본 지식  (0) 2026.02.08
초보자들을 위한 Redis 자료구조 & 활용 기초  (0) 2026.02.07
[인프런 챌린지] 4주 완성 백엔드 설계 챌린지 섹션7 타임라인 서비스(4주차)  (0) 2026.02.05
'온라인강의' 카테고리의 다른 글
  • 에이전트 시스템 설계 및 UX디자인
  • 랭체인 1.0 단기 메모리(Short-term-Memory)
  • 랭체인 1.0 설치 및 랭체인 기본 지식
  • 초보자들을 위한 Redis 자료구조 & 활용 기초
AI강선생
AI강선생
AI강선생의 블로그 입니다.
  • AI강선생
    나의 배움과 성장의 궤적
    AI강선생
  • 전체
    오늘
    어제
    • 분류 전체보기 (59)
      • 온라인강의 (45)
      • 오프라인강의 (2)
      • 독서 (1)
      • 생각과다짐 (6)
      • 도메인 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    오레일리
    길벗
    랭체인
    LangChain
    스프링부트
    llmagent
    cursor
    Claude
    유리링
    Python
    Redis
    claude code
    인프런
    docker
    AI agent
    한빛미디어
    이지스퍼블리싱
    java
    rustfs
    FastAPI
    혼공바이브코딩
    PostgreSQL
    AI시대
    국회
    티스토리
    클로드코드
    챌린지
    spring
    게임기획
    에이전트
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
AI강선생
랭체인 Agent, Tools, 구조화된 출력 정리
상단으로

티스토리툴바