본문으로 건너뛰기
AIPida
채택됨초급AI 개발

LLM 출력 JSON이 가끔 ```json 코드펜스 붙어서 json.loads가 터집니다. 안정적으로 구조화 출력 받는 법?

LLM한테 분류 결과를 JSON으로 받아서 후처리하는 파이프라인인데, 대부분은 잘 오다가 가끔 앞에 "```json" 같은 코드펜스가 붙거나, 설명 문장을 앞에 한 줄 달거나, 마지막에 따옴표가 안 닫혀서 json.loadsExpecting value: line 1 column 1 같은 걸로 터집니다.

프롬프트에 "반드시 JSON만 출력"이라고 대문자로 박아도 100%는 아니라서, 야간 배치에서 하루 한두 건씩 실패하고 그게 조용히 쌓입니다. 정규식으로 중괄호 추출하는 땜빵을 하긴 했는데 따옴표 안 닫히는 케이스는 그것도 못 살려서요. 근본적으로 해결하고 싶습니다.

답변 2

  • 채택된 답변에디터 검증

    프롬프트로 "JSON만 출력해"라고 비는 건 마지노선이 아니라 보조 수단이에요. 모델이 따르려고는 하는데 확률적이라 100%가 안 됩니다. 디코딩 단계에서 포맷을 강제하는 쪽으로 가야 근본 해결이 됩니다.

    쓰는 제공자에 따라 두 갈래가 있는데, 분류/추출 작업이면 저는 tool use(function calling) 강제 쪽을 더 추천해요.

    • structured output / JSON 스키마 모드: response_format에 JSON 스키마를 넘기면 그 스키마를 따르도록 출력이 보장됩니다. 코드펜스나 "네, 다음은~" 머리말이 원천적으로 안 붙어요.
    • tool use: submit_classification 같은 가짜 도구를 하나 만들어서 input_schema에 원하는 필드를 박고, tool_choice로 그 도구를 무조건 부르게 강제합니다. 그러면 결과가 이미 파싱된 객체로 와서 json.loads 자체를 안 거치게 돼요. 직렬화를 모델 텍스트가 아니라 런타임이 해주는 거라 따옴표 escape 깨짐도 같이 사라집니다.

    그래도 야간 배치면 방어는 깔아두세요. 파싱(또는 도구 호출) 실패 시 1회 재시도, 그래도 실패하면 배치 죽이지 말고 dead-letter로 따로 빼서 다음 날 모아 보기. 한두 건 때문에 전체가 멈추는 게 제일 손해입니다.

    정규식 중괄호 추출({[\s\S]*})은 따옴표 안 닫힘 같은 건 못 살리니까 메인 전략으로 두지 마세요. 어차피 위 방법 들어가면 안 써도 됩니다.

  • 윗분 말이 정답이고, 스키마 강제 기능을 못 쓰는 환경(로컬 모델 직접 서빙 같은) 한정 차선책만 보탤게요.

    • 출력 예시 1~2개를 프롬프트에 깔고 "JSON 객체 하나만, 다른 텍스트 금지" 명시. 빈도는 줄어듭니다.
    • assistant 응답을 { 로 prefill 할 수 있는 API면 그걸 쓰세요. 머리말 텍스트가 물리적으로 못 붙습니다. 로컬이면 보통 가능.
    • 파싱은 표준 json.loads만 두지 말고 폴백으로 관대한 파서를 한 단계 더. 파이썬이면 json_repair 같은 거 쓰면 코드펜스/후행 콤마/홑따옴표 정도는 살려줍니다. (단 따옴표 안 닫힘은 이것도 케바케)

    순위는 명확합니다. 1순위 제공자 스키마 강제, 안 되면 prefill, 그것도 안 되면 repair 파서. 그리고 야간 배치면 뭘 쓰든 실패분 dead-letter로 빼는 설계가 운영상 제일 중요해요. 솔직히 이게 모델 선택보다 더 중요함.