Function Calling에서 Tool Calling – LLM의 진화 과정과 LangChain 생태계

시작하며

2023년 AI 생태계에서 가장 중요한 변화 중 하나는 바로 Function Calling 기술의 도입이었습니다. 이 기술은 단순히 텍스트만 생성하던 LLM이 외부 도구와 상호작용할 수 있게 만들어주는 혁신적인 기능이었죠. 오늘은 이 기술이 어떻게 발전해왔는지, 그리고 현재 가장 주목받는 LangGraph와 @tool 데코레이터까지의 여정을 자세히 살펴보겠습니다.

Function Calling의 탄생과 발전 연대기

2022년: LangChain의 선구적 역할

사실 많은 사람들이 모르는 것이 있습니다. OpenAI가 Function Calling을 공식 발표하기 훨씬 전부터, LangChain은 이미 LLM이 외부 도구를 사용할 수 있도록 하는 프레임워크를 구축하고 있었습니다. 2022년 ChatGPT가 출시되기 직전에 탄생한 LangChain은 체인, API 연결, 그리고 도구 사용 기능을 포함한 포괄적인 언어 모델 프레임워크를 제공했습니다.

이는 정말 놀라운 선견지명이었습니다. 당시 개발자들은 이미 LangChain을 통해 GPT 모델로 API 호출을 하고, 외부 시스템과 상호작용하는 애플리케이션을 구축하고 있었으니까요.

2023년 6월: OpenAI의 공식 Function Calling 발표

2023년 6월 13일, OpenAI는 마침내 공식적으로 Function Calling 기능을 발표했습니다. 이는 gpt-4-0613과 gpt-3.5-turbo-0613 모델과 함께 도입되었습니다. 이 발표와 함께 여러 중요한 업데이트가 있었습니다:

  • gpt-3.5-turbo의 컨텍스트 길이가 16k 토큰으로 4배 증가
  • 임베딩 모델 비용 75% 절감 (text-embedding-ada-002가 1k 토큰당 $0.0001)
  • gpt-3.5-turbo 입력 토큰 비용 25% 절감

Function Calling의 핵심 개념은 간단했습니다. 개발자가 함수를 정의하고 그 스키마를 모델에 제공하면, 모델이 적절한 상황에서 해당 함수를 호출하는 JSON 응답을 생성한다는 것이었죠.

2023년 11월: Function Calling에서 Tool Calling으로

흥미롭게도 2023년 11월경부터 OpenAI를 비롯한 업계에서는 “Function Calling”이라는 용어 대신 “Tool Calling”이라는 용어를 더 선호하기 시작했습니다. 이는 단순히 용어의 변화가 아니라, 개념의 확장을 의미했습니다. 함수라는 좁은 개념보다는 도구라는 더 넓은 개념으로 확장된 것이죠.

LangChain 문서에서도 “We use the term tool calling interchangeably with function calling”이라고 명시하며, 두 용어를 동일하게 사용한다고 밝혔습니다.

각 LLM 제공업체별 도입 현황

업계 확산의 물결

OpenAI가 Function Calling을 도입한 후, 다른 주요 LLM 제공업체들도 빠르게 이 기능을 도입했습니다:

  • OpenAI: 2023년 6월 (Function Calling) → 2023년 11월 (Tool Calling)
  • Google Gemini: 2023년 12월
  • Mistral: 2024년 2월
  • Fireworks: 2024년 3월
  • Together: 2024년 3월
  • Groq: 2024년 4월
  • Cohere: 2024년 4월
  • Anthropic: 2024년 4월

각 제공업체마다 약간씩 다른 인터페이스를 제공했지만, 핵심 개념은 동일했습니다. 특히 OpenAI, Anthropic, Gemini는 서로 호환되지 않는 형식을 사용했는데, 이는 개발자들에게 큰 불편을 초래했습니다.

표준화의 필요성

예를 들어, OpenAI는 다음과 같은 형식을 사용했습니다:

{
  "tool_calls": [
    {
      "id": "id_value",
      "function": {
        "arguments": '{"arg_name": "arg_value"}',
        "name": "tool_name"
      },
      "type": "function"
    }
  ]
}

반면 Anthropic은 완전히 다른 형식을 사용했죠. 이런 차이점 때문에 LangChain 같은 프레임워크의 표준화 역할이 더욱 중요해졌습니다.

LangChain의 역할과 @tool 데코레이터

표준화의 핵심: @tool 데코레이터

LangChain은 이런 혼란을 해결하기 위해 통합된 인터페이스를 제공했습니다. 그 핵심이 바로 @tool 데코레이터입니다.

from langchain_core.tools import tool

@tool
def add(a: int, b: int) -> int:
    """두 숫자를 더합니다."""
    return a + b

@tool
def multiply(a: int, b: int) -> int:
    """두 숫자를 곱합니다."""
    return a * b

이 데코레이터의 놀라운 점은 모든 방식에서 공통으로 사용된다는 것입니다. 기본 LLM Tool Calling, LangChain Agent (AgentExecutor), 그리고 최신의 LangGraph까지 모두 동일한 @tool 데코레이터를 사용합니다.

bind_tools 메서드의 혁신

LangChain은 또한 bind_tools() 메서드를 통해 표준화된 인터페이스를 제공했습니다:

llm_with_tools = llm.bind_tools([add, multiply])

이 메서드는 Pydantic 클래스, LangChain 도구, 일반 Python 함수, 심지어 OpenAI 형식의 딕셔너리까지 모두 받아들여서 각 모델 제공업체의 형식으로 자동 변환해줍니다.

LangGraph와 create_react_agent의 등장

2024년: LangGraph의 혁신

2024년 3월 LangGraph가 출시되면서 에이전트 개발에 새로운 패러다임이 시작되었습니다. LangGraph는 단순한 도구 호출을 넘어서 복잡한 상태 관리와 그래프 기반 워크플로우를 제공했습니다.

LangSmith 데이터에 따르면, 2024년 평균 21.9%의 트레이스가 tool call을 포함하고 있으며, 이는 2023년 0.5%에서 대폭 증가한 수치입니다. 또한 43%의 LangSmith 조직이 현재 LangGraph 트레이스를 전송하고 있어, 이 기술의 급속한 확산을 보여줍니다.

create_react_agent의 단순함과 강력함

LangGraph의 가장 인상적인 기능 중 하나는 create_react_agent입니다:

from langgraph.prebuilt import create_react_agent

def get_weather(city: str) -> str:
    """주어진 도시의 날씨를 가져옵니다."""
    return f"It's always sunny in {city}!"

agent = create_react_agent(
    model="anthropic:claude-3-sonnet-20240229",
    tools=[get_weather],
    prompt="You are a helpful assistant"
)

agent.invoke(
    {"messages": [{"role": "user", "content": "what is the weather in sf"}]}
)

이 코드가 보여주는 것은 단순함 속의 강력함입니다. 몇 줄의 코드만으로 완전한 ReAct (Reasoning and Acting) 에이전트를 구축할 수 있습니다.

ReAct 패턴의 이해

ReAct란 무엇인가?

ReAct는 “Reasoning and Acting in Language Models”라는 2022년 논문에서 소개된 패턴입니다. 이는 LLM이 다음과 같은 순환 과정을 거치도록 설계되었습니다:

  1. Thought(사고): 현재 상황을 분석하고 다음 행동을 계획
  2. Action(행동): 적절한 도구를 선택하고 실행
  3. Observation(관찰): 도구 실행 결과를 분석
  4. 필요시 1-3 과정 반복

이 패턴의 핵심은 인간과 같은 유연한 문제 해결 방식을 모방한다는 것입니다. 고정된 워크플로우 대신 상황에 따라 동적으로 적응하는 방식이죠.

LangGraph에서의 구현

LangGraph의 create_react_agent는 이 패턴을 그래프 구조로 구현합니다:

  • Assistant Node: LLM이 사고하고 다음 행동을 결정
  • Tools Node: 선택된 도구를 실행
  • Conditional Edge: 도구 호출 여부를 결정하는 조건부 엣지

이 구조는 메모리, 인간 개입, 구조화된 출력 등 고급 기능을 쉽게 추가할 수 있게 해줍니다.

기술 발전의 실제 영향

복잡성의 증가

LangSmith 데이터는 흥미로운 트렌드를 보여줍니다. 트레이스당 평균 단계 수가 2023년 2.8단계에서 2024년 7.7단계로 두 배 이상 증가했습니다. 이는 개발자들이 단순한 질문-답변 상호작용을 넘어서 정보 검색, 처리, 실행 가능한 결과 생성을 연결하는 복잡한 워크플로우를 구축하고 있음을 의미합니다.

흥미롭게도 트레이스당 LLM 호출 수는 1.1에서 1.4로 완만하게 증가했습니다. 이는 개발자들이 비용이 많이 드는 LLM 요청을 줄이면서도 더 많은 기능을 달성하는 시스템을 설계하고 있다는 것을 보여줍니다.

오픈소스 모델의 부상

2024년에는 오픈소스 모델 도입이 극적으로 증가했습니다. Ollama와 Groq 같은 플랫폼이 상위 5위 안에 진입했으며, 오픈소스 제공업체들의 집합적 사용량이 상위 20개 LLM 제공업체 중 20%를 차지하고 있습니다.

이는 개발자들이 더 유연한 배포 옵션과 맞춤형 AI 인프라에 관심을 갖고 있음을 보여줍니다.

세 가지 구현 방식의 심화 분석

방식 1: 기본 LLM Tool Calling

가장 기본적인 방식으로, LLM에 직접 도구를 바인딩하는 방법입니다:

from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

@tool
def calculate(expression: str) -> str:
    """수학 계산을 수행합니다."""
    try:
        result = eval(expression)
        return str(result)
    except:
        return "계산 오류"

llm = ChatOpenAI(model="gpt-4")
llm_with_tools = llm.bind_tools([calculate])

response = llm_with_tools.invoke("10 곱하기 5는 얼마인가요?")

이 방식의 장점은 단순함이지만, 복잡한 상호작용이나 여러 단계의 추론에는 제한적입니다.

방식 2: LangChain Agent (AgentExecutor)

더 정교한 제어가 필요할 때 사용하는 방식입니다:

from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "당신은 수학 문제 해결 전문가입니다."),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

agent = create_tool_calling_agent(llm, [calculate], prompt)
agent_executor = AgentExecutor(agent=agent, tools=[calculate])

result = agent_executor.invoke({"input": "복잡한 수학 문제를 단계별로 해결해주세요."})

이 방식은 더 많은 제어권을 제공하지만, 설정이 복잡할 수 있습니다.

방식 3: LangGraph의 현대적 접근법

가장 현대적이고 유연한 방식입니다:

from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import InMemorySaver

checkpointer = InMemorySaver()

agent = create_react_agent(
    model="anthropic:claude-3-sonnet-20240229",
    tools=[calculate],
    checkpointer=checkpointer,
    prompt="당신은 단계별 문제 해결을 돕는 AI입니다."
)

config = {"configurable": {"thread_id": "math_session_1"}}
response = agent.invoke(
    {"messages": [{"role": "user", "content": "복잡한 계산 문제를 해결해주세요."}]},
    config
)

LangGraph 방식의 핵심 장점들:

  • 상태 관리: checkpointer를 통한 대화 상태 저장
  • 메모리 기능: 이전 대화 내용 기억
  • 인간 개입: 필요시 인간의 승인을 받는 워크플로우
  • 구조화된 출력: response_format으로 특정 형식의 응답 강제
  • 스트리밍: 실시간 응답 스트리밍 지원

고급 기능들의 실제 활용

메모리와 체크포인팅

LangGraph의 가장 강력한 기능 중 하나는 대화 상태를 저장하고 복원하는 능력입니다:

from langgraph.checkpoint.memory import InMemorySaver

checkpointer = InMemorySaver()
agent = create_react_agent(
    model="anthropic:claude-3-sonnet-20240229",
    tools=[weather_tool, calculator_tool],
    checkpointer=checkpointer
)

# 첫 번째 대화
config = {"configurable": {"thread_id": "user_123"}}
response1 = agent.invoke(
    {"messages": [{"role": "user", "content": "서울 날씨 알려줘"}]},
    config
)

# 나중에 같은 스레드로 계속
response2 = agent.invoke(
    {"messages": [{"role": "user", "content": "부산은 어때?"}]},
    config
)

이 기능을 통해 에이전트는 이전 대화를 기억하고 문맥을 유지할 수 있습니다.

구조화된 출력

특정 형식의 응답이 필요할 때 response_format을 사용할 수 있습니다:

from pydantic import BaseModel

class WeatherResponse(BaseModel):
    city: str
    temperature: int
    conditions: str
    recommendation: str

agent = create_react_agent(
    model="anthropic:claude-3-sonnet-20240229",
    tools=[weather_tool],
    response_format=WeatherResponse
)

이렇게 하면 에이전트의 최종 응답이 항상 지정된 구조를 따르게 됩니다.

성능과 효율성 고려사항

비용 최적화 전략

Tool calling 시스템을 운영할 때 비용은 중요한 고려사항입니다. 몇 가지 최적화 전략이 있습니다:

  1. 적절한 모델 선택: 복잡한 추론이 필요하지 않은 도구 호출에는 gpt-3.5-turbo나 gpt-4o-mini 사용
  2. 도구 설명 최적화: 명확하고 간결한 도구 설명으로 불필요한 토큰 사용 방지
  3. 조건부 도구 호출: tool_choice 매개변수로 필요한 경우에만 도구 호출

에러 처리와 복원력

실제 프로덕션 환경에서는 도구 호출이 실패할 수 있습니다. 이를 대비한 전략이 필요합니다:

@tool
def robust_api_call(endpoint: str) -> str:
    """외부 API를 안전하게 호출합니다."""
    try:
        # API 호출 로직
        response = make_api_call(endpoint)
        return response
    except requests.RequestException as e:
        return f"API 호출 실패: {str(e)}. 나중에 다시 시도해주세요."
    except Exception as e:
        return f"예상치 못한 오류: {str(e)}"

미래 전망과 트렌드

Agentic AI의 확산

2024년 데이터를 보면 에이전트 기반 AI 애플리케이션이 급속히 확산되고 있습니다. 단순한 질문-답변을 넘어서 복잡한 작업을 자율적으로 수행하는 시스템들이 등장하고 있죠.

멀티 에이전트 시스템

향후에는 여러 전문 에이전트가 협력하는 시스템이 더욱 일반화될 것으로 예상됩니다. 각각의 에이전트가 특정 도메인에 특화되어 있고, 필요에 따라 서로 협력하는 구조입니다.

더 나은 표준화

현재 각 LLM 제공업체마다 다른 인터페이스를 사용하고 있지만, 업계 표준이 점차 수렴할 것으로 예상됩니다. OpenAI의 Structured Outputs, Anthropic의 도구 호출 등이 더욱 호환성을 갖춰갈 것입니다.

실무에서의 권장사항

프로젝트 시작 시 고려사항

새로운 프로젝트를 시작할 때는 다음 순서로 접근하는 것을 권장합니다:

  1. 요구사항 분석: 단순한 도구 호출인지, 복잡한 에이전트가 필요한지 판단
  2. 기본 프로토타입: create_react_agent로 빠른 프로토타입 구축
  3. 점진적 개선: 필요에 따라 커스텀 그래프 구조 적용
  4. 프로덕션 최적화: 성능, 비용, 안정성 고려한 최적화

도구 설계 베스트 프랙티스

좋은 도구를 만들기 위한 핵심 원칙들:

  1. 명확한 문서화: 도구의 목적과 사용법을 명확히 설명
  2. 적절한 에러 처리: 예상 가능한 오류 상황에 대한 대비
  3. 일관된 반환 형식: 예측 가능한 출력 형식 유지
  4. 성능 고려: 불필요한 지연 시간 최소화

맺음말

Function Calling에서 Tool Calling으로, 그리고 LangGraph의 create_react_agent까지의 여정은 AI 생태계의 놀라운 발전을 보여줍니다. 불과 2년 전만 해도 LangChain이 선구적으로 구축한 기능들이 이제는 업계 표준이 되었고, 더 나아가 복잡한 에이전트 시스템의 기반이 되었습니다.

@tool 데코레이터라는 간단한 인터페이스가 모든 방식에서 공통으로 사용된다는 것은 LangChain 생태계의 일관성과 사용자 친화성을 보여주는 대표적인 예입니다. 기본적인 LLM Tool Calling부터 고도로 정교한 LangGraph 에이전트까지, 동일한 도구 정의 방식을 사용할 수 있다는 것은 개발자에게 큰 편의성을 제공합니다.

현재 우리는 AI 에이전트가 단순한 도구를 넘어서 인간과 같은 추론과 행동을 수행하는 시대의 초입에 서 있습니다. Tool calling은 더 이상 단순한 기능이 아니라, 지능형 시스템의 핵심 구성 요소가 되었습니다.

앞으로도 이 기술은 계속 발전할 것이며, 더욱 정교하고 효율적인 에이전트 시스템들이 등장할 것입니다. 중요한 것은 이런 변화의 흐름을 이해하고, 적절한 도구와 패턴을 선택하여 실제 가치를 창출하는 애플리케이션을 구축하는 것입니다.

개발자로서 우리가 해야 할 일은 이런 도구들을 깊이 이해하고, 실제 문제 해결에 효과적으로 활용하는 방법을 지속적으로 학습하는 것입니다. Function calling의 역사는 아직 쓰여지고 있으며, 우리 모두가 그 역사의 일부가 될 수 있습니다.

Leave a Comment