통합 검색·근거 기반 답변 규약 (EXPLORE ANSWER CONVENTION)
| 항목 | 내용 |
|---|---|
| 상태 | 채택 (2026-09-25) |
| 범위 | portal-foundation 을 소비하는 모든 포털의 통합 검색과 근거 기반 답변(대화형 매뉴얼 진입) |
| 결정 | 입력은 셸 통합 검색창(PortalShell 상단 검색)과 페이지 내 검색창(PortalExploreSearch) 두 표면만 쓴다. 대화 전용 패널을 새로 만들지 않는다. 검색 조합은 ExploreGateway, 답변 생성은 포털 서버(ModelGateway), 색인·검색·인용은 포털이 소유한다 |
| 대상 | AgentCenter(참조 구현) · ProcessCenter(선행 구현) · GuideCenter · DocumentLibrary · lunchapp |
| 관련 | MANUAL_CONVENTION.md(매뉴얼 진입점) · ROUTING_CONVENTION.md(경로가 화면의 원천) |
1. 왜 검색창인가
"업무 중 물어보면 근거 문서로 답한다"를 위해 새 대화 UI를 만들지 않는다. 셸에는 이미 검색창이 있고, 검색 결과·답변·인용을 그릴 자리가 이미 있다.
| 이미 있는 자산 | 위치 | 대화형 진입에서의 역할 |
|---|---|---|
| 셸 상단 검색창 | PortalShell → PortalTopNav → PortalExploreDialog | 어느 화면에서든 Ctrl/⌘ K 로 열리는 대화 표면 |
| 페이지 내 검색창 | PortalExploreSearch(exploreSearch.tsx) | 화면 안에서 검색·답변을 그대로 쓰는 표면 |
| 결과·답변 타입 | PortalExploreResult · PortalExploreAnswer(citations) | 메뉴·업무 데이터·문서·답변을 한 모양으로 표시 |
| 답변 카드 | PortalExploreAnswerCard | 답변 본문 + 인용 링크 + 권한 범위 고지를 한 곳에서 렌더 |
| 게이트웨이 클라이언트 | createExploreGatewayClient(portal-operations) | 여러 포털 색인을 한 번에 조회 |
| 대안 | 채택하지 않은 이유 |
|---|---|
| 전용 대화 패널(우측 드로어·플로팅 챗) | 진입점이 셋이 되어 "매뉴얼은 한 곳에서 찾는다"(MANUAL_CONVENTION.md §1)가 깨진다. 셸이 대화 상태·인용·권한 고지를 중복 소유한다 |
| 페이지마다 별도 채팅 컴포넌트 | 포털·화면마다 답변 모양이 갈라진다. 인용·권한 고지를 매번 다시 만든다 |
| 검색과 답변을 한 요청으로 | 검색은 즉시, 답변은 문서 독해 후 응답이다. 묶으면 첫 결과가 답변 생성만큼 느려진다(onAsk 를 분리한 이유) |
2. 규칙 (필수)
- 입력은 두 표면만 — 셸 상단 검색(
PortalShell) · 페이지 내PortalExploreSearch. 새 대화 컴포넌트를 만들지 않는다. - 결과·답변 모양은 하나 —
PortalExploreResult(kind:menu·record·document)와PortalExploreAnswer { answer, citations }. 답변 렌더는PortalExploreAnswerCard하나가 담당한다. - 입력 변경과 제출을 구분한다 —
onSearchChange(타이핑) /onSearchSubmit(Enter·검색 버튼). 조회·이동은 제출에서만 한다. - 답변은 근거와 함께만 —
citations가 비면 답변을 렌더하지 않는다(결과만 표시). 서버도 인용 없는 답변을 만들지 않는다. 출처 없는 문장은 사용자가 검증할 수 없다. - 권한 필터는 데이터 원천에서 — 결과를 모은 뒤 거르지 않는다. 게이트웨이 호출은 사용자 위임 토큰(
audience=explore-gateway)으로 하고 브라우저에 서비스 토큰을 노출하지 않는다. 부분 실패(partial·unavailablePortals)는 숨기지 않는다. - 포털은 어댑터를 제공한다 —
POST /api/explore/search(서비스 토큰 인증 +x-explore-subject-id)와 ExploreGateway 카탈로그 등록. 게이트웨이는 색인을 소유하지 않는다. - 답변 생성은 포털 서버가 ModelGateway 로 — 검색(
retrieval)과 생성(completion)을 분리 호출하고 AI 기능 대장에 등록한다. 게이트웨이 스트리밍은 미지원이므로 1차는 비스트리밍이다. - 매뉴얼 대화형 진입도 같은 계약 — 매뉴얼 화면·셸 어디서 열어도 같은 검색창·같은 답변 카드를 쓴다(
MANUAL_CONVENTION.md§7). - 실패는 대화창 안에서 말한다 — 연합 검색의 부분 실패는
exploreUnavailablePortals, 답변 실패는exploreAnswerError로 셸에 넘기고 대화창 안에 표시한다. 포털 화면 상단에 상시 배너를 두지 않는다: 사라지지 않는 경고는 배경이 되어 읽히지 않고, 레이아웃을 밀어낸다(2026-10 피드백). 답변 실패는 대기 표시도 멈춘다 — 영원한 "읽는 중"은 실패를 숨긴다.
3. 두 표면 계약
| 표면 | 컴포넌트 | 조회 | 답변 | 실패 표시 |
|---|---|---|---|---|
| 셸 상단 | PortalShell → PortalTopNav → PortalExploreDialog | exploreResults(미지정 시 현재 포털 메뉴를 바로가기 결과로 채운다) | onExploreAsk · exploreAnswer · exploreAnswerLoading | exploreUnavailablePortals · exploreAnswerError(대화창 안) |
| 페이지 내 | PortalExploreSearch | onSearch(query) => Promise<PortalExploreSearchResponse> | onAsk · answer · answerLoading | 검색창이 partial·unavailablePortals 를 스스로 표시 |
공통 props: locale(ko·en) · onSubmit(셸은 onSearchSubmit) · onSelect · answer.citations.
4. 포털 구현 순서
- 어댑터 — 포털 데이터에서 결과를 만들어
POST /api/explore/search로 노출한다(권한은 그 포털이 판단). - 카탈로그 — ExploreGateway 에 포털을 등록한다(주소·색인 범위). 미등록이면 게이트웨이가 그 포털을 건너뛴다(
unavailablePortals). - 배선 — 셸에는
exploreResults·onExploreSelect·onExploreAsk·exploreAnswer를, 화면에는PortalExploreSearch를 붙인다. - 답변 — 포털 서버가 검색 결과 상위 문서를 ModelGateway 로 요약하고
citations를 채운다. 인용이 없으면 답변 대신 결과만 돌려준다.
5. 상태
| 항목 | 상태 |
|---|---|
셸 검색창 제출 구분(onSearchSubmit) · 답변 슬롯(onExploreAsk·exploreAnswer) · 공용 답변 카드 | 구현 완료(portal-foundation 0.6.158) |
페이지 내 검색창 답변 슬롯(onAsk·answer) | 구현 완료(0.6.158) |
관리자 셸(PortalAdminShell)의 같은 슬롯 | 구현 완료(0.6.158 — PortalShellProps 상속으로 그대로 전달되므로 관리자 셸도 같은 검색창·같은 답변 카드를 쓴다) |
| AgentCenter 어댑터·카탈로그 | 구현 완료(2026-09-25 · D-070 — POST /api/explore/v1/search 어댑터, 셸 explore.search) |
| AgentCenter 근거 기반 답변 | 구현 완료(2026-09-25 · D-071·D-072 — 포털 서버가 ModelGateway /v1/chat/completions 로 요약, 근거 0건이면 모델을 부르지 않음, AI_ENABLED 구성 시에만 슬롯 활성) |
| ProcessCenter 게이트웨이 어댑터 | 선행 구현 |
부분 실패·답변 실패의 대화창 내 표시(exploreUnavailablePortals·exploreAnswerError) | 구현 완료(0.6.185 — 포털 화면 상단의 상시 배너를 걷어내고 대화창 안에서 표시) |
6. 검증
- 셸: 제출/입력 구분, 답변 카드·질문 콜백 전달 —
packages/ui-react/src/explore/portalShellExplore.contract.test.tsx. - 대화창: 제출 분리·답변 인용·로딩 비활성 —
packages/ui-react/src/explore/PortalExploreDialog.browser.test.tsx. - 페이지 검색창: 질문 전달·답변 인용·로딩 —
packages/ui-react/src/exploreSearch.contract.test.tsx. - 포털: 어댑터 응답이
PortalExploreSearchResponse계약(results·partial·unavailablePortals·requestId)을 만족하는지 테스트로 고정한다. - 부분 실패·답변 실패: 셸 전달 —
packages/ui-react/src/explore/portalShellExplore.contract.test.tsx, 대화창 표시(부분 실패 줄·대기 정지) —packages/ui-react/src/explore/PortalExploreDialog.browser.test.tsx.
7. 소비 포털 체크리스트
- 검색창을 새로 만들지 않고 셸/
PortalExploreSearch를 썼는가. - 조회는
onSubmit에서만 하는가(타이핑마다 게이트웨이를 때리지 않는가). - 답변에
citations를 채웠는가(없으면 답변을 렌더하지 않는다). - 브라우저에 서비스 토큰을 노출하지 않고 위임 토큰을 쓰는가.
- 부분 실패를 대화창 안에서 알리는가(
exploreUnavailablePortals·exploreAnswerError) — 포털 화면 상단에 상시 배너를 두지 않았는가.