# rb8001: web_search 의도 처리 시 안내문만 반환(기업 검색 미수행)

- 일시(KST): 2025-10-22 23:50
- 작성자: Claude (51124)
- 범위: rb8001 Slack DM 요청 → 기업 검색 응답 품질

---

## 증상
- 사용자가 “이 기업을 검색해줘.” 입력 시, 실제 검색(결과/링크/출처) 없이 일반적인 “기업 정보 검색 안내”만 반환.
- 기업명 지시어(“이 기업”) 해소 없이 일반론으로 응답 → 사용자 의도와 불일치.

## 로그 근거
- 의도 판정 및 처리 흐름(23:50:02~09):
  - `rb8001/logs/rb8001.log:66624` — [DecisionEngine] 의도 분석 시작: '이 기업을 검색해줘.'
  - `rb8001/logs/rb8001.log:66625` — [DecisionEngine] 매칭됨 - 의도: web_search
  - `rb8001/logs/rb8001.log:66630` — [DecisionEngine] web_search detected
  - `rb8001/logs/rb8001.log:66641` — Router result: success True, 결과 content가 “기업 정보 검색에 대해 안내해 드릴게요 …” (일반 안내)
  - `rb8001/logs/rb8001.log:66643` — Slack 전송 성공

## 원인(가설)
- 지시어 해소 미흡: “이 기업”에 해당하는 기업명을 이전 메시지/스레드/파일에서 추출하지 못함.
- web_search 파이프라인 미호출: `DecisionEngine → router_helper.handle_web_search()`가 실제 외부 검색 호출(Tavily/뉴스 스킬 등) 대신 LLM 안내문 생성 경로로 단축.
- 구성/키 누락 가능성: `TAVILY_API_KEY` 미설정 또는 `SKILL_NEWS_URL`/검색 서비스 엔드포인트 미활성화로 검색 호출이 비활성화되었을 수 있음.

## 개선안(실행 항목)
1) 지시어 해소(“이 기업” 해석)
- 스레드/직전 메시지/업로드된 파일 메타에서 기업명을 역추적.
- 미확정 시 단문 보충질문: “어떤 기업을 찾으시나요? (예: 컴퍼니엑스)”

2) 검색 파이프라인 강제
- `router_helper.handle_web_search()`에서 Tavily(또는 내부 뉴스 스킬) 호출을 필수화하고, 결과 3건 최소 확보 후 요약.
- 구성 확인: `TAVILY_API_KEY`, `SEARCH_MAX_RESULTS`(예: 5), 타임아웃/재시도(백오프) 적용.

3) 결과 형식 표준화(출처 포함)
- 제목 · 간략 요약 · 출처 도메인 · 날짜(UTC→KST 변환) · 클릭 가능 URL 포함.
- 0건일 때 보충질문 또는 대안 소스(DART/KED/SEC) 자동 제시.

4) 국내/해외 소스 분기
- 한국어/국내 맥락이면 DART/KED/크레딧뷰 등 1순위, 해외는 SEC/Hoovers/Thomasnet 보조.

5) 로깅/메트릭
- `search_query`, `results_count`, `sources[]`, `response_ms` 로깅.
- OpenSearch 인덱스(dataprepper-static)에 검색 이벤트 기록.

## 체크리스트(코드/설정)
- 코드
  - `rb8001/app/router/router.py` — web_search 분기에서 `router_helper.handle_web_search()` 경로로 강제, 결과 미존재 시 보충질문.
  - `rb8001/app/commands/skill_commands.py` — `_call_tavily_api()` 정상 동작/타임아웃/재시도 확인.
- 설정
  - `.env` — `TAVILY_API_KEY`, `SEARCH_MAX_RESULTS`, 네트워크 아웃바운드 허용 확인.
- 검증
  - “컴퍼니엑스 검색해줘”, “이 기업 검색해줘”(직전 메시지에 기업명 포함) 각각 3건 이상 결과/링크 포함 응답 확인.

## 교훈 ✍️
- 지시어(“이/그/저 …”)는 스레드 문맥 또는 보충질문으로 해소해야 사용 의도를 정확히 충족.
- 검색 의도는 안내문이 아니라 “결과+출처”가 핵심 가치. 외부 호출 실패 시에도 대안·재시도·보충질문이 필요.
- 결과는 시간축과 출처를 반드시 포함해 재현 가능성을 높인다.

## 대명사(이/그/저 …) 처리 방안

1) 대화 맥락 기반 대명사 해소(Heuristic + NER)
- 최근 N턴(예: 5~10)에서 NER로 기업명 후보 추출 → “이/그/저 기업/회사” 출현 시 최신·salience 높은 엔티티로 치환(스레드/파일 메타 포함).
- 모호/다중 후보면 보충질문(버튼) 노출.
- 장점/비용/난이도: 빠름·저비용·낮은 난이도. 한국어 대명사 패턴에 강함.

2) 질의 재작성(CQR: Conversational Query Rewriting)
- 직전 대화·스레드·첨부 요약을 입력으로 LLM에 “대명사를 구체 엔티티로 치환한 단일 검색 질의” 생성(예: “이 기업을 검색해줘” → “컴퍼니엑스 기업 정보 최신 뉴스/재무/프로필 검색”).
- 신뢰도 점수 낮으면 재질문으로 confirm.
- 장점/비용/난이도: 정확도 높음·중간 비용·중간 난이도(rb8001 내장 LLM로 구현 용이).

3) 엔티티 캐시/지식그래프 연동(Neo4j + Thread cache)
- 스레드별 최근 언급 엔티티 스택과 Neo4j(Organization 노드) 매핑 유지 → “이/그/저” 등장 시 캐시→그래프 순으로 정규화 후 검색.
- 캐시 미스/복수 후보는 상위 2~3개 버튼으로 확인.
- 장점/비용/난이도: 일관성/재현성 우수·중간 비용·중간 난이도(rb8001 이미 Neo4j 연결).

권장 적용 순서: Heuristic+NER → CQR 재작성 → Neo4j 캐시 통합.

---
## 조치/검증 (251023)
- 코드 적용: `app/router/message_router.py` — 대명사(이/그/저 기업·회사) 해소 후 `/search` 질의로 강제 호출 (커밋: `d0f203d`)
- 코드 적용: `app/commands/skill_commands.py` — LLM 요약 뒤 `출처:` 블록에 상위 3건 `[n] 제목 - URL` 표준 표기 (커밋: `8c40f7a`)
- 코드 적용: `app/services/workflows/web_search_workflow.py` — LangGraph 기반 웹검색 워크플로우(resolve→search→summarize→format) + sqlite 체크포인터(`state/web_search_checkpoint.sqlite`) (커밋: `a1b3b03`)
- 코드 적용: `app/router/router.py:371-383` — 내부 LLM 호출 시 `task_type='web_search'`를 `chat`으로 보정해 스키마 검증 오류 방지 (커밋: `baadbf7`)
- 코드 적용: `app/router/message_router.py` — 대명사→기업명 치환 로깅 및 그래프 실패 시 `/search` 폴백 로깅 추가 (커밋: `baadbf7`)
- 단위 테스트: `rb8001/tests/test_web_search_resolution.py` (로컬 전용, Git ignore)
  - `_resolve_search_query('이 기업을 검색해줘')` → 최근 대화의 “지에프솔루션”으로 치환 확인
  - `handle_web_search()`이 `/search 지에프솔루션 ...` 호출 확인
- 단위 테스트: `rb8001/tests/test_web_search_formatting.py` (로컬 전용, Git ignore)
  - `handle_search('지에프솔루션')` → 요약 + 출처(3건) URL 포함 응답 확인
- 단위 테스트: `rb8001/tests/test_web_search_graph.py` (로컬 전용, Git ignore)
  - `run_web_search()` → 요약 + 출처(3건) 표준 출력, 체크포인터 파일 생성 확인
- 배포: Gitea Actions(51123) → 51124 자동 반영, 헬스체크 OK (`/health` healthy)

## 추가 개선(251023 오후)
- Checkpointer 예외 수정: `thread_id`, `checkpoint_ns`를 config로 지정해 워크플로우 실행 (커밋: `7e1c057`)
- 대명사 치환 정밀화: 영문 상수(DOCS/API/URL/…) 제외, 한글 기업 접미사·영문 Company/Inc/Corp/Ltd/Labs 패턴 우선 (커밋: `7e1c057`)
- CQR/엔티티링크·rerank 추가: resolve→link→cqr→search→rerank→summarize→format 파이프 및 AsyncSqliteSaver 도입 (커밋: `8cdb444`)

---
문서 규칙: `DOCS/300_architecture/312_writing-principles.md` 준수
