개념: OpenAI API 이해하기
이 가이드는 AI 에이전트 구축의 기초가 되는 OpenAI 언어 모델 작동의 기본 개념을 설명합니다.
OpenAI API란 무엇인가요?
OpenAI API는 GPT-4o 및 GPT-3.5-turbo와 같은 강력한 언어 모델에 대한 프로그래밍 방식 액세스를 제공합니다. 모델을 로컬에서 실행하는 대신, OpenAI 서버로 요청을 보내고 응답을 받습니다.
주요 특징:
- 클라우드 기반: 모델은 OpenAI 인프라에서 실행됩니다.
- 사용량 기반 과금: 토큰 소비량에 따라 요금이 부과됩니다.
- 운영 준비 완료: 엔터프라이즈급 안정성과 성능
- 최신 모델: 새로운 모델 출시 즉시 액세스 가능
로컬 LLM(예: node-llama-cpp)과의 비교:
| 측면 | OpenAI API | 로컬 LLM |
|---|---|---|
| 설정 | API 키만 필요 | 모델 다운로드, GPU/RAM 필요 |
| 비용 | 토큰당 지불 | 초기 설정 후 무료 |
| 성능 | 일관되고 고품질 | 사용자의 하드웨어에 따라 다름 |
| 개인 정보 보호 | 데이터가 OpenAI로 전송됨 | 완전히 로컬/비공개 |
| 확장성 | 무제한(결제 시) | 하드웨어로 제한됨 |
Chat Completions API
요청-응답 주기
사용자 (클라이언트) OpenAI (서버)
| |
| POST /v1/chat/completions |
| { |
| model: "gpt-4o", |
| messages: [...] |
| } |
|------------------------------->|
| |
| [처리 중...] |
| [모델 추론] |
| [응답 생성] |
| |
| 응답 |
| { |
| choices: [{ |
| message: { |
| content: "..." |
| } |
| }] |
| } |
|<-------------------------------|
| |
핵심 사항: 각 요청은 독립적입니다. API는 대화 기록을 저장하지 않습니다.
메시지 역할: 대화 구조
모든 메시지에는 목적을 결정하는 role(역할)이 있습니다.
1. 시스템 메시지 (System Messages)
{ role: 'system', content: 'You are a helpful Python tutor.' }목적: AI의 동작, 성격 및 기능을 정의합니다.
다음으로 생각할 수 있습니다:
- AI의 “직무 기술서”
- 최종 사용자에게는 보이지 않음
- 제약 조건 및 지침 설정
예시:
// 전문 에이전트
"You are an expert SQL database administrator."
// 어조 및 스타일
"You are a friendly customer support agent. Be warm and empathetic."
// 출력 형식 제어
"You are a JSON API. Always respond with valid JSON, never plain text."
// 동작 제약 조건
"You are a code reviewer. Be constructive and focus on best practices."모범 사례:
- 간결하지만 구체적으로 유지
- 메시지 배열의 맨 앞에 배치
- 에이전트 동작을 변경하려면 업데이트
- 윤리적 지침 및 출력 형식 지정에 사용
2. 사용자 메시지 (User Messages)
{ role: 'user', content: 'How do I use async/await?' }목적: 사용자의 입력 또는 질문을 나타냅니다.
다음으로 생각할 수 있습니다:
- AI에게 묻는 내용
- 프롬프트 또는 쿼리
- 따라야 할 지침
3. 어시스턴트 메시지 (Assistant Messages)
{ role: 'assistant', content: 'Async/await is a way to handle promises...' }목적: AI의 이전 응답을 나타냅니다.
다음으로 생각할 수 있습니다:
- AI의 대화 기록
- 후속 질문을 위한 맥락
- AI가 이미 말한 내용
대화 흐름 예시
[
{ role: 'system', content: 'You are a math tutor.' },
// 첫 번째 교환
{ role: 'user', content: 'What is 15 * 24?' },
{ role: 'assistant', content: '15 * 24 = 360' },
// 후속 질문 (맥락을 알고 있음)
{ role: 'user', content: 'What about dividing that by 3?' },
{ role: 'assistant', content: '360 ÷ 3 = 120' },
]이것이 중요한 이유: 역할 구조는 다음을 가능하게 합니다.
- 맥락 인식: AI가 대화 기록을 이해합니다.
- 동작 제어: 시스템 프롬프트가 응답을 형성합니다.
- 다중 턴 대화: 자연스러운 앞뒤 대화
상태 비저장(Statelessness): 중요한 개념
가장 중요한 원칙: OpenAI API는 상태를 저장하지 않습니다(stateless).
상태 비저장(Stateless)이란 무엇을 의미하나요?
각 API 호출은 독립적입니다. 모델은 이전 요청을 기억하지 않습니다.
요청 1: "My name is Alice"
응답 1: "Hello Alice!"
요청 2: "What's my name?"
응답 2: "I don't know your name." ← 기억 없음!
맥락을 유지하는 방법
전체 대화 기록을 보내야 합니다.
const messages = [];
// 첫 번째 턴
messages.push({ role: 'user', content: 'My name is Alice' });
const response1 = await client.chat.completions.create({
model: 'gpt-4o',
messages: messages // ["My name is Alice"]
});
messages.push(response1.choices[0].message);
// 두 번째 턴 - 전체 기록 포함
messages.push({ role: 'user', content: "What's my name?" });
const response2 = await client.chat.completions.create({
model: 'gpt-4o',
messages: messages // 전체 대화!
});영향
장점:
- ✅ 간단한 아키텍처(서버 측 상태 불필요)
- ✅ 확장 용이성(모든 서버가 모든 요청 처리 가능)
- ✅ 맥락에 대한 완전한 제어(포함할 내용 결정)
과제:
- ❌ 대화 기록을 직접 관리해야 함
- ❌ 대화 길이가 길어질수록 토큰 비용 증가
- ❌ 자체 메모리/지속성 구현 필요
- ❌ 결국 맥락 창 한계에 도달함
실제 해결책:
// 너무 길면 이전 메시지 자르기
if (messages.length > 20) {
messages = [messages[0], ...messages.slice(-10)]; // 시스템 메시지와 최신 10개 유지
}
// 이전 맥락 요약
if (totalTokens > 10000) {
const summary = await summarizeConversation(messages);
messages = [systemMessage, summary, ...recentMessages];
}온도(Temperature): 무작위성 제어
온도는 모델 출력의 “창의성” 또는 “무작위성”을 제어합니다.
기술적 작동 방식
각 토큰을 생성할 때, 모델은 가능한 다음 토큰에 확률을 할당합니다.
입력: "The sky is"
가능한 다음 토큰:
- "blue" → 70% 확률
- "clear" → 15% 확률
- "dark" → 10% 확률
- "purple" → 5% 확률
온도는 이러한 확률을 수정합니다:
온도 = 0.0 (결정적)
항상 가장 높은 확률의 토큰 선택
"The sky is blue" ← 매번 동일한 출력
온도 = 0.7 (균형 잡힌)
약간의 무작위성을 가지고 확률적으로 샘플링
"The sky is blue" 또는 "The sky is clear"
온도 = 1.5 (창의적)
확률 평탄화, 가능성이 낮은 선택 허용
"The sky is purple" 또는 "The sky is dancing" ← 더 놀라움!
실용적인 지침
온도 0.0 - 0.3: 집중된 작업
- 코드 생성
- 데이터 추출
- 사실 기반 Q&A
- 분류
- 번역
예시:
// 텍스트에서 JSON 추출 - 일관성이 필요함
temperature: 0.1온도 0.5 - 0.9: 균형 잡힌 작업
- 일반 대화
- 고객 지원
- 콘텐츠 요약
- 교육 콘텐츠
예시:
// 친근한 챗봇
temperature: 0.7온도 1.0 - 2.0: 창의적인 작업
- 스토리 작성
- 브레인스토밍
- 시/창작 콘텐츠
- 다양한 변형 생성
예시:
// 10가지 다른 마케팅 슬로건 생성
temperature: 1.3스트리밍(Streaming): 실시간 응답
비스트리밍 (기본값)
사용자: "Tell me a story"
[대기...]
[대기...]
[대기...]
응답: "Once upon a time, there was a..." (한 번에 모두)
장점:
- 구현이 간단함
- 오류 처리가 쉬움
- 전체 응답을 받은 후 처리 가능
단점:
- 긴 응답의 경우 느리게 보임
- 생성 중 피드백 없음
- 채팅 시 사용자 경험 저하
스트리밍
사용자: "Tell me a story"
"Once"
"Once upon"
"Once upon a"
"Once upon a time"
"Once upon a time there"
...
장점:
- 즉각적인 피드백
- 더 빠르게 보임
- 더 나은 사용자 경험
- 토큰 도착 시 처리 가능
단점:
- 더 복잡한 코드
- 오류 처리가 더 어려움
- 표시하기 전에 전체 응답을 볼 수 없음
각 방식 사용 시점
비스트리밍 사용:
- 배치 처리 스크립트
- 전체 응답을 분석해야 할 때
- 간단한 명령줄 도구
- 완료된 결과를 반환하는 API 엔드포인트
스트리밍 사용:
- 채팅 인터페이스
- 대화형 애플리케이션
- 장문 콘텐츠 생성
- UX가 중요한 모든 사용자 대면 애플리케이션
토큰: LLM의 화폐
토큰이란 무엇인가요?
토큰은 언어 모델이 처리하는 기본 단위입니다. 단어와 정확히 같지는 않으며 텍스트의 일부입니다.
토큰화 예시:
"Hello world" → ["Hello", " world"] = 2 토큰
"coding" → ["coding"] = 1 토큰
"uncoded" → ["un", "coded"] = 2 토큰
토큰이 중요한 이유
1. 비용 토큰당 비용이 부과됩니다(입력 + 출력):
요청: 100 토큰
응답: 150 토큰
총 청구: 250 토큰
2. 맥락 한계 각 모델에는 최대 토큰 한계가 있습니다.
gpt-4o: 128,000 토큰 (≈96,000 단어)
gpt-3.5-turbo: 16,384 토큰 (≈12,000 단어)
3. 성능 토큰이 많을수록 처리 시간이 길어지고 비용이 증가합니다.
토큰 사용량 관리
사용량 모니터링:
console.log(response.usage.total_tokens);
// 예산 관리를 위해 누적 사용량 추적응답 길이 제한:
max_tokens: 150 // 응답 길이 제한대화 기록 자르기:
// 최신 메시지만 유지
if (messages.length > 20) {
messages = messages.slice(-20);
}보내기 전에 추정:
import { encode } from 'gpt-tokenizer';
const text = "Your message here";
const tokens = encode(text).length;
console.log(`Estimated tokens: ${tokens}`);모델 선택: 올바른 도구 선택하기
GPT-4o: 강력한 모델
최적:
- 복잡한 추론 작업
- 코드 생성 및 디버깅
- 기술 콘텐츠
- 높은 정확도가 필요한 작업
- 구조화된 데이터 작업
특징:
- 가장 유능한 모델
- 비용이 더 높음
- GPT-3.5보다 느림
- 품질이 중요한 애플리케이션에 최적
사용 사례 예시:
- 법률 문서 분석
- 복잡한 코드 리팩토링
- 연구 및 분석
- 교육 튜터링
GPT-4o-mini: 균형 잡힌 선택
최적:
- 범용 애플리케이션
- 비용과 성능의 좋은 균형
- 대부분의 일상적인 작업
특징:
- 좋은 성능
- 적당한 비용
- 빠른 응답 시간
- 많은 애플리케이션의 적정점
사용 사례 예시:
- 고객 지원 챗봇
- 콘텐츠 요약
- 일반 Q&A
- 중간 복잡도 작업
GPT-3.5-turbo: 속도 전문가
최적:
- 대용량, 간단한 작업
- 속도가 중요한 애플리케이션
- 예산이 빠듯한 프로젝트
- 분류 및 추출
특징:
- 매우 빠름
- 가장 낮은 비용
- 간단한 작업에 좋음
- 추론 능력이 낮음
사용 사례 예시:
- 감성 분석
- 텍스트 분류
- 간단한 형식 지정
- 높은 처리량 처리
의사 결정 프레임워크
작업이 중요하고 복잡한가요?
├─ 예 → GPT-4o
└─ 아니요
└─ 속도가 중요하고 작업이 간단한가요?
├─ 예 → GPT-3.5-turbo
└─ 아니요 → GPT-4o-mini
오류 처리 및 복원력
일반적인 오류 시나리오
1. 인증 오류 (401)
// 잘못된 API 키
Error: Incorrect API key provided2. 속도 제한 (429)
// 너무 많은 요청
Error: Rate limit exceeded3. 토큰 한도 (400)
// 맥락이 너무 김
Error: This model's maximum context length is 16385 tokens4. 서비스 오류 (500)
// OpenAI 서비스 문제
Error: The server had an error processing your request모범 사례
1. 항상 try-catch 사용:
try {
const response = await client.chat.completions.create({...});
} catch (error) {
if (error.status === 429) {
// 백오프 및 재시도 구현
} else if (error.status === 500) {
// 지수 백오프를 사용하여 재시도
} else {
// 적절하게 로깅 및 처리
}
}2. 재시도 로직 구현:
async function retryWithBackoff(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
await sleep(Math.pow(2, i) * 1000); // 지수 백오프
}
}
}3. 토큰 사용량 모니터링:
let totalTokens = 0;
totalTokens += response.usage.total_tokens;
if (totalTokens > MONTHLY_BUDGET_TOKENS) {
throw new Error('Monthly token budget exceeded');
}아키텍처 패턴
패턴 1: 간단한 요청-응답
사용 사례: 일회성 쿼리, 간단한 자동화
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: query }]
});장점: 간단하고 이해하기 쉬움 단점: 맥락 없음, 메모리 없음
패턴 2: 상태를 유지하는 대화
사용 사례: 채팅 애플리케이션, 튜터링, 고객 지원
class Conversation {
constructor() {
this.messages = [
{ role: 'system', content: 'Your behavior' }
];
}
async ask(userMessage) {
this.messages.push({ role: 'user', content: userMessage });
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: this.messages
});
this.messages.push(response.choices[0].message);
return response.choices[0].message.content;
}
}장점: 맥락 유지, 자연스러운 대화 단점: 토큰 비용 증가, 관리 필요
패턴 3: 전문화된 에이전트
사용 사례: 도메인별 애플리케이션
class PythonTutor {
async help(question) {
return await client.chat.completions.create({
model: 'gpt-4o',
messages: [
{
role: 'system',
content: 'You are an expert Python tutor. Explain concepts clearly with code examples.'
},
{ role: 'user', content: question }
],
temperature: 0.3 // 집중된 응답
});
}
}장점: 일관된 동작, 도메인에 최적화됨 단점: 유연성 낮음
하이브리드 접근 방식: 독점 모델과 오픈 소스 모델 결합
실제 프로젝트에서 최선의 해결책은 OpenAI와 로컬 LLM 중 하나를 선택하는 것이 아니라, 둘 다 전략적으로 사용하는 것입니다.
하이브리드 접근 방식 사용 이유
비용 최적화: 필요한 경우에만 비용이 많이 드는 모델 사용 개인 정보 보호 준수: 일반 작업에는 클라우드를 활용하는 동시에 민감한 데이터는 로컬에 보관 성능 균형: 간단한 작업에는 빠른 로컬 모델, 복잡한 작업에는 강력한 클라우드 모델 사용 신뢰성: 한 서비스가 다운될 경우 대체 옵션 유연성: 각 특정 작업에 올바른 도구 매칭
일반적인 하이브리드 아키텍처
패턴 1: 계층화된 처리
간단한 작업 → 로컬 LLM (빠르고, 무료이며, 비공개)
↓ 복잡한 경우
복잡한 작업 → OpenAI API (강력하고, 정확함)
예시 워크플로:
async function processQuery(query) {
const complexity = await assessComplexity(query);
if (complexity < 0.5) {
// 간단한 쿼리에는 로컬 모델 사용
return await localLLM.generate(query);
} else {
// 복잡한 추론에는 OpenAI 사용
return await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: query }]
});
}
}사용 사례:
- 고객 지원: FAQ에는 로컬 모델, 복잡한 문제에는 GPT-4
- 코드 생성: 간단한 스크립트는 로컬, 아키텍처는 GPT-4
- 콘텐츠 조정: 명확한 사례는 로컬, 예외 사례는 클라우드
패턴 2: 개인 정보 보호 기반 라우팅
공개 데이터 → OpenAI (최고 품질)
민감한 데이터 → 로컬 LLM (비공개, 안전)
예시:
async function handleRequest(data, containsSensitiveInfo) {
if (containsSensitiveInfo) {
// 로컬에서 처리 - 데이터가 인프라를 벗어나지 않음
return await localLLM.generate(data, {
systemPrompt: "You are a HIPAA-compliant assistant"
});
} else {
// 더 나은 품질을 위해 클라우드 사용
return await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: data }]
});
}
}사용 사례:
- 의료: 환자 데이터 → 로컬, 일반 의학 정보 → OpenAI
- 금융: 거래 내역 → 로컬, 시장 분석 → OpenAI
- 법률: 고객 통신 → 로컬, 법률 연구 → OpenAI
패턴 3: 전문화된 에이전트 생태계
에이전트 1 (로컬): 빠른 분류기
↓ 라우팅
에이전트 2 (OpenAI): 심층 분석기
↓ 라우팅
에이전트 3 (로컬): 실행 에이전트
예시:
class MultiModelAgent {
async process(input) {
// 1단계: 로컬 모델로 의도 분류 (빠르고 저렴함)
const intent = await localLLM.classify(input);
// 2단계: 적절한 핸들러로 라우팅
if (intent.requiresReasoning) {
// GPT-4로 복잡한 추론
const analysis = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: input }]
});
return analysis.choices[0].message.content;
} else {
// 로컬 모델로 간단한 응답 생성
return await localLLM.generate(input);
}
}
}사용 사례:
- 서로 다른 복잡도 수준을 가진 다단계 파이프라인
- 각 에이전트가 전문화된 기능을 갖는 에이전트 시스템
- 속도와 지능이 모두 필요한 워크플로
패턴 4: 개발 대 프로덕션
개발 → OpenAI (빠른 반복, 최고의 결과)
↓ 최적화
프로덕션 → 로컬 LLM (비용 효율적, 비공개)
워크플로:
const MODEL_PROVIDER = process.env.NODE_ENV === 'production'
? 'local'
: 'openai';
async function generateResponse(prompt) {
if (MODEL_PROVIDER === 'local') {
return await localLLM.generate(prompt);
} else {
return await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }]
});
}
}전략:
- GPT-4로 개발하여 최고의 결과를 신속하게 얻음
- 프롬프트 미세 조정 및 철저한 테스트
- 프로덕션에서는 로컬 모델로 전환
- 예외 사례에 대해서는 OpenAI로 폴백
패턴 5: 앙상블 접근 방식
쿼리 → [로컬 모델, OpenAI, 다른 API]
↓ ↓ ↓
응답 응답 응답
↓ ↓ ↓
집합기 / 검증기
↓
최상의 응답
예시:
async function ensembleGenerate(prompt) {
// 여러 소스에서 응답 가져오기
const [local, openai, backup] = await Promise.allSettled([
localLLM.generate(prompt),
openaiClient.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }]
}),
backupAPI.generate(prompt)
]);
// 최상의 응답을 선택하거나 결합하기 위해 검증기 사용
return validator.selectBest([local, openai, backup]);
}사용 사례:
- 높은 신뢰도가 필요한 중요 애플리케이션
- 사실 확인 및 검증
- 합의를 통한 환각 감소
비용-편익 분석
시나리오: 고객 지원 챗봇 (하루 10,000건의 쿼리)
옵션 A: OpenAI만 사용
10,000 쿼리 × 평균 500 토큰 = 일일 5백만 토큰
비용: 하루 $25-50 = 월 $750-1500
장점: 최고 품질, 인프라 제로
단점: 대규모에서 비쌈, 개인 정보 보호 우려
옵션 B: 로컬 LLM만 사용
인프라: 월 $100-500 (서버/GPU)
비용: 월 $100-500
장점: 예측 가능한 비용, 비공개, 무제한 사용
단점: 설정 복잡성, 유지 관리, 품질 저하
옵션 C: 하이브리드 (80% 로컬, 20% OpenAI)
8,000건의 간단한 쿼리 → 로컬 LLM (설정 후 무료)
2,000건의 복잡한 쿼리 → OpenAI (~하루 $5-10)
인프라: 월 $100-500
API 비용: 월 $150-300
합계: 월 $250-800
장점: 비용 효율적, 필요할 때 고품질, 유연성
단점: 더 복잡한 아키텍처
대부분의 프로젝트 승자: 하이브리드 접근 방식 ✓
의사 결정 프레임워크
시작: 쿼리 도착
↓
데이터가 민감하거나 규제 대상인가요?
├─ 예 → 로컬 모델 사용 (개인 정보 보호 우선)
└─ 아니요 → 계속
↓
작업이 간단하거나 반복적인가요?
├─ 예 → 로컬 모델 사용 (비용 효율성)
└─ 아니요 → 계속
↓
높은 정확도가 중요한가요?
├─ 예 → OpenAI 사용 (품질 우선)
└─ 아니요 → 계속
↓
볼륨이 높은가요?
├─ 예 → 로컬 모델 사용 (규모에 따른 비용 절감)
└─ 아니요 → OpenAI 사용 (단순성)
미래: 지능형 모델 선택
고급 시스템은 실시간 요인에 따라 자동으로 모델을 선택합니다.
class IntelligentModelSelector {
async selectModel(query, context) {
const factors = {
complexity: await this.analyzeComplexity(query),
latency: context.userTolerance,
budget: context.remainingBudget,
accuracy: context.requiredConfidence,
privacy: context.dataClassification
};
// ML 모델이 최적 제공업체를 예측
const selection = await this.mlSelector.predict(factors);
return {
provider: selection.provider, // 'local' | 'openai-mini' | 'openai-4'
confidence: selection.confidence,
reasoning: selection.reasoning
};
}
}핵심 요약
선택할 필요가 없습니다. 현대 AI 애플리케이션은 각 작업에 적합한 모델을 사용하는 것에서 이점을 얻습니다.
- OpenAI / Claude / 자체 호스팅 대규모 오픈 소스 모델: 복잡한 추론, 중요한 정확도, 빠른 개발
- 규모를 위한 로컬: 개인 정보 보호, 비용 제어, 높은 볼륨, 오프라인 작동
- 성공을 위한 둘 다: 비용 효율적이고 유연하며 안정적인 프로덕션 시스템
최고의 아키텍처는 각 접근 방식의 강점을 활용하고 약점은 완화하는 것입니다.
에이전트를 위한 준비
여기에 설명된 개념들은 AI 에이전트를 구축하는 데 기초가 되는 요소입니다.
이제 이해한 내용:
- LLM과의 통신 방법 (API 기본 사항)
- 동작 모양 만들기 (시스템 프롬프트)
- 맥락 유지 방법 (메시지 기록)
- 출력 제어 방법 (온도, 토큰)
- 응답 처리 방법 (스트리밍, 오류 처리)
에이전트를 위한 다음 단계:
- 함수 호출 / 도구 사용 - AI가 행동하도록 허용
- 메모리 시스템 - 세션 전반에 걸친 지속적인 상태
- ReAct 패턴 - 반복적인 추론 및 관찰
결론: 이러한 기본 사항을 숙달하지 않고는 훌륭한 에이전트를 구축할 수 없습니다. 모든 에이전트 패턴은 이 기초 위에 구축됩니다.
주요 통찰
- 상태 비저장성은 힘이자 짐입니다: 맥락은 사용자가 제어하지만, 관리해야 합니다.
- 시스템 프롬프트는 비밀 무기입니다: 동일한 모델 → 다른 동작
- 온도는 모든 것을 바꿉니다: 작업 유형에 맞게 조정하세요.
- 토큰이 실제 화폐입니다: 사용량을 모니터링하고 최적화하세요.
- 모델 선택이 중요합니다: 못 박는 데 해머를 사용할 필요는 없습니다.
- 스트리밍은 UX를 향상시킵니다: 사용자 대면 애플리케이션에는 사용하세요.
- 오류 처리는 선택 사항이 아닙니다: 네트워크는 실패할 것이므로 대비하세요.