Programmatic Tool Calling 운영법: 도구 호출을 코드로 줄일 때 생기는 이득과 위험
요약: Programmatic Tool Calling은 모델이 Responses API 안에서 JavaScript 프로그램을 작성해 여러 도구 호출을 조율하게 하는 기능이다. 반복 조회, 필터링, 집계에는 강하지만 승인, 외부 쓰기, 최종 근거 검증에는 신중해야 한다.
어떤 문제를 풀기 위한 기능인가
도구를 쓰는 AI 에이전트는 생각보다 낭비가 많다. 예를 들어 재고 확인, 주문량 조회, 배송 가능 지역 확인을 순서대로 해야 하는 작업이 있다고 하자. 일반 tool calling에서는 모델이 도구 하나를 호출하고, 결과를 읽고, 다음 도구를 호출하고, 다시 결과를 읽는다. 각 단계마다 모델 왕복이 생긴다. 중간 결과가 크면 토큰도 많이 쓴다.
Programmatic Tool Calling은 이 중 일부를 코드 실행으로 바꾼다. OpenAI 문서 기준으로 모델은 격리된 V8 런타임에서 JavaScript 프로그램을 만들고, 허용된 도구를 병렬 호출하거나 반복문과 조건문으로 조율할 수 있다. 프로그램은 중간 데이터를 런타임 안에서 줄인 뒤, 최종적으로 작은 구조화 결과만 모델에게 돌려준다.
핵심은 “모델이 모든 중간 결과를 읽지 않아도 된다”는 점이다. 100개의 검색 결과에서 중복을 제거하고 상위 5개만 뽑는 일은 언어 판단보다 코드 처리에 가깝다. 이런 구간을 프로그램으로 넘기면 토큰과 지연 시간을 줄일 수 있다.
언제 쓰면 좋은가
적합한 작업은 예측 가능한 제어 흐름을 가진다. 독립적인 조회를 동시에 실행하고, 결과를 합치고, 필터링하고, 숫자를 계산하고, 정해진 스키마로 반환하는 작업이다.
예를 들어 다음 작업은 후보가 된다.
- 여러 SKU의 재고와 수요를 동시에 조회한 뒤 부족 수량 계산
- 여러 문서 검색 결과에서 중복 URL 제거
- 로그 조각 50개에서 같은 trace_id별로 묶기
- 후보 모델 3개의 가격표를 읽고 예상 비용 계산
- 내부 도구 여러 개에서 사용자 상태를 읽고 권한 요약 생성
반대로 매 단계마다 의미 판단이 필요한 작업은 직접 tool calling이 낫다. 검색 결과 하나를 보고 다음 검색어를 바꿔야 하거나, 법적 근거를 검토해야 하거나, 사용자의 승인 없이 실행하면 안 되는 작업은 프로그램 안에 숨기면 안 된다.
OpenAI 문서도 쓰기 작업이나 승인 민감 작업은 기본적으로 direct tool calling을 권장한다. 이유는 명확하다. 프로그램 안에서 여러 도구가 자동 실행되면 사용자가 무엇을 승인했는지 흐려진다. 특히 결제, 삭제, 이메일 발송, 권한 변경 같은 작업은 코드 최적화 대상이 아니라 승인 경계 대상이다.
설계할 때 가장 중요한 것은 allowed_callers
Programmatic Tool Calling은 도구별로 allowed_callers를 지정한다. direct만 허용할지, programmatic만 허용할지, 둘 다 허용할지 정할 수 있다. 이 설정이 운영 안전성의 핵심이다.
권장 패턴은 읽기 도구와 쓰기 도구를 분리하는 것이다. 조회, 검색, 집계, 검증 도구는 programmatic 호출을 허용할 수 있다. 하지만 사용자에게 영향을 주는 쓰기 도구는 direct로 남긴다. 예를 들어 get_inventory는 programmatic 허용, reserve_inventory는 direct만 허용하는 식이다.
또 하나는 output_schema를 엄격히 정의하는 것이다. 함수 결과가 예측 가능한 JSON이면 프로그램이 안전하게 필드를 읽고 계산할 수 있다. 반대로 도구가 자유 텍스트를 반환하면 프로그램은 파싱 실패, 누락, 잘못된 숫자 계산에 취약해진다. 프로그램에게 일을 맡기려면 도구 출력부터 기계가 읽기 쉬워야 한다.
실패 처리를 프롬프트에 맡기지 말 것
Programmatic Tool Calling을 붙일 때 흔한 실수는 “효율적으로 처리해” 같은 지시만 넣는 것이다. 그러면 모델은 어느 단계에서 프로그램을 쓰고, 언제 direct 호출로 돌아와야 하는지 애매해진다.
대신 경계를 명시해야 한다. 예를 들면 다음처럼 정한다.
- 이 단계에서는 get_inventory와 get_demand만 programmatic으로 호출한다.
- 두 호출은 병렬로 실행한다.
- 결과 필드 sku, available_units, requested_units가 없으면 실패로 반환한다.
- transient error는 최대 1회만 재시도한다.
- 재고 변경은 절대 프로그램에서 하지 않는다.
- 최종 사용자에게 보여줄 설명은 direct 단계에서 작성한다.
이 정도로 적어야 운영 중 이상 동작을 추적할 수 있다. 프로그램 호출은 내부에서 여러 동작을 압축하기 때문에 실패도 압축된다. 재시도 제한, 종료 조건, 반환 스키마가 없으면 한 번의 모델 호출이 예측하기 어려운 실행 덩어리가 된다.
관측성은 프로그램 단위가 아니라 도구 단위로 남겨야 한다
비용과 장애를 추적하려면 프로그램이 호출한 개별 도구 로그가 필요하다. “programmatic call 1회 성공”만 남기면 부족하다. 어떤 도구를 몇 번 호출했는지, 병렬 호출 중 어느 것이 실패했는지, 재시도했는지, 결과 크기가 얼마나 줄었는지 봐야 한다.
추천 지표는 다음과 같다.
- 프로그램 1회당 도구 호출 수
- 도구별 성공률과 p95 latency
- 프로그램 내부 재시도 횟수
- 입력 결과 크기 대비 최종 반환 크기
- schema validation 실패율
- direct 호출 대비 토큰 절감량
- 승인 필요 도구가 programmatic으로 노출되지 않았는지 검사 결과
특히 마지막 항목은 배포 전 자동 테스트로 막아야 한다. 새 도구를 추가하면서 allowed_callers를 잘못 열면 사고가 난다. 보안 리뷰에서는 프롬프트보다 도구 매니페스트를 먼저 봐야 한다.
실행 체크리스트
- 도구 목록을 읽기, 쓰기, 승인 필요 작업으로 분류한다.
- programmatic 호출은 읽기와 계산 중심 도구부터 허용한다.
- 모든 programmatic 도구에 parameters와 output_schema를 엄격히 둔다.
- 프로그램이 사용할 도구, 종료 조건, 재시도 횟수, 반환 형식을 명시한다.
- 쓰기 작업은 direct 호출과 사용자 승인 경계를 유지한다.
- 도구별 호출 로그, 재시도, 스키마 실패율을 수집한다.
- programmatic 적용 전후 토큰, 지연 시간, 완료율을 비교한다.
- 새 도구 추가 시 allowed_callers 설정을 CI에서 검사한다.
Programmatic Tool Calling은 에이전트를 더 똑똑하게 만드는 기능이라기보다, 반복적이고 구조적인 도구 사용을 덜 낭비하게 만드는 기능이다. 그래서 성공 기준도 창의성이 아니라 비용, 지연 시간, 실패율이어야 한다.