🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

LLM 다중 도구 호출 완벽 가이드 - 슬라이드 1/6
A

AI Generated

2025. 12. 27. · 4 Views

LLM 다중 도구 호출 완벽 가이드

AI 모델에게 여러 도구를 동시에 제공하고 병렬로 호출하는 방법을 배웁니다. 실무에서 10개 이상의 도구를 효과적으로 통합하는 방법까지 단계별로 설명합니다.


목차

  1. 여러_도구_동시_제공
  2. 도구_선택_로직
  3. Parallel_Function_Calling
  4. 도구_세트_구축_실습
  5. 대규모_도구_통합_실습

1. 여러 도구 동시 제공

김개발 씨는 사내 챗봇 프로젝트를 맡게 되었습니다. 처음에는 간단한 질문 응답 봇이었는데, 기획팀에서 요구사항이 점점 늘어났습니다.

"날씨도 알려주고, 일정도 조회하고, 메일도 보낼 수 있으면 좋겠어요." 하나의 도구만 연결하던 시절은 끝났습니다.

다중 도구 제공이란 LLM에게 여러 개의 도구를 한꺼번에 알려주는 것입니다. 마치 만능 스위스 아미 나이프처럼, AI가 상황에 맞는 도구를 골라 사용할 수 있게 해줍니다.

이렇게 하면 사용자의 다양한 요청을 하나의 대화 안에서 처리할 수 있습니다.

다음 코드를 살펴봅시다.

import openai

# 여러 도구를 tools 배열에 정의합니다
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "특정 도시의 현재 날씨를 조회합니다",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "도시 이름"}
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "search_calendar",
            "description": "일정을 검색합니다",
            "parameters": {
                "type": "object",
                "properties": {
                    "date": {"type": "string", "description": "검색할 날짜"}
                },
                "required": ["date"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "send_email",
            "description": "이메일을 발송합니다",
            "parameters": {
                "type": "object",
                "properties": {
                    "to": {"type": "string"},
                    "subject": {"type": "string"},
                    "body": {"type": "string"}
                },
                "required": ["to", "subject", "body"]
            }
        }
    }
]

# LLM 호출 시 모든 도구를 함께 전달합니다
response = openai.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "서울 날씨 알려줘"}],
    tools=tools
)

김개발 씨는 입사 6개월 차 주니어 개발자입니다. 최근 회사에서 AI 챗봇 프로젝트를 맡게 되었는데, 처음에는 단순히 질문에 답변하는 수준이었습니다.

그런데 어느 날 기획팀 이과장 씨가 찾아왔습니다. "김개발 씨, 챗봇이 날씨도 알려주고, 회의 일정도 조회하고, 급한 건 메일도 보내주면 안 될까요?" 김개발 씨는 고민에 빠졌습니다.

도구를 하나씩 연결하는 건 해봤는데, 여러 개를 동시에 제공하려니 막막했습니다. 그때 옆자리 박시니어 씨가 슬쩍 다가왔습니다.

"걱정 마세요. 다중 도구 제공이라는 방법이 있어요." 그렇다면 다중 도구 제공이란 정확히 무엇일까요?

쉽게 비유하자면, 다중 도구 제공은 마치 스위스 아미 나이프를 건네주는 것과 같습니다. 칼, 가위, 드라이버, 병따개가 모두 들어있는 그 작은 도구 말입니다.

사용자가 "병 좀 따줘"라고 하면 병따개를 꺼내고, "종이 좀 잘라줘"라고 하면 가위를 꺼냅니다. LLM도 마찬가지입니다.

여러 도구를 한꺼번에 알려주면, 상황에 맞는 도구를 스스로 선택해서 사용합니다. 다중 도구 제공이 없던 시절에는 어땠을까요?

개발자들은 사용자의 의도를 먼저 파악한 뒤, 그에 맞는 단일 도구를 연결해야 했습니다. "날씨 관련 질문이면 날씨 API로", "일정 관련 질문이면 캘린더 API로" 이런 식으로 분기 처리를 해야 했습니다.

코드가 복잡해지고, 새로운 기능을 추가할 때마다 조건문이 늘어났습니다. 바로 이런 문제를 해결하기 위해 tools 파라미터가 등장했습니다.

tools 파라미터에 여러 도구를 배열로 전달하면, LLM이 알아서 적절한 도구를 선택합니다. 개발자는 더 이상 의도 파악 로직을 작성할 필요가 없습니다.

AI가 대화의 맥락을 이해하고 스스로 판단합니다. 위의 코드를 살펴보겠습니다.

먼저 tools라는 배열을 정의합니다. 이 배열 안에 get_weather, search_calendar, send_email 세 가지 도구가 들어있습니다.

각 도구는 name, description, parameters로 구성됩니다. description이 특히 중요합니다.

LLM은 이 설명을 읽고 어떤 도구를 사용할지 결정하기 때문입니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 고객 서비스 챗봇을 만든다고 가정해봅시다. 주문 조회, 배송 추적, 환불 요청, FAQ 검색 등 다양한 기능이 필요합니다.

이 모든 기능을 각각의 도구로 정의하고 tools 배열에 담으면, 고객이 어떤 질문을 하든 적절한 도구가 호출됩니다. 하지만 주의할 점도 있습니다.

도구의 description을 모호하게 작성하면 LLM이 잘못된 도구를 선택할 수 있습니다. "데이터를 처리합니다"보다는 "사용자의 주문 내역을 데이터베이스에서 조회합니다"처럼 구체적으로 작성해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언대로 세 가지 도구를 tools 배열에 정의했더니, 챗봇이 "서울 날씨 어때?"라는 질문에는 날씨 API를, "내일 회의 있어?"라는 질문에는 캘린더 API를 알아서 호출했습니다.

"와, 이렇게 간단한 거였어요?" 김개발 씨의 눈이 반짝였습니다.

실전 팁

💡 - description은 도구 선택의 핵심입니다. 명확하고 구체적으로 작성하세요.

  • 비슷한 기능의 도구가 있다면 description에서 차이점을 분명히 해야 합니다.
  • 도구가 너무 많으면 토큰 비용이 증가하므로, 꼭 필요한 도구만 포함하세요.

2. 도구 선택 로직

김개발 씨의 챗봇이 잘 동작하는 것 같았습니다. 그런데 이상한 일이 벌어졌습니다.

"오늘 날씨 좋으면 야외 미팅 잡아줘"라고 했더니, 날씨 API만 호출하고 캘린더는 건드리지 않았습니다. LLM은 어떤 기준으로 도구를 선택하는 걸까요?

도구 선택 로직은 LLM이 여러 도구 중에서 적합한 것을 고르는 내부 판단 과정입니다. 마치 숙련된 요리사가 재료를 보고 어떤 칼을 쓸지 결정하는 것처럼, LLM은 사용자의 의도와 도구의 description을 비교하여 최적의 도구를 선택합니다.

다음 코드를 살펴봅시다.

import openai

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "현재 날씨 정보만 조회합니다. 예보는 제공하지 않습니다.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string"}
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_weather_forecast",
            "description": "향후 7일간의 날씨 예보를 조회합니다.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string"},
                    "days": {"type": "integer", "description": "예보 일수 (1-7)"}
                },
                "required": ["city"]
            }
        }
    }
]

# tool_choice로 도구 선택 방식을 제어할 수 있습니다
response = openai.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "이번 주 서울 날씨 어때?"}],
    tools=tools,
    tool_choice="auto"  # auto, none, 또는 특정 도구 지정
)

# 응답에서 선택된 도구 확인
if response.choices[0].message.tool_calls:
    selected_tool = response.choices[0].message.tool_calls[0].function.name
    print(f"선택된 도구: {selected_tool}")

김개발 씨는 챗봇이 도구를 선택하는 방식이 궁금해졌습니다. 분명히 세 가지 도구를 다 알려줬는데, 왜 어떤 건 호출하고 어떤 건 호출하지 않는 걸까요?

박시니어 씨가 화이트보드 앞으로 김개발 씨를 데려갔습니다. "LLM의 도구 선택 로직을 이해하려면, 먼저 LLM이 뭘 보고 판단하는지 알아야 해요." 그렇다면 LLM은 정확히 무엇을 보고 도구를 선택할까요?

쉽게 비유하자면, LLM의 도구 선택은 마치 레스토랑 웨이터가 주문을 받는 것과 같습니다. 손님이 "시원한 거 주세요"라고 하면, 웨이터는 메뉴판의 설명을 보고 아이스 아메리카노나 냉면 중에서 고릅니다.

메뉴 설명이 "차가운 커피 음료"라고 되어 있으면 커피를, "시원한 면 요리"라고 되어 있으면 냉면을 추천합니다. LLM도 마찬가지로 description 필드를 보고 판단합니다.

도구 선택 로직에서 가장 중요한 것은 세 가지입니다. 첫째, description의 명확성입니다.

"날씨를 알려줍니다"보다 "현재 날씨 정보만 조회합니다. 예보는 제공하지 않습니다"가 훨씬 좋습니다.

LLM이 현재 날씨를 원하는 질문과 예보를 원하는 질문을 구분할 수 있기 때문입니다. 둘째, 파라미터의 설명입니다.

parameters 안의 description도 LLM이 참고합니다. "days"라는 파라미터에 "예보 일수 (1-7)"이라고 적어두면, LLM은 사용자가 "3일치"를 요청했을 때 이 파라미터를 적절히 채웁니다.

셋째, tool_choice 옵션입니다. 이 파라미터로 도구 선택 방식을 제어할 수 있습니다.

tool_choice에는 여러 값을 넣을 수 있습니다. **"auto"**는 LLM이 알아서 판단하게 합니다.

도구가 필요하면 호출하고, 필요 없으면 그냥 텍스트로 응답합니다. **"none"**은 도구를 절대 호출하지 않게 합니다.

특정 도구를 강제로 호출하려면 {"type": "function", "function": {"name": "도구이름"}} 형태로 지정합니다. 실무에서 자주 마주치는 상황을 살펴보겠습니다.

쇼핑몰 챗봇에서 "상품 검색"과 "상품 추천"이라는 두 도구가 있다고 가정해봅시다. 사용자가 "빨간 원피스 보여줘"라고 하면 어떤 도구를 호출해야 할까요?

description이 모호하면 LLM도 헷갈립니다. 이럴 때는 "특정 키워드로 상품을 검색합니다"와 "사용자 취향에 맞는 상품을 추천합니다"처럼 용도를 명확히 구분해야 합니다.

주의할 점이 있습니다. LLM이 도구를 선택하지 않는 경우도 있습니다.

"안녕하세요"처럼 도구가 필요 없는 질문에는 그냥 텍스트로 응답합니다. 이건 정상적인 동작입니다.

반대로 도구가 필요한데 선택하지 않는다면, description을 다시 점검해보세요. 김개발 씨는 고개를 끄덕였습니다.

"아, 그래서 제 챗봇이 날씨만 조회한 거군요. description에 조건부 로직은 없으니까요." 박시니어 씨가 웃으며 말했습니다.

"맞아요. 복합적인 요청을 처리하려면 다음에 배울 병렬 호출이 필요해요."

실전 팁

💡 - description은 도구의 "사용 설명서"입니다. 사용자 입장에서 작성하세요.

  • 비슷한 도구는 description에서 차이점을 명확히 구분하세요.
  • tool_choice="auto"가 기본값이며, 대부분의 상황에서 잘 동작합니다.

3. Parallel Function Calling

김개발 씨의 챗봇이 점점 똑똑해지고 있었습니다. 하지만 한 가지 불만이 있었습니다.

"서울 날씨랑 내일 일정 둘 다 알려줘"라고 하면, 날씨를 먼저 조회하고, 그 다음에 일정을 조회했습니다. 둘 다 동시에 하면 더 빠를 텐데 말이죠.

Parallel Function Calling은 LLM이 여러 도구를 한 번의 응답에서 동시에 호출하는 기능입니다. 마치 식당에서 여러 요리를 동시에 조리하는 것처럼, 독립적인 작업들을 병렬로 처리하여 응답 속도를 크게 향상시킵니다.

다음 코드를 살펴봅시다.

import openai
import json
import asyncio

# LLM 호출 - 병렬 호출이 가능하도록 설정
response = openai.chat.completions.create(
    model="gpt-4-turbo",
    messages=[{
        "role": "user",
        "content": "서울 날씨랑 내일 회의 일정 알려줘"
    }],
    tools=tools,
    parallel_tool_calls=True  # 병렬 호출 활성화
)

# 여러 도구가 동시에 호출될 수 있습니다
tool_calls = response.choices[0].message.tool_calls

# 각 도구 호출을 처리합니다
async def execute_tools(tool_calls):
    tasks = []
    for tool_call in tool_calls:
        name = tool_call.function.name
        args = json.loads(tool_call.function.arguments)

        # 각 도구를 비동기로 실행
        if name == "get_weather":
            tasks.append(fetch_weather(args["city"]))
        elif name == "search_calendar":
            tasks.append(search_calendar(args["date"]))

    # 모든 도구를 동시에 실행
    results = await asyncio.gather(*tasks)
    return results

# 결과를 LLM에게 다시 전달
tool_results = asyncio.run(execute_tools(tool_calls))

김개발 씨는 챗봇의 응답 속도가 느린 게 불만이었습니다. 사용자가 두 가지를 물어보면, 하나씩 순차적으로 처리하다 보니 시간이 오래 걸렸습니다.

날씨 API 호출에 1초, 캘린더 API 호출에 1초, 총 2초가 걸렸습니다. "이거 동시에 못 하나요?" 김개발 씨가 물었습니다.

박시니어 씨가 빙그레 웃었습니다. "당연히 되죠.

Parallel Function Calling이라고 해요." 병렬 함수 호출이란 정확히 무엇일까요? 쉽게 비유하자면, 이것은 마치 패밀리 레스토랑의 주방과 같습니다.

손님이 스테이크와 파스타를 동시에 주문하면, 좋은 주방에서는 두 요리를 동시에 조리합니다. 스테이크가 굽는 동안 파스타 면도 삶습니다.

스테이크를 다 굽고 나서 파스타를 시작하면 손님은 한참을 기다려야 하니까요. LLM의 병렬 호출도 마찬가지입니다.

서로 의존성이 없는 도구들은 동시에 호출할 수 있습니다. 날씨 조회와 일정 조회는 서로 관련이 없으니, 굳이 순서를 기다릴 필요가 없습니다.

병렬 호출의 핵심은 parallel_tool_calls 파라미터입니다. 이 값을 True로 설정하면, LLM은 한 번의 응답에서 여러 개의 tool_calls를 반환할 수 있습니다.

반환된 tool_calls 배열에는 호출해야 할 여러 도구의 정보가 담겨 있습니다. 하지만 여기서 끝이 아닙니다.

받은 도구 호출들을 실제로 병렬로 실행하는 건 개발자의 몫입니다. 위 코드에서 asyncio.gather를 사용한 이유가 바로 이것입니다.

LLM이 "날씨 API와 캘린더 API를 둘 다 호출해야 해"라고 알려주면, 우리가 비동기 처리를 통해 동시에 실행해야 합니다. 순차적으로 실행하면 병렬 호출의 의미가 없어집니다.

실무에서 병렬 호출이 빛나는 순간이 있습니다. 대시보드 화면을 생각해보세요.

"오늘의 매출, 방문자 수, 인기 상품 TOP 5 알려줘"라는 요청이 들어왔습니다. 이 세 가지 정보는 서로 독립적입니다.

병렬로 조회하면 1초 만에 끝날 일을, 순차적으로 하면 3초가 걸립니다. 사용자 경험에서 이 2초는 엄청난 차이입니다.

주의해야 할 점도 있습니다. 모든 도구 호출이 병렬로 가능한 건 아닙니다.

예를 들어 "장바구니에 상품 추가하고, 총 금액 알려줘"라는 요청은 순서가 있습니다. 상품을 먼저 추가해야 총 금액을 계산할 수 있으니까요.

이런 의존성이 있는 경우에는 LLM도 순차적으로 호출하거나, 개발자가 로직을 분리해야 합니다. 김개발 씨는 코드를 수정했습니다.

parallel_tool_calls를 활성화하고, asyncio로 병렬 실행을 구현했더니, 응답 시간이 2초에서 1.2초로 줄었습니다. "와, 거의 절반이나 줄었네요!" 김개발 씨가 감탄했습니다.

박시니어 씨가 덧붙였습니다. "도구가 많아질수록 효과는 더 커져요.

이제 본격적으로 도구 세트를 구축해볼까요?"

실전 팁

💡 - 병렬 호출은 독립적인 작업에만 사용하세요. 의존성이 있으면 순차 처리가 필요합니다.

  • LLM이 병렬로 호출해도, 실제 실행은 개발자가 비동기로 처리해야 합니다.
  • API 호출 비용도 고려하세요. 병렬 호출은 동시에 여러 API를 사용하므로 rate limit에 주의해야 합니다.

4. 도구 세트 구축 실습

이론은 충분히 배웠습니다. 이제 김개발 씨는 실제로 도구 세트를 구축해보기로 했습니다.

단순히 도구를 나열하는 게 아니라, 체계적으로 관리하고 재사용할 수 있는 구조를 만들고 싶었습니다. 실무에서 쓸 수 있는 진짜 코드를 작성해봅시다.

도구 세트 구축은 여러 도구를 체계적으로 정의하고 관리하는 패턴입니다. 마치 공구함에 드라이버, 망치, 렌치를 정리하듯, 도구들을 카테고리별로 분류하고 쉽게 확장할 수 있는 구조를 만드는 것입니다.

다음 코드를 살펴봅시다.

from dataclasses import dataclass
from typing import Callable, Any
import json

@dataclass
class Tool:
    name: str
    description: str
    parameters: dict
    handler: Callable

class ToolRegistry:
    def __init__(self):
        self._tools: dict[str, Tool] = {}

    def register(self, name: str, description: str, parameters: dict):
        """데코레이터로 도구를 등록합니다"""
        def decorator(func: Callable):
            self._tools[name] = Tool(
                name=name,
                description=description,
                parameters=parameters,
                handler=func
            )
            return func
        return decorator

    def get_openai_tools(self) -> list:
        """OpenAI API 형식으로 도구 목록을 반환합니다"""
        return [
            {
                "type": "function",
                "function": {
                    "name": tool.name,
                    "description": tool.description,
                    "parameters": tool.parameters
                }
            }
            for tool in self._tools.values()
        ]

    def execute(self, name: str, arguments: str) -> Any:
        """도구를 실행하고 결과를 반환합니다"""
        tool = self._tools.get(name)
        if not tool:
            raise ValueError(f"Unknown tool: {name}")
        args = json.loads(arguments)
        return tool.handler(**args)

# 사용 예시
registry = ToolRegistry()

@registry.register(
    name="calculate",
    description="수학 계산을 수행합니다",
    parameters={
        "type": "object",
        "properties": {
            "expression": {"type": "string", "description": "계산할 수식"}
        },
        "required": ["expression"]
    }
)
def calculate(expression: str) -> float:
    return eval(expression)  # 실무에서는 안전한 파서 사용

김개발 씨는 지금까지 도구를 딕셔너리 형태로 하드코딩했습니다. 도구가 3개일 때는 괜찮았는데, 10개, 20개로 늘어나면 관리가 어려워질 것 같았습니다.

"선배님, 도구를 좀 더 체계적으로 관리하는 방법 없을까요?" 김개발 씨가 물었습니다. 박시니어 씨가 노트북을 열며 말했습니다.

"레지스트리 패턴을 사용하면 돼요. 도구를 등록하고, 조회하고, 실행하는 걸 한 곳에서 관리하는 거죠." 레지스트리 패턴이란 무엇일까요?

쉽게 비유하자면, 이것은 마치 회사의 직원 명부와 같습니다. 새 직원이 입사하면 명부에 등록하고, 누군가를 찾을 때는 명부에서 검색합니다.

퇴사하면 명부에서 삭제합니다. 모든 직원 정보가 한 곳에 모여 있으니 관리가 편합니다.

ToolRegistry 클래스도 마찬가지입니다. 모든 도구 정보를 한 곳에서 관리합니다.

위 코드의 핵심은 세 가지 메서드입니다. 첫째, register 메서드입니다.

이건 데코레이터로 사용됩니다. 함수 위에 @registry.register(...)를 붙이면, 그 함수가 자동으로 도구로 등록됩니다.

도구의 이름, 설명, 파라미터 정보가 함께 저장됩니다. 둘째, get_openai_tools 메서드입니다.

등록된 모든 도구를 OpenAI API가 요구하는 형식으로 변환해줍니다. 이 메서드를 호출하면 tools 파라미터에 바로 넣을 수 있는 리스트가 반환됩니다.

셋째, execute 메서드입니다. LLM이 특정 도구를 호출하라고 응답하면, 이 메서드로 실제 실행을 합니다.

도구 이름과 인자를 받아서 해당 함수를 실행하고 결과를 반환합니다. 이 패턴의 장점은 무엇일까요?

새 도구를 추가할 때 기존 코드를 건드릴 필요가 없습니다. 그냥 새 함수를 만들고 데코레이터를 붙이면 끝입니다.

도구가 100개가 되어도 관리 방식은 동일합니다. 실무에서 이 패턴을 확장하는 방법도 있습니다.

도구를 카테고리별로 분류할 수 있습니다. "날씨 도구", "캘린더 도구", "이메일 도구"처럼요.

각 카테고리를 별도의 파일로 분리하고, 메인 레지스트리에 합치면 됩니다. 팀원들이 각자 맡은 도구를 개발하고, 나중에 통합하기도 쉬워집니다.

주의할 점도 있습니다. 위 코드에서 eval을 사용한 부분은 예시일 뿐입니다.

실제 서비스에서는 절대 eval을 쓰면 안 됩니다. 악의적인 코드가 실행될 수 있기 때문입니다.

안전한 수식 파서 라이브러리를 사용해야 합니다. 김개발 씨는 ToolRegistry를 도입한 후 코드가 훨씬 깔끔해졌습니다.

새 도구를 추가하는 것도 함수 하나 만들고 데코레이터 붙이면 끝이었습니다. "이제 도구가 몇 개든 두렵지 않아요!" 김개발 씨가 자신감 있게 말했습니다.

박시니어 씨가 고개를 끄덕였습니다. "좋아요.

그럼 이제 진짜 도전을 해볼까요? 10개 이상의 도구를 통합하는 겁니다."

실전 팁

💡 - 데코레이터 패턴을 사용하면 도구 등록이 선언적이고 깔끔해집니다.

  • 도구 핸들러 함수는 순수 함수로 작성하면 테스트하기 쉽습니다.
  • 실무에서는 도구별로 별도 파일로 분리하고, 메인에서 import하여 사용하세요.

5. 대규모 도구 통합 실습

김개발 씨의 챗봇이 점점 성장했습니다. 이제 날씨, 일정, 이메일뿐만 아니라 번역, 검색, 계산, 파일 관리, 알림, 데이터 분석까지 다양한 기능이 필요해졌습니다.

10개 이상의 도구를 어떻게 효과적으로 통합할 수 있을까요?

대규모 도구 통합은 많은 수의 도구를 효율적으로 관리하고 LLM에게 제공하는 전략입니다. 마치 대형 백화점이 층별로 매장을 나누고 안내 데스크를 두듯, 도구를 그룹화하고 동적으로 선택하여 토큰 비용과 성능을 최적화합니다.

다음 코드를 살펴봅시다.

from typing import Optional
import openai

class AdvancedToolManager:
    def __init__(self):
        self.registry = ToolRegistry()
        self.categories: dict[str, list[str]] = {}

    def register_with_category(
        self,
        name: str,
        description: str,
        parameters: dict,
        category: str
    ):
        """카테고리와 함께 도구를 등록합니다"""
        def decorator(func):
            self.registry.register(name, description, parameters)(func)
            if category not in self.categories:
                self.categories[category] = []
            self.categories[category].append(name)
            return func
        return decorator

    def get_tools_by_category(self, categories: list[str]) -> list:
        """특정 카테고리의 도구만 반환합니다"""
        tool_names = []
        for cat in categories:
            tool_names.extend(self.categories.get(cat, []))

        all_tools = {t["function"]["name"]: t
                     for t in self.registry.get_openai_tools()}
        return [all_tools[name] for name in tool_names if name in all_tools]

    def smart_select_tools(self, user_message: str) -> list:
        """사용자 메시지를 분석하여 필요한 도구만 선택합니다"""
        # 간단한 키워드 기반 선택 (실무에서는 임베딩 사용)
        keyword_map = {
            "날씨": ["weather"],
            "일정": ["calendar"],
            "메일": ["email"],
            "번역": ["translation"],
            "계산": ["math"],
            "검색": ["search"],
            "파일": ["file"],
            "알림": ["notification"],
            "분석": ["analytics"],
            "데이터베이스": ["database"]
        }

        needed_categories = set()
        for keyword, categories in keyword_map.items():
            if keyword in user_message:
                needed_categories.update(categories)

        # 키워드가 없으면 기본 도구 제공
        if not needed_categories:
            needed_categories = {"search", "math"}

        return self.get_tools_by_category(list(needed_categories))

# 도구 등록 예시
manager = AdvancedToolManager()

@manager.register_with_category(
    name="web_search",
    description="웹에서 정보를 검색합니다",
    parameters={"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]},
    category="search"
)
def web_search(query: str):
    return f"검색 결과: {query}"

@manager.register_with_category(
    name="translate_text",
    description="텍스트를 다른 언어로 번역합니다",
    parameters={
        "type": "object",
        "properties": {
            "text": {"type": "string"},
            "target_lang": {"type": "string"}
        },
        "required": ["text", "target_lang"]
    },
    category="translation"
)
def translate_text(text: str, target_lang: str):
    return f"번역됨: {text} -> {target_lang}"

# 동적 도구 선택으로 API 호출
user_input = "이 영어 문장을 한국어로 번역해줘"
selected_tools = manager.smart_select_tools(user_input)

response = openai.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": user_input}],
    tools=selected_tools  # 필요한 도구만 전달
)

김개발 씨는 자신감 있게 도구를 추가해나갔습니다. 1개, 3개, 5개...

어느새 15개의 도구가 등록되었습니다. 그런데 문제가 생겼습니다.

API 호출 비용이 갑자기 늘어났습니다. 그리고 가끔 LLM이 엉뚱한 도구를 선택하는 경우도 생겼습니다.

"번역해줘"라고 했는데 검색 도구를 호출한다거나요. "선배님, 도구가 많아지니까 오히려 문제가 생겨요." 김개발 씨가 걱정스러운 표정으로 말했습니다.

박시니어 씨가 설명을 시작했습니다. "도구가 많아지면 두 가지 문제가 생겨요.

첫째는 토큰 비용, 둘째는 선택 정확도예요." 왜 도구가 많으면 토큰 비용이 늘어날까요? tools 파라미터에 도구 정의를 넣으면, 그 내용이 프롬프트에 포함됩니다.

도구 하나당 대략 100-200 토큰을 차지합니다. 15개 도구면 최소 1500 토큰입니다.

매 요청마다 이 비용이 발생합니다. 한 달이면 상당한 금액이 됩니다.

선택 정확도 문제도 있습니다. 도구가 너무 많으면 LLM도 헷갈립니다.

마치 메뉴가 100가지인 식당에서 뭘 먹을지 고르기 어려운 것처럼요. 특히 비슷한 도구가 여러 개 있으면 잘못된 선택을 할 확률이 높아집니다.

해결책은 동적 도구 선택입니다. 위 코드의 핵심은 smart_select_tools 메서드입니다.

사용자 메시지를 분석해서 필요한 도구만 골라 제공합니다. "번역해줘"라는 메시지가 오면 번역 관련 도구만 전달합니다.

15개 전체가 아니라 2-3개만요. 이 방식의 장점은 명확합니다.

토큰 비용이 대폭 줄어듭니다. 15개 도구를 매번 보내는 대신 3개만 보내면 비용이 5분의 1로 줄어듭니다.

선택 정확도도 높아집니다. 선택지가 적으니 LLM이 더 정확하게 판단할 수 있습니다.

실무에서는 더 정교한 방법을 사용합니다. 위 코드에서는 단순 키워드 매칭을 사용했지만, 실제 서비스에서는 임베딩 기반 유사도 검색을 사용합니다.

사용자 메시지와 각 도구의 description을 벡터로 변환하고, 가장 유사한 도구를 선택하는 방식입니다. 이렇게 하면 "영어를 한국어로 바꿔줘"처럼 "번역"이라는 단어가 없어도 번역 도구를 찾아낼 수 있습니다.

주의할 점도 있습니다. 동적 선택이 너무 공격적이면 필요한 도구를 빠뜨릴 수 있습니다.

"날씨 좋으면 산책하면서 음악 들을래"처럼 여러 의도가 섞인 문장에서는 날씨 도구뿐만 아니라 음악 도구도 필요할 수 있습니다. 따라서 관련성이 조금이라도 있으면 포함하는 게 안전합니다.

김개발 씨는 AdvancedToolManager를 도입한 후 놀라운 변화를 경험했습니다. API 비용이 60% 줄었고, 도구 선택 정확도는 오히려 높아졌습니다.

"도구가 많다고 다 좋은 게 아니었군요. 적재적소에 맞는 도구만 제공하는 게 핵심이었어요." 김개발 씨가 깨달음을 얻은 표정으로 말했습니다.

박시니어 씨가 마무리했습니다. "맞아요.

적은 게 더 많은 것이라는 말이 여기서도 통하죠. 이제 김개발 씨도 다중 도구 호출의 고수가 되었네요!"

실전 팁

💡 - 도구 개수가 10개를 넘으면 반드시 동적 선택을 고려하세요.

  • 임베딩 기반 유사도 검색을 사용하면 키워드 없이도 적절한 도구를 찾을 수 있습니다.
  • 기본 도구 세트를 정의해두고, 특정 의도가 감지되면 추가 도구를 붙이는 방식도 효과적입니다.

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#LLM#FunctionCalling#ParallelExecution#ToolIntegration#LLM,MultiTool,병렬

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

Context Fundamentals - AI 컨텍스트의 기본 원리

AI 에이전트 개발의 핵심인 컨텍스트 관리를 다룹니다. 시스템 프롬프트 구조부터 Attention Budget, Progressive Disclosure까지 실무에서 바로 적용할 수 있는 컨텍스트 최적화 전략을 배웁니다.

Phase 1 보안 사고방식 구축 완벽 가이드

초급 개발자가 보안 전문가로 성장하기 위한 첫걸음입니다. 해커의 관점에서 시스템을 바라보는 방법부터 OWASP Top 10, 포트 스캐너 구현, 실제 침해사고 분석까지 보안의 기초 체력을 다집니다.

프로덕션 워크플로 배포 완벽 가이드

LLM 기반 애플리케이션을 실제 운영 환경에 배포하기 위한 워크플로 최적화, 캐싱 전략, 비용 관리 방법을 다룹니다. Airflow와 서버리스 아키텍처를 활용한 실습까지 포함하여 초급 개발자도 프로덕션 수준의 배포를 할 수 있도록 안내합니다.

워크플로 모니터링과 디버깅 완벽 가이드

LLM 기반 워크플로의 실행 상태를 추적하고, 문제를 진단하며, 성능을 최적화하는 방법을 다룹니다. LangSmith 통합부터 커스텀 모니터링 시스템 구축까지 실무에서 바로 적용할 수 있는 내용을 담았습니다.

LlamaIndex Workflow 완벽 가이드

LlamaIndex의 워크플로 시스템을 활용하여 복잡한 RAG 파이프라인을 구축하는 방법을 알아봅니다. 이벤트 기반 워크플로부터 멀티 인덱스 쿼리까지 단계별로 학습합니다.