더 나은 도구 호출 (Tool-calling) 모델이 필요하다고 생각했지만, 사실 제 에이전트의 도구가 너무 많았던 것이었습니다
요약
에이전트의 도구 호출 오류가 모델의 지능 문제가 아닌, 과도하고 잘못 설계된 도구 노출(Tool surface) 문제임을 지적합니다. 효율적인 에이전트 구축을 위해 모델 교체보다 도구 스키마 리팩터링과 태스크 수준의 라우팅이 중요함을 강조합니다.
핵심 포인트
- 도구 호출 오류는 모델의 지능보다 메뉴 설계(가시성) 문제일 가능성이 높음
- 모든 함수를 한꺼번에 노출하는 방식은 프로덕션 환경에서 실패함
- 도구 설명의 중복 제거 및 내부 전용 도구의 분리가 필요함
- 태스크 수준의 라우팅과 도구 표면적 관리가 아키텍처의 핵심임
몇 달 전이었다면, 저는 모델을 탓했을 것입니다.
에이전트가 잘못된 함수를 선택한다고요? 괜찮습니다, GPT-5로 업그레이드하면 됩니다. Claude Opus를 써보세요. 더 똑똑한 라우터 (Router)를 추가하세요. 로컬에서 실행 중이라면 Qwen이나 Llama를 벤치마킹해 볼 수도 있겠죠.
모델을 교체하는 것은 깔끔하게 느껴지기 때문에, 이런 본능은 어디에나 존재합니다. 반면 에이전트의 도구 표면 (Tool surface)을 리팩터링 (Refactoring)하는 것은 아키텍처 (Architecture)가 엉망이라는 것을 인정하는 것처럼 느껴집니다.
그러다 저는 OpenClaw 스레드 몇 개를 접하게 되었고, 이 문제는 "모델의 지능" 문제라기보다 "나쁜 메뉴 설계" 문제에 훨씬 가깝다는 것을 알게 되었습니다.
버그는 모델 버그처럼 보이지 않았습니다
숨겨진 도구에 관한 한 r/openclaw 스레드에서, 한 사용자가 에이전트가 특정 이미지 파일을 보내도록 시도하고 있었습니다.
에이전트는 미디어 전송 경로를 사용하는 대신, 에이전트 간 내부 통신용으로 의도된 것으로 보이는 sessions_send를 계속 호출했습니다.
그것만으로도 이미 문제입니다.
하지만 더 흥미로운 세부 사항은 이것이었습니다: 사용자는 message 기능이 특정 프롬프트 (Prompt)에서만 안정적으로 트리거되며, 그마저도 10번 중 8번 정도만 작동한다고 말했습니다. 그들은 이를 다음과 같이 설명했습니다:
도구가 에이전트가 그 존재를 완전히 거부할 정도로 숨겨져 있습니다.
이것은 "더 똑똑한 모델을 구매하라"는 문제가 아닙니다.
이것은 가시성 (Visibility) 문제입니다.
당신의 에이전트는 당신이 보여준 그대로 행동하고 있을지도 모릅니다
사람들이 도구 호출 (Tool calling)을 위한 최고의 모델을 물을 때, 보통 다음을 의미합니다:
- 어떤 모델이 다음 행동을 선택하는 데 가장 뛰어난가?
- 어떤 모델이 모호한 지시사항으로부터 가장 잘 회복하는가?
- 어떤 모델이 혼란 없이 방대한 도구 인벤토리 (Tool inventory)를 처리하는가?
이것들은 타당한 질문들입니다.
더 강력한 모델은 분명히 도움이 됩니다. GPT-5는 일반적으로 약한 모델보다 라우팅 (Routing)을 더 잘할 것입니다. Claude Opus는 도구 설명 (Tool description)이 모호할 때 종종 더 나은 성능을 보입니다. 작은 오픈 모델 (Open models)들은 컨텍스트 (Context)가 노이즈로 가득 차면 더 빨리 무너질 수 있습니다.
하지만 많은 에이전트 실패에는 훨씬 더 지루한 설명이 있습니다:
당신이 잘못된 시점에, 잘못된 도구를, 잘못된 설명과 함께 노출했다는 것입니다.
만약 일반적인 사용자 요청 중에 sessions_send가 노출되고, message가 반쯤 숨겨져 있다면, 모델이 지능 지수(IQ) 테스트에서 낙제하고 있는 것이 아닙니다.
모델은 잘못된 액션 메뉴에서 선택하고 있는 것입니다.
이것은 모델의 문제만이 아니라 아키텍처의 문제입니다
많은 팀이 여전히 다음과 같은 방식으로 에이전트를 구축합니다:
- 모든 함수를 연결하고
- 모든 기술을 연결하고
- 거대한 스키마 (Schema)를 노출하며
- GPT-5나 Claude가 알아서 해결해주길 기대하는 방식
이 방식은 데모용으로는 작동합니다.
하지만 프로덕션(Production) 환경에서는 무너집니다.
도구의 표면적(Tool surface)이 커지면, 잘못된 도구 선택은 마치 "모델이 멍청하다"는 것처럼 보이기 시작하지만, 실제로는 다음과 같은 상황인 경우가 많습니다:
- 너무 많은 노출된 옵션
- 중복되는 설명
- 사용자 대면 흐름(User-facing flows)에 노출된 내부 전용 도구
- 태스크 수준의 라우팅 (Task-level routing) 부재
- 트레이싱 (Tracing) 부재
만약 n8n 에이전트, Make 시나리오, Zapier AI 단계, 또는 OpenClaw 워크플로우가 가능한 가장 이상한 함수를 호출하는 것을 본 적이 있다면, 아마 이 상황이 익숙할 것입니다.
OpenAI의 가이드라인도 조용히 같은 방향을 가리키고 있습니다
저를 놀라게 했던 한 가지는, OpenAI의 현재 함수 호출 (Function-calling) 가이드라인이 단순히 모델을 업그레이드하는 것이 아니라, 모델이 보는 것을 줄이는 방향으로 기울어져 있다는 점입니다.
함수가 많거나 스키마가 큰 경우, 권장 사항은 모델이 매 턴마다 전체 함수 인벤토리를 평가하게 만들지 않는 것입니다.
이는 두 가지 실질적인 패턴으로 이어집니다:
- 태스크(Task) 또는 워크플로우 단계에 따라 노출되는 도구 세트를 직접 좁힙니다.
- 지연된 도구 로딩 (Deferred tool loading)을 사용하여, 드물게 사용되는 함수는 필요할 때만 고려되도록 합니다.
이는 보통의 "그냥 전부 노출하자"는 본능과는 정반대되는 방식입니다.
그리고 솔직히 말해서, 이는 타당합니다.
사용자가 명확하게 파일을 보내려고 시상하고 있는데, 왜 모델이 관련 없는 40개의 액션을 쳐다보고 있어야 할까요?
도구 호출을 망가뜨리는 가장 쉬운 방법
여기 간단한 나쁜 예시가 있습니다.
TOOLS = [
send_message,
sessions_send,
...
이것은 만들기 쉽습니다.
하지만 동시에 새벽 2시에 말도 안 되는 버그를 디버깅하게 만드는 원인이기도 합니다.
이제 이를 태스크 범위 지정 노출 (Task-scoped exposure)과 비교해 보겠습니다:
def tools_for_task(task_type: str):
if task_type == "send_file":
return [find_file, prepare_attachment, send_message]
...
동일한 모델입니다.
성공 확률이 훨씬 높습니다.
LangChain의 멘탈 모델 (mental model)은 여기서 실제로 유용합니다
LangChain은 에이전트 (agent)를 다음과 같이 설명합니다:
모델 (model) + 하네스 (harness)
그 하네스는 사람들이 인정하는 것보다 더 중요합니다.
여러분의 하네스에는 다음이 포함됩니다:
- 시스템 프롬프트 (system prompt)
- 도구 목록 (tool list)
- 도구 설명 (tool descriptions)
- 미들웨어 (middleware)
- 라우팅 로직 (routing logic)
- 메모리 설정 (memory setup)
- 재시도 및 가드 (retries and guards)
하네스가 허술하다면, 더 강력한 모델이 도움이 될 수는 있지만, 완전히 문제를 해결해주지는 못할 것입니다.
작은 LangChain 예제들이 작동하는 이유는 액션 공간 (action space)이 매우 작기 때문입니다:
from langchain.agents import create_agent
def get_weather(city: str) -> str:
...
도구 하나는 쉽습니다.
의미론적 중복 (overlapping semantics)이 있는 도구 50개는 상황을 엉망으로 만듭니다.
이상한 출력은 종종 도구 표면 (tool-surface) 버그가 변장한 것입니다
두 번째 OpenClaw 스레드가 이를 더욱 명확하게 만들었습니다.
한 사용자가 깨진 것처럼 보이는 기괴한 출력 블록을 보고했습니다. 다른 사용자는 Graphify가 범인이라고 답했습니다:
엄청난 양의 숫자 블록, 벨라루스에 관한 무작위 정보, 만다린어 텍스트 등이 전송되곤 했습니다. 그것을 제거하니 문제가 사라졌습니다.
이는 완벽한 디버깅 사례입니다. 왜냐하면 모든 이상한 에이전트 동작이 프런티어 모델 (frontier-model)의 불안정성 때문이라는 일반적인 환상을 깨뜨리기 때문입니다.
때때로 해결책은 다음과 같은 것이 아닙니다:
- 온도를 낮추기 (lower temperature)
- 모델 교체
- 3일 동안 프롬프트 다시 쓰기
때때로 해결책은 다음과 같습니다:
- 잘못된 도구 제거
- 내부 도구 숨기기
- 프로덕션 (production) 환경에서 실험적인 기술 노출 중단
이 사실은 여러분이 도구 표면 (tool surface)에 대해 더 편집증적으로 경계해야 함을 시사합니다.
실제로 무엇이 더 효과적인가
| 접근 방식 | 실제 상황에서 발생하는 일 |
|---|---|
| 하나의 에이전트에 모든 도구를 노출 | 배포는 빠르지만, 잘못된 도구 선택 가능성이 훨씬 높아지며 디버깅이 추측에 의존하게 됨 |
| ... |
광범위한 연구용 에이전트 (research agents)의 경우, 넓은 노출이 타당할 수 있습니다.
하지만 대부분의 프로덕션 자동화 (production automations)의 경우, 대개 그렇지 않습니다.
여기에는 다음이 포함됩니다:
- n8n 워크플로우 (workflows)
- Make 시나리오 (scenarios)
- Zapier AI 액션 (actions)
- OpenClaw 스킬 (skills)
- 지원 에이전트 (support agents)
- 파일 처리 워커 (file-processing workers)
- 내부 운영 봇 (internal ops bots)
이러한 시스템에서 모든 것을 노출하는 것은 대개 유연성으로 위장된 게으름입니다.
모델을 바꾸기 전에 수정해야 할 다섯 가지
GPT-5 vs Claude Opus vs Qwen vs Llama를 벤치마킹하기 전에, 먼저 이 작업을 수행하세요.
1. 내부 전용 함수를 완전히 숨기세요
함수가 사용자 대상 요청을 위한 것이 아니라면, 보이는 인벤토리(inventory)에 남겨두지 마세요.
나쁜 예:
tools=[send_message, sessions_send, search_docs]
좋은 예:
user_tools=[send_message, search_docs]
internal_tools=[sessions_send]
내부 도구(internal tools)는 다음 뒤에 배치하세요:
- 감독자 (supervisor)
- 미들웨어 (middleware)
- 별도의 워커 (separate worker)
- 자체 정책을 가진 하위 에이전트 (sub-agent)
사용자 대상 모델이 내부 전송 프리미티브 (internal transport primitives)를 볼 수 있다면, 실패를 자초하는 것입니다.
2. 전역 기능이 아닌 작업(task) 단위로 도구를 노출하세요
파일 전송 요청이 시스템 어딘가에 존재한다는 이유만으로 관련 없는 함수들을 노출해서는 안 됩니다.
좋은 규칙:
- 파일 작업 (file task) -> 파일 조회 (file lookup), 첨부 파일 준비 (attachment prep), 메시지 전송 (send message)
- 캘린더 작업 (calendar task) -> 가용성 (availability), 일정 예약 (scheduling), 확인 (confirmation)
- 리서치 작업 (research task) -> 검색 (search), 가져오기 (fetch), 요약 (summarize)
모델이 매 턴마다 귀사의 전체 API 표면 (API surface)을 뒤지게 만들지 마세요.
3. 도구 설명을 UI 레이블처럼 다시 작성하세요
대부분의 도구 설명은 엉망입니다.
마치 잠이 부족한 엔지니어가 남긴 내부 메모처럼 읽힙니다.
나쁜 예:
def sessions_send(...):
"""세션 데이터를 전송합니다."""
좋은 예:
def sessions_send(...):
"""에이전트 간 통신을 위한 내부 전용 전송 도구입니다. 최종 사용자에게 답장하거나 미디어/파일을 전송하는 용도로 절대 사용하지 마세요."""
나쁜 예:
def message(...):
"""메시지 기능."""
좋은 예:
def message(...):
"""외부 수신자에게 사용자 대상 메시지 또는 미디어 첨부 파일을 전송합니다. 일반적인 외부 통신에 사용하세요."""
설명(descriptions)은 인터페이스의 일부입니다.
그렇게 취급하세요.
4. 추측을 시작하기 전에 트레이싱 (Tracing)을 켜세요
LangChain을 사용 중이라면, LangSmith 트레이싱 (tracing)을 활성화하세요:
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=your_key_here
그런 다음 다음 사항들을 점검하세요:
- 어떤 도구들이 노출되었는지
- 모델이 실제로 무엇을 보았는지
- 모델이 어떤 도구를 선택했는지
- 설명 (descriptions)들이 서로 중복되지는 않았는지
- 프롬프트 (prompt)가 의도치 않게 잘못된 경로를 유도하지는 않았는지
트레이싱 (tracing) 없이는, 당신은 민간요법 식의 엔지니어링 (folklore engineering)을 하고 있는 것입니다.
5. 그 후에야 모델 업그레이드를 테스트하세요
도구 노출 (tool surface)이 깔끔해진 후에 모델 벤치마크 (benchmark)를 수행하세요.
그때가 되어야 질문이 실질적인 의미를 갖게 됩니다:
- GPT-5는 긴 컨텍스트 (long-context) 작업에서 더 나은 라우팅 (route)을 수행하는가?
- Claude Opus는 모호함 (ambiguity)으로부터 더 잘 회복하는가?
- Qwen이나 Llama가 이 더 좁은 액션 스페이스 (action space)에 충분히 적합한가?
정리하기 전의 벤치마크 (benchmarks)는 노이즈가 심합니다. 왜냐하면 모델의 품질 (quality)인 것처럼 아키텍처 (architecture) 상의 실수를 측정하고 있기 때문입니다.
자동화를 위한 실용적인 라우팅 (routing) 패턴
n8n, Make, Zapier 또는 커스텀 워커 (custom worker) 설정을 통해 자동화를 구축하고 있다면, 다음과 같은 간단한 패턴이 잘 작동합니다:
- 작업을 분류 (classify) 합니다.
- 관련 있는 도구의 서브셋 (subset)만 노출합니다.
- 모델을 호출 (call) 합니다.
- 선택된 도구를 실행 (execute) 합니다.
- 모든 것을 트레이스 (trace) 합니다.
예시:
def classify_task(user_input: str) -> str:
if "send" in user_input and "file" in user_input:
return "send_file"
...
이것은 화려하지 않습니다.
그것이 핵심입니다.
지루한 라우팅 (routing)이 마법 같은 생각보다 낫습니다.
Standard Compute가 이 과정에서 차지하는 위치
도구 노출을 정리하기 시작하면, 또 다른 문제가 빠르게 나타납니다. 에이전트 (agents)를 제대로 테스트하려면 엄청난 양의 모델 호출 (model calls)이 소모된다는 점입니다.
단순히 프롬프트 (prompt) 하나만 실행하는 것이 아닙니다.
당신은 다음과 같은 것들을 실행합니다:
- 반복적인 도구 선택 (tool-selection) 테스트
- 라우팅 (routing) 실험
- 긴 워크플로우 (workflow) 시뮬레이션
- 다양한 모델에 걸친 재시도 (retries)
- 모든 하네스 (harness) 변경 후의 회귀 테스트 (regression checks)
토큰 (token) 단위로 비용을 지불한다면, 이는 빠르게 비용이 상승하는 원인이 됩니다.
이것이 바로 제가 에이전트 팀들에게 정액제 (flat-rate) AI 인프라가 과소평가되어 있다고 생각하는 정확한 이유입니다.
Standard Compute를 사용하면 OpenAI 호환 API를 사용할 수 있으며, 매시간 토큰 소모량을 감시할 필요 없이 이러한 반복 작업 (iterations)을 수행할 수 있습니다. 이는 GPT-5.4, Claude Opus 4.6, Grok 4.20 전반에 걸쳐 지속적인 테스트가 필요한 에이전트 (agents) 및 자동화 (automations)를 구축할 때 특히 유용합니다.
만약 여러분의 워크플로우 (workflow)가 이미 OpenAI 호환 SDK를 사용하고 있다면, 교체 작업은 매우 간단합니다.
이것이 중요한 이유는 도구 라우팅 (tool-routing) 버그가 단 한 번에 해결되는 경우가 드물기 때문입니다. 테스트하고, 추적 (trace)하고, 다시 실행할 수 있는 여유 공간이 필요합니다.
토큰당 과금 방식 (Per-token pricing)은 사람들이 반복적인 평가 (evaluation)가 가장 절실히 필요한 바로 그 시스템들을 충분히 테스트하지 못하게 만듭니다.
만약 정말로 모델이 문제라면?
때로는 모델이 문제일 수도 있습니다.
긴 컨텍스트 (Long context)는 도구 선택 (tool selection)을 방해할 수 있습니다. 취약한 메모리 설정 (memory setup)은 의도 (intent)를 오염시킬 수 있습니다. 도구 표면 (tool surface)이 괜찮더라도 작은 모델들은 미세한 차이를 구분하는 데 어려움을 겪을 수 있습니다.
그리고 네, 더 강력한 모델이 많은 도움이 되는 것도 사실입니다.
하지만 저는 너무 많은 팀이 다음과 같은 질문을 먼저 던진다고 생각합니다.
도구 호출 (tool calling)에 가장 좋은 모델은 무엇인가?
다음 질문을 던하기 전 말입니다.
왜 이 에이전트가 해당 함수 (function)를 아예 볼 수 있는가?
두 번째 질문은 덜 흥미롭습니다.
하지만 더 많은 버그를 해결해 줍니다.
핵심 요약 (The takeaway)
만약 여러분의 에이전트가 계속해서 잘못된 함수를 선택한다면, 모델 교체부터 시작하지 마세요.
메뉴부터 점검하세요.
다음 사항을 확인하십시오:
- 어떤 도구들이 보이는가
- 어떤 것들이 내부 전용 (internal-only)인가
- 설명 (descriptions)이 중복되지는 않는가
- 태스크 수준의 라우팅 (task-level routing)이 존재하는가
- 추적 (tracing) 기능이 있는가
많은 "모델의 어리석음 (model stupidity)"은 사실 도구 표면 (tool-surface)의 부주의함에서 비롯됩니다.
제가 에이전트의 실패를 이런 방식으로 바라보기 시작했을 때, 디버깅 (debugging)은 훨씬 덜 신비주의적으로 변했습니다.
GPT-5가 내 마음을 읽어주길 구걸하는 일은 줄어들었습니다.
대신 모델에게 공정한 기회를 주는 시스템을 설계하는 일이 늘어났습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기