불완전한 JSON 처리의 비효율성
AI 툴 호출 인자가 JSON 형태로 스트리밍될 때, 대부분의 청크는 불완전한 JSON 문자열을 형성합니다. 초기에는 완전한 JSON이 도착할 때까지 기다린 후 파싱하는 방식이 사용되었으나, 이는 수 초에서 수 분에 이르는 로딩 스피너로 인해 사용자에게 진행 상황을 알 수 없게 만들었습니다. 이 문제를 해결하기 위해 불완전한 JSON을 프로그래밍 방식으로 완성하는 json-repair와 같은 라이브러리들이 검토되었으나, 이러한 라이브러리들은 매번 전체 JSON 문자열을 처음부터 다시 파싱하는 O(n²) 동작 특성을 가집니다.
예를 들어, 12KB의 툴 인자가 5자 청크로 스트리밍될 경우, 총 1,500만 문자를 처리해야 하는 비효율성이 발생하며, 응답 길이가 길어질수록 성능 저하가 가시적으로 나타나 UI 지연을 초래합니다.
O(n) 상태 기반 점진적 파싱의 도입
이러한 O(n²) 문제의 해결책은 호출 간 파싱 상태를 유지하여 새로운 청크가 도착하면 중단된 지점부터 새 문자만 처리하는 O(n) 점진적 파싱 방식입니다. 이 방식의 핵심은 다음 상태 정보를 유지하는 것입니다:
-
파싱을 중단한 마지막 인덱스
-
현재 컨텍스트(객체, 배열, 중첩 깊이)
-
구성 중인 불완전한 토큰(문자열, 숫자)
-
유니코드 및 특수 문자를 위한 이스케이프 시퀀스 상태
JsonCompleter와 같은 라이브러리는 이러한 상태를 활용하여 각 호출에서 오직 새로운 데이터(delta)만을 처리합니다. 예를 들어, completer.complete('{"users": [{"name": "') 호출 후, 다음 호출 completer.complete('{"users": [{"name": "Alice"}')에서는 이전 호출에서 처리된 인덱스 21까지의 데이터를 건너뛰고 인덱스 21-28의 새로운 문자만 처리합니다. 이는 진정한 O(n) 동작으로, n은 오직 새로운 데이터의 크기만을 의미합니다.
복잡한 엣지 케이스 처리
JSON 파싱에는 여러 엣지 케이스가 존재합니다. 예를 들어, `