SaaS 앱에 LLM을 왜, 어떻게 통합할 것인가
- LLM
- SaaS
- TypeScript
- Claude
- Integration
“저희 앱에 AI를 좀 넣고 싶어요.” 매주 듣는 요청입니다. 대부분의 경우 클라이언트가 진짜 원하는 건 구체적인 비즈니스 문제 해결이지, UI 한 구석에 챗봇을 박는 게 아닙니다. 제가 어떻게 정리하는지 적습니다.
왜 지금 해야 하는가
이유는 딱 세 가지입니다.
- 사용자 시간 절약. 요약, 분류, 추출, 재작성. 지금 손으로 하는 작업들입니다.
- 새로운 유스케이스 개방. 시맨틱 검색, 컨텍스트 어시스턴트, 초안 생성. 3년 전엔 불가능했던 것들입니다.
- 경쟁력 유지. 경쟁사는 이미 붙였습니다. 6개월 늦으면 1년치 따라잡아야 합니다.
반면, UI 한구석에 박힌 일반 챗봇은 전략이 아닙니다. 가치 없는 마찰일 뿐입니다.
실제로 통하는 세 가지 유스케이스
제 프로젝트에서 측정 가능한 ROI를 낸 건 이 세 가지뿐입니다.
타깃 텍스트 변환
사용자가 어떤 텍스트를 가지고 있고, 다른 형태의 텍스트를 원합니다. 티켓 요약, 이메일 재작성, PDF에서 추출. 온디맨드 호출, 명확한 컨텍스트, 구조화된 출력. 단순하고 수익성 좋습니다.
데이터 검색과 Q&A
자연어 질문, 계정 데이터 기반 답변. RAG입니다. 사내 문서, 지식 베이스, 티켓 아카이브에 유용합니다. “사이트에 챗봇 다는 것”과 혼동하면 안 됩니다.
보조 구조화 생성
사용자가 비즈니스 객체를 만들고 싶어 합니다 — 상품, 캠페인, 템플릿. LLM이 짧은 브리핑으로부터 미리 채우고, 사람이 수정합니다. 수락률 측정, 무작정 자동화 금지.
최소 아키텍처
Next.js나 Hono 프로젝트에 처음 배포하는 구조입니다.
// app/api/llm/summarize/route.ts
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
export async function POST(req: Request) {
const { text, userId } = await req.json();
// 1. 사용자별 쿼터
const allowed = await checkQuota(userId);
if (!allowed) {
return Response.json({ error: "quota_exceeded" }, { status: 429 });
}
// 2. 버전 관리되는 시스템 프롬프트로 LLM 호출
const response = await client.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
system: SUMMARIZE_SYSTEM_PROMPT,
messages: [{ role: "user", content: text }],
});
// 3. 내부 가격 산정용 사용량 로깅
await logUsage(userId, response.usage);
const summary = response.content[0].type === "text"
? response.content[0].text
: "";
return Response.json({ summary });
}
타협 불가 네 가지입니다.
- 사용자별 쿼터. 없으면 고객 한 명이 하룻밤에 계정을 비웁니다. 직접 봤습니다.
- 버전 관리되는 시스템 프롬프트. 첫 달에만 열 번은 바꿉니다.
- 사용량 로깅. 토큰, 비용, 지연 시간. 없으면 깜깜이 운영입니다.
- 깔끔한 에러 처리. 백오프 재시도, 폴백 프로바이더. Anthropic 500 에러는 진짜로 납니다.
흔한 함정
무지성 스트리밍
스트리밍은 길고 인터랙티브한 응답에서 유용합니다. 200ms짜리 구조화 추출에는 무의미합니다. 트렌드가 아니라 UX 기준으로 선택하세요.
캐시 무시
사용자 두 명, 같은 질문, 같은 문서. 두 번 결제할 이유가 없습니다. hash(prompt + 컨텍스트)를 키로 한 Redis 캐시는 반복 기능에서 비용을 30~70% 깎아줍니다.
체감 지연 무시
LLM은 2~10초 걸립니다. 스켈레톤이나 스트리밍이 없으면 사용자는 앱이 망가졌다고 생각합니다. 광케이블이 아니라 4G에서 테스트하세요.
단일 프로바이더에 종속
저는 1일차부터 Claude와 OpenAI를 같은 인터페이스 뒤에 둡니다. 한쪽이 다운되거나(일어납니다) 가격을 세 배로 올리면(이것도 일어납니다) 환경 변수 하나로 전환합니다.
interface LLMProvider {
complete(prompt: string, opts: CompleteOpts): Promise<string>;
}
class ClaudeProvider implements LLMProvider { /* ... */ }
class OpenAIProvider implements LLMProvider { /* ... */ }
const llm: LLMProvider = pickProvider(process.env.LLM_PROVIDER);
진짜 이슈: 비용
기술적으로 잘 동작하는 LLM 기능이 유닛 이코노믹스를 망칠 수 있습니다. 프로덕션 전에 반드시 계산하세요.
- 사용자당, 하루당 호출 수 추정.
- 평균 비용(입력 + 출력 토큰) 곱하기.
- 사용자당 마진과 비교.
마진의 20~30%를 넘으면 결정해야 합니다 — 캐시, 단순 케이스에는 더 작은 모델, 플랜별 레이트 리밋, 사용량 기반 과금. 저는 표준 플랜의 마진을 희석시키느니 “AI 유료 옵션” 플랜을 따로 두는 걸 선호합니다. 고객에게도, 회사에게도 더 정직합니다.
어디서부터 시작할 것인가
제 2~4주 프로토콜입니다.
- 1주차. 명확한 수동 작업을 대체하는 유스케이스 하나. “챗봇”이 아니라 “주간 리포트 자동 요약”.
- 2주차. 로컬 프로토타입, 프롬프트 반복, 실제 케이스 10~20개.
- 3주차. 쿼터, 로그, 폴백 프로바이더 통합.
- 4주차. 피처 플래그 뒤에서 프로덕션 배포, 점진적 롤아웃.
이게 제가 유럽 B2B SaaS에 한 달 안에 LLM 통합을 납품하면서 비용 구조를 망가뜨리지 않는 프레임워크입니다. 구체적인 케이스가 있으면 30분 무료 상담에서 같이 검토해 드립니다.