스트리밍으로 흘러나오는 AI 답변을 중간에 끊어 보았습니다
생성형 AI 응답은 스트리밍으로 조금씩 도착합니다. 다 받은 뒤 검사하면 늦기 때문에 창 단위로 보류하며 검증했는데, 노출이 0이라는 가정이 실측에서 깨졌습니다.
AI 정보보호 설계 (1)에서 응답도 검사해야 한다고 썼습니다. 그 응답 검사를 실제로 만든 기록입니다.
안녕하세요. 오픈소프트랩에서 생성형 AI 거버넌스 제품을 만드는 개발팀입니다.
저희 제품이 하는 일을 한 줄로 정리하면, 직원이 외부 생성형 AI 서비스를 사용할 때 그 요청과 응답이 회사가 정한 정책을 벗어나지 않는지 중간에서 확인하는 것입니다. 직원 PC와 AI 서비스 사이에 저희 서버를 한 단계 두고 오가는 내용을 거쳐 가게 만드는 구조이고, 이런 중계 서버를 프록시(Proxy)라고 부릅니다.
이번 글은 그중 응답을 검사하면서 겪은 문제들에 대한 것입니다.
요청 쪽은 비교적 단순합니다. 사용자가 입력을 마치고 전송하면 문장이 완성된 상태로 도착하므로, 검사한 뒤 통과시키거나 차단하면 됩니다. 응답은 상황이 다릅니다.
응답은 한 번에 오지 않는다
생성형 AI 서비스는 답변을 전부 만든 뒤 한 번에 보내지 않습니다. 생성되는 대로 조금씩 나눠 보내는 스트리밍(Streaming) 방식을 씁니다. 화면에 글자가 하나씩 이어서 나타나는 그 방식입니다.
여기서 문제가 생깁니다.
[AI 서비스] → 조각1 → 조각2 → 조각3 → … → 완료
↓ ↓ ↓
사용자 화면에 즉시 표시응답이 모두 끝난 뒤에 검사해서 정책 위반으로 판정해도, 사용자는 이미 전부 읽은 상태입니다. 차단의 의미가 없습니다.
그래서 저희는 조각을 곧바로 전달하지 않고, 일정 단위로 모아 검사한 뒤 내보내는 구조를 선택했습니다. 설정에 따라 문장 단위로 끊거나 일정 크기 단위로 끊습니다. 이 단위를 창(window)이라고 부르기로 했습니다. 검사를 통과한 창만 사용자에게 전달됩니다.
[AI 서비스] → 조각1,2,3 → [창 버퍼] → 검사 → 통과하면 전달
↓
위반이면 차단구조를 만든 뒤에는 이렇게 판단했습니다. 검사한 다음 내보내므로 위반 내용이 사용자에게 노출되는 경우는 없다고 본 것입니다.
이 가정은 사실이 아니었습니다.
문제 1. 보류해도 노출이 발생한다
창 단위로 검사한다는 것은 창 하나하나가 각각 판정된다는 뜻입니다. 앞선 창 세 개가 통과해서 화면에 이미 표시된 상태에서, 네 번째 창에서 위반이 발견되는 경우가 나옵니다. 실제 확인 과정에서 315자가 전달된 뒤에 차단이 발생한 사례가 있었습니다.
문제가 되는 지점은 감사 로그(Audit Log, 누가 언제 무엇을 했고 시스템이 어떻게 판정했는지 남기는 기록)였습니다. 사고가 났을 때 사실 확인의 근거가 되므로 실제와 일치해야 합니다.
저희는 차단이 확정되면 응답 로그에 "정책에 의해 차단되었습니다" 같은 안내 문구를 기록하고 있었습니다. 아무것도 전달되지 않았다는 가정 위에서 만든 동작입니다. 그런데 315자가 이미 전달됐다면, 로그에 남은 내용과 사용자가 실제로 본 내용이 달라집니다. 거버넌스 제품에서 감사 기록이 실제와 어긋나는 것은 기능 문제가 아니라 제품의 목적에 관한 문제입니다.
수정 방향은 단순했습니다. 전달하는 시점에 실제로 나간 텍스트를 그대로 누적하고, 마스킹이 적용됐다면 마스킹된 내용을 누적합니다. 차단이 확정되면 그 뒤에 안내 문구를 이어 붙입니다. 로그를 보면 사용자가 어디까지 봤고 어디서 끊겼는지 그대로 확인됩니다.
수정 과정에서 한 가지가 더 발견됐습니다. 사용자가 탭을 여러 개 열면 처리 경로가 여러 개 생기는데, 그대로 두면 같은 텍스트가 경로마다 중복으로 누적됩니다. 누적을 담당하는 경로를 하나로 지정해 해결했습니다.
문제 2. 차단했는데 브라우저가 계속 대기한다
저희의 차단은 처음에 억제 방식이었습니다. 위반이 발견되면 텍스트만 제거하고, 스트리밍의 구조적인 신호는 그대로 통과시켰습니다. 화면에 글자는 나가지 않지만 통신 자체는 정상적으로 흐르게 두는 방식입니다.
이 방식에서는 응답이 언제 끝나는지가 AI 서비스가 보내는 완료 신호에 달려 있습니다. AI 서비스가 "답변이 끝났다"는 신호를 보내야 브라우저가 응답 종료를 인식합니다.
그런데 차단 이후 완료 신호가 도착하기 전에 연결이 끊기는 경우가 있었습니다. 저희 확인 과정에서는 통신 방식이 대체 경로로 전환되는 상황에서 재현됐습니다. 완료 신호가 도착하지 않으면 브라우저는 응답을 계속 기다리는 상태로 남습니다. 그리고 연결이 복구되면 같은 요청을 처음부터 다시 보냅니다.
수정 방향은 이렇습니다. 위반이 확정된 시점에 답변은 이미 끝난 것이므로, AI 서비스의 완료 신호를 기다리지 않고 저희 쪽에서 그 자리에서 응답을 종료 처리합니다.
이 과정에서 한 번 방향을 잘못 잡았습니다. 처음에는 종료 처리용으로 새 응답 항목을 만들어 보냈는데, 그렇게 하면 원래 열려 있던 항목이 그대로 남아 같은 증상이 재현됐습니다. AI 서비스가 사용하던 항목 식별자를 그대로 사용해야 했습니다.
완료 신호는 응답당 한 번이어야 한다
완료 신호는 하나의 응답에서 정확히 한 번만 나가야 합니다. 이를 확인하지 못하고 신호마다 완료를 넣었다가 완료가 6번 전송됐고, 화면에 "연결 상태가 불안정합니다" 오류가 표시됐습니다. 최종적으로 다음과 같이 정리했습니다.
- 종료 처리 이후 뒤늦게 도착한 AI 서비스의 완료 신호는 제거한다
- 저희가 자체적으로 내보내는 안전 응답도 완료 신호를 포함하므로, 그 시점에 응답을 종료된 것으로 표시한다
두 번째 항목이 특히 문제였습니다. 안전 응답을 내보내는 호출 지점이 네 곳이었는데, 어느 곳도 응답을 종료된 것으로 표시하지 않고 있었습니다. 그래서 안전 응답 이후 AI 서비스의 완료 신호가 도착하면 두 번째 완료가 될 수 있었습니다. 이 경로에는 회귀 테스트를 추가했습니다.
문제 3. 재연결이 차단을 다시 실행시킨다
마지막 문제는 두 번째와 원인이 이어집니다.
브라우저는 연결이 복구되면 진행 중이던 요청을 다시 보냅니다. 확인 결과 동일한 호출 식별자가 3번 전송됐습니다.
저희에게는 중복 요청을 걸러내는 처리가 있었지만, 이 처리가 특정 유형의 요청은 검사 없이 건너뛰도록 되어 있었습니다. 그래서 재전송된 요청이 매번 AI 서비스로 전달됐고, 전달될 때마다 차단 처리가 처음부터 다시 실행됐습니다.
결과적으로 관리자에게 전송되는 위반 알림이 3건, 메일도 3통 발송됐고, 사용자 화면에는 안내 문구가 세 번 이어 붙었습니다. 한 번의 위반에 대해 알림이 세 번 나간 것입니다.
수정은 간단했습니다. 응답 단계에서 위반이 이미 확정된 요청은 종료된 요청이므로 다시 실행하지 않고 빈 응답으로 마무리합니다. 이를 판별하는 데 필요한 검증 상태 값을 중복 확인 조회에 추가했습니다.
세 문제의 원인은 하나였습니다
증상은 제각각이었지만 원인은 같았습니다. 스트리밍을 조금씩 도착하는 하나의 응답으로 이해하고 있었는데, 실제로는 각자 상태를 가진 여러 조각의 흐름이었습니다.
조각마다 판정이 달라지므로 보류해도 노출이 발생했고(문제 1), 흐름의 종료 시점을 상대가 결정하므로 연결이 끊기면 종료되지 않았으며(문제 2), 흐름이 다시 시작되므로 끝난 작업이 다시 실행됐습니다(문제 3). 응답 전체를 하나의 덩어리로 다루던 기존 이해를 조각 단위 상태로 바꾼 뒤에야 세 문제가 하나의 그림으로 정리됐습니다.
한 가지 더 있습니다. 세 문제 모두 실제 측정이 먼저 있었습니다. 315자, 완료 신호 6회, 호출 식별자 3회입니다. 스트리밍처럼 시점이 얽힌 문제는 코드만 읽어서는 확인되지 않습니다. 로그를 넣고 실제로 몇 번 발생하는지 세어본 뒤에야 수정할 수 있었습니다.
저희가 이번에 배운 것은 가정을 의심하기 전에 먼저 측정하자는 쪽에 가깝습니다. 보류 후 검증이면 노출이 없다는 가정도, 315라는 숫자를 확인한 뒤에야 수정했기 때문입니다.
오픈소프트랩 개발팀이 작성합니다. 생성형 AI 사용 통제와 AI 에이전트의 도구 실행 통제를 만들고 있습니다.