
LLM의 JSON 출력이 깨져서 json-repair로 해결한 이야기
요약
LLM의 JSON 출력 오류로 발생하는 파싱 에러를 해결하기 위한 'json-repair' 라이브러리를 소개합니다. 규칙 기반으로 깨진 JSON을 자동으로 수리하여 애플리케이션의 안정성을 높이는 방법을 다룹니다.
핵심 포인트
- LLM의 불완전한 JSON 출력(쉼표, 따옴표 누락 등)을 자동으로 수리
- json.loads()를 대체하여 기존 코드에 쉽게 도입 가능
- 코드 펜스나 설명문이 포함된 응답도 효과적으로 처리
- return_objects=True 옵션으로 딕셔너리 직접 반환 가능
LLM에게 JSON 형식으로 출력을 시키는 애플리케이션을 구현하고 있을 때, 출력이 사양대로 되지 않는 일이 계속되어 유효성 검사(Validation) 에러가 빈번하게 발생하고 있었습니다. 특히 JSON의 구조가 커질수록, 끝에 쉼표(Comma)가 남거나 코드 펜스(Code Fence)로 둘러싸여 반환되는 케이스가 늘어갔습니다.
먼저 PydanticModel로 스키마를 전달하여 출력을 제어하려고 시도했지만, 그럼에도 불구하고 구조가 깨지는 경우가 있어 근본적인 해결책이 되지 않았습니다. "결국 재시도(Retry) 처리로 억지로 대책을 세울 수밖에 없는 건가"라고 생각하던 차에, json-repair라는 라이브러리를 발견했습니다. 깨진 JSON을 자동으로 수리해 주는 것으로, 시도해 보니 생각보다 편리하여 소개합니다.
json-repair란
깨진 JSON을 자동으로 수리하는 Python 라이브러리입니다. 끝부분의 쉼표, 따옴표 누락, 닫는 괄호 결락 등 LLM이 자주 출력하는 구문 에러를 규칙 기반(Rule-based)으로 탐지하여 고쳐줍니다. LLM의 출력뿐만 아니라, API 응답이나 로그 파일의 깨진 JSON에도 사용할 수 있습니다.
설치
pip install json-repair
직접 사용해 보고 싶은 분께
실제로 어떤 동작을 하는지 라이브 데모(Live Demo)로 동작 확인을 할 수 있습니다. 깨진 JSON을 붙여넣기만 하면 수리 결과가 실시간으로 표시되므로, 먼저 시도해 보는 것을 추천합니다.
기본적인 사용법
from json_repair import repair_json
bad_json = '{"name": "Bob", "age": 30,}' # 끝부분 쉼표
good_json = repair_json(bad_json)
...
json.loads() 대신 사용하는 경우에는 json_repair.loads()가 편리합니다. 내부적으로 먼저 json.loads()를 시도한 후 수리로 폴백(Fallback)하기 때문에, 정상적인 JSON은 그대로 통과합니다.
예를 들어 기존 코드에서 json.loads(response)라고 작성된 부분을 json_repair.loads(response)로 바꾸는 것만으로 도입할 수 있습니다. 정상적인 JSON은 이전과 동일하게 처리되고, JSON이 깨져 있을 때만 자동으로 수리가 실행되므로, 기존 동작을 바꾸지 않고 파싱 에러를 줄일 수 있습니다.
import json_repair
# json.loads()와 동일한 기술로 사용 가능
obj = json_repair.loads(json_string)
dict를 그대로 받고 싶다면 return_objects=True를 전달하면 JSON 문자열로의 변환 처리가 생략되는 만큼 더 빠릅니다.
obj = repair_json(json_string, return_objects=True)
실제로 수리되는지 확인해 보았다
LLM이 자주 답변하는 패턴을 테스트했습니다.
| 실수 패턴 | 수리 가능 여부 |
|---|---|
| 끝부분 쉼표 | ○ |
| 따옴표 없는 키 | ○ |
Python 리터럴 (True → true) | ○ |
Python 리터럴 (None → null) | △ ("None" 문자열이 됨) |
코드 펜스 포함 (json ... ) | ○ |
| 앞뒤에 설명문이 혼입됨 | ○ |
| 중간에 끊긴 JSON | ○ |
| 여러 가지 깨짐이 혼재된 복잡한 JSON | × (구조 추측을 틀리는 경우가 있음) |
각각 실제로 실행한 결과입니다.
끝부분 쉼표 / 따옴표 없는 키
repair_json('{"name": "Bob", age: 30,}')
# => '{"name": "Bob", "age": 30}'
끝부분 쉼표와 따옴표 없는 키, 둘 다 올바르게 수리되는 것을 확인할 수 있었습니다.
Python 리터럴
repair_json('{"active": True, "value": None}')
# => '{"active": true, "value": "None"}'
True → true는 변환되었지만, None은 null이 아니라 문자열인 "None"이 된 것을 확인할 수 있었습니다. None
를 기대하고 있는 경우에는 복구 후에 별도로 체크가 필요합니다. 이러한 동작을 전환할 수 있는 옵션은 현재 제공되지 않고 있습니다.
코드 펜스(Code Fence) 포함
repair_json('```json\n{"key": "value"}\n```')
# => '{"key": "value"}'
프롬프트로 "JSON을 반환해줘"라고 지시해도, 모델에 따라서는 코드 블록으로 감싸서 답변하는 경우가 있습니다. 코드 펜스가 제거되고 JSON만 추출되는 것을 확인할 수 있었습니다.
앞뒤에 설명문이 혼입
repair_json('Sure! Here is the JSON: {"key": "value"} Let me know if you need more.')
# => '{"key": "value"}'
Sure! Here is the JSON:
와 같은 서두나, 끝부분의 Let me know if you need more.
를 통째로 제거하고 JSON만 추출되는 것을 확인할 수 있었습니다.
중간에 끊긴 JSON
repair_json('{"name": "Bob", "items": ["a", "b"')
# => '{"name": "Bob", "items": ["a", "b"]}'
max_tokens 설정이 부족하여 응답이 중간에 끊겼을 때, 닫는 괄호가 자동으로 보완되는 것을 확인할 수 있었습니다. 보완은 규칙 기반(Rule-based)이므로, 후술할 주의사항도 참조해 주세요.
여러 가지 오류가 혼재된 복잡한 JSON
실제로 발생할 법한 복합 패턴도 시도해 보았습니다. Python 리터럴, trailing comma(끝에 붙는 쉼표), 따옴표 없는 키, 닫는 괄호 누락이 동시에 혼재된 상태입니다.
어디서 무엇이 잘못되었는지 알기 쉽게 하기 위해 각 행에 주석을 달았습니다. 이 주석은 설명용이며, '''로 둘러싸인 문자열 안에 그대로 포함되기 때문에, 그대로 복사해서 실행할 수는 없다는 점에 주의해 주세요.
bad_json = '''
{
"user": {
...
출력 결과:
{
"user": {
"id": 1,
...
True → true, trailing comma 제거, 따옴표 없는 키 보완은 모두 수정되었습니다. 반면, 본래 `
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기