저는 이번 달만 세 번째이나 GPT-4.1 모델이 이상한 형식으로 응답해서 파싱 오류가 발생했습니다. "Validation Error: Cannot read properties of undefined"라는 메시지와 함께 서버가 크래시됐죠. 이 문제를 해결하기 위해 Zod 스키마 검증과 AI SDK의 구조화 출력 기능을 결합하는 방법을 소개하겠습니다.
왜 구조화 출력이 필요한가?
AI 모델은 자연어로 응답하기 때문에 파싱이 불안정합니다. 예를 들어:
// ❌ 불안정한 응답 예시
{
"이름": "김철수",
"나이": 28,
"이메일": "[email protected]"
}
// ⚠️ 모델이 이렇게 응답할 수도 있음
{
"name": "김철수",
"age": "스물여덟",
"email": "[email protected]"
}
Zod 스키마를 사용하면 응답 형태를 정확히 정의하고 자동으로 검증할 수 있습니다. HolySheep AI의 구조화 출력 기능과 결합하면 안정적인 파이프라인을 구축할 수 있습니다.
환경 설정
먼저 필요한 패키지를 설치합니다.
npm install zod @ai-sdk/openai ai
또는 providers 설치
npm install @ai-sdk/anthropic @ai-sdk/google
HolySheep AI API 키를 환경 변수로 설정합니다.
// .env 파일
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY
// 직접 설정
process.env.HOLYSHEEP_API_KEY = "YOUR_HOLYSHEEP_API_KEY";
기본 구조화 출력 구현
저는 HolySheep AI의 GPT-4.1 모델을 사용하여 사용자 프로필 추출 시스템을 구축했습니다. Zod 스키마를 정의하고 AI SDK의 generateStructured 함수를 사용하면 됩니다.
import { z } from 'zod';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
// HolySheep AI 설정
const holysheep = openai({
baseURL: 'https://api.holysheep.ai/v1',
apiKey: process.env.HOLYSHEEP_API_KEY,
});
// 사용자 프로필 스키마 정의
const UserProfileSchema = z.object({
name: z.string().describe('사용자 이름'),
age: z.number().int().min(0).max(150).describe('나이'),
email: z.string().email().describe('이메일 주소'),
interests: z.array(z.string()).describe('관심 분야 목록'),
isPremium: z.boolean().default(false).describe('프리미엄 사용자 여부'),
});
type UserProfile = z.infer;
// 구조화 출력 함수
async function extractUserProfile(text: string): Promise<UserProfile> {
const { object } = await generateText({
model: holysheep('gpt-4.1'),
prompt: 다음 텍스트에서 사용자 정보를 추출하세요:\n\n${text},
schema: UserProfileSchema,
});
return object as UserProfile;
}
// 사용 예시
const text = "안녕하세요, 저는 김철수입니다. 28살이고 [email protected]을 사용하고 있어요. 프로그래밍과 요리에 관심이 있습니다.";
const profile = await extractUserProfile(text);
console.log('추출된 프로필:', profile);
// 출력: { name: '김철수', age: 28, email: '[email protected]', interests: ['프로그래밍', '요리'], isPremium: false }
응답 오브젝트 구조 활용
복잡한 데이터를 구조화할 때는 응답 오브젝트를 활용하세요. 저는 최근 AI 비서 시스템에서 툴 호출과 구조화 출력을 결합한 패턴을 사용했습니다.
import { z } from 'zod';
import { generateText, stepCount } from 'ai';
import { openai } from '@ai-sdk/openai';
const holysheep = openai({
baseURL: 'https://api.holysheep.ai/v1',
apiKey: process.env.HOLYSHEEP_API_KEY,
});
// 제품 리뷰 스키마
const ProductReviewSchema = z.object({
overallScore: z.number().min(1).max(5).describe('전체 평점'),
pros: z.array(z.string()).describe('장점 목록'),
cons: z.array(z.string()).describe('단점 목록'),
summary: z.string().max(200).describe('요약'),
recommended: z.boolean().describe('추천 여부'),
sentiment: z.enum(['positive', 'neutral', 'negative']).describe('감성 분석 결과'),
});
// 복잡한 응답 스키마
const ComplexResponseSchema = z.object({
review: ProductReviewSchema,
categories: z.object({
quality: z.number().min(1).max(5),
value: z.number().min(1).max(5),
service: z.number().min(1).max(5),
}).describe('카테고리별 평점'),
keyInsights: z.array(z.string()).describe('주요 인사이트'),
});
async function analyzeProductReview(reviewText: string) {
const result = await generateText({
model: holysheep('gpt-4.1'),
messages: [
{
role: 'system',
content: '당신은 전문 제품 리뷰 분석가입니다. 정확한 구조화된 데이터를 제공하세요.',
},
{
role: 'user',
content: 다음 제품 리뷰를 분석하세요:\n\n${reviewText},
},
],
schema: ComplexResponseSchema,
maxTokens: 2048,
});
return result.object;
}
// 실행
const review = `
이 제품 정말 훌륭합니다! 품질은 최고이고, 가성비도 뛰어나네요.
다만 배송이 조금 늦었습니다.客户服务도 친절하고対応也很好합니다.
전체적으로는 만족합니다. 다음에 또 살 것 같습니다.
`;
const analysis = await analyzeProductReview(review);
console.log('분석 결과:', JSON.stringify(analysis, null, 2));
Anthropic Claude 모델 사용
HolySheep AI는 Anthropic Claude 모델도 지원합니다. Claude의 구조화 출력은 응답 포맷을 더 엄격하게 검증합니다.
import { z } from 'zod';
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const holysheepAnthropic = anthropic({
baseURL: 'https://api.holysheep.ai/v1',
apiKey: process.env.HOLYSHEEP_API_KEY,
});
// 코드 분석 스키마
const CodeAnalysisSchema = z.object({
language: z.string().describe('프로그래밍 언어'),
complexity: z.enum(['low', 'medium', 'high', 'very_high']).describe('복잡도'),
issues: z.array(z.object({
severity: z.enum(['error', 'warning', 'info']),
message: z.string(),
line: z.number().optional(),
})).describe('발견된 문제점'),
suggestions: z.array(z.string()).describe('개선 제안'),
estimatedLines: z.number().int().positive().describe('예상 코드 라인 수'),
securityScore: z.number().min(0).max(100).describe('보안 점수'),
});
async function analyzeCode(code: string): Promise<z.infer<typeof CodeAnalysisSchema>> {
const { object } = await generateText({
model: holysheepAnthropic('claude-sonnet-4-20250514'),
system: '당신은 코드 분석 전문가입니다. 입력된 코드를 분석하고 구조화된 결과를 반환하세요.',
messages: [{ role: 'user', content: 이 코드를 분석하세요:\n\n${code} }],
schema: CodeAnalysisSchema,
temperature: 0.3,
});
return object;
}
// 테스트
const sampleCode = `
function calculateTotal(items) {
let total = 0;
for (let i = 0; i < items.length; i++) {
total += items[i].price * items[i].quantity;
}
return total;
}
`;
const result = await analyzeCode(sampleCode);
console.log('코드 분석:', result);
저장 및 재사용
스키마를 파일로 분리하면 프로젝트 전반에서 재사용할 수 있습니다.
// schemas/user.ts
import { z } from 'zod';
export const UserProfileSchema = z.object({
name: z.string().min(1).max(100),
age: z.number().int().min(0).max(150),
email: z.string().email(),
phone: z.string().regex(/^\+?[0-9]{10,15}$/).optional(),
preferences: z.object({
theme: z.enum(['light', 'dark', 'auto']),
language: z.string().default('ko'),
notifications: z.boolean().default(true),
}),
createdAt: z.string().datetime().optional(),
});
export type UserProfile = z.infer<typeof UserProfileSchema>;
// schemas/product.ts
export const ProductSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(200),
price: z.number().positive(),
currency: z.string().default('KRW'),
category: z.enum(['electronics', 'clothing', 'food', 'books', 'other']),
tags: z.array(z.string()).max(10),
inStock: z.boolean(),
rating: z.object({
average: z.number().min(0).max(5),
count: z.number().int().nonnegative(),
}),
});
export type Product = z.infer<typeof ProductSchema>;
// schemas/index.ts
export * from './user';
export * from './product';
가격 및 성능 최적화
HolySheep AI의 모델별 가격표를 참고하여 비용을 최적화하세요:
- GPT-4.1: $8.00/1M 토큰 (정밀한 구조화 출력에 적합)
- Claude Sonnet 4.5: $15.00/1M 토큰 (복잡한 추론 작업)
- Gemini 2.5 Flash: $2.50/1M 토큰 (대량 처리,性价比最高)
- DeepSeek V3.2: $0.42/1M 토큰 (비용 최적화首选)
저는 배치 처리 시 DeepSeek V3.2를 사용하고, 정밀한 스키마 검증이 필요한 경우 GPT-4.1을 선택합니다. 이 조합으로 월간 비용을 약 60% 절감했습니다.
자주 발생하는 오류와 해결
1. ZodValidationError: schema parsing failed
모델이 스키마와 일치하지 않는 응답을 반환할 때 발생합니다.
// ❌ 오류 발생 코드
const { object } = await generateText({
model: holysheep('gpt-4.1'),
prompt: '사용자 정보를 알려주세요',
schema: z.object({
name: z.string(),
age: z.number(), // 모델이 "스물다섯"처럼 한글로 반환
}),
});
// Error: Invalid value provided for "age": expected number, received "스물다섯"
/// ✅ 해결 방법: 스키마에 parseSafe 사용
import { z } from 'zod';
import { generateText } from 'ai';
const UserSchema = z.object({
name: z.string(),
age: z.union([
z.number(),
z.string().transform(val => {
const num = parseInt(val, 10);
return isNaN(num) ? undefined : num;
}),
]),
});
const result = await generateText({
model: holysheep('gpt-4.1'),
prompt: '사용자 정보를 알려주세요',
schema: UserSchema,
});
// 또는 세밀한 프롬프트 작성
const { object } = await generateText({
model: holysheep('gpt-4.1'),
prompt: 'JSON 형식으로 사용자 정보를 반환하세요. age는 반드시 숫자로 표현하세요. 예: {"name": "김철수", "age": 25}',
schema: UserSchema,
system: '응답은 반드시 유효한 JSON 형식으로, 숫자 필드는 숫자 값으로 반환하세요.',
});
2. 401 Unauthorized / Invalid API Key
API 키가 올바르지 않거나 HolySheep AI 서버 연결 문제입니다.
// ❌ 잘못된 설정
const holysheep = openai({
baseURL: 'https://api.holysheep.ai/v1',
apiKey: 'sk-openai-xxxx', // 이렇게 직접 입력 ❌
});
// ✅ 올바른 설정
import 'dotenv/config';
// 방법 1: 환경 변수 사용
const holysheep = openai({
baseURL: 'https://api.holysheep.ai/v1',
apiKey: process.env.HOLYSHEEP_API_KEY,
});
// 방법 2: 직접 키 지정 (테스트용)
const holysheepDirect = openai({
baseURL: 'https://api.holysheep.ai/v1',
apiKey: 'YOUR_HOLYSHEEP_API_KEY', // HolySheep 대시보드에서 발급받은 키
});
// 연결 테스트
async function testConnection() {
try {
const response = await fetch('https://api.holysheep.ai/v1/models', {
headers: {
'Authorization': Bearer ${process.env.HOLYSHEEP_API_KEY},
},
});
if (!response.ok) {
const error = await response.json();
console.error('API 오류:', error);
if (response.status === 401) {
console.error('API 키가 유효하지 않습니다. HolySheep AI 대시보드에서 확인하세요.');
}
} else {
console.log('연결 성공!');
const data = await response.json();
console.log('사용 가능한 모델:', data.data.map(m => m.id));
}
} catch (err) {
console.error('네트워크 오류:', err.message);
}
}
3. ConnectionTimeout: Request timeout exceeded
긴 응답이나 네트워크 지연 시 발생합니다.
// ❌ 기본 설정 (타임아웃 없음)
const result = await generateText({
model: holysheep('gpt-4.1'),
messages: [{ role: 'user', content: veryLongPrompt }],
});
// 타임아웃 발생 가능
// ✅ 타임아웃 및 재시도 로직 추가
import { generateText, retry } from 'ai';
import { rateLimit } from 'ai';
const result = await generateText({
model: holysheep('gpt-4.1'),
messages: [{ role: 'user', content: longPrompt }],
maxTokens: 4096,
temperature: 0.7,
// 타임아웃 설정 (ms)
headers: {
'timeout': '60000', // 60초
},
});
// 또는 retry 로직 사용
async function generateWithRetry(prompt: string, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await generateText({
model: holysheep('gpt-4.1'),
messages: [{ role: 'user', content: prompt }],
schema: UserProfileSchema,
maxTokens: 1024,
});
return result;
} catch (error) {
console.error(시도 ${attempt}/${maxRetries} 실패:, error.message);
if (attempt === maxRetries) {
throw new Error(최대 재시도 횟수 초과: ${error.message});
}
// 지수 백오프
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
}
}
}
4. UnexpectedToken: Cannot parse JSON response
모델이 JSON이 아닌 형식으로 응답할 때 발생합니다.
// ❌ 위험: raw 모델 응답 직접 사용
const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${process.env.HOLYSHEEP_API_KEY},
},
body: JSON.stringify({
model: 'gpt-4.1',
messages: [{ role: 'user', content: '사용자 정보 줘' }],
response_format: { type: 'json_object' }, // 위험!
}),
});
const data = await response.json();
const content = data.choices[0].message.content;
// 모델이 {"name": "김철수"} 또는 "김철수입니다" 등 예측 불가능한 응답 가능
// ✅ AI SDK의 구조화 출력 사용
import { generateText } from 'ai';
import { z } from 'zod';
const { object, finishReason, usage } = await generateText({
model: holysheep('gpt-4.1'),
messages: [{ role: 'user', content: '사용자 정보를 JSON으로 줘' }],
schema: z.object({
name: z.string(),
age: z.number(),
email: z.string().email(),
}),
// force: true는 모델이 반드시 스키마를 따르도록 강제
providerOptions: {
openai: {
responseFormat: { type: 'json_object' },
},
},
});
console.log('파싱된 객체:', object); // 항상 유효한 객체 보장
실전 활용 패턴
저는 실제로 다음과 같은 패턴으로 구조화 출력을 활용합니다:
- 데이터 추출 파이프라인: 웹페이지나 문서에서 구조화된 데이터 추출
- 자동 분류 시스템: 제품 리뷰, 고객 문의 등을 카테고리로 분류
- 형식 변환기: 자연어를 SQL, Regex, API等形式으로 변환
- 검증 및 분석: 입력 데이터의 유효성을 AI로 사전 검증
// 실전 예시: 이메일부터 중요 정보 추출
import { z } from 'zod';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
const holysheep = openai({
baseURL: 'https://api.holysheep.ai/v1',
apiKey: process.env.HOLYSHEEP_API_KEY,
});
const EmailAnalysisSchema = z.object({
priority: z.enum(['urgent', 'high', 'normal', 'low']),
category: z.enum(['inquiry', 'complaint', 'order', 'support', 'other']),
sentiment: z.enum(['positive', 'neutral', 'negative']),
actionRequired: z.boolean(),
keyPoints: z.array(z.string()).max(5),
responseSuggestion: z.string().max(500).optional(),
extractEntities: z.object({
dates: z.array(z.string()),
orderNumbers: z.array(z.string()).optional(),
productNames: z.array(z.string()).optional(),
}),
});
async function analyzeEmail(emailContent: string) {
const result = await generateText({
model: holysheep('gpt-4.1'),
system: '당신은 이메일 분석 전문가입니다. 이메일을 분석하여 구조화된 정보를 반환하세요.',
messages: [{ role: 'user', content: emailContent }],
schema: EmailAnalysisSchema,
});
return result.object;
}
// 사용
const email = `
안녕하세요, 先週注文した商品还没收到...
注文番号: ORD-2024-78945
商品名: 프리미엄 키보드
予定到着日: 2024-12-20
まだ届いていないので非常に困っています。
`;
const analysis = await analyzeEmail(email);
console.log(analysis);
// { priority: 'high', category: 'order', sentiment: 'negative', actionRequired: true, ... }
Zod와 AI SDK의 구조화 출력 결합은 AI 애플리케이션의 안정성을 크게 높입니다. HolySheep AI의 글로벌 게이트웨이를 사용하면 단일 API 키로 다양한 모델을 쉽게 전환하고 비용을 최적화할 수 있습니다.
👉 HolySheep AI 가입하고 무료 크레딧 받기