없는 도구를 부르는 에이전트를 앞에 세운 전화번호부로 막는 법

존재하지 않는 도구를 호출하는 tool hallucination을 레지스트리 대조와 시그니처 확인으로 걸러내는 closed-world resolution과 파이프라인 순서에 대한 해설

원문 논문 보기
'없는 도구를 부르는 에이전트를 앞에 세운 전화번호부로 막는 법' 기사 커버 이미지

오늘 살펴볼 논문은 Closed-World Resolution Against Tool Hallucination in LLM Agents로, arXiv에 공개된 프리프린트 arXiv:2609.19425입니다. 존재하지 않는 도구를 부르는 hallucination은 고르기나 보안 게이트로 막을 수 없기에 파이프라인 맨 앞에서 레지스트리 대조로 걸러내야 한다는 것이 이 논문이 내세우는 주장입니다. 원문은 제목 아래 걸어둔 링크에서 확인할 수 있습니다.

에이전트가 내놓은 함수 이름이 어색하게 느껴진 적 있으신가요

안녕하세요, 패트릭입니다.

저는 에이전트를 돌리다가 로그에서 낯선 함수 이름 하나를 본 적이 있습니다. 분명히 등록해 둔 도구 목록에는 없는 이름이었는데, 에이전트는 태연하게 그 이름을 불렀고 인자까지 그럴듯하게 채워 넣었죠. 처음에는 제가 등록을 빠뜨린 줄 알고 문서를 뒤졌는데, 문서를 뒤질수록 그런 도구는 어디에도 없었습니다. 그렇다면 에이전트는 왜 없는 것을 있는 것처럼 불렀을까요?

오늘 다룰 논문이 바로 그 질문에서 출발합니다. 제목은 Closed-World Resolution Against Tool Hallucination in LLM Agents이고, arXiv 번호 2609.19425로 공개된 연구입니다. 제가 이 논문에서 가장 눈길이 간 부분은 문제를 정의하는 태도였습니다. 흔히 도구 실패라고 하면 엉뚱한 도구를 고른 경우를 떠올리는데, 연구진은 그보다 앞단을 가리키고 있으니까요. 도구가 여럿 있을 때 무엇을 고를지는 그다음 문제이고, 지금 묻는 것은 애초에 목록에 없는 것을 왜 부르는가입니다.

저희가 새로운 AI 아키텍처를 볼 때도 benchmark 숫자만 보지는 않습니다. 실제로 무엇이 바뀌었고, 그 변화가 시스템에서 어떤 비용 구조를 만드는지를 함께 봅니다. 그런 눈으로 보면 이 논문은 숫자를 내세우기 전에 순서를 묻는 논문입니다. 무엇을 먼저 막아야 하는가, 그리고 그 막는 일을 어디에 두어야 하는가 말이죠.

없는 메뉴를 주문하는 손님을 상상해 보시면 됩니다

식당에 간 손님이 메뉴에 없는 요리를 주문하는 장면을 떠올려 보시면 됩니다. 메뉴에는 김치찌개와 된장찌개만 있는데, 손님은 느닷없이 메뉴에 없는 스테이크를 주문해 버리죠. 종업원이 아무리 친절해도 메뉴에 없는 요리는 주방에 전달할 수가 없습니다. 그런데 AI 에이전트에서는 이 장면이 조금 다르게 흘러갑니다. 손님이 없는 메뉴를 외치면, 그 말이 그대로 주방으로 넘어가 버리는 경우가 생기니까요.

연구진이 tool hallucination이라고 부르는 현상이 바로 그것입니다. 존재하지 않는 도구를 호출하거나, 스키마가 선언한 적 없는 인자를 끼워 넣는 일을 말합니다. 여기서 재미있는 질문이 생깁니다. 모델이 왜 메뉴를 외워두지 못할까요? 도구 목록은 프롬프트에 다 적혀 있는데 말이죠.

이유는 생각보다 단순합니다. 언어 모델은 목록을 외우는 기계가 아니라 다음 말을 이어가는 기계이기 때문입니다. 문맥상 그럴듯한 이름이 떠오르면, 그 이름이 실제 등록 이름인지 매번 되짚지 않고 그냥 내뱉을 수 있죠. 특히 함수 이름이 서로 비슷하게 생겼거나, 예전에 본 다른 프로젝트의 이름이 떠오르면 손이 먼저 나갑니다. 그래서 목록을 길게 적어주는 것만으로는 이 실수가 잘 줄지 않습니다. 메뉴판을 크게 인쇄한다고 손님이 없는 메뉴를 안 외우는 것은 아니니까요.

고르기를 잘하는 것과 없는 것을 안 부르는 일은 다릅니다

그렇다면 흔히 쓰는 방어책과는 무엇이 다를까요? 지금까지 도구 안전을 이야기할 때는 대개 두 가지를 떠올렸습니다. 하나는 여러 도구 중에서 맞는 것을 고르는 선택 문제이고, 다른 하나는 위험한 호출을 실행 전에 걸러내는 보안 게이트입니다. 둘 다 나름대로 쓸모가 있는데, 연구진은 이 둘이 전제부터 다르다고 말합니다.

선택과 게이트는 둘 다 호출이 실존 도구를 가리킨다고 가정하고 출발합니다. A 도구와 B 도구 중에서 어느 쪽이 맞는지 고르거나, 이미 고른 호출이 위험한지를 따지는 것이죠. 문제는 그다음입니다. 목록에 없는 C라는 이름을 불러버리면, A와 B 중에서 고르는 장치는 할 일이 없어집니다. 고를 대상 자체가 목록 밖에 있으니까요.

저는 이 구분이 논문에서 가장 힘을 가진 대목이라고 봅니다. 겉으로 보면 다 같이 도구 실패 같지만, 막는 위치가 완전히 다르니까요. 선택을 잘하게 가르치는 일과 없는 이름을 밖으로 못 나가게 하는 일은 서로 대신할 수 없습니다. 실제로 운영 환경에서는 없는 이름이 실행기로 넘어가면 뒤쪽 게이트가 그 이름을 처음 보고 당황하는 일이 생깁니다. 게이트는 참과 거짓을 가리는 장치이지, 없는 메뉴를 메뉴판에 되돌려 적는 장치가 아니니까요.

게이트를 앞에 세운다고 막히지 않는 이유

앞 문단에서 던진 의문을 조금 더 밀어보겠습니다. 그렇다면 인과 게이트를 더 똑똑하게 만들면 되지 않을까요? 호출의 전후 관계를 따져서 이상한 호출을 실행 전에 잡아내면 될 것 같기도 하죠. 그런데 연구진은 이 길이 구조적으로 막혀 있다고 말합니다. hallucination된 호출은 게이트가 판단한 결정이 애초에 아니었기 때문입니다.

표면적으로 보면 단순합니다. 실제로는 그렇지 않습니다. 게이트는 에이전트가 고른 호출을 보고 그 호출이 이어지는 행동과 어떤 인과를 만드는지를 따집니다. 그런데 목록에 없는 호출은 게이트가 본 적 없는 세계에서 온 셈이라서, 인과를 따질 출발점이 없습니다. 비유하자면 우편 배달부가 편지를 들고 주소를 찾는데, 주소 자체가 이 세상에 없는 것과 비슷합니다. 배달 경로를 아무리 최적화해도 없는 주소에는 도착할 수 없죠.

그래서 연구진은 순서를 증명이라는 형태로 못 박습니다. hallucination 방어는 어떤 인과 게이트보다 앞에 있어야 합니다. 뒤에 서 있는 게이트는 앞단에서 걸러지지 않은 호출을 보고 판단하는데, 앞단에서 없는 이름이 그대로 넘어오면 뒤쪽 판단 자체가 성립하지 않기 때문입니다. 저는 여기서 조금 멈칫했습니다. 보통은 게이트를 뒤에 두는 것이 안전해 보이는데, 이 논문은 반대로 말하고 있으니까요. 그런데 로그를 떠올리면 수긍이 됩니다. 없는 이름이 실행 직전까지 내려가면, 그때는 이미 늦은 경우가 많습니다. 함수 디스패처가 없는 이름을 찾는 동안 에러가 터지거나, 더 나쁜 경우에는 비슷한 이름에 엉뚱하게 걸려 실행이 되어버리니까요.

도구 호출을 내보내는 에이전트와 레지스트리를 대조하는 리졸버를 보여주는 개념도
도구 호출을 내보내는 에이전트와 레지스트리를 대조하는 리졸버를 보여주는 개념도

전화번호부부터 확인하는 한 단계를 앞에 두자는 제안

여기까지 오면 자연스럽게 다음 질문이 생깁니다. 그렇다면 앞에 무엇을 두어야 할까요? 연구진이 제안하는 답은 Resolution Rung이라는 장치입니다. 이름이 조금 거창하게 들리지만, 하는 일은 전화번호부를 확인하는 것과 비슷합니다. 호출이 오면 목록에 그 이름이 있는지 먼저 보고, 이어서 인자가 선언된 스키마와 맞는지 확인합니다. 두 검사를 다 통과한 호출만 뒤로 보내는 것이죠.

이 장치가 눈에 띄는 이유는 학습이 필요 없다는 점입니다. 모델 가중치를 다시 만지거나 새로운 판별기를 가르칠 필요가 없고, 지금 가진 레지스트리와 스키마만으로 돌아갑니다. 등록된 이름 집합에 속하는지 보는 소속 확인과, 인자 이름과 형태가 선언과 어긋나지 않는지 보는 시그니처 검사가 전부입니다. 그래서 closed-world라는 말이 붙습니다. 세상 전체를 아는 척하지 않고, 닫힌 목록 안에서만 참과 거짓을 가리겠다는 뜻이죠.

실제 시스템 관점에서 보면 다음 질문이 바로 생깁니다. 좋은데, 이걸 어디에 올릴까요? 연구진의 답은 맨 앞입니다. 어떤 선택 로직이나 인과 게이트보다 먼저 두어야 하고, 실행기와도 직접 닿기 전에 두어야 합니다. 저는 이 대목에서 전화 교환원 생각이 났습니다. 예전 교환원은 외선으로 나가기 전에 먼저 내선 번호가 실제로 있는지를 확인했죠. 번호가 없으면 바깥으로 연결 자체를 안 했습니다. Resolution Rung도 같은 역할을 합니다. 없는 이름은 바깥으로 내보내지 않고 앞에서 돌려보내는 것이죠. (참고로 저는 이런 단순한 앞단 장치가 오래간다는 쪽에 서 있습니다. 뒤쪽을 복잡하게 만드는 것보다 앞쪽에서 한 번 걸러내는 편이 장애 지점이 적으니까요.)

자유롭게 쓰게 두면 엉뚱한 이름이 더 자주 튀어나옵니다

이제 숫자 이야기를 해보겠습니다. 숫자는 언제나 조건과 함께 읽어야 하니까요. 연구진은 열 개의 호스티드 모델을 두 가지 호출면에 걸쳐 시험했고, 그 과정에서 322건의 진짜 hallucination을 측정했습니다. 호출면이라는 말이 낯설 수 있는데, 모델이 도구를 부르는 통로가 두 가지였다고 보시면 됩니다. 하나는 비교적 자유로운 raw JSON 면이고, 다른 하나는 틀에 맞춰 부르게 하는 제약된 면입니다.

결과는 꽤 또렷했습니다. 날조된 도구 호출은 자유로운 면에 몰렸고, 34건 대 3건으로 벌어졌습니다. 같은 모델에 같은 질문을 던져도 통로가 바뀌면 결과가 크게 달라질 수 있다는 것을 경험으로 알고 있죠. 자유로운 면에서는 모델이 이름을 지어내기 쉽고, 제약된 면에서는 이름이 틀에서 벗어나면 바로 티가 나니까요.

그런데 여기서 조심해서 읽을 필요가 있습니다. 3건이라는 숫자가 제약된 면은 안전하다는 뜻은 아닙니다. 제약된 면에서도 날조가 완전히 사라지지 않았고, 뒤에서 다룰 MCP 실험에서는 제약된 환경에서도 다른 경로로 hallucination이 다시 튀어나옵니다. 저는 이 숫자보다 그 뒤의 배치를 더 눈여겨봅니다. 통로를 바꾸는 것만으로 날조 분포가 크게 바뀌었다는 것은, hallucination이 모델 성격만의 문제가 아니라 통로 모양의 문제이기도 하다는 뜻이니까요. 운영 측면에서 보면 이 차이는 바로 돈과 장애로 이어집니다. 자유로운 JSON을 그대로 실행기에 물리면 없는 이름이 곧바로 에러나 오작동으로 번지는데, 앞에서 틀을 강제하면 그중 상당수가 조용히 걸러지니까요.

자유로운 JSON 입력면과 제약된 호출면을 나란히 둔 비교 그림
자유로운 JSON 입력면과 제약된 호출면을 나란히 둔 비교 그림

몸집이 커진다고 헛발질이 줄지 않았습니다

다음으로 모델 크기에 대한 통념을 점검할 차례입니다. 흔히 모델이 커지면 이런 사소한 실수는 줄어들 거라고 기대하죠. 파라미터가 많으면 목록도 더 잘 외울 것 같으니까요. 그런데 이번 측정에서는 그 기대가 어긋났습니다. 675B짜리 큰 모델의 hallucination 비율이 7-8B급 작은 모델과 비슷한 수준으로 나왔습니다.

이 대목은 처음 읽으면 고개가 갸웃해집니다. 큰 모델이 더 똑똑한 것 아니었냐고요. 그런데 문장을 이어가며 생각하면 이해가 됩니다. 없는 이름을 지어내는 능력도 일종의 유창함이라서, 큰 모델은 오히려 더 그럴듯한 가짜 이름을 만들어낼 수 있기 때문입니다. 작은 모델이 맞춤법을 틀리듯 엉뚱한 이름을 내놓는다면, 큰 모델은 그럴듯한 철자로 없는 이름을 내놓는 셈이죠. 두 경우 다 전화번호부에 없는 것은 마찬가지입니다.

개인적으로 인상 깊었던 점은, 크기보다 면과 배치가 결과를 더 크게 흔들었다는 것입니다. 같은 큰 모델이라도 제약된 면에서는 날조가 줄고, 작은 모델이라도 자유로운 면에서는 날조가 늘었습니다. 저희가 시스템을 볼 때 파라미터 수보다 통로와 순서를 먼저 보는 이유도 여기에 있습니다. 숫자가 크다고 운영이 편해지는 것은 아니고, 어디에서 걸러내는지가 밤샘 장애를 가르기 때문입니다. 만약 규모가 답이었다면 이 문제는 이미 사라졌을 텐데, 그렇지 않았다는 점이 오히려 이 논문을 읽을 값어치를 만들어줍니다.

매우 큰 모델과 작은 모델의 hallucination 비율이 비슷함을 보여주는 개념 그림
매우 큰 모델과 작은 모델의 hallucination 비율이 비슷함을 보여주는 개념 그림

서버를 한데 모아 합치면 멀쩡하던 모델도 흔들립니다

앞선 실험이 단일 레지스트리에서의 이야기였다면, 이제 무대를 옮겨보겠습니다. 요즘 에이전트는 MCP처럼 바깥 서버 여러 개를 한꺼번에 붙여서 쓰는 경우가 많죠. 연구진은 여러 서버를 하나의 이름공간으로 합친 live MCP 면을 따로 만들었고, 거기서 154건의 hallucination을 측정했습니다. 여기서 눈여겨볼 점은 따로 있습니다. 단일 레지스트리 면에서는 깨끗했던 frontier 모델들까지 이 합친 면에서는 hallucination을 내놓기 시작했다는 것입니다.

왜 그럴까요? 서버를 합치면 이름이 부딪히고 가려지는 일이 생기기 때문입니다. A 서버의 도구와 B 서버의 도구가 비슷한 이름을 쓰거나, 한쪽 이름이 다른 쪽을 가리는 shadowing이 일어나면 모델이 어느 쪽을 불러야 할지 헷갈립니다. 그러다 둘 다 아닌 세 번째 이름을 지어내거나, A의 이름에 B의 인자를 섞어 부르는 일이 생깁니다. 단일 메뉴판일 때는 멀쩡하던 손님이, 여러 식당 메뉴를 한 장으로 합쳐 놓으니 엉뚱한 요리를 외치기 시작한 셈이죠.

구체적인 장면을 그려보겠습니다. 어느 날 오후에 에이전트가 파일 도구 서버와 검색 도구 서버를 같이 물고 일을 하고 있습니다. 파일 쪽에는 읽기와 쓰기가 있고, 검색 쪽에도 비슷한 이름의 도구가 있습니다. 에이전트는 중간에 두 목록이 섞이면서 존재하지 않는 조합을 만들어냅니다. 목록을 프롬프트에 다 적어주었는데도 말이죠. 통로가 넓어지고 이름이 겹칠수록 모델의 말솜씨는 오히려 독이 됩니다. 매끈하게 이어지는 문장 안에 없는 이름이 슬쩍 끼어들기 때문입니다. 저는 그래서 단일 레지스트리에서 측정한 깨끗한 숫자만 보고 안심하면 안 된다고 봅니다. 실제 배포는 대개 여러 서버를 합친 모양이라서, 그 합친 모양에서 다시 재야 하니까요.

여러 MCP 서버를 하나의 이름공간으로 합칠 때 생기는 충돌과 가림을 그린 그림
여러 MCP 서버를 하나의 이름공간으로 합칠 때 생기는 충돌과 가림을 그린 그림

빌려 온 인자 하나는 끝까지 남는다는 증명

그렇다면 앞단에서 다 걸러낼 수 있을까요? 연구진은 여기서 정직하게 선을 긋습니다. 소속 확인과 시그니처 검사를 앞에 두면 대부분은 막히지만, 끝까지 남는 잔여물이 하나 있다는 것입니다. 바로 빌려 온 인자 문제입니다. 실존 도구의 이름을 빌리고, 그 도구의 스키마와 겉보기에 구분이 안 되는 인자를 끼워 넣으면 검사 입장에서는 올바른 호출과 구별할 길이 없습니다.

예를 들어 어떤 도구가 텍스트 인자를 받는다고 해보겠습니다. 에이전트가 그 칸에 실제로는 다른 도구에서 본 값을 슬쩍 가져와 채워 넣으면, 형태만 보는 검사는 통과해 버립니다. 이름도 맞고 모양도 맞으니까요. 내용까지 들여다보지 않는 한 이 호출은 올바른 호출처럼 보입니다. 연구진이 irreducible하다고 표현한 이유가 여기에 있습니다. 검사를 더 촘촘하게 만들어도, 스키마만으로는 구별이 안 되는 자리는 남는다는 것이죠.

이 대목에서 논문의 순서 증명이 다시 살아납니다. 앞에서 걸러낼 것은 앞에서 걸러내되, 그래도 남는 자리는 인과 게이트나 실행 정책이 맡아야 한다는 것입니다. 앞단 장치가 만능이 아니라는 고백이기도 하고, 각 장치가 맡을 자리를 나누는 설계도이기도 합니다. 저는 이처럼 남는 자리를 분명히 적는 논문을 신뢰하는 편입니다. 다 막을 수 있다고 말하는 쪽보다, 어디까지 막고 어디부터는 못 막는지 적는 쪽이 운영에 도움이 되니까요. 밤에 장애가 났을 때 필요한 것은 장담이 아니라 경계 지도니까요.

실존 도구의 인자를 빌려 온 호출이 검사를 통과하는 잔여 사례를 보여주는 그림
실존 도구의 인자를 빌려 온 호출이 검사를 통과하는 잔여 사례를 보여주는 그림

그래서 다음 실험에서는 무엇을 보면 될까요

이제 남은 질문을 모아보겠습니다. 그렇다면 앞으로 무엇을 보면 될까요? 연구진은 버전이 있는 Hallucinated-Tools Benchmark, 줄여서 HTB를 내놓았습니다. 어떤 리졸버를 만들어도 같은 잣대로 견줄 수 있게 하겠다는 뜻입니다. 단일 레지스트리 면과 합친 MCP 면을 함께 재고, 날조된 이름과 선언 없는 인자를 따로 세는 방식이죠.

제가 앞으로 보고 싶은 것은 이 잣대가 실제 배포 통로와 얼마나 닿아 있는지입니다. 합친 이름공간에서의 충돌과 가림을 잣대 안에 얼마나 살려냈는지, 그리고 제약된 면에서의 3건 같은 작은 숫자를 어떻게 해석해야 하는지가 궁금합니다. 작은 숫자라고 해서 무시하면, 합친 면에서 다시 커져 돌아올 수 있으니까요. 그리고 빌려 온 인자처럼 스키마만으로 안 잡히는 자리를 다음 연구가 어떻게 다루는지도 지켜볼 일입니다. 내용까지 보는 검사는 비용이 크고, 자칫하면 정상 호출까지 막을 수 있으니까요.

마지막으로 사람 이야기로 닫겠습니다. 이 논문을 읽으면서 저는 오래된 전화 교환원 일을 떠올렸습니다. 없는 번호는 바깥으로 연결하지 않고 앞에서 돌려보내는 일 말이죠. 에이전트가 더 많은 도구를 더 오래 붙들고 일할수록, 모델 하나가 똑똑한지만 보는 것을 넘어 앞에서 무엇을 걸러내고 어떤 순서로 막는지까지 함께 보아야 할 것 같습니다. 그렇다면 여러분의 에이전트에서는 없는 이름이 어디까지 내려가고 있을까요. 로그에서 낯선 이름 하나를 찾아보는 일부터 시작해도 늦지 않으니까요.

참고 자료

  1. Closed-World Resolution Against Tool Hallucination in LLM Agents · arxiv.org

    리뷰 원문

없는 도구를 부르는 에이전트를 앞에 세운 전화번호부로 막는 법 | Patty