`backend` 옆에 `front` 가 있을 이유가 없었다. negosium 의 negodata/front 를 그대로 베꼈고 그게 왜 front 인지는 따져보지 않았다 — 근거 없이 들여온 이름이라 바로잡는다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019uYhHQdssRubirPirrdJJC
211 lines
12 KiB
Markdown
211 lines
12 KiB
Markdown
# API 사용 이력 · 비용 기록
|
||
|
||
**집계 시각: 2026-08-27 08:59 KST**
|
||
집계 대상: o2o-web4ai 프로젝트 전체 (backend · solution/frontend · solution/site)
|
||
|
||
---
|
||
|
||
## 0. 먼저 — 섞으면 안 되는 두 가지 비용
|
||
|
||
| | 무엇 | 언제 나가나 | 현재 |
|
||
|---|---|---|---|
|
||
| **A. 제품 원가** | 사이트 **1건 만들 때** 나가는 외부 API 비용 (Perplexity·Kakao·Gemini) | 업소 1곳 처리할 때마다 **반복** | **0원** (아직 한 번도 호출 안 함) |
|
||
| **B. 개발비** | 이 코드를 **짜는 데** 쓴 Claude API 비용 | 개발 기간 동안 **한 번** | $70.07 |
|
||
|
||
**A 의 상한은 $1(1,400원)이다.** B 는 사이트를 만들 때마다 나가는 돈이 아니다 — 코드가 완성되면 끝나는 일회성 지출이다. 이 문서의 2절은 전부 **B** 얘기다.
|
||
|
||
---
|
||
|
||
## 0-1. 한 줄 요약
|
||
|
||
| 구분 | 실제 호출 | 비용 |
|
||
|---|---|---|
|
||
| **외부 유료 API** (Perplexity · Kakao · Gemini · TourAPI) | **0건** | **0원** |
|
||
| **개발 도구 API** (Claude Code / Anthropic Messages API) | 348 요청 | **$70.07** (≈ 98,100원, 1,400원/USD 가정) |
|
||
|
||
지금까지 나간 돈은 **전부 개발 과정의 Claude API 요금**이고, 제품이 쓸 외부 API는 아직 **한 번도 실제로 때리지 않았다.**
|
||
|
||
---
|
||
|
||
## 1. [A] 제품 원가 — 외부 API 계약 현황과 실호출 이력
|
||
|
||
### 1-1. 키 상태
|
||
|
||
| API | 용도 | 키 설정 | 어댑터 | 실호출 |
|
||
|---|---|---|---|---|
|
||
| Perplexity Sonar (`api.perplexity.ai`) | 채널 URL 발견 | ✅ 설정됨 | `backend/services/external/perplexity.py` 구현 완료 | **0건** |
|
||
| Kakao Local (`dapi.kakao.com`) | 동일 업소 검증 · 주변 정보 | ⬜ 비어 있음 | 미구현 (설계만) | **0건** |
|
||
| Gemini (Google AI Studio) | 사진 분류 · 카피 작성 | ✅ 설정됨 | 미구현 | **0건** |
|
||
| TourAPI (`data.go.kr`) | 축제 · 관광지 | ⬜ 비어 있음 | 미구현 | **0건** |
|
||
| Open-Meteo | 날씨 | 키 불필요 | `frontend/.../useWeather.ts` | **0건** (프론트 코드만 작성) |
|
||
|
||
> `.env` 의 키가 비어 있으면 해당 어댑터만 비활성되고 서버는 그대로 뜬다 — 설계상 의도된 동작.
|
||
|
||
### 1-2. 실호출이 0건인 근거
|
||
|
||
1. **코드 레벨** — 전체 세션 트랜스크립트에서 위 호스트로 나간 HTTP 요청이 없다. 등장하는 건 전부 소스 작성·문서 편집 텍스트다.
|
||
2. **테스트 레벨** — `backend/tests/` 안에서 실제 네트워크를 타는 코드가 없다. `test_perplexity.py` · `test_collector.py` 는 전부 목(mock) 기반이고, `httpx.get/post` 직접 호출은 0건.
|
||
3. **설정 레벨** — `DECISIONS.md` 의 결정대로 **`APP_ENV=test` 면 `.env` 를 읽지 않는다.** 실키가 테스트로 새어 외부 API 요금이 나가는 경로 자체를 막아 뒀다.
|
||
4. **DB 레벨** — 어떤 테이블에도 외부 API 응답이 적재된 흔적이 없다 (호출 로그 테이블도 아직 없다 — 3-2 참고).
|
||
|
||
### 1-3. 단가표 (호출 시작 시 적용될 요금)
|
||
|
||
| API | 과금 방식 | 단가 |
|
||
|---|---|---|
|
||
| Perplexity Sonar | **토큰 요금 + 검색 호출 요금이 별도로 붙는다** | `sonar` 기본, `sonar-pro` 는 더 비쌈. 호출당 검색 횟수는 이미 로그로 남기고 있음 (`perplexity.py` 의 `usage.num_search_queries`) |
|
||
| Kakao Local | 무료 쿼터 초과분 종량 | 키워드/카테고리 검색 **2원**, 좌표 변환 **0.5원** (키워드가 4배 비싸다) |
|
||
| Gemini | 토큰 종량 (Google AI Studio) | 모델·티어별 |
|
||
| TourAPI | 공공데이터 무료 (일 트래픽 제한) | 0원 |
|
||
| Open-Meteo | 무료 | 0원 |
|
||
|
||
> ⚠️ **Kakao 무료 쿼터 함정** — 무료 쿼터는 개발자 계정의 "첫 번째 활성 앱" 에만 붙는다. dev/stage/prod 앱을 따로 파면 **하나만 무료**다. 앱을 나누기 전에 확인할 것.
|
||
|
||
---
|
||
|
||
## 2. [B] 개발비 — Claude (Anthropic Messages API)
|
||
|
||
> 사이트 1건당 원가가 **아니다.** 코드를 짜는 데 든 일회성 비용이다.
|
||
|
||
### 2-1. 총계
|
||
|
||
| 항목 | 값 |
|
||
|---|---|
|
||
| 기간 | 2026-08-26 16:23 ~ 2026-08-27 08:58 KST (약 16.6시간) |
|
||
| 모델 | `claude-opus-5` (1M 컨텍스트) 단일 |
|
||
| 과금 요청 수 | **348건** (메시지 ID 기준 중복 제거) |
|
||
| 출력 토큰 | 450,509 |
|
||
| 캐시 쓰기 (1h TTL) | 2,020,375 |
|
||
| 캐시 쓰기 (5m TTL) | 96,213 |
|
||
| 캐시 읽기 | 76,004,125 |
|
||
| 캐시 미적용 입력 | 690 |
|
||
| 입력 환산 합계 | 78,121,403 |
|
||
| **총 비용** | **$70.07** |
|
||
|
||
### 2-2. 비용 내역
|
||
|
||
| 항목 | 토큰 | 요율 ($/MTok) | 금액 |
|
||
|---|---|---|---|
|
||
| 캐시 읽기 (기본 입력가 ×0.1) | 76,004,125 | $0.50 | **$38.00** |
|
||
| 캐시 쓰기 1h (기본 입력가 ×2) | 2,020,375 | $10.00 | **$20.20** |
|
||
| 출력 | 450,509 | $25.00 | **$11.26** |
|
||
| 캐시 쓰기 5m (기본 입력가 ×1.25) | 96,213 | $6.25 | **$0.60** |
|
||
| 캐시 미적용 입력 | 690 | $5.00 | $0.003 |
|
||
| | | **합계** | **$70.07** |
|
||
|
||
> 각 항목은 센트 단위 반올림이라, 눈으로 더하면 합계와 1센트 차이가 날 수 있다.
|
||
|
||
**캐시 효과** — 프롬프트 캐싱이 없었다면 같은 작업이 **$401.87** 이었다. 즉 **$331.80 (82.6%) 를 캐시로 아꼈다.**
|
||
동시에, 비용의 **54%($38.00)가 캐시 *읽기*** 다. 출력 토큰 값($11.26)의 3배가 넘는다 — 긴 컨텍스트를 반복해서 다시 읽는 패턴이 이 프로젝트 비용 구조를 지배한다는 뜻이다. 줄이려면 출력을 줄이는 게 아니라 **컨텍스트를 작게 유지**해야 한다.
|
||
|
||
### 2-3. 세션별 내역
|
||
|
||
| 세션 | 종류 | 시각 (KST) | 요청 | 출력 토큰 | 캐시 읽기 | 비용 |
|
||
|---|---|---|---|---|---|---|
|
||
| `4db7aca5` | 메인 (backend 주력) | 08-26 16:23 ~ 08-27 08:55 | 157 | 266,385 | 35,748,750 | **$32.65** |
|
||
| `99be2a6c` | 메인 | 08-26 17:24 ~ 08-27 08:58 | 92 | 124,641 | 16,660,246 | **$19.79** |
|
||
| `19be092e` | 메인 (solution/frontend) | 08-27 08:42 ~ 08:58 | 29 | 25,903 | 2,476,298 | $3.37 |
|
||
| `agent-a58…` | 서브에이전트 | 08-27 08:51 ~ 08:57 | 13 | 6,173 | 5,741,359 | $3.21 |
|
||
| `agent-afa…` | 서브에이전트 | 08-27 08:52 ~ 08:55 | 14 | 423 | 6,132,381 | $3.21 |
|
||
| `6052d29a` | 메인 (이 기록 작성 세션) | 08-27 08:52 ~ 08:58 | 21 | 23,936 | 1,678,581 | $2.92 |
|
||
| `agent-aa5…` | 서브에이전트 | 08-27 08:52 ~ 08:58 | 9 | 68 | 3,905,434 | $2.07 |
|
||
| `agent-ab1…` | 서브에이전트 | 08-27 08:52 ~ 08:57 | 8 | 165 | 3,466,831 | $1.90 |
|
||
| `d56c8333` | 메인 | 08-27 08:52 ~ 08:53 | 5 | 2,815 | 194,245 | $0.95 |
|
||
| `2ac02dde` | 메인 (중복 세션) | — | 0 | 0 | 0 | $0.00 |
|
||
|
||
**읽을 거리 두 가지:**
|
||
|
||
- **서브에이전트 4개 합계 $10.39** — Perplexity/Kakao 클라이언트를 병렬로 짠 fork 들이다. 출력 토큰은 다 합쳐 6,829개뿐인데 캐시 읽기가 **1,925만 토큰**이다. **fork 는 부모 컨텍스트를 통째로 들고 시작하기 때문에**, 실제 작업이 짧아도 부모 컨텍스트가 크면 읽기 요금이 그만큼 붙는다. 병렬 fork 를 쓸지 말지는 "일이 병렬인가" 보다 **"부모 컨텍스트가 큰가"** 로 판단하는 편이 비용상 맞다.
|
||
- **`2ac02dde` 는 `99be2a6c` 로 이어진 세션(fork/resume)이라 메시지가 전부 중복** — 0원으로 처리했다. 세션 간 중복을 제거하지 않으면 408건 / **$79.16** 이 나와 **$9.09 과대집계**된다. 재집계할 때 메시지 ID 중복 제거를 반드시 넣을 것.
|
||
|
||
### 2-4. 이 숫자를 읽을 때 주의할 점
|
||
|
||
- **구독제(Max/Pro)로 쓰고 있다면 이 금액이 그대로 청구되지는 않는다.** 토큰 사용량을 공개 API 요율로 환산한 "이만큼 썼다" 값이다. 종량제 API 키로 같은 작업을 돌렸다면 나왔을 금액.
|
||
- 요율 기준: Opus 5 입력 $5 / 출력 $25 per MTok, 캐시 읽기 ×0.1, 캐시 쓰기 5m ×1.25 · 1h ×2.
|
||
- 1M 컨텍스트 구간에 별도 프리미엄이 붙는 경우 실제 금액은 이보다 높을 수 있다 — **하한선으로 읽을 것.**
|
||
- 원화 환산은 1,400원/USD 가정이다. 실제 청구 환율과 다를 수 있다.
|
||
- **이 스냅샷은 찍는 순간에도 움직인다.** 집계 시각 기준으로 `4db7aca5`·`99be2a6c`·`19be092e`·`6052d29a` 등 여러 세션이 동시에 살아 있었다. 4절의 스크립트로 언제든 다시 뽑을 것.
|
||
|
||
---
|
||
|
||
## 3. 앞으로 할 일
|
||
|
||
### 3-1. 비용이 실제로 발생하기 시작하는 지점
|
||
|
||
수집 파이프라인이 도는 순간부터다:
|
||
|
||
```
|
||
상호명 → [Perplexity: 채널 URL 발견] ← 토큰 + 검색 호출 과금
|
||
→ [Kakao Local: 동일 업소 검증] ← 건당 2원
|
||
→ 크롤링(무료)
|
||
→ [Gemini: 사진 분류 + 카피] ← 토큰 과금
|
||
```
|
||
|
||
업소 1건당 Perplexity 1~2회 + Kakao 여러 건 + Gemini(사진 수만큼)가 붙는다. **업소당 원가를 먼저 실측한 뒤 배치를 키우는 순서**가 안전하다.
|
||
|
||
### 3-2. 사이트 1건당 예산: **$1 (1,400원)** — 코드로 강제한다
|
||
|
||
제품 제약이 정해졌다: **업소 1곳의 사이트를 만드는 외부 API 비용은 $1 을 넘으면 안 된다.**
|
||
|
||
문서에만 적으면 지켜지지 않으므로 가드를 구현했다 — `backend/common/cost.py` (`CostMeter`), 테스트 `backend/tests/test_cost.py` 8건.
|
||
|
||
```python
|
||
meter = CostMeter(place_id=42)
|
||
meter.guard(Provider.PERPLEXITY, searches=2, tokens=3000) # 호출 "전" 에 확인 → 넘으면 BudgetExceeded
|
||
...실제 호출...
|
||
meter.charge(Provider.PERPLEXITY, searches=2, tokens=3100) # 호출 "후" 에 실측 기록
|
||
```
|
||
|
||
- `guard()` 가 막으면 **그 호출을 하지 않는다.** 이미 쓴 돈은 못 돌려받지만 손실이 선형으로 늘어나는 걸 끊는다.
|
||
- 예산 70% 소진 시 경고 로그, 100% 초과 시 예외.
|
||
- 미터 1개 = 사이트 1건.
|
||
|
||
**⚠️ 단가표에 아직 추정치가 섞여 있다.** 확정된 건 카카오뿐이다(레포에 2원/0.5원이 적혀 있다). Perplexity·Gemini 는 공식 단가표를 확인해서 `RATES` 를 교체해야 한다. 그때까지 `assert_rates_confirmed()` 가 실배치를 막는다 — 추정치 위에 "예산 안에 들어온다" 는 결론을 세우지 않기 위해서다.
|
||
|
||
### 3-3. 지금 없는 것 — 호출 로그 테이블
|
||
|
||
현재 DB 스키마(`backend/common/database/model/models.py`)에 **API 호출·비용을 적재하는 테이블이 없다.** 지금은 애플리케이션 로그로만 남는다 (`perplexity.py` 가 `tokens=` 와 검색 횟수를 찍는 정도).
|
||
|
||
파이프라인을 돌리기 전에 `api_call_logs` 같은 테이블(공급자 · 엔드포인트 · 토큰/호출수 · 추정단가 · place_id · 시각)을 얹어두면, 이 문서를 손으로 갱신하지 않고 쿼리로 뽑을 수 있다.
|
||
|
||
---
|
||
|
||
## 4. 재집계 방법
|
||
|
||
Claude 사용액은 아래로 다시 뽑는다 (세션 트랜스크립트 기준, 메시지 ID로 중복 제거):
|
||
|
||
```bash
|
||
cd ~/.claude/projects && python3 - <<'PY'
|
||
import json,glob,collections,os
|
||
files=sorted(glob.glob("-Users-minachoi-projects-o2o-web4ai*/*.jsonl")
|
||
+ glob.glob("-Users-minachoi-projects-o2o-web4ai*/*/subagents/*.jsonl"),
|
||
key=lambda p:-os.path.getsize(p))
|
||
IN,OUT,CR,W5,W1H = 5.0/1e6, 25.0/1e6, 0.50/1e6, 6.25/1e6, 10.0/1e6 # Opus 5 요율
|
||
seen=set(); t=collections.Counter()
|
||
for f in files:
|
||
for line in open(f,encoding="utf-8",errors="replace"):
|
||
try: o=json.loads(line)
|
||
except: continue
|
||
m=o.get("message") or {}; u=m.get("usage")
|
||
if not u: continue
|
||
rid=m.get("id")
|
||
if rid:
|
||
if rid in seen: continue # ★ fork/resume 중복 제거 — 빠뜨리면 과대집계
|
||
seen.add(rid)
|
||
cc=u.get("cache_creation") or {}
|
||
w5=cc.get("ephemeral_5m_input_tokens",0); w1=cc.get("ephemeral_1h_input_tokens",0)
|
||
if w5+w1==0: w5=u.get("cache_creation_input_tokens",0)
|
||
t["in"]+=u.get("input_tokens",0); t["out"]+=u.get("output_tokens",0)
|
||
t["w5"]+=w5; t["w1h"]+=w1; t["cr"]+=u.get("cache_read_input_tokens",0); t["n"]+=1
|
||
cost=t["in"]*IN+t["out"]*OUT+t["w5"]*W5+t["w1h"]*W1H+t["cr"]*CR
|
||
print(dict(t)); print(f"요청 {t['n']}건 / ${cost:.2f}")
|
||
PY
|
||
```
|
||
|
||
외부 API 실호출 여부 확인:
|
||
|
||
```bash
|
||
cd ~/.claude/projects && grep -c "api.perplexity.ai\|generativelanguage.googleapis.com\|dapi.kakao.com" \
|
||
-- -Users-minachoi-projects-o2o-web4ai*/*.jsonl
|
||
# 매칭이 있어도 대부분 소스 작성 텍스트다 — tool_use 의 command/url 필드인지 확인할 것
|
||
```
|