공지 분석과 메시지 전송 분리
로스트아크 공지를 읽고 요약해 Discord로 보내는 작업을 만들었습니다. 초기에는 공지를 처리했는지만 기록했지만, 재시도를 붙이면서 이 상태 하나로는 부족해졌습니다.
공지를 분석하는 데 성공해도 전송은 실패할 수 있습니다. 긴 요약을 여러 메시지로 나눠 보냈다면 일부만 도착할 수도 있습니다. 이때 처음부터 다시 실행하면 분석을 반복하고 이미 도착한 메시지까지 다시 보내게 됩니다. 그래서 수집, 분석, 전송의 상태를 나눠 관리하도록 바꿨습니다.
분석 입력의 길이 제한
7월에는 공지 뒤쪽 내용이 요약에서 빠지는 문제가 있었습니다. 긴 결과를 Discord로 보내는 과정이 아니라, 분석 전에 본문을 5,000자로 자르던 코드가 원인이었습니다.
요약 입력에서 누락된 내용은 전송 길이를 늘려도 돌아오지 않습니다. 본문을 자르던 제한을 제거하고, 긴 요약을 여러 메시지로 나눠 보내는 처리는 유지했습니다. 이후 공지를 다시 분석해 도착한 메시지를 확인했습니다.
이 변경은 코드에 임의로 넣어 둔 제한을 없앤 것입니다. 모델의 입력 한도와 실행 시간 제한은 별도로 남아 있습니다.
전송 실패 시 분석 결과 보관
9월에는 모델 호출 실패와 메시지 전송 실패를 구분했습니다. 원문을 가져오지 못했거나 응답 JSON이 잘못된 경우에는 정상 분석 결과로 저장하지 않도록 했습니다. 실행에 사용한 CLI 경로와 버전도 로그에 남겼습니다.
정상 분석 결과가 있다면 전송 오류 때문에 모델을 다시 호출할 필요는 없습니다. 긴 결과를 나눠 보낼 때도 성공한 부분을 기록하고 실패한 나머지부터 이어 보내도록 수정했습니다. 복사해서 쓰기 불편했던 임베드 카드는 일반 텍스트로 바꿨습니다.
| 단계 | 기록하는 상태 |
|---|---|
| 목록·본문 수집 | 원문 수집의 성공 여부 |
| AI 분석 | 사용할 수 있는 결과 생성 여부 |
| Discord 전송 | 전송을 마친 메시지 범위 |
| 음성 안내 | 음성 실행 결과 |
음성 안내도 전송과 별도로 처리합니다. 음성 재생에 실패했다고 이미 도착한 Discord 메시지까지 다시 보낼 필요는 없기 때문입니다.
부분 전송을 표현하는 상태 예시
“처리 완료” 플래그 하나 대신 분석 결과와 전송 위치를 나누면 재시도할 대상을 분명하게 정할 수 있습니다. 다음은 이 방식을 설명하는 상태 예시이며 실제 저장 형식을 그대로 공개한 것은 아닙니다.
{
"notice_id": "sample-notice-001",
"source_revision": "content-hash-A",
"analysis_revision": "summary-A",
"analysis_status": "ready",
"message_count": 3,
"next_message_index": 2,
"delivery_status": "pending"
}메시지 인덱스는 0부터 시작합니다. 위 상태에서는 0번과 1번 전송을 마치고 2번을 남긴 것입니다. 모델을 다시 호출하는 대신 보관한 summary-A의 마지막 메시지부터 이어 보내면 됩니다. 본문이 바뀌거나 메시지 분할 방식이 달라졌다면 기존 위치를 그대로 적용하면 안 됩니다. 상태가 어떤 분석 결과와 연결되는지도 함께 있어야 합니다.
| 상태 | 다시 실행할 작업 |
|---|---|
| 원문 수집 실패 | 목록·본문 수집 |
| 본문 있음, 분석 결과 없음 | 같은 원문 분석 |
| 분석 결과 있음, 일부 전송 성공 | 남은 메시지 전송 |
| 전송 완료, 음성 실패 | 음성 안내만 재시도 |
| 원문 내용 변경 | 변경된 원문으로 새 분석 생성 |
다만 전송 위치를 기록한다고 중복이 완전히 사라지는 것은 아닙니다. Discord가 메시지를 받은 직후 로컬 상태 저장 전에 프로세스가 종료되면, 다음 실행에서는 성공 여부가 애매합니다. 전송 응답의 메시지 ID와 실행 기록을 남겨 판단할 수 있게 해야 합니다. 원격 전송과 로컬 파일 기록을 하나의 트랜잭션으로 묶을 수 없다는 점은 남습니다.
입력 잘림을 찾는 방법
뒤쪽 내용이 빠지는 문제를 확인할 때는 최종 요약만 비교하지 않고 모델에 넘기기 직전의 입력을 봅니다. 아래 코드는 5,000자 제한이 어떤 정보를 없애는지 보여주는 작은 재현 예시입니다.
body = "앞부분 " * 2000 + "\n마지막 항목: 점검 종료 시각 변경"
truncated = body[:5000]
assert "점검 종료 시각 변경" in body
assert "점검 종료 시각 변경" not in truncated이 상태에서는 모델이 마지막 항목을 놓친 것이 아니라 처음부터 받지 못했습니다. 입력 제한을 없앤 뒤에는 공지 본문의 끝이 모델 입력에도 들어가는지 먼저 확인하고, 그다음 요약과 나눠 보낸 메시지를 봅니다. 원문·분석 입력·분석 결과·발송 메시지를 단계별로 남기는 것이 이 문제를 다시 찾을 때도 도움이 됩니다.
무조건 큰 입력 하나로 보내는 것이 해결책은 아닙니다. 모델의 한도를 넘는 문서라면 항목 단위 분할과 결과 결합이 필요하지만, 그때도 섹션 순서와 누락 여부를 확인할 수 있어야 합니다. 이번 변경에서는 그 설계를 새로 도입했다고 쓰기보다 임의의 문자 제한을 제거한 범위를 기록했습니다.
종료 코드와 실제 수집 상태
10월에 확인한 로그에는 공지 목록을 가져오지 못한 기록이 쌓여 있었습니다. 예외를 잡은 뒤 정상 종료하고 있어서, 프로세스가 끝났다는 사실만 보면 문제를 알아채기 어려웠습니다.
새 공지가 없어서 할 일이 없는 경우와 목록 수집 자체가 실패한 경우부터 구분했습니다. 이후 수집·본문·분석·전송 중 처음 실패한 단계를 알리고, 해당 단계가 다시 성공했을 때 복구 알림을 보내도록 했습니다. 같은 오류가 계속될 때는 알림을 반복하지 않습니다.
임시파일 생성 오류로 분석이 중단된 경우에는 macOS에서 사용하는 파일 이름 형식을 수정했습니다. 이 오류를 모델 호출이나 공지 수집 실패로 묶으면 실제 원인을 찾기 어렵습니다. 재시도 여부를 정하기 전에 어느 단계에서 어떤 오류가 났는지 남겨야 했습니다.
알림 종류별 반복 처리
외부 서비스의 장애 공지는 게임 공지와 다른 기준으로 다뤘습니다. 중요도가 높은 장애 위주로 받고, 같은 장애는 최초 발생과 중요도 상승, 해결 상태를 추적했습니다. 모든 갱신을 알리는 대신 필요한 상태 변화만 전달하는 방식입니다.
공지 하나의 처리를 성공이나 실패로만 나누면, 일부 전송과 후속 작업 실패를 표현할 수 없습니다. 분석 결과와 전송 진행 상태를 따로 남긴 뒤에는 끝난 작업을 되풀이하지 않고 남은 단계부터 처리할 수 있게 됐습니다.