AI 자동화를 도입할 때 가장 많이 받는 질문은 성능이 아닙니다. "그거 한 달에 얼마 나와요?" 입니다. 그리고 대부분의 팀은 첫 청구서를 받기 전까지 대답하지 못합니다.
이건 기술 문제가 아니라 기록 문제입니다. 토큰 사용량은 이미 AI 응답 안에 들어 있습니다. 모든 OpenAI 응답에는 usage 필드가 함께 옵니다. 빠진 건 그걸 워크플로 이름과 함께 쌓아두고, 단가를 곱해, 예산과 비교하는 습관뿐입니다. 이 케이스는 그 세 가지를 자동화합니다.
왜 지나고 나서야 알게 되는가
- 응답에 있는 `usage`를 아무도 저장하지 않습니다. 대부분의 워크플로는
choices[0].message.content만 꺼내 쓰고 나머지는 버립니다. 비용 정보가 매번 눈앞을 지나가는데 저장이 안 됩니다. - 단가가 모델마다 다르고, 계속 바뀝니다. 입력 토큰과 출력 토큰의 단가도 보통 다릅니다(출력이 몇 배 비쌉니다). 그래서 코드 안에 숫자를 박아두면 몇 달 뒤 조용히 틀린 값을 보고하게 됩니다.
- 공급사 대시보드는 워크플로별로 안 나눠줍니다. 이번 달 총액은 보여주지만 '어느 자동화가 범인인지'는 안 알려줍니다. 그래서 비용이 튀어도 무엇을 줄여야 할지 결정할 수가 없습니다.
어떻게 동작하나
- AI를 호출하는 워크플로마다 AI 노드 바로 뒤에 Postgres 노드를 하나 붙여, 응답의
usage에서 입력·출력 토큰을 꺼내ai_usage테이블에 한 줄 기록합니다. - 단가는
ai_model_prices테이블에 모델별로 넣어둡니다. 백만 토큰당 입력 단가와 출력 단가, 그리고 확인한 날짜를 함께 저장합니다. Schedule Trigger가 매일 오전 9시에 감시 워크플로를 깨웁니다.Postgres노드가 이번 달 1일부터 지금까지의 사용량을 워크플로별로 집계하고, 단가표와 조인해 실제 비용을 계산합니다. 단가표에 없는 모델은 0원으로 세지 않고 '단가 미등록'으로 따로 표시합니다.Code노드가 지금까지의 지출로 월말 예상 비용을 직선 추정합니다(현재 지출 ÷ 경과일 × 그달 총일수). 예산 대비 비율을 계산해 정상 / 임박(80%) / 초과(100%)를 판정합니다.- 정상이고 단가 미등록도 없으면 빈 배열을 반환해 조용히 끝냅니다. 매일 "괜찮습니다" 알림이 오면 아무도 안 읽게 되기 때문입니다. 임박·초과이거나 단가 미등록이 있을 때만 Slack에 보고합니다.
필요한 것
- n8n — 자체 호스팅 또는 Cloud. 기본 노드 네 개(
Schedule Trigger,Postgres,Code,Slack)만 씁니다. - Postgres 데이터베이스 하나 — 테이블 두 개를 추가합니다. 호출 한 건당 한 줄이라 하루 수천 건이어도 부담이 없습니다.
- Postgres 자격증명 — n8n → Credentials →
Postgres. n8n이 Docker 안이고 DB가 호스트 머신이면 호스트에localhost가 아니라host.docker.internal을 넣어야 합니다. - Slack Bot User OAuth Token —
chat:write,channels:read스코프. n8n → Credentials →Slack API에 등록. - 이미 AI를 호출하고 있는 워크플로 한 개 이상 — 이 케이스는 새 AI 워크플로를 만드는 게 아니라, 이미 돌고 있는 것에 계량기를 다는 작업입니다.
- 사용 중인 모델의 현재 공시 단가 — 공급사 요금 페이지에서 직접 확인하세요. 백만 토큰당 입력 단가와 출력 단가 두 개가 필요합니다. 이 문서에는 특정 모델의 단가를 적지 않습니다. 적어두는 순간 틀린 값이 되기 때문입니다.
1단계 — 테이블 두 개 만들기
-- 호출 한 건당 한 줄. 여기에 쌓이는 것이 곧 원장입니다.
CREATE TABLE IF NOT EXISTS ai_usage (
id bigserial PRIMARY KEY,
workflow_name text NOT NULL, -- 어느 자동화가 쓴 비용인지
model text NOT NULL, -- 호출한 모델 이름 (단가표와 조인되는 키)
called_at timestamptz NOT NULL DEFAULT now(),
input_tokens integer NOT NULL DEFAULT 0,
output_tokens integer NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS ai_usage_time_idx ON ai_usage (called_at DESC);
-- 단가표. 코드가 아니라 여기에 둡니다.
CREATE TABLE IF NOT EXISTS ai_model_prices (
model text PRIMARY KEY,
input_per_1m numeric NOT NULL, -- 백만 입력 토큰당 USD
output_per_1m numeric NOT NULL, -- 백만 출력 토큰당 USD
updated_at date NOT NULL -- 단가를 확인한 날짜
);
-- 사용 중인 모델을 공급사 요금 페이지에서 확인해 넣으세요.
-- 아래는 형식 예시이며 실제 단가가 아닙니다.
-- INSERT INTO ai_model_prices VALUES ('사용중인-모델명', 0.00, 0.00, CURRENT_DATE);updated_at을 반드시 채우세요. 반년 뒤 리포트 숫자가 이상할 때 가장 먼저 의심해야 할 것이 단가표의 신선도입니다. 확인 날짜가 없으면 그 판단을 할 수 없습니다.2단계 — AI 노드 뒤에 계량기 달기
AI를 호출하는 워크플로를 열어 AI 노드 바로 뒤에 Postgres 노드를 추가합니다. Operation은 Execute Query로 두고 아래를 넣습니다. cs-triage 자리에 해당 워크플로 이름을 넣으세요.
INSERT INTO ai_usage
(workflow_name, model, input_tokens, output_tokens)
VALUES (
'cs-triage',
'{{ $json.model }}',
{{ $json.usage.prompt_tokens }},
{{ $json.usage.completion_tokens }}
);이 값들은 어디서 오는가 — OpenAI Chat Completions API(https://api.openai.com/v1/chat/completions)의 응답에는 본문(choices[0].message.content)과 나란히 `model`과 `usage`가 들어 있습니다. usage.prompt_tokens가 입력, usage.completion_tokens가 출력 토큰입니다. 즉 추가 API 호출이 전혀 필요 없습니다. 원래 버리고 있던 데이터를 줍는 것뿐입니다. 다른 공급사를 쓴다면 응답에서 사용량 필드 이름만 바꿔 넣으면 됩니다.
3단계 — 이번 달 집계 쿼리
감시 워크플로의 Month-to-Date Cost by Workflow 노드에 들어가는 쿼리입니다. 다운로드한 JSON에 이미 들어 있습니다. 워크플로 하나당 한 행이 나오고, 비용이 큰 순서로 정렬됩니다.
WITH priced AS (
SELECT u.workflow_name,
u.input_tokens,
u.output_tokens,
-- 단가표에 없는 모델은 0이 아니라 NULL. 0으로 세면 비용이 숨습니다.
CASE WHEN p.model IS NULL THEN NULL
ELSE (u.input_tokens::numeric / 1000000) * p.input_per_1m
+ (u.output_tokens::numeric / 1000000) * p.output_per_1m
END AS cost_usd
FROM ai_usage u
LEFT JOIN ai_model_prices p ON p.model = u.model
WHERE u.called_at >= date_trunc('month', now()) -- 이번 달 1일부터
)
SELECT workflow_name,
count(*) AS calls,
count(*) FILTER (WHERE cost_usd IS NULL) AS unpriced_calls,
COALESCE(sum(input_tokens), 0) AS input_tokens,
COALESCE(sum(output_tokens), 0) AS output_tokens,
round(COALESCE(sum(cost_usd), 0), 4) AS cost_usd
FROM priced
GROUP BY workflow_name
ORDER BY cost_usd DESC;LEFT JOIN + CASE WHEN p.model IS NULL이 중요합니다. 단가표에 없는 모델을 그냥 JOIN으로 빼버리면 그 호출은 리포트에서 통째로 사라지고, 비용은 실제보다 적게 보고됩니다. 모르는 건 0원이 아니라 '모른다'로 표시해야 합니다.4단계 — 예산을 넘으면 아예 못 쓰게 막기
여기까지는 보고입니다. 실제로 막으려면 AI 노드 *앞에* 관문을 하나 둡니다. 비용이 큰 워크플로에만 붙이면 됩니다.
- AI 노드 앞에
Postgres노드를 추가하고 Operation을Execute Query로 둔 뒤 아래 쿼리를 넣습니다 — 이번 달 누적 비용 한 줄이 나옵니다. - 그 뒤에
IF노드를 놓고 조건을{{ $json.mtd_cost_usd }}Number / Smaller than 예산값(예:100)으로 설정합니다. IF의 true 출력을 원래의 AI 노드로 연결합니다. 예산 안이면 평소대로 동작합니다.IF의 false 출력은Stop and Error노드로 연결하고 메시지를월 AI 예산 초과 — 실행 중단으로 둡니다. 이러면 실행이 에러로 기록되므로Error Trigger나 앞서 만든 감시 워크플로가 바로 잡아냅니다.
SELECT round(COALESCE(sum(
(u.input_tokens::numeric / 1000000) * p.input_per_1m +
(u.output_tokens::numeric / 1000000) * p.output_per_1m
), 0), 4) AS mtd_cost_usd
FROM ai_usage u
JOIN ai_model_prices p ON p.model = u.model
WHERE u.called_at >= date_trunc('month', now());unpriced_calls로 분리되는지, 지난달 데이터가 이번 달 집계에 섞이지 않는지까지 포함합니다. Code 노드는 그 쿼리의 실제 출력을 입력으로 Node.js에서 실행해 정상 / 임박(86%) / 초과(101%) 세 구간과 '이상 없으면 빈 배열 반환' 경로를 모두 확인했습니다. 노드 타입·버전은 기존에 배포 중인 워크플로우와 동일한 조합입니다(scheduleTrigger 1.2 / postgres 2.5 / code 2 / slack 2.3). 실제 Slack 워크스페이스 게시와 실제 OpenAI 청구서 대조는 이 문서 작성 시점에 확인하지 않았습니다. 마지막 검증: 2026-08-15.설정 순서 (30분)
- 테이블 생성 — 1단계 SQL을 DB에서 실행합니다.
CREATE TABLE이 두 번,CREATE INDEX가 한 번 출력되면 성공입니다. - 단가 입력 — 사용 중인 모델의 현재 공시 단가를 공급사 요금 페이지에서 확인해
ai_model_prices에 INSERT 합니다. 입력·출력 단가가 다르므로 각각 확인하세요.updated_at에는 오늘 날짜를 넣습니다. - Postgres 자격증명 등록 — n8n → Credentials → Add credential →
Postgres→ 접속 정보 입력 → Test로 초록불 확인. - Slack 앱 만들기 — api.slack.com/apps → Create New App → From scratch → OAuth & Permissions → Bot Token Scopes에
chat:write,channels:read추가 → Install to Workspace →xoxb-토큰 복사. - Slack 자격증명 등록 — n8n → Credentials →
Slack API→ 토큰 붙여넣기 → Test. - 봇 초대 — 알림 받을 Slack 채널에서
/invite @봇이름. 빠뜨리면not_in_channel에러가 납니다. - 계량기 달기 — AI를 호출하는 워크플로를 하나씩 열어 AI 노드 뒤에 2단계의 Postgres 노드를 추가합니다. 워크플로 이름을 각각 맞게 넣으세요. 이 단계를 해야 데이터가 쌓이기 시작합니다.
- 쌓이는지 확인 — 해당 워크플로를 한 번 실행한 뒤
SELECT * FROM ai_usage ORDER BY called_at DESC LIMIT 5;로 행이 들어왔는지 봅니다. 토큰 수가 0이면 응답 필드 경로가 다른 것이니 AI 노드의 출력 JSON을 열어usage위치를 확인하세요. - 감시 워크플로 import — 위 다운로드 버튼으로 JSON 저장 → n8n → Workflows →
...→ Import from File. - 예산 설정 —
Budget Check + Format노드를 열어 맨 위MONTHLY_BUDGET_USD값을 실제 월 예산으로 바꿉니다. - 채널 이름 교체 + 자격증명 연결 —
Post to #ai-cost노드에서REPLACE_WITH_YOUR_CHANNEL_NAME_OR_ID를 실제 채널명으로 바꾸고, 두 노드에 자격증명을 연결합니다. - 테스트 — Execute Workflow를 누릅니다. 아직 사용량이 적어 정상 판정이면 아무 일도 안 일어나는 게 맞습니다. 메시지를 보고 싶으면
MONTHLY_BUDGET_USD를 아주 작은 값(예:0.01)으로 잠시 바꿔 실행해 보고, 확인 후 되돌리세요. - 활성화 — Activate 토글을 켭니다. 매일 오전 9시에 점검합니다.
임계값 조정
- `MONTHLY_BUDGET_USD` (기본 100) — 월 예산. 이 케이스에서 실제로 바꾸게 되는 거의 유일한 값입니다.
- `WARN_RATIO` (기본 0.8) — 월말 예상이 예산의 몇 할을 넘으면 경고할지. 0.8이면 80%에서 알립니다. 대응할 시간이 필요하다면 0.7로 낮추세요.
- `ALWAYS_REPORT` (기본 false) —
true로 두면 정상일 때도 매일 리포트가 옵니다. 도입 첫 달에는 감을 잡기 위해 켜두고, 익숙해지면 끄는 것을 권합니다. - `USD_TO_KRW` (기본 1380) — 원화 병기용 환율입니다. 참고용 괄호 표시에만 쓰이고 판정에는 영향을 주지 않습니다. 정확한 청구 금액은 공급사 청구서를 보세요.
예외 상황이 있으면 어떻게 되나
- 단가표에 없는 모델을 호출한 경우 — 비용 0원으로 숨기지 않고 '단가 미등록 N건'으로 Slack에 경고합니다. 정상 상태여도 이 경우엔 알림이 발송됩니다. 모델을 바꾸고 단가표 갱신을 잊는 일이 실제로 자주 일어나기 때문입니다.
- 월 초라 표본이 적을 때 — 직선 추정이라 1~2일차에는 예상치가 크게 흔들립니다. 첫 며칠의 초과 경고는 참고만 하시고, 판단은 5일 이후 수치로 하세요.
- 재시도로 인한 중복 과금 — n8n이 실패한 AI 호출을 재시도하면 토큰은 두 번 소모되고, 기록도 두 줄 남습니다. 이건 버그가 아니라 실제로 두 번 청구되기 때문에 맞는 동작입니다. 재시도가 잦다면 그 자체가 비용 문제입니다.
- 스트리밍 응답 — 스트리밍으로 받으면
usage가 마지막 청크에만 오거나 아예 안 올 수 있습니다. 이 케이스는 비스트리밍(일반 Chat Completions) 호출을 전제로 합니다. 스트리밍을 쓴다면 해당 호출은 별도 처리가 필요합니다. - 임베딩·이미지 등 단가 체계가 다른 호출 — 토큰 기반이 아닌 호출은 이 테이블 구조로 정확히 계산되지 않습니다. 별도 모델명으로 기록해 두고 단가표에서 제외한 뒤, '단가 미등록'으로 눈에 띄게 두는 편이 낫습니다.
- 환율 변동 — 원화 표기는 고정 환율이라 실제 청구액과 차이가 납니다. 예산 판정은 USD 기준으로만 이뤄지므로 판단에는 영향이 없습니다.
최근에서야 RAG라는 것을 알게 되었습니다. n8n을 이용해서 만들어보려고 합니다. 그런데 어떤 AI를 결제해야 할 지 모르겠습니다.— 국내 개발자 커뮤니티