도구 호출은 왜 JSON 출력이 아니라 왕복 프로토콜일까
LLM이 function_call을 만들면 끝일까, 아니면 호출 id·실행 책임·결과 매핑·재시도를 가진 작은 프로토콜로 봐야 할까?
도구 호출은 왜 JSON 출력이 아니라 왕복 프로토콜일까
- 카테고리: ai_dev
- 예상 읽기 시간: 10분
- 오늘의 질문: LLM이
function_call을 만들면 끝일까, 아니면 호출 id·실행 책임·결과 매핑·재시도를 가진 작은 프로토콜로 봐야 할까? - 핵심 출처:
- Function calling with the Gemini API - Interactions API - 게시일 미표기, 확인일 2026-07-06
- Function calling with the Gemini API - generateContent API - 게시일 미표기, 확인일 2026-07-06
- Tool use with Claude - Claude Platform Docs - 게시일 미표기, 확인일 2026-07-06
- Function calling - OpenAI API - 게시일 미표기, 확인일 2026-07-06
1. 왜 지금 봐야 하나
요즘 AI 앱의 실패는 “모델이 JSON을 못 만들었다”보다 더 복잡한 곳에서 난다. 모델은 날씨 조회, 결제 취소, 파일 검색, 코드 실행 같은 도구 호출을 제안하고, 애플리케이션은 그 호출을 실제 API나 내부 함수로 실행한 뒤 결과를 다시 모델에게 돌려준다. 이 흐름은 한 번의 텍스트 생성이 아니라 모델-앱-외부 시스템 사이의 왕복 프로토콜이다.
Google Gemini 문서는 function calling을 네 단계로 설명한다. 함수 선언을 모델에 제공하고, 모델이 호출 이름과 인자를 만들고, 애플리케이션이 직접 실행하고, 결과를 다시 모델에 보내 최종 응답을 만든다. generateContent 문서는 특히 Gemini 3 모델의 functionCall에 고유 id가 포함되며, functionResponse에 같은 id를 넣어 원래 요청과 결과를 매핑해야 한다고 강조한다. Anthropic 문서도 client tool은 Claude가 tool_use 블록을 반환하면 애플리케이션이 실행하고 tool_result를 다시 보내는 구조라고 설명한다. OpenAI 문서 역시 tool call output이 특정 tool call을 call_id로 참조해야 하며, reasoning 모델에서는 tool call과 함께 반환된 reasoning item도 다시 전달해야 한다고 말한다.
즉 오늘 볼 핵심은 “도구 호출 스키마를 예쁘게 정의하는 법”이 아니다. 더 중요한 질문은 이것이다. 우리 앱은 모델이 낸 호출과 실제 실행 결과를 안정적으로 연결하고, 실패와 재시도를 추적할 수 있는가?
2. 핵심 개념
Function calling 또는 tool use는 모델에게 함수를 “실행”시키는 기능이 아니다. 대부분의 client-side 도구에서 모델은 실행 의도를 구조화해서 반환할 뿐이고, 실제 실행 권한은 애플리케이션에 있다. 이 차이를 놓치면 위험한 설계가 된다. 모델 출력이 곧 DB 업데이트나 결제 취소가 되는 것처럼 코드를 짜면, 검증·승인·감사 로그를 넣을 지점이 사라진다.
실무적으로 도구 호출 프로토콜에는 다섯 가지 필드가 필요하다.
첫째, 선언이다. 도구 이름, 설명, 입력 스키마, 제한 사항을 모델에게 알려준다. Anthropic 문서는 도구 설명이 언제 사용해야 하는지와 각 파라미터 의미를 자세히 설명해야 성능이 좋아진다고 한다. OpenAI와 Gemini도 JSON Schema나 유사한 schema로 입력 구조를 정의한다.
둘째, 호출 식별자다. 병렬 tool call이나 연속 tool call이 생기면 “이 결과가 어느 호출의 결과인가”를 명확히 해야 한다. Gemini generateContent 문서는 functionCall의 id를 functionResponse에 그대로 넣으라고 한다. OpenAI 문서도 tool call output이 call_id를 참조한다고 설명한다. 이 id가 없거나 무시되면 병렬 호출 결과가 뒤섞인다.
셋째, 실행 경계다. 모델이 반환한 {name, args}는 요청일 뿐이다. 애플리케이션은 allowlist에 있는 함수인지, 사용자가 권한을 가진 작업인지, 인자가 비즈니스 규칙에 맞는지 확인해야 한다. strict schema는 모양을 줄여줄 수 있지만, “환불 가능한 주문인가” 같은 의미 검증까지 대신하지는 않는다.
넷째, 결과 포맷이다. 도구 결과는 모델이 다음 판단을 하는 입력이 된다. 너무 많은 내부 필드를 보내면 컨텍스트가 오염되고, 너무 적게 보내면 모델이 추측한다. 좋은 결과는 안정적인 id, 핵심 상태, 사람이 볼 메시지, 다음 가능한 액션 정도만 담는다.
다섯째, 상태 보존이다. 여러 턴의 도구 호출에서는 모델이 앞선 호출과 결과를 기억해야 한다. Gemini 문서는 stateless 환경에서 function calling을 다룰 때 thought signature 같은 맥락 보존 규칙을 언급한다. OpenAI 문서도 reasoning 모델의 tool call 흐름에서 reasoning item을 함께 다시 전달해야 한다고 한다. 이름은 다르지만 원리는 같다. “도구 결과만 던져주면 모델이 알아서 이어가겠지”가 아니라, 다음 턴에 필요한 모델 상태를 프로토콜 규칙대로 되돌려줘야 한다.
3. 최신 이슈와 연결
최근 공식 문서들의 공통점은 도구 호출을 점점 더 “상태 있는 실행 루프”로 다룬다는 점이다. Gemini는 Interactions API 문서에서 function calling이 여러 턴으로 반복될 수 있고, 병렬 function calling과 compositional function calling을 지원한다고 설명한다. generateContent 문서는 이전 API 문맥에서도 호출 id 매핑과 function response 규칙을 강조한다.
Anthropic은 client tools와 server tools를 분리한다. client tool은 애플리케이션이 실행하고 tool_result를 돌려줘야 하지만, server tool은 Anthropic 인프라에서 실행되어 결과가 같은 응답 안에 들어올 수 있다. 이 구분은 제품 설계에서 중요하다. 같은 “도구”라도 누가 실행하고, 누가 비용을 내고, 누가 실패를 재시도하고, 누가 로그를 남기는지가 달라진다.
OpenAI 문서는 function tool, built-in tool, remote MCP, tool search 같은 여러 도구 형태를 한 흐름으로 묶는다. 특히 tool call output이 특정 call을 참조해야 하고, reasoning 모델의 중간 item을 함께 전달해야 한다는 점은 AI 앱이 단순 request-response API가 아니라 실행 trace를 관리해야 함을 보여준다.
정리하면, 최신 tool use 문서의 방향은 “모델이 더 똑똑하게 함수를 고른다”만이 아니다. 개발자에게 요구되는 능력은 도구 호출을 안전한 transaction처럼 기록하고, 매핑하고, 검증하고, 복구하는 능력으로 이동하고 있다.
4. 개발자 관점 해석
도구 호출을 JSON 출력으로만 보면 첫 구현은 빠르다. 모델이 get_weather({location: "Seoul"})처럼 반환하면 그대로 실행하고, 결과를 붙여 다시 모델에 보낸다. 데모에서는 잘 된다. 하지만 운영에서는 곧 네 가지 문제가 나온다.
첫 번째는 결과 뒤섞임이다. 사용자가 “서울과 도쿄 날씨를 비교해줘”라고 했고 모델이 두 개의 병렬 호출을 만들었다고 하자. 실행 순서는 네트워크 지연에 따라 바뀔 수 있다. call id 없이 배열 순서만 믿으면 서울 결과를 도쿄에 붙일 수 있다.
두 번째는 중복 실행이다. 네트워크 오류로 같은 tool output 전송을 재시도하거나, 모델 응답 파싱 후 워커가 재시작될 수 있다. 결제, 이메일 발송, 티켓 생성처럼 side effect가 있는 작업은 idempotency key가 필요하다. 모델의 call id를 그대로 쓰거나, conversation_id + call_id + tool_name으로 별도 실행 id를 만들어야 한다.
세 번째는 권한 우회다. 스키마가 맞는 호출이라도 실행하면 안 되는 경우가 많다. 예를 들어 refund_order의 인자가 {order_id: "123"}로 정확해도, 그 주문이 해당 사용자 소유인지, 이미 환불됐는지, 금액이 승인 한도 안인지 확인해야 한다. 도구 호출 schema는 문법 계약이고, 권한·정책 검사는 애플리케이션 계약이다.
네 번째는 관측성 부족이다. “모델이 이상한 답을 했다”는 로그만 있으면 원인을 찾기 어렵다. 실제로는 도구 선택 오류, 인자 생성 오류, 실행 실패, 결과 요약 오류, 후속 reasoning 오류가 다 다르다. 각 tool call마다 call_id, tool_name, args_hash, permission_result, execution_status, latency_ms, result_summary, retry_count를 남겨야 회귀 테스트와 장애 분석이 가능하다.
5. 내 프로젝트에 적용할 체크포인트
- 도구 호출을 저장할
tool_invocations테이블이나 로그 이벤트가 있는가? - 모든 tool call에 내부
call_id또는 provider가 준 id를 보존하는가? - 병렬 호출 결과를 배열 순서가 아니라 id로 매핑하는가?
- side effect 도구에는 idempotency key를 붙이는가?
- 모델 출력 schema 검증과 비즈니스 권한 검증을 분리했는가?
- 도구 실행 실패를 모델에게 그대로 던지지 않고, 짧고 안전한 error object로 변환하는가?
- 도구 결과에는 모델이 다음 판단에 필요한 핵심 필드만 포함하는가?
- server-side 도구와 client-side 도구의 로그·비용·재시도 책임을 구분했는가?
- reasoning/thought state를 다시 전달해야 하는 provider 규칙을 SDK에만 맡기지 않고 테스트로 확인했는가?
- “도구를 호출하지 않아야 하는 경우”도 eval 케이스에 포함했는가?
6. 오늘 10분 액션
내 AI 기능에서 가장 위험한 도구 하나를 고른다. 예를 들어 send_email, create_invoice, refund_order, run_sql, deploy_service 같은 도구다. 그리고 아래 표를 10분 안에 채워보자.
| 항목 | 적을 내용 |
|---|---|
| tool_name | 실제 함수 이름 |
| call_id 규칙 | provider id 사용 여부, 없으면 생성 방식 |
| 실행 전 검증 | schema 검증 외 권한·상태·한도 검사 |
| idempotency | 같은 호출이 두 번 들어오면 어떻게 막을지 |
| result 요약 | 모델에게 돌려줄 최소 필드 |
| 실패 응답 | timeout, permission denied, validation error를 어떻게 표현할지 |
| 로그 | call_id, args_hash, status, latency, retry_count 저장 위치 |
마지막으로 테스트 케이스 3개만 만든다. 정상 호출 1개, 권한 때문에 막혀야 하는 호출 1개, 같은 call id가 두 번 들어오는 중복 호출 1개. 이 세 개가 통과하면 “JSON이 나온다” 수준에서 “프로토콜로 운영된다” 수준으로 한 단계 올라간다.
7. 더 볼 자료
- Function calling with the Gemini API - Interactions API
- Function calling with the Gemini API - generateContent API
- Tool use with Claude - Claude Platform Docs
- Function calling - OpenAI API
중복 회피 메모
최근 ai_dev 글은 서브에이전트의 컨텍스트 격리, 백그라운드 에이전트의 작업 큐, 장기 실행 에이전트의 memory/checkpoint, 실시간 세션 상태 머신, tool surface가 커질 때의 tool search, 에이전트 권한 런타임 계약을 다뤘다. 이번 글은 도구 권한 자체나 에이전트 분해가 아니라, 모델이 만든 tool/function call과 애플리케이션 실행 결과를 call id, idempotency, 결과 매핑, 상태 보존, 관측성으로 다루는 “왕복 프로토콜” 설계에 집중한다.
핵심 출처
로그인하면 이 글을 북마크하고, 나만 보는 한 줄 메모를 남길 수 있어요.
댓글 0
최신순 ▾혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)