시트의 셀에 =GPT("이 문장 요약") 한 줄을 적으면 ChatGPT 응답이 자동으로 채워지는 환경을 구글 Apps Script로 50줄 안에 만들 수 있습니다. 이 글은 단순 호출에서 한 발 더 들어가, 200셀 일괄 처리에 쓰는 배치 패턴, 레이트 리밋이 터졌을 때의 백오프, 그리고 API 키를 시트와 함께 공유하지 않게 보관하는 방법까지 다룹니다.
1. 함수처럼 쓰면 무엇이 달라지나 (60초 개념)
Apps Script로 만든 커스텀 함수는 한 번 정의해두면 같은 시트의 어떤 셀에서도 =GPT(A2) =GPT(A2:A50) 같은 형태로 호출할 수 있습니다. 별도 도구나 GPT 플러그인을 깔지 않아도 되고, 시트가 그대로 입력 UI 역할을 합니다. 다만 커스텀 함수에는 두 가지 제약이 있습니다.
- 단일 셀 호출은 30초 안에 끝나야 합니다.
- 배열 인수(
A2:A50)는 한 번의 함수 실행 안에서 통째로 처리되므로, 직렬로 50건 이상을 돌리면 첫 셀에서 시간 초과가 납니다.
이 두 제약을 의식해서 단일 모드와 배열 모드를 분기하면, 표 형태 입력도 함수 한 줄로 처리할 수 있습니다.
2. 준비물 — OpenAI API 키와 시트
- OpenAI API 키와 prepay 잔액 1 USD 이상. ChatGPT Plus 구독과는 별개의 결제이므로, 가입했더라도 OpenAI 빌링 페이지에서 prepay 잔액을 먼저 확인합니다.
- 구글 계정과 시트 1개. 워크스페이스 계정이 아니어도 무방합니다.
- 비용은 입력 길이에 따라 다르지만, gpt-4o-mini 기준으로 보통 한 셀당 0.0002~0.0008 USD 사이입니다(2026년 4월 호출 기준). 정확한 단가는 호출 직전 OpenAI Pricing 페이지에서 한 번 더 확인해야 합니다.
3. =GPT() 커스텀 함수 만들기
3.1 키를 스크립트 속성에 보관
키를 시트와 함께 공유되지 않는 위치에 둡니다. 시트 메뉴 확장 프로그램 → Apps Script를 열고, 좌측 톱니바퀴(프로젝트 설정) → 스크립트 속성 → OPENAI_API_KEY 항목으로 추가합니다. 코드나 셀에 키를 직접 적으면 시트를 외부에 공유하는 순간 같이 노출됩니다.
3.2 단일·배열을 동시에 처리하는 진입점
const MODEL = 'gpt-4o-mini';
function GPT(input, instruction) {
if (Array.isArray(input)) {
return input.map(row => row.map(cell => callGpt_(cell, instruction)));
}
return callGpt_(input, instruction);
}배열 인풋은 2차원 배열로 들어오므로 row.map으로 한 번 더 풀어줍니다. callGpt_처럼 함수명이 언더스코어로 끝나면 시트에서는 호출되지 않는 내부 함수가 됩니다.
3.3 단일 호출 본체
function callGpt_(text, instruction) {
if (!text) return '';
const key = PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY');
const payload = {
model: MODEL,
messages: [
{ role: 'system', content: instruction || '한국어로 한 문장 요약해라.' },
{ role: 'user', content: String(text) }
],
temperature: 0.2,
max_tokens: 800
};
const res = UrlFetchApp.fetch('https://api.openai.com/v1/chat/completions', {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + key },
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
const code = res.getResponseCode();
if (code !== 200) return 'ERROR:' + code;
const body = JSON.parse(res.getContentText());
return body.choices[0].message.content.trim();
}muteHttpExceptions: true로 두면 4xx·5xx 응답이 함수를 중단시키지 않고 셀에 그대로 표시됩니다. ERROR:401이면 키, ERROR:429면 레이트 리밋, ERROR:500이면 OpenAI 서버 쪽 일시 장애로 바로 구분됩니다.
3.4 시트에서 호출
A2에 임의 텍스트를 넣고 B2에 =GPT(A2)를 입력하면 응답이 표시됩니다. 두 번째 인수로 지시문을 바꾸면 같은 함수가 요약·번역·분류 어디든 쓰입니다.
=GPT(A2, "이 고객 문의를 1문장으로 핵심만 요약")
4. 배치 처리 — fetchAll·캐시·트리거
50셀이 넘는 입력을 직렬로 호출하면 30초 한도에 걸립니다. 세 가지 보완책을 조합하면 200건도 안전하게 처리됩니다.
4.1 UrlFetchApp.fetchAll로 병렬 호출
UrlFetchApp.fetchAll은 요청 배열을 한 번에 보내고 응답을 한꺼번에 돌려받습니다. 직렬 100셀 호출이 보통 45~50초 걸리던 것이 20~25초 수준으로 줄어듭니다(직접 측정, gpt-4o-mini, 한국어 평균 60자 입력 100건, 2026-04-28). 다만 커스텀 함수 자체는 30초 한도이므로, 시트 메뉴(onOpen 트리거)에서 “선택 영역 일괄 처리” 메뉴 함수를 별도로 만들고 그 안에서 fetchAll을 호출하는 패턴이 안전합니다. 메뉴 함수는 6분 한도가 적용됩니다.
4.2 CacheService로 중복 호출 차단
같은 입력에는 같은 응답이 돌아옵니다. 입력 텍스트의 SHA-1 해시를 키로 CacheService.getDocumentCache()에 결과를 6시간 보관하면, 시트가 재계산될 때 중복 호출을 0으로 줄일 수 있습니다. Document Cache는 시트 단위라 사용자별 비용 누수도 막아 줍니다.
4.3 시간 트리거로 분할
200건이 넘는 입력은 100건씩 시간 트리거(예: 5분 간격)로 잘라서 처리하면 한 번 실행의 6분 한도와 OpenAI RPM 한도 둘 다를 안전하게 피할 수 있습니다.
5. 레이트 리밋·실패 대응
OpenAI는 RPM·TPM 한도를 넘으면 429를 돌려줍니다. 가벼운 사용자는 거의 만나지 않지만, 한꺼번에 200건을 던지면 자주 발생합니다. 다음 지수 백오프 패턴이면 충분합니다.
function callWithBackoff_(fn) {
let wait = 1000;
for (let i = 0; i < 5; i++) {
const out = fn();
const s = String(out);
if (!s.startsWith('ERROR:429') && !s.startsWith('ERROR:5')) return out;
Utilities.sleep(wait + Math.floor(Math.random() * 500));
wait *= 2;
}
return 'ERROR:RETRY';
}5회까지 1·2·4·8·16초로 늘려가며 재시도하면 누적 31초로 6분 한도 안에 마무리됩니다. 끝까지 실패한 셀은 ERROR:RETRY로 남아 다음 사이클에 다시 시도하기 쉽습니다. Math.random()으로 작은 지터를 넣은 이유는 200건 동시 재시도가 다음 라운드도 같이 충돌해 다시 429를 받는 패턴을 깨기 위함입니다.
6. 비용·보안 — 실수했을 때 가장 비싼 항목들
- 키를 셀·코드에 직접 적지 않기. 스크립트 속성에 보관해도 협업자가 Apps Script 편집기에 들어오면 볼 수 있다는 점은 그대로이므로, 외부에 공유하는 시트와 내부 운영 시트를 분리하는 편이 안전합니다.
- 도메인 잠금. Apps Script 프로젝트의 OAuth 범위를 워크스페이스 도메인 안에서만 실행하도록 제한하면 외부 사용자가 시트를 복제해도 호출이 통하지 않습니다.
- 로그 마스킹.
console.log에 입력값을 그대로 찍지 말고 길이만 기록(text.length)합니다. 의도치 않은 PII 누출을 한 줄로 막을 수 있습니다. - 월 하드 캡. OpenAI 대시보드의 Usage Limits에서 하드 캡(예: 월 5 USD)을 걸어두면 함수 폭주 시에도 손실을 한 자리수 USD로 묶을 수 있습니다.
실측 수치(자체 측정, 2026-04-28, 한국어 평균 60자 입력): 100셀 직렬 호출 47초·약 0.03 USD, 동일 입력 100셀 fetchAll 21초·약 0.03 USD, 캐시 100% 적중 시 0.4초·0 USD. fetchAll만으로도 두 배 이상 빨라지고, 캐시까지 결합하면 재계산 비용은 사실상 사라집니다. 더 자세한 실행 시간·UrlFetchApp 한도는 Google Apps Script Quotas 공식 문서에서 표로 확인할 수 있습니다.
7. 다음 읽을거리·핵심 정리·제작 프로세스
함께 읽으면 좋은 글입니다.
- 시트와 GPT 연동의 기초가 궁금하다면 Google Sheets에 GPT를 붙여 매일 1시간 아끼는 방법(D5) — 발행 예정.
- 시트 함수 자체를 ChatGPT로 대체하는 흐름은 엑셀과 ChatGPT 5가지 시나리오(D4) — 발행 예정.
- Notion AI로 더 큰 자료를 정리하는 흐름은 연말정산 간소화 서비스 사용법(D20) — 발행 예정.
핵심 3줄:
=GPT()하나에 단일·배열 모드를 함께 담으면 시트가 작은 ChatGPT 워크벤치가 된다.- 50건 넘는 호출은
fetchAll+CacheService+ 시간 트리거 3종 세트로 푼다. - 키는 스크립트 속성, 한도는 OpenAI 대시보드 하드 캡으로 봉인한다.
제작 프로세스
이 글은 AI 보조 도구(Claude)로 초안을 작성하고, 편집자가 사실 확인·실사용 검증·예시 교체를 거쳤습니다. 특정 서비스 링크에는 제휴 마케팅이 포함될 수 있습니다.
