Ollama 구조화 출력 실전, JSON 스키마와 검증을 분리하는 법
AI를 활용하여 생성한 이미지입니다.
로컬 AI에게 JSON으로 답해 달라고 요청하는 것과, 애플리케이션이 정해진 구조의 JSON만 받는 것은 다릅니다. Ollama의 format: "json"은 JSON 형태를 유도하지만 필드 이름, 자료형, 필수 항목까지 고정하지는 않습니다. 데이터 파이프라인에 연결하려면 JSON 스키마를 전달하고 프로그램에서 다시 검증해야 합니다.[1]
| 방법 | 확인하는 것 | 남는 문제 |
|---|---|---|
format: "json" |
응답을 JSON 형태로 유도합니다. | 필드와 자료형이 일정하지 않을 수 있습니다. |
| JSON 스키마 | 필드, 타입, 필수값을 계약으로 제시합니다. | 값의 진실성이나 원문 근거까지 보장하지 않습니다. |
| Pydantic·Zod 검증 | 프로그램이 구조와 허용값을 확인합니다. | 실패 처리와 재시도 설계가 필요합니다. |
Ollama 문서는 format에 JSON 스키마 객체를 전달하는 예시와, Python에서 Pydantic 모델의 model_json_schema()를 넘긴 뒤 model_validate_json()으로 확인하는 흐름을 안내합니다.[1] 형식 검증과 사실 확인을 같은 단계로 취급하지 않는 것이 핵심입니다.
가장 작은 스키마부터 시작하기
처음부터 중첩 배열과 선택 필드를 모두 넣으면 실패 원인을 찾기 어렵습니다. 로그 한 줄을 분류하는 예라면 category, severity, summary처럼 반드시 필요한 필드부터 고정하세요. 원인, 조치, 참조 문서는 실제 후속 처리에서 필요할 때 추가합니다.
from ollama import chat
from pydantic import BaseModel
class Finding(BaseModel):
category: str
severity: str
summary: str
response = chat(
model="qwen3:8b",
messages=[{"role": "user", "content": "로그를 분류해줘: ..."}],
format=Finding.model_json_schema(),
options={"temperature": 0},
)
finding = Finding.model_validate_json(response.message.content)
스키마를 프롬프트에도 간단히 설명하면 모델이 출력 구조를 따르는 데 도움이 될 수 있지만, 프롬프트에 규칙을 적었다고 검증기를 생략할 수는 없습니다.[1] 허용값과 필수 여부는 코드의 스키마를 마지막 방어선으로 두세요.
파싱 실패와 검증 실패를 나누기
응답 앞뒤에 설명 문장이 붙은 JSON 파싱 실패, 숫자를 문자열로 보낸 타입 오류, 허용하지 않은 열거값, 빈 필드는 원인이 다릅니다. 원문 응답을 검증 전에 보존하고 모델 이름과 스키마 버전도 함께 기록하세요. 파싱이 안 된 응답을 임의로 잘라내 데이터베이스에 넣기보다, 원문을 남긴 채 제한적으로 재요청하는 편이 추적하기 쉽습니다.
- JSON 파싱 실패와 스키마 검증 실패를 서로 다른 상태로 기록합니다.
- 첫 실패에서는 같은 입력으로 한 번 재시도하되, 원문과 오류를 보존합니다.
- 계속 실패하면 입력을 줄이거나 스키마를 단순화합니다.
- 자동 처리에 넣지 못한 결과는 사람 검토 대기열로 보냅니다.
severity에 low, medium, high만 허용한다면 Pydantic이나 Zod에서 다시 확인해야 합니다. 필드가 사라진 경우와 원문에 근거가 없어 null인 경우도 구분하세요. 선택 필드를 허용할 때 후속 코드가 null을 정상적으로 처리하는지까지 확인해야 합니다.
구조화 출력과 컨텍스트·메모리
Ollama FAQ는 기본 컨텍스트 창을 4096토큰으로 설명하며 API 요청의 options.num_ctx로 바꿀 수 있다고 안내합니다.[2][3] 긴 문서를 구조화할 때 원문과 JSON 출력이 같은 컨텍스트를 나눠 쓰므로, 스키마만 맞추려고 값을 무작정 키우면 메모리 압박이 커질 수 있습니다. 짧은 입력은 기본값을 쓰고, 긴 입력은 문서를 나누어 처리한 뒤 결과를 합치는 방식이 관리하기 쉽습니다.
API 문서에는 num_ctx, temperature, seed 같은 실행 옵션이 정의돼 있습니다.[2] 회귀 확인에서 seed를 고정할 수는 있지만, 한 입력의 응답이 반복된다고 다른 입력에서도 안정적이라고 판단하면 안 됩니다. 정상 분류, 누락 필드, 긴 입력, 예상 밖 문자처럼 다른 사례를 고정해 확인하세요.
keep_alive는 모델을 메모리에 유지하는 시간과 관련됩니다. 같은 모델을 연속 호출하는 파이프라인에서는 로딩 반복을 줄일 수 있지만, 여러 모델을 번갈아 쓰는 컴퓨터에서는 상주 모델이 메모리를 오래 차지할 수 있습니다. 병렬 요청도 컨텍스트 길이와 함께 메모리를 늘릴 수 있으므로, 출력 형식보다 자원 한도를 먼저 정하세요.[3]
스키마를 버전이 있는 데이터 계약으로 관리하기
한 번 만든 스키마를 프롬프트 문장처럼만 관리하면 시간이 지나면서 필드 의미가 흔들립니다. 허용값 목록이나 필수 필드를 바꿀 때는 스키마 버전을 올리고, 기존 결과를 읽는 코드와 새 결과를 만드는 코드가 잠시 함께 동작할 수 있게 하세요. 원문 근거가 필요한 분류라면 결과에 근거 문장이나 원문 위치 필드를 둘 수 있지만, 그 문장이 실제 원문에 있는지는 문자열 검사나 사람 검토로 다시 확인해야 합니다.
로컬 모델은 네트워크 비용이 없어도 CPU·GPU 시간과 사용자의 대기 시간이 듭니다. 무한 재시도 대신 첫 실패 한 번만 재요청하고, 두 번째 실패에서는 입력을 줄이거나 사람 검토로 넘기는 식으로 정책을 정하세요. 긴 문서 전체를 매번 다시 보내지 않도록 실패한 단위만 재처리할 수 있게 나누면 시간과 메모리를 아낄 수 있습니다. 개인정보가 포함된 원문 로그는 저장 전에 마스킹해야 합니다.
저장 전 검증과 운영 상태를 분리하기
검증을 통과한 결과라도 데이터베이스에 바로 확정하지 말고, 자동 저장 가능 상태와 사람 확인 필요 상태를 나누세요. 예를 들어 필수 필드와 허용값은 맞지만 원문 위치가 비어 있으면 구조적으로는 성공이어도 업무상 보류해야 합니다. 반대로 원문 근거가 있고 선택 필드가 null인 결과는 스키마가 허용하는 범위에서 정상 처리할 수 있습니다. 이런 상태를 코드에 명시하면 후속 담당자가 오류와 정상적인 미확인 값을 혼동하지 않습니다.
스키마를 바꿀 때는 기존 결과를 새 코드로 읽을 수 있는지 먼저 확인하고, 호환되지 않으면 변환기를 두거나 버전별 저장 경로를 분리하세요. 결과에 모델명, 스키마 버전, 입력 식별자, 검증 결과를 함께 남기면 나중에 문제가 모델 응답인지 입력인지 코드 변경인지 추적하기 쉽습니다. 단, 원문에 개인정보가 있다면 운영 로그에는 필요한 식별자만 남겨야 합니다.
OpenAI 호환 API에서 확인할 점
Ollama 문서는 구조화 출력이 OpenAI 호환 API의 response_format을 통해서도 동작한다고 설명합니다.[1] 하지만 클라이언트마다 스키마 필드와 오류 형태가 다를 수 있습니다. 로컬 /api/chat 호출이 성공했다고 호환 API 경로도 같다고 가정하지 말고, 실제 사용할 엔드포인트에서 작은 스키마로 먼저 확인하세요.
Ollama 설치와 첫 모델 실행이 아직이라면 Ollama로 로컬 AI 시작하기에서 기본 실행 순서를 먼저 확인할 수 있습니다.
운영 전 체크리스트
- 스키마와 모델 이름, 버전을 코드와 함께 저장합니다.
- 응답 원문을 검증 전 기록하되 개인정보를 제거합니다.
- 필수 필드, 타입, 허용값을 코드에서 확인합니다.
- 파싱·구조·사실 근거 실패를 각각 다른 상태로 기록합니다.
- 재시도 횟수와 사람 검토 경로를 정합니다.
- 검증을 통과해도 데이터베이스 저장 전 값의 범위와 원문 근거를 확인합니다.
자주 묻는 질문
JSON 스키마를 쓰면 내용도 사실인가요?
아닙니다. 스키마는 구조를 확인할 뿐입니다. 값의 범위, 원문 근거, 최신성은 별도로 검증해야 합니다.
실패하면 temperature만 낮추면 되나요?
항상 그렇지는 않습니다. 입력 길이, 스키마 복잡도, 허용값 설계, 컨텍스트와 메모리 상태를 함께 봐야 합니다.
검증 실패 응답을 자동으로 고쳐도 되나요?
중요 데이터라면 원문을 보존하고 제한된 재시도나 사람 검토로 보내세요. 임의 문자열 절단은 근거와 오류를 숨길 수 있습니다.
자동 후속 처리의 경계는 결과의 중요도에 따라 달라져야 합니다. 단순 분류 화면이라면 실패를 보류 목록으로 보내도 되지만, 결제·권한 변경·고객 통지처럼 되돌리기 어려운 작업은 구조 검증과 원문 근거 확인을 모두 통과한 뒤에도 사람 승인을 두는 편이 안전합니다. 구조화 출력은 연결을 쉽게 만드는 도구이지 책임 판단을 없애는 기능이 아닙니다.