대화가 남아 있어도 첨부 파일을 계속 쓸 수는 없다

저장된 대화를 다시 열 수 있다는 것과 그 대화의 제공자 파일 ID가 여전히 유효하다는 것은 별개다. 첨부 파일이 만료되거나 삭제되면, 기존 ID를 참조하는 다음 답변 요청이 실패할 수 있다. 같은 요청을 반복해도 사라진 파일이 돌아오지는 않는다.

AI Chat / Tutor에서는 이 오류를 대화의 전환 정책으로 연결했다. 서버는 상태 404와 특정 Files [...] was/were not found 문구가 함께 있는 경우 EXPIRED_FILE_INPUT으로 분류한다. 모든 404를 파일 만료로 보는 조건은 아니다.

분류 이름은 만료이지만 실제 문구만으로 만료와 다른 삭제·접근 불가를 정확히 구분하지는 못한다. 외부 오류 문구가 달라지면 이 분기에 들어오지 않을 수도 있다. 이 코드가 다루는 것은 저장된 파일 참조를 더 쓸 수 없다고 판단한 특정 응답이다.

파일 접근과 대화 보존을 나누는 문제는 Chat·Tutor의 OpenAI 프로젝트 분리에서도 드러났다. 상품별 사용량과 비용을 구분해야 했지만, 새 프로젝트의 키로 바꾸면 기존 프로젝트의 파일과 벡터스토어에 접근할 수 없었다. 당시에는 제품별 호출 모듈과 키를 나누고, Tutor는 기존 프로젝트에 남겨 과목 자료를 유지했다. Chat은 새 프로젝트로 옮기되 벡터스토어가 연결된 기존 대화는 읽기 전용으로 전환했다. 과거 대화의 열람은 보존하지만, 첨부 자료로 새 답변을 이어 만드는 기능에는 제한을 둔 선택이었다.

상태를 저장한 뒤 새 대화로 안내한다

서버의 스트림 오류 처리기는 분류 결과가 파일 만료이면 대화의 activate를 false로 바꾸고, 그 await가 끝난 뒤 SSE 오류를 보낸다. 상태 저장이 실패하면 그 오류를 기록하되 원래 파일 오류 이벤트는 계속 전달한다.

파일 오류 이후의 상태와 안내
경로저장 상태클라이언트에 전달하는 결과
비활성화 성공activate=false원래 파일 오류를 알리고 새 대화로 이동
비활성화 실패비활성 상태의 지속성 미확정원래 파일 오류는 계속 전달
다른 스트림 오류이 분기에서 대화 비활성화 안 함해당 오류를 전달

클라이언트 후속 수정은 현재 대화 쿼리를 무효화하는 대신 새 대화로 이동하도록 바꿨다. 재조회가 로컬 메시지를 초기화하는 문제를 피하려는 변경이다. 비활성화가 저장된 옛 대화를 다시 방문하면 비활성 상태에 따라 배너와 입력 제한을 표시한다.

자동 재업로드를 선택하면 새 파일 ID와 기존 대화 문맥을 어떻게 다시 연결할지 정해야 한다. 이 경로는 기존 대화에서 생성을 중단하고 새 대화를 시작하게 한다. 사용자의 재시작 부담이 생기며, 비활성화 저장을 기다리는 시간만큼 오류 알림도 늦어진다.

프로젝트 분리는 이 오류 처리와 별도의 마이그레이션이었다. 새 키를 먼저 설정한 뒤 기존 Chat 첨부 대화를 비활성화하고, 자료 조회·응답 생성·만료 자산 정리도 제품별 프로젝트를 사용하도록 맞췄다. 파일을 새 프로젝트로 옮겨 과거 대화의 생성을 복구한 것은 아니다.

허용 범위의 끝값도 실제 요청에 들어가야 한다

파일 준비 호출은 만료 설정에 2,592,000초를 넘겼지만 당시 업로드 함수는 > 3600, < 2592000일 때만 옵션을 넣었다. 저장소가 정한 범위의 양 끝을 빠뜨린 조건이다. 수정은 두 비교를 >=와 <=로 바꿨다. 여기서 말하는 범위는 그 시점의 업로드 함수 계약이다.

테스트 코드는 만료 오류와 다른 오류의 분류, 만료 때만 비활성화하기, 상태 저장 실패 뒤에도 원래 오류 보내기, 만료 설정의 두 끝값·범위 밖·생략을 검사한다. 화면 전환에는 별도 브라우저 회귀 검사가 추가되지 않았다. 서버 오류 분류와 상태 변경의 검사만으로 실제 화면의 메시지 보존이나 외부 파일의 수명까지 검증됐다고 할 수는 없다.