도구

도구는 에이전트가 작업을 수행하기 위해 호출하는 구성 요소입니다. 도구는 잘 정의된 입력과 출력을 통해 모델이 외부 세계와 상호작용할 수 있도록 하여 모델의 기능을 확장합니다. 도구는 호출 가능한 함수와 해당 입력 스키마를 캡슐화합니다. 이러한 도구는 호환되는 채팅 모델에 전달될 수 있으며, 모델이 도구를 호출할지 여부와 어떤 인수로 호출할지 결정할 수 있게 합니다. 이러한 시나리오에서 도구 호출은 모델이 지정된 입력 스키마에 부합하는 요청을 생성할 수 있도록 합니다.

서버 측 도구 사용

일부 챗 모델(예: OpenAI, Anthropic, Gemini)은 웹 검색 및 코드 인터프리터와 같이 서버 측에서 실행되는 내장 도구를 제공합니다. 특정 모델에서 이러한 도구에 액세스하는 방법을 알아보려면 프로바이더 개요를 참고합니다.

도구 생성

기본 도구 정의

도구를 생성하는 가장 간단한 방법은 @tool 데코레이터를 사용하는 것입니다. 기본적으로 함수의 docstring이 모델이 도구를 언제 사용해야 하는지 이해하는 데 도움이 되는 도구 설명이 됩니다:

from langchain.tools import tool
 
 
@tool
def search_database(query: str, limit: int = 10) -> str:
    """쿼리와 일치하는 레코드를 고객 데이터베이스에서 검색합니다.
 
    Args:
        query: 검색할 검색어
        limit: 반환할 최대 결과 수
    """
    return f"'{query}'에 대해 {limit}개의 결과를 찾았습니다."
search_database
StructuredTool(name='search_database', description='쿼리와 일치하는 레코드를 고객 데이터베이스에서 검색합니다.\n\n    Args:\n        query: 검색할 검색어\n        limit: 반환할 최대 결과 수', args_schema=<class 'langchain_core.utils.pydantic.search_database'>, func=<function search_database at 0x10ca5e480>)

타입 힌트는 도구의 입력 스키마를 정의하므로 필수입니다. docstring은 모델이 도구의 목적을 이해하는 데 도움이 되도록 유익하고 간결해야 합니다.

도구 속성 사용자 정의

사용자 정의 도구 이름

기본적으로 도구 이름은 함수 이름에서 가져옵니다. 더 설명적인 이름이 필요한 경우 재정의할 수 있습니다.

@tool("web_search")  # 사용자 정의 이름
def search(query: str) -> str:
    """웹에서 정보를 검색합니다."""
 
    return f"{query}에 대한 결과"
 
 
print(search.name)  # web_search
web_search

사용자 정의 도구 설명

더 명확한 모델 가이드를 위해 자동 생성된 도구 설명을 재정의할 수 있습니다:

@tool(
    "calculator",
    description="산술 계산을 수행합니다. 모든 수학 문제에 사용하세요.",
)
def calc(expression: str) -> str:
    """수학 표현식을 평가합니다."""
 
    return str(eval(expression))
calc
StructuredTool(name='calculator', description='Performs arithmetic calculations. Use this for any math problems.', args_schema=<class 'langchain_core.utils.pydantic.calculator'>, func=<function calc at 0x1144b8400>)

고급 스키마 정의

Pydantic 모델 또는 JSON 스키마로 복잡한 입력을 정의할 수 있습니다.

Pydantic model

from typing import Literal
 
from pydantic import BaseModel, Field
 
 
class WeatherInput(BaseModel):
    """날씨 쿼리를 위한 입력."""
 
    location: str = Field(description="도시 이름 또는 좌표")
    units: Literal["celsius", "fahrenheit"] = Field(
        default="celsius", description="온도 단위 설정"
    )
    include_forecast: bool = Field(default=False, description="5일 예보 포함 여부")
 
 
@tool(args_schema=WeatherInput)
def get_weather(
    location: str,
    units: str = "celsius",
    include_forecast: bool = False,
) -> str:
    """현재 날씨와 선택적 예보를 가져옵니다."""
 
    temp = 22 if units == "celsius" else 72
    result = f"{location}의 현재 날씨: {temp}{units[0].upper()}"
 
    if include_forecast:
        result += "\n향후 5일: 맑음"
    return result
get_weather.args_schema
__main__.WeatherInput

JSON Schema

weather_schema = {
    "type": "object",
    "properties": {
        "location": {"type": "string"},
        "units": {"type": "string"},
        "include_forecast": {"type": "boolean"},
    },
    "required": ["location", "units", "include_forecast"],
}
 
 
@tool(args_schema=weather_schema)
def get_weather2(
    location: str,
    units: str = "celsius",
    include_forecast: bool = False,
) -> str:
    """현재 날씨와 선택적 예보를 가져옵니다."""
 
    temp = 22 if units == "celsius" else 72
    result = f"{location}의 현재 날씨: {temp}{units[0].upper()}"
 
    if include_forecast:
        result += "\n향후 5일: 맑음"
    return result
get_weather2.args_schema
{'type': 'object',
 'properties': {'location': {'type': 'string'},
  'units': {'type': 'string'},
  'include_forecast': {'type': 'boolean'}},
 'required': ['location', 'units', 'include_forecast']}

컨텍스트 액세스

중요한 이유: 도구는 에이전트 상태, 런타임 컨텍스트 및 장기 메모리에 액세스할 수 있을 때 가장 강력합니다. 이를 통해 도구는 컨텍스트 인식 의사 결정을 내리고 응답을 개인화하며 대화 전반에 걸쳐 정보를 유지할 수 있습니다.

도구는 ToolRuntime 매개변수를 통해 런타임 정보에 액세스할 수 있으며, 다음을 제공합니다:

  • State - 실행 과정에서 흐르는 변경 가능한 데이터(메시지, 카운터, 사용자 정의 필드)
  • Context - 사용자 ID, 세션 세부 정보 또는 애플리케이션별 구성과 같은 불변 구성
  • Store - 대화 전반에 걸친 영구 장기 메모리
  • Stream Writer - 도구 실행 시 사용자 정의 업데이트 스트리밍
  • Config - 실행을 위한 RunnableConfig
  • Tool Call ID - 현재 도구 호출의 ID

ToolRuntime

ToolRuntime을 사용하여 단일 매개변수로 모든 런타임 정보에 액세스할 수 있습니다. 도구 시그니처에 runtime: ToolRuntime을 추가하기만 하면 LLM에 노출되지 않고 자동으로 주입됩니다.

ToolRuntime: 도구가 state, context, store, streaming, config 및 tool call ID에 액세스할 수 있도록 하는 통합 매개변수입니다. 이는 별도의 InjectedState, InjectedStore, get_runtime, InjectedToolCallId 어노테이션을 사용하는 이전 패턴을 대체합니다.

상태 액세스:

도구는 ToolRuntime을 사용하여 현재 그래프 상태에 액세스할 수 있습니다:

from langchain.tools import ToolRuntime, tool
 
 
# 현재 대화 상태 액세스
@tool
def summarize_conversation(runtime: ToolRuntime) -> str:
    """지금까지의 대화를 요약합니다."""
    messages = runtime.state["messages"]
 
    human_msgs = sum(1 for m in messages if m.__class__.__name__ == "HumanMessage")
    ai_msgs = sum(1 for m in messages if m.__class__.__name__ == "AIMessage")
    tool_msgs = sum(1 for m in messages if m.__class__.__name__ == "ToolMessage")
 
    return f"Conversation has {human_msgs} user messages, {ai_msgs} AI responses, and {tool_msgs} tool results"
 
 
# 사용자 정의 상태 필드 액세스
@tool
def get_user_preference(
    pref_name: str,
    runtime: ToolRuntime,  # ToolRuntime 매개변수는 모델에 보이지 않습니다
) -> str:
    """사용자 기본 설정 값을 가져옵니다."""
 
    preferences = runtime.state.get("user_preferences", {})
    return preferences.get(pref_name, "Not set")

tool_runtime 매개변수는 모델에서 숨겨집니다. 위 예제의 경우 모델은 도구 스키마에서 pref_name만 볼 수 있으며, tool_runtime은 요청에 포함되지 않습니다.

from libs.helpers import pretty_print
 
from langchain.agents import AgentState, create_agent
 
 
class State(AgentState):
    user_preferences: dict
 
 
agent = create_agent(
    "openai:gpt-4.1-mini",
    tools=[summarize_conversation, get_user_preference],
    state_schema=State,
    system_prompt="당신은 유능한 어시스턴트입니다",
)
 
response = agent.invoke(
    input={
        "messages": [("user", "제 current_status를 확인해주세요.")],
        "user_preferences": {"current_status": "good"},
    },
)
pretty_print(response)
================================ Human Message =================================

제 current_status를 확인해주세요.
================================== Ai Message ==================================
Tool Calls:
  summarize_conversation (call_L1R32S7PFLcyz0mcpMekh5LG)
 Call ID: call_L1R32S7PFLcyz0mcpMekh5LG
  Args:
================================= Tool Message =================================
Name: summarize_conversation

Conversation has 1 user messages, 1 AI responses, and 0 tool results
================================== Ai Message ==================================
Tool Calls:
  get_user_preference (call_dIYmp93J9G74b8HbJA3613a7)
 Call ID: call_dIYmp93J9G74b8HbJA3613a7
  Args:
    pref_name: current_status
================================= Tool Message =================================
Name: get_user_preference

good
================================== Ai Message ==================================

현재 상태는 "good"입니다. 추가로 도와드릴 것이 있을까요?

상태 업데이트:

Command를 사용하여 에이전트의 상태를 업데이트하거나 그래프의 실행 흐름을 제어할 수 있습니다:

from langchain.messages import RemoveMessage
from langchain.tools import ToolRuntime, tool
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from langgraph.types import Command
 
 
# 모든 메시지를 제거하여 대화 기록 업데이트
@tool
def clear_conversation() -> Command:
    """대화 기록을 지웁니다."""
 
    return Command(
        update={
            "messages": [RemoveMessage(id=REMOVE_ALL_MESSAGES)],
        }
    )
 
 
# 에이전트 상태에서 user_name 업데이트
@tool
def update_user_name(new_name: str, runtime: ToolRuntime) -> Command:
    """사용자 이름을 업데이트합니다."""
 
    return Command(update={"user_name": new_name})

Context

runtime.context를 통해 사용자 ID, 세션 세부 정보 또는 애플리케이션별 구성과 같은 불변 구성 및 컨텍스트 데이터에 액세스할 수 있습니다.

도구는 ToolRuntime을 통해 런타임 컨텍스트에 액세스할 수 있습니다:

from dataclasses import dataclass
 
from langchain.agents import create_agent
from langchain.tools import ToolRuntime, tool
 
 
USER_DATABASE = {
    "user123": {
        "name": "Alice Johnson",
        "account_type": "Premium",
        "balance": 5000,
        "email": "[email protected]",
    },
    "user456": {
        "name": "Bob Smith",
        "account_type": "Standard",
        "balance": 1200,
        "email": "[email protected]",
    },
}
 
 
@dataclass
class UserContext:
    user_id: str
 
 
@tool
def get_account_info(runtime: ToolRuntime[UserContext]) -> str:
    """현재 사용자의 계정 정보를 가져옵니다."""
 
    user_id = runtime.context.user_id
 
    if user_id in USER_DATABASE:
        user = USER_DATABASE[user_id]
        return f"Account holder: {user['name']}\nType: {user['account_type']}\nBalance: ${user['balance']}"
 
 
agent = create_agent(
    "openai:gpt-4.1-nano",
    tools=[get_account_info],
    context_schema=UserContext,
    system_prompt="당신은 재정 어시스턴트입니다",
)
 
result = agent.invoke(
    {"messages": [{"role": "user", "content": "현재 잔액이 얼마인가요?"}]},
    context=UserContext(user_id="user123"),
)
pretty_print(result)
================================ Human Message =================================

현재 잔액이 얼마인가요?
================================== Ai Message ==================================
Tool Calls:
  get_account_info (call_a8nkq27uzytd9p8EIZ4MLU8y)
 Call ID: call_a8nkq27uzytd9p8EIZ4MLU8y
  Args:
================================= Tool Message =================================
Name: get_account_info

Account holder: Alice Johnson
Type: Premium
Balance: $5000
================================== Ai Message ==================================

현재 잔액은 5000달러입니다.

메모리 (Store)

store를 사용하여 대화 전반에 걸쳐 영구 데이터에 액세스할 수 있습니다. store는 runtime.store를 통해 액세스되며 사용자별 또는 애플리케이션별 데이터를 저장하고 검색할 수 있습니다.

도구는 ToolRuntime을 통해 store에 액세스하고 업데이트할 수 있습니다:

from typing import Any
 
from langchain.agents import create_agent
from langchain.tools import ToolRuntime, tool
from langgraph.store.memory import InMemoryStore
 
 
# 메모리 액세스
@tool
def get_user_info(user_id: str, runtime: ToolRuntime) -> str:
    """사용자 정보를 조회합니다."""
 
    store = runtime.store
    user_info = store.get(("users",), user_id)
    return str(user_info.value) if user_info else "알 수 없는 사용자"
 
 
# 메모리 업데이트
@tool
def save_user_info(
    user_id: str, user_info: dict[str, Any], runtime: ToolRuntime
) -> str:
    """사용자 정보를 저장합니다."""
 
    store = runtime.store
    store.put(("users",), user_id, user_info)
    return "사용자 정보 저장에 성공했습니다."
 
 
agent = create_agent(
    "openai:gpt-4.1",
    tools=[get_user_info, save_user_info],
    store=InMemoryStore(),
)
# 첫 번째 세션: 사용자 정보 저장
result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "다음 사용자를 저장합니다.\n\n사용자 ID: abc123, 이름: Foo, 나이: 25, 이메일: [email protected]",
            }
        ]
    }
)
pretty_print(result)
================================ Human Message =================================

다음 사용자를 저장합니다.

사용자 ID: abc123, 이름: Foo, 나이: 25, 이메일: [email protected]
================================== Ai Message ==================================
Tool Calls:
  save_user_info (call_WFWWk7HuM9J8nccNJB6oExiv)
 Call ID: call_WFWWk7HuM9J8nccNJB6oExiv
  Args:
    user_id: abc123
================================= Tool Message =================================
Name: save_user_info

Error invoking tool 'save_user_info' with kwargs {'user_id': 'abc123'} with error:
 user_info: Field required
 Please fix the error and try again.
================================== Ai Message ==================================

사용자 정보를 저장하려면 이름, 나이, 이메일 등 모든 정보가 함께 필요합니다. 

다음과 같이 입력해 주세요:
- 사용자 ID
- 이름
- 나이
- 이메일

이미 제공해주신 정보를 바탕으로 저장을 다시 시도하겠습니다. 정보를 확인하겠습니다.

사용자 ID: abc123  
이름: Foo  
나이: 25  
이메일: [email protected]

이 정보를 저장하겠습니다. 잠시만 기다려 주세요.
Tool Calls:
  save_user_info (call_jEeJlUJfg0gC8D0myspfeUUN)
 Call ID: call_jEeJlUJfg0gC8D0myspfeUUN
  Args:
    user_id: abc123
    user_info: {'이름': 'Foo', '나이': 25, '이메일': '[email protected]'}
================================= Tool Message =================================
Name: save_user_info

사용자 정보 저장에 성공했습니다.
================================== Ai Message ==================================

사용자 정보가 성공적으로 저장되었습니다!

- 사용자 ID: abc123
- 이름: Foo
- 나이: 25
- 이메일: [email protected]

다른 요청이나 저장할 정보가 있다면 알려주세요.
# 두 번째 세션: 사용자 정보 가져오기
result = agent.invoke(
    {
        "messages": [
            {"role": "user", "content": "ID 'abc123' 사용자의 사용자 정보 가져오기"}
        ]
    }
)
pretty_print(result)
================================ Human Message =================================

ID 'abc123' 사용자의 사용자 정보 가져오기
================================== Ai Message ==================================
Tool Calls:
  get_user_info (call_U7jpR94mnMb4zg9zQTGavSoL)
 Call ID: call_U7jpR94mnMb4zg9zQTGavSoL
  Args:
    user_id: abc123
================================= Tool Message =================================
Name: get_user_info

{'이름': 'Foo', '나이': 25, '이메일': '[email protected]'}
================================== Ai Message ==================================

ID 'abc123' 사용자의 정보는 다음과 같습니다:
- 이름: Foo
- 나이: 25세
- 이메일: [email protected]

추가로 궁금한 점이 있으신가요?

Stream Writer

runtime.stream_writer를 사용하여 도구가 실행되는 동안 사용자 정의 업데이트를 스트리밍할 수 있습니다. 이는 도구가 수행하는 작업에 대한 실시간 피드백을 사용자에게 제공하는 데 유용합니다.

from langchain.tools import ToolRuntime, tool
 
 
@tool
def get_weather(city: str, runtime: ToolRuntime) -> str:
    """주어진 도시의 날씨를 가져옵니다."""
 
    writer = runtime.stream_writer
 
    # 도구가 실행되는 동안 사용자 정의 업데이트 스트리밍
    writer(f"도시 {city} 데이터 조회 중")
    writer(f"도시 {city} 데이터 획득 완료")
 
    return f"{city}에는 항상 햇살이 가득하네요!"

도구 내에서 runtime.stream_writer를 사용하는 경우 도구는 LangGraph 실행 컨텍스트 내에서 호출되어야 합니다. 자세한 내용은 스트리밍을 참조하세요.