fix: 재해 질문을 news_search로 유도 — web_search 기본 쿼리가 실제 사건을 놓침

실제 사고(2026-08-10): "태풍 돌핀 지금 어디 있지?"에 web_search가 자체
생성한 "태풍 돌핀 현재 위치 실시간 정보" 쿼리로 검색해 일반적인/내용없는
결과 3개(기상청 빈 포털, 오래된 지도 SPA, 무관한 결과 1개)만 받았다.
모델은 정직하게(좌표를 지어내지 않고) "검색결과가 없으니 이미 소멸했을
가능성이 높다"고 추론했다 — 추론 자체는 멀쩡했지만 전제(검색해도 안
나온다)가 검색 실패 때문에 거짓이었다. 실제로는 돌핀이 막 중국에
상륙해 500mm 폭우 경보, 100만명 이상 대피 중이었고, 같은 사건을 다룬
국내 언론사 기사가 6개 이상 있었다 — "태풍 돌핀 중국 상륙 피해"처럼
상황어를 넣은 쿼리로는 바로 찾아졌다.

news_search(NewsData.io, 실제 발행일 보장)가 정확히 이런 "지금 벌어지고
있는 일" 질문을 위한 도구인데, 재해 질문은 뉴스로 분류되지 않아
(isNewsRequest는 "뉴스/속보" 키워드가 있어야 걸림) 이 라우팅을 안 탔다.

isLiveDataRequest 안에 있던 재해 정규식을 isDisasterRequest로 분리해
handle-chat.ts의 사전 리마인더와 AUTO-RECOVER 재시도 리마인더 양쪽에
재사용했다 — 이 파일 헤더의 원칙대로, 검증을 강제하는 게이트끼리
어긋나면 한쪽만 고친 게 무의미해진다. 재해 질문엔 news_search를
우선 권하고, web_search를 쓰더라도 "현재 위치/실시간 정보" 같은
일반 쿼리 대신 "...상륙 피해", "...대피" 같은 상황어를 쓰도록
안내한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kim
2026-08-10 15:42:31 +09:00
co-authored by Claude Opus 5
parent f972b184d6
commit c5c3c0449b
3 changed files with 65 additions and 4 deletions
+31
View File
@@ -28,6 +28,7 @@ import {
isMessagingRequest,
isCancelIntent,
isRerunIntent,
isDisasterRequest,
} from '../src/gateway/guards/prompt-gates';
describe('looksLikeUnverifiedSpecClaim — 출력에서 검증 안 된 수치 감지', () => {
@@ -277,6 +278,36 @@ describe('isNewsRequest / isLiveDataRequest', () => {
});
});
// [2026-08-10] isLiveDataRequest의 인라인 재해 정규식을 분리한 것 — handle-chat.ts의 검색
// 리마인더가 재해 질문에도 news_search를 권하도록 재사용하기 위함. 실제 사고: "태풍 돌핀 지금
// 어디 있지?"에 web_search가 자체 생성한 "현재 위치 실시간 정보" 쿼리로 검색해 일반적인/내용없는
// 결과 3개만 받았고, 모델은 정직하게(수치를 지어내지 않고) "이미 소멸했을 가능성이 높다"고
// 추론했다 — 틀린 결론이었다. 같은 날 같은 사건을 다룬 국내 언론사 기사가 6개 이상 있었는데,
// "태풍 돌핀 중국 상륙 피해"처럼 상황어를 넣은 쿼리로는 바로 찾아졌다. 추론 자체는 멀쩡했지만
// 전제(검색해도 안 나온다)가 검색 실패 때문에 거짓이었다 — news_search(NewsData.io, 실제
// 발행일 보장)가 정확히 이런 "지금 벌어지고 있는 일" 질문을 위한 도구다.
describe('isDisasterRequest — 재해 질문을 news_search로 유도', () => {
test('태풍/지진 등 재해 키워드를 잡는다', () => {
assert.equal(isDisasterRequest('태풍 돌핀 지금 어디 있지?'), true);
assert.equal(isDisasterRequest('방금 지진 났어?'), true);
assert.equal(isDisasterRequest('산불 피해 상황 어때'), true);
});
test('재해 키워드 없는 일반 질문은 안 잡는다', () => {
assert.equal(isDisasterRequest('오늘 서울 날씨 어때'), false);
assert.equal(isDisasterRequest('파이썬 리스트 정렬법'), false);
});
test('긴 붙여넣기 문서 한가운데의 우연한 재해 키워드로는 안 잡힌다', () => {
const doc = '회의록을 정리해주세요.\n'
+ '참석자 발언을 순서대로 기록했습니다.\n'.repeat(20)
+ '작년 태풍 피해 복구 예산 집행 내역을 참고자료로 첨부합니다.\n'
+ '이후 논의는 다음 안건으로 넘어갔습니다.\n'.repeat(20);
assert.ok(doc.length > 600);
assert.equal(isDisasterRequest(doc), false);
});
});
describe('isUsableGroundingResult — 빈 검색결과 판별', () => {
test('실제 내용이 있으면 사용 가능', () => {
assert.equal(isUsableGroundingResult({ result: '[1] 어떤 기사 제목' }), true);