05. 도구(Tools): 에이전트에게 손발을 달아주기
- 도구(tool / function calling)가 무엇이고 왜 필요한지 이해한다
- LLM이 직접 함수를 실행하지 않는다는 점, 그리고 실제 실행 흐름을 안다
- 함수를 도구로 정의하고 에이전트에 연결하는 패턴을 익힌다
- 검색 도구(Tavily 등)를 예로 도구의 실전 활용을 본다
비유로 시작하기: 똑똑하지만 책상에 묶인 사람
매우 박식한 사람이 책상에 묶여 있다고 합시다. 그는 추론은 뛰어나지만, 오늘 날씨도 모르고 최신 뉴스도 모르며 계산기도 없습니다. 아는 것은 "학습한 시점까지의 지식"뿐입니다. 이 사람에게 전화기, 검색 엔진, 계산기를 쥐여주면 어떨까요? 비로소 실시간 정보를 얻고, 정확히 계산하고, 외부 시스템을 움직일 수 있습니다.
도구(tool)가 바로 이것입니다. LLM의 약점(실시간 정보 없음, 계산 부정확, 외부 시스템 접근 불가)을 보완해, 에이전트가 세상과 상호작용하게 만듭니다.
왜 도구가 필요한가
LLM 단독으로는 다음을 잘 못합니다. 최신 정보(학습 이후 사건)를 모르고, 정밀한 산술 계산에 약하며, 데이터베이스·API·파일 같은 외부 시스템에 접근할 수 없습니다. 도구는 이 빈틈을 메웁니다. 검색 도구로 최신 정보를, 계산 도구로 정확한 수치를, API 도구로 외부 시스템을 다룰 수 있게 됩니다.
📌 핵심: 1장의 ReAct 루프에서 "행동(Acting)"이 바로 도구 호출입니다. 도구가 없으면 에이전트는 추론만 하는 챗봇에 머뭅니다.
중요한 오해 바로잡기: LLM은 함수를 직접 실행하지 않는다
이것이 함수 호출(function calling)에서 가장 많이 오해하는 지점입니다. LLM은 코드를 직접 실행하지 않습니다. 대신 "이 함수를 이런 인자로 호출하고 싶다"는 요청(JSON)을 우리에게 돌려줄 뿐입니다. 여기서 JSON(JavaScript Object Notation)은 {"key": "value"} 형태로 데이터를 적는 가벼운 텍스트 형식으로, 프로그램끼리 데이터를 주고받을 때 널리 쓰입니다. 실제 실행은 우리 코드가 하고, 그 결과를 다시 모델에 넣어줍니다.
sequenceDiagram
participant U as 사용자
participant A as 우리 코드(에이전트)
participant L as LLM
participant T as 실제 함수(도구)
U->>A: "서울 날씨 어때?"
A->>L: 메시지 + 사용 가능한 도구 목록
L-->>A: "get_weather('서울') 호출해줘" (실행 아님, 요청)
A->>T: get_weather("서울") 직접 실행
T-->>A: "맑음, 24도"
A->>L: 도구 결과를 전달
L-->>A: "서울은 맑고 24도입니다"
A-->>U: 최종 답변
이 흐름을 이해하면, 나중에 어떤 프레임워크를 쓰든 "도구가 어떻게 호출되는지"가 명확해집니다. 프레임워크는 이 왕복(요청 → 실행 → 결과 전달 → 재추론)을 자동으로 돌려줄 뿐, 본질은 같습니다.
도구 정의하기 (직접 구현)
함수를 만들고, 모델이 이해할 수 있도록 스키마(이름·설명·인자)를 함께 제공합니다. 설명이 도구 사용의 품질을 좌우합니다.
import json from openai import OpenAI client = OpenAI() # 1) 실제 함수 def get_weather(city: str) -> str: # 실전에서는 날씨 API를 호출. 여기서는 예시. fake = {"서울": "맑음, 24도", "부산": "흐림, 22도"} return fake.get(city, "정보 없음") # 2) 모델에게 알려줄 도구 스키마 tools = [{ "type": "function", "function": { "name": "get_weather", "description": "도시 이름을 받아 현재 날씨를 반환한다.", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "도시 이름, 예: 서울"} }, "required": ["city"], }, }, }]
💡 팁: description은 모델이 "언제 이 도구를 써야 하는지" 판단하는 근거입니다. 모호하면 도구를 엉뚱하게 쓰거나 아예 안 씁니다. 명확하고 구체적으로 적으세요. 이는 모든 프레임워크에 공통으로 적용되는 황금률입니다.
도구 호출 루프 구현
모델이 도구를 요청하면 실행하고 결과를 돌려주는 루프입니다.
def agent_with_tools(user_input: str) -> str: messages = [{"role": "user", "content": user_input}] while True: response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=tools, ) msg = response.choices[0].message # 모델이 도구를 호출하지 않았다면 → 최종 답변 if not msg.tool_calls: return msg.content messages.append(msg) # 모델의 도구 호출 요청을 기록 # 요청된 도구들을 실제로 실행 for call in msg.tool_calls: args = json.loads(call.function.arguments) if call.function.name == "get_weather": result = get_weather(**args) messages.append({ "role": "tool", "tool_call_id": call.id, "content": result, }) # 루프를 돌아 모델이 결과를 보고 다시 판단하게 함 print(agent_with_tools("서울 날씨 알려줘"))
role: "tool" 메시지로 결과를 돌려주는 것, 그리고 tool_call_id로 어떤 호출에 대한 답인지 연결하는 것이 핵심입니다. 이 while 루프가 바로 1장에서 본 에이전트 루프의 구체적 구현입니다.
실전 예: 검색 도구 (Tavily)
강의는 실시간 웹 검색을 위해 Tavily(AI 에이전트용으로 설계된 웹 검색 API 서비스) 같은 검색 API를 도구로 붙입니다. 패턴은 위 get_weather와 동일합니다. 검색 함수를 만들고, 스키마로 설명하고, 도구 목록에 넣으면 됩니다.
# pip install tavily-python from tavily import TavilyClient import os tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY")) def web_search(query: str) -> str: """질의어로 웹을 검색해 요약 결과를 반환한다.""" res = tavily.search(query=query, max_results=3) return "\n".join(r["content"] for r in res["results"])
이 함수를 도구 스키마로 감싸 에이전트에 연결하면, 에이전트는 "최신 정보가 필요하다"고 판단할 때 스스로 검색을 호출합니다.
⚠️ 흔한 실수: 도구 실행 결과를 모델에 다시 넣지 않고 바로 사용자에게 보여주는 것. 그러면 모델이 결과를 해석·종합하는 단계가 빠져, "검색 결과 원문 덩어리"만 나옵니다. 반드시 결과를 모델에 돌려주어 자연어로 종합하게 하세요.
빌트인 도구
매번 도구를 직접 만들 필요는 없습니다. OpenAI를 비롯한 플랫폼은 웹 검색, 코드 실행, 파일 검색 등 빌트인 도구를 제공합니다. 또 7부에서 배울 MCP는 외부 도구를 표준 방식으로 연결해, 도구를 일일이 코딩하지 않고도 끌어다 쓰게 해줍니다. 지금은 "도구를 직접 만드는 원리"를 확실히 익혀두면, 빌트인이든 MCP든 응용은 쉽습니다.
이 장에서 배운 것
- 도구는 LLM의 약점(실시간 정보·정밀 계산·외부 접근)을 보완해 에이전트가 세상과 상호작용하게 한다.
- LLM은 함수를 직접 실행하지 않는다. "호출 요청(JSON)"만 내고, 실제 실행은 우리 코드가 하며 결과를 다시 모델에 넣는다.
- 도구 정의의 핵심은 명확한
description이다 — 모델이 언제 쓸지 판단하는 근거다. - 도구 호출 루프가 곧 ReAct 에이전트 루프의 구체적 구현이다. 프레임워크는 이 왕복을 자동화한다.
✍️ 확인 문제
- "LLM이 계산기 도구로 직접 1234 × 5678을 계산했다"는 설명이 부정확한 이유를 실행 흐름 관점에서 바로잡아라.
- 같은 검색 도구를 붙였는데도 에이전트가 검색을 거의 안 쓴다. 가장 먼저 점검할 부분은 무엇인가?
- 도구 실행 결과를 모델에 다시 전달하지 않고 곧장 사용자에게 출력하면 어떤 품질 문제가 생기는가?
이전 장: 04. 메모리
다음 부: 2부 · OpenAI Agents SDK — 지금까지 손으로 만든 루프·메모리·도구를, 프로덕션급 SDK가 어떻게 깔끔하게 추상화하는지 봅니다.