내 JSON 파싱 코드를 모두 삭제해 버린 단 한 줄의 코드
요약
Gemini API를 사용하여 AI 응답을 구조화된 JSON으로 받는 과정에서 발생하는 파싱 오류 문제를 해결하는 방법을 다룹니다. Pydantic 모델과 response_schema를 활용해 출력 형식을 강제함으로써 복잡한 파싱 코드 없이 안정적인 데이터를 얻는 과정을 설명합니다.
핵심 포인트
- LLM의 비정형 응답으로 인한 JSON 파싱 오류 문제 해결
- Pydantic 모델을 활용한 데이터 스키마 정의 방법
- Gemini API의 response_schema를 통한 출력 형식 강제
- 불필요한 문자열 처리 및 try/except 코드 제거
몇 시간 동안 막혀 있었습니다. 그러다 response_schema를 발견했습니다.
AI 가맹점 컴플라이언스 도구인 K-Trust를 구축하며 겪은 실제 이야기
솔직하게 말씀드릴게요.
K-Trust를 만들기 시작했을 때, 저는 가장 어려운 부분이 AI 로직일 것이라고 생각했습니다. 프롬프트 (Prompts), 채점 기준 (Scoring rubric), 컴플라이언스 요약 (Compliance summary). 그런 것들 말이죠.
JSON과 싸우느라 몇 시간을 허비하게 될 줄은 몰랐습니다.
제가 계속 마주쳤던 문제
K-Trust의 아이디어는 간단했습니다. 가맹점의 원시 데이터 (Raw data)를 Gemini에 보내고, 구조화된 리스크 보고서를 돌려받는 것이었죠. 신뢰 점수 (Trust score), 리스크 수준 (Risk level), 법적 상태 (Legal status), 요약 (Summary). 깔끔하고 예측 가능하게 말이죠.
하지만 실제로는 그렇지 않았습니다.
저의 첫 번째 접근 방식은 당연한 것이었습니다. Gemini에게 무엇을 반환할지 그냥 말해주는 것이었죠:
prompt = """
Analyze this merchant and return a JSON with:
- trust_score (int)
...
때로는 완벽하게 작동했습니다. 하지만 때때로 Gemini는 다음과 같이 반환했습니다:
Sure! Here's the compliance report:
```json
{
"trust_score": 87,
...
}
```
그러면 저의 json.loads()는 충돌(Crash)이 발생했습니다.
그래서 저는 .strip()을 추가했습니다. 그다음 .replace("`json", "")를 추가했습니다. 그다음 전체 try/except` 블록을 추가했습니다. 그리고 또 다른 블록을 추가했습니다. 저는 실제 기능 코드보다 파싱 (Parsing) 코드를 더 많이 작성하고 있었고, 그럼에도 불구하고 여전히 무작위로 오류가 발생했습니다.
터미널을 멍하니 바라보며 생각했던 것이 기억납니다. 분명 더 나은 방법이 있을 거야.
저를 구원해 준 탐구 과정
저는 Google Gemini API docs로 돌아갔습니다. 이번에는 대충 훑어보는 것이 아니라 제대로 읽었습니다. 그리고 구조화된 출력 (Structured output) 섹션 깊숙한 곳에서, 제가 완전히 놓치고 있었던 무언가를 발견했습니다:
정확한 출력 형태를 강제하기 위해 Pydantic 모델을
response_schema로 전달할 수 있습니다.
잠깐만요. 그냥 정중하게 요청하는 게 아니라 _강제 (Enforce)_할 수 있다고요?
또한 Google GenAI Python SDK docs에서 실제 코드 예제와 함께 이 내용이 언급된 것을 발견했습니다. 스키마 (Schema)를 제대로 정의하는 방법을 이해하기 위해 Pydantic BaseModel docs를 정독했습니다.
그리고 그것을 시도해 보았습니다.
모든 것이 해결된 순간
1단계 — Gemini가 반환하도록 원하는 것을 정확히 정의했습니다
from pydantic import BaseModel
class GeminiReport(BaseModel):
trust_score: int
risk_level: str
risk_tags: list[str]
legal_status: bool
financial_risk: bool
sentiment_score: bool
executive_stability: bool
summary: str
2단계 — 이를 response_schema로 Gemini에 직접 전달했습니다
ai_response = client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
response_mime_type="application/json",
response_schema=GeminiReport, # Gemini는 이 형태와 일치해야 합니다
),
)
structured_data = json.loads(ai_response.text)
그리고 끝이었습니다.
마크다운 제거도 없고. 방어적인 파싱도 없습니다. 새벽 2시에 발생하는 무작위 충돌도 없습니다.
structured_data는 매번 완벽하게 타입 지정되어 돌아왔습니다. trust_score는 항상 int였습니다. legal_status는 항상 bool이었습니다. 필드는 제가 정의한 것과 정확히 일치했습니다.
처음 작동했을 때 저는 말 그대로
그렇지 않습니다. 단지 올바른 도구가 필요할 뿐입니다.
| 지저분한 방식 | 깔끔한 방식 |
|---|---|
| LLM이 프롬프트를 따르기를 기도함 | 정확한 출력 형태 (output shape)를 강제함 |
| ... |
만약 구조화된 출력 (structured output)이 필요한 무언가를 Gemini로 구축하고 있다면, 제가 겪었던 고생을 건너뛰세요. 먼저 구조화된 출력 (structured output) 문서를 읽어보시기 바랍니다. 저도 그랬더라면 좋았을 텐데요.
도움이 된 리소스들
- 📖 Gemini Structured Output Docs — 이것이 모든 것을 바꾸어 놓았습니다
- 📖 Google GenAI Python SDK — 실제 코드 예제들이 여기에 있습니다
- 📖 Pydantic BaseModel Docs — 스키마 (schema)를 제대로 정의하기 위한 문서
- 🔗 K-Trust Live Demo — 실제 작동하는 전체 모습을 확인하세요
- 📦 K-Trust GitHub — 전체 소스 코드
- 작성자: SweetyCodes (Subhashree Behera) — AI & 풀스택 (Full-Stack) 개발자 *
- 현재 AI 기반 웹 앱을 구축하며 한국어를 배우고 있습니다 *
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기