모든 기록
Spring 오픈소스 · 2026.09.05

Spring AI 스트리밍 청크 id 누락 NPE, 직접 재현해보기로 했다

id가 없는 스트리밍 청크를 맵 키로 쓰면서 발생하는 Spring AI NPE 이슈를, mock 서버로 대조군/실험군을 나눠 재현해보는 계획을 기록한다.

Spring AI 스트리밍 청크 id 누락 NPE, 직접 재현해보기로 했다 대표 이미지

Spring AI 스트리밍 NPE 이슈 스크린샷

spring-projects/spring-ai#2765

이 깃헙 이슈 하나를 재현할 예정이다.

문제

스트리밍 응답 청크의 id가 없을 때 그걸 맵 키로 쓰면 NPE(null pointer error)가 난다.

문제 설명

  • Spring AI가 스트리밍을 처리 시 여러 청크에 걸쳐 나뉘어 오는 조각들을 하나로 모아야 함
  • 그렇기에 어느 응답에 속한 조각인지 구분할 키가 필요, 거기에 응답 id를 사용함
  • 에러 이유 : ConcurrentHashMap은 null키를 허용하지 않음
Cannot invoke "Object.hashCode()" because "key" is null
at ConcurrentHashMap.get(...)
  • OpenAI 본사 API는 모든 스트리밍 청크에 id를 채워 보냄
  • OpenAI 호환을 표방하는 다른 서버는 생략하기도 함
  • 이 사례로서 DeepSeek R1이 사례로 보고
  • 표면적 이유 : null 체크가 없으니 확인
  • 실제 이유 : 프레임워크 자체가 OpenAI 본사 API를 스펙으로 가정, 호환성이 떨어진다.

실제 각 사이트의 공식 문서와 함께 확인해 본 결과, OpenAI는 id가 필수인 상황, Deepseek의 경우 공식 스펙으로도 id는 사용. 그리고 옛날 예시이기 때문에 공식 제공하는 공식 API에서 Deepseek R1이 제외됨. 현재는 V4계열로 넘어감. (오픈 가중치라 셀프 호스팅 자체만 가능) 이로 인하여 해당 이슈에서 deepseekR1이 공식 API인지, 셀프 호스팅인지, 프록시 상황인지 모두 알 수 없음. 따라서 누락의 출처를 신경 쓰는것이 아닌 id가 제외된 상황에 에러가 나는지를 확인하고 이것을 방지하고 피드백 에러를 줄 수 있는 처리가 되어있는지를 확인하는게 맞다고 봄

재현 이유

  • 2025.04.16에 작성된 이슈
  • Spring AI는 현재 2.0.1 버전인데, 해당 이슈는 1.0 GA가 나오기 전에 작성된 이슈, 그리고 2.0에서 공식 모듈 SDK로 통으로 교체되어 지금도 유효한지 자체가 불확실함
  • 공식 OpenAI API로는 재현이 불가능함 (id를 항상 채워 보냄)

왜 안고쳐졌을까? 혹은 왜 태크 봇만 확인하고 사람이 확인하지 않았을까

  • 열린 이슈와 PR이 너무 많음
  • 재현하기 위해선 별도 게이트 웨이 설정하여 확인해야함
  • 특정 서드파티 게이트웨이 이슈 아닌가? 하는 수준
  • 해당 이슈에 대해서 말한 것이 1명 밖에 없음

내가 재현할까 생각한 이유

  • 특정 이슈 하나를 해결하기 위한 게이트웨이를 만들고 이슈 재현 하는 것이 어렵지 않고 귀찮은 일이라고 생각했기 때문
  • 내가 감당한 가능한 귀찮음과 어려움이라 생각함

확인하지 않아도 되는 부분

  • deepseekR1이 실제로 id를 반환하는가 → 공식 API에서 이미 서비스 종료되어 확인 자체가 불가능
  • 리포터의 환경이 공식 API인지, 셀프호스팅인지, 프록시를 거쳤는지 → 이슈에 명시되어 있지 않고, 추적한다 해도 결론이 달라지지 않음
  • 어떤 서빙 프레임워크나 게이트웨이가 id를 빠뜨리는가 → 출처를 특정하는 것은 이 이슈의 해결과 무관함

확인해야 하는 부분

목 서버로 다음 두 가지를 대조한다.

  • 대조군: id가 포함된 정상 SSE 청크 → 스트리밍이 정상 동작하는가
  • 실험군: id를 생략한 SSE 청크 → 여기서만 문제가 발생하는가

실험 환경

  • 게이트 웨이 : Spring boot, Spring AI
  • 실제 구현 (mock 서버) : FastAPI
  • 조건 : 환경 변수를 통하여 응답 ID를 제거했다가 붙였다가를 반복할 것, 이때 이를 받는 Spring 게이트웨이가 올바른 에러를 뱉는 지를 확인
  • 이렇게 하는 이유 : 실제로 OpenAPI로 공개된 어딘가에서 응답을 받아오냐, 로컬에 셀프 호스팅을 통하여 가져오냐는 중요하지 않음. 결과적으로 Spring을 탈거고 저 에러대로면 Spring은 항상 같은 에러를 뱉을 것이라는게 중요. 그리고 없을 경우 그게 대한 방어가 유지되고 있냐도 중요함

실험군에서 확인할 것

  • Spring AI 2.0.1에서도 NPE가 발생하는가 (원본 보고는 1.0.0-M7 기준)
  • 발생한다면 스택트레이스의 위치가 원본과 대응되는가
  • 발생하지 않는다면 어떻게 처리되는가 — 조용히 무시되는가, 대체 키로 정상 동작하는가
  • 실패하는 경우, 사용자가 원인을 알 수 있는 형태인가

이어 읽으면 좋은 기록