본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 30. 04:39

채팅, Searchbox를 만나다

요약

Algolia Agent Studio와 InstantSearch를 활용하여 검색 UI와 채팅 에이전트를 하나의 통합된 인터페이스로 구축하는 방법을 소개합니다. 기존의 분리된 채팅 방식에서 벗어나 키워드 검색과 AI 대화가 매끄럽게 전환되는 사용자 경험을 구현하는 과정을 다룹니다.

핵심 포인트

  • 검색과 채팅이 통합된 단일 검색 인터페이스(retrieval interface) 구축
  • Algolia Agent Studio를 통한 대화형 AI 에이전트 설정 방법
  • InstantSearch <Chat> 위젯을 활용한 React 기반 UI 통합
  • 키워드 검색과 AI 의도 파악 간의 매끄러운 전환 구현

당신의 에이전트는 너무 2025년 스타일이군요

컨퍼런스에서 Algolia는 참가자들이 실제 자판기에서 수집용 트레이딩 카드를 뽑은 다음, 제가 작성한 키오스크 앱을 사용하여 카드를 등록하고 그 가치를 확인하는 데모를 운영합니다. 개발자들에게 매우 인기가 많았습니다!

지난 11월, 저는 Algolia Agent Studio와 InstantSearch <Chat> 위젯을 기반으로 구축된 에이전트 컴포넌트가 포함된 이 키오스크의 v1 버전을 출시했습니다. 제대로 작동은 했지만, 통합 방식에 근본적인 설계 문제가 있었습니다. 채팅이 페이지 하단 구석에 위치하여 나머지 사용자 인터페이스 (UI)와는 별개의 경험으로 존재했습니다. 이는 사용자가 채팅을 찾기 위해 아래로 스크롤해야 하고, 채팅을 사용하기 위해 검색 UI를 떠났다가, 사용을 마친 후 다시 돌아와야 함을 의미했습니다.

The v1 kiosk app with the chat docked in the bottom-right corner, separate from the search results

v2에서는 검색과 채팅이 하나의 검색 인터페이스 (retrieval interface)처럼 느껴지기를 원했습니다. 즉, 쿼리가 정확할 때는 키워드 검색을, 사용자의 의도가 더 광범위할 때는 AI를 사용하며, 이 둘 사이의 매끄러운 전환 (handoff)이 이루어지는 방식입니다. 다행히 11월 이후 Agent Studio와 InstantSearch 모두 이를 더 쉽게 만들어주는 기능들을 출시했습니다.

이 포스트에서는 해당 통합 과정을 안내하고, Algolia의 라이브러리가 어떤 부분에서 구성 요소를 제공하는지, 그리고 여전히 경계가 드러나는 부분은 어디인지 보여드리겠습니다.

잠깐, Agent Studio가 무엇인가요?

잘 모르시는 분들을 위해 설명하자면, Agent Studio는 여러분의 검색 데이터(search data)를 기반으로 호스팅되는 대화형 AI (conversational AI) 경험을 구축할 수 있는 Algolia의 플랫폼입니다. 여러분은 시스템 프롬프트 (system prompt), LLM 제공자 (LLM provider), 그리고 에이전트가 쿼리할 수 있는 Algolia 인덱스 (indices)로 구성된 에이전트를 설정합니다. 이 에이전트는 모델 학습 (model training)에만 의존하는 대신, 실제 라이브 데이터를 검색함으로써 질문에 답변합니다. 그런 다음 현대적인 에이전트에서 기대할 수 있는 추가적인 도구 (tools), 메모리 (memory), LLM 가드레일 (guardrails) 및 기타 모든 기능들을 계층적으로 추가할 수 있습니다. 처음부터 에이전트를 설정하는 과정을 안내하는 quickstart가 준비되어 있습니다.

에이전트를 원하는 방식으로 설정한 후, 저는 react-instantsearch의 일부로 제공되며 검색 UI 위젯과 동일한 InstantSearch 트리 내에 존재하는 <Chat> 위젯을 사용하여 애플리케이션에 추가했습니다. 두 경험은 모두 동일한 검색 클라이언트 (search client), 동일한 인덱스 (index), 그리고 동일한 렌더링 컨텍스트 (rendering context)를 공유합니다. 이러한 공유된 기반이 진정한 통합을 가능하게 만드는 핵심입니다.

v1의 격차: 두 개의 경험, 연결되지 않은 상태

Agent Studio 덕분에 저의 원래 앱은 작동하는 챗봇과 함께 출시되었습니다. 만약 트레이딩 카드에 대해 질문하면, 에이전트는 인덱스를 검색하고 그 결과를 사용하여 답변을 생성했습니다. 하지만 검색 인터페이스와의 연결 고리는 전혀 없었습니다:

  • 검색창과 채팅을 연결하는 UI 어포던스 (UI affordance)가 없음 — 사용자는 임베디드된 위젯을 찾기 위해 스크롤해야 했습니다.
  • 검색창에 이미 입력된 쿼리로 채팅을 사전 로드 (pre-load)할 수 있는 방법이 없음
  • 검색 결과가 없는 상태 (zero-results search)에서 대화형 에이전트로 돌아가는 경로가 없음
  • 에이전트가 추천한 카드를 패싯 검색 (faceted search) 화면으로 다시 가져올 방법이 없음
  • 검색창에 자연어 질문을 입력했을 때 결과가 나오지 않는 사용자들에 대한 처리 방식이 없음

기본적으로, 앱은 우연히 한 웹페이지를 공유하고 있을 뿐인 두 개의 분리된 경험을 가지고 있었습니다. 이를 해결한다는 것은 두 경험 사이의 핸드오프 (handoff)를 구축하는 것을 의미했습니다.

검색 도구로서의 에이전트

여기서의 큰 개념적 변화는 에이전트를 단순히 검색을 수행하는 챗봇 (chatbot)이 아니라, 인덱스 (index)를 위한 두 번째 검색 모드 (retrieval mode)로 생각하는 것입니다. 이를 확정 짓는 플래그는 에이전트 설정 (config)의 한 줄입니다:

"config": { "enableAlgoliaMcp": true }

이 설정은 에이전트의 검색 동작을 Algolia의 Public MCP로 전환합니다. 이 변경 사항은 설정 수준에서는 보이지 않습니다. 동일한 인덱스, 동일한 에이전트 프롬프트 (prompt), 동일한 UI를 유지합니다. 변하는 것은 에이전트가 검색을 시도할 때 실제로 무엇을 보느냐 하는 점입니다. 새로운 에이전트에서는 이것이 기본적으로 적용되어 있지만, 저의 에이전트와 같은 기존 에이전트의 경우 직접 활성화해야 합니다.

MCP 이전에는 에이전트가 단일한 범용 algolia_search_index 도구 (tool)와 통신했습니다. 그 도구는 쿼리 (query), 선택적 필터 (filters), 그리고 페이지네이션 (pagination)을 받았습니다. 그것이 인터페이스의 전부였습니다. 에이전트가 귀하의 데이터에 대해 알기를 원하는 모든 것 — 무엇을 필터링할 수 있는지, 어떤 패싯 값 (facet values)이 존재하는지, 가격 범위는 얼마인지, 언제 어떤 방식으로 정렬해야 하는지 등 — 은 모두 프롬프트에 포함되어야 했습니다. 에이전트는 귀하의 인덱스를 불투명한 검색 엔드포인트 (endpoint)로 취급했으며, 그 엔드포인트 뒤에 무엇이 있는지 에이전트에게 가르치는 책임은 귀하에게 있었습니다.

MCP를 활성화하면 상황이 훨씬 더 풍부해집니다. 에이전트는 인덱스당 별도의 검색 도구를 갖게 되며, 공유된 패싯 값 조회 (facet-values lookup) 기능도 추가됩니다. 저의 트레이딩 카드 앱의 경우 총 4개의 도구가 생성됩니다:

  • algolia_search_index_tcg_cards_event-2026
  • algolia_search_index_tcg_cards_event-2026_price_asc
  • algolia_search_index_tcg_cards_event-2026_price_desc
  • algolia_search_for_facet_values

각 검색 도구는 자체적인 설명 (description)과 입력 스키마 (input schema)를 가집니다. 그리고 이 설명과 스키마는 매우 밀도가 높습니다! 일반적으로 수기로 작성된 도구 명세 (tool spec)보다 훨씬 더 밀도가 높은데, 이는 Algolia가 LLM을 사용하여 인덱스의 메타데이터 (metadata)로부터 이를 자동으로 생성하기 때문입니다.

다음은 실제 MCP 도구 정의를 의역하여, 에이전트가 기본 인덱스에 대해 보게 되는 내용의 일부입니다:

이 인덱스에 대하여:

  • 1개 레코드 (record) = 1개의 Pokémon 트레이딩 카드 항목.
  • 산업 분야 (Industry): 수집품 (Collectibles), 특히 트레이딩 카드 게임 (trading card games).
  • 이 인덱스를 사용할 대상: 수집가 (Collectors), 소매업자 (retailers), 재고 관리자 (inventory managers).
  • 가장 관련성 높은 속성 (attributes):
    • pokemon_name (검색 가능 (searchable))
    • card_type (검색 가능/필터링 가능 (searchable/filterable))
    • estimated_value (필터링 가능 (filterable))
    • is_chase_card (필터링 가능 (filterable))
    • is_full_art (필터링 가능 (filterable))
    • set_name (검색 가능/필터링 가능 (searchable/filterable))
    • pokemon_types (검색 가능/필터링 가능 (searchable/filterable))
  • pokemon_types는 다중 값 (multi-valued)입니다.

이것은 인덱스 게시 (index-publish) 시점에 생성된 인덱스에 대한 의미론적 설명 (semantic description)입니다. 에이전트는 단 하나의 사용자 쿼리 (user query)를 처리하기 전에 이 내용을 읽습니다. 에이전트는 이미 무엇이 레코드인지, 어떤 속성이 필터링 가능한지, 어떤 것이 다중 값인지 알고 있습니다.

입력 스키마 (input schema)는 훨씬 더 구체적입니다. 각 패싯 필터 (facet filter) 파라미터는 제 데이터에서 가져온 예시 값과 함께 자체적인 설명을 포함합니다. 이 부분이 정말로 중요한 지점입니다. MCP 레이어는 Algolia 대시보드에서 얻을 수 있는 것과 같은 구조화된 자기 성찰 (structured introspection) 기능을 에이전트에게 제공합니다. 패싯 값 (facet values) 목록, 데이터 범위, 검색 가능/필터링 가능 주석 (annotations) — MCP는 이러한 지식을 프롬프트 (prompt)에서 다시 설명하도록 요구하는 대신, 에이전트의 도구 정의 (tool definitions)로 라우팅합니다. 에이전트는 세션이 열릴 때 이를 한 번 로드합니다. 시스템 프롬프트 (system prompt)는 인덱스에 무엇이 들어있는지가 아니라, 에이전트가 무엇을 _해야 하는지_에 집중할 수 있게 됩니다.

인덱스별 분할 (per-index split)은 가격 정렬 (price sorts)에서도 구체적인 이점을 제공합니다. "가장 저렴한 에스퍼 (Psychic) 타입 카드는 뭐야?"라는 질문은 에이전트를 가상 복제본 (virtual replica)으로 유도하는데, 왜냐하면 해당 도구의 설명에 가격 오름차순 (price ascending)으로 정렬되어 있다고 명시되어 있기 때문입니다. "지금 이 머신에서 가장 가치 있는 카드는?"라는 질문은 price_desc로 향합니다. 에이전트는 도구 설명 (tool descriptions)을 읽음으로써 올바른 복제본을 선택합니다.

구체적인 보상. 저는 에이전트에게 이러한 정확한 사례들을 안내하던 여러 줄의 시스템 프롬프트 (system prompt)를 삭제했습니다. 즉, 빈 쿼리 패싯 검색 (empty-query facet searches)을 어떻게 구성하는지, 어떤 질문에 어떤 정렬 (sort)을 사용해야 하는지, 패싯 값 (facet values)이 무엇인지 등에 대한 내용이었습니다. 이제 MCP 기반 도구 (MCP-backed tools)들이 해당 컨텍스트를 스스로 보유하고 있습니다. 결과적으로 동일한 모델임에도 불구하고 제 프롬프트는 더 짧아졌고 답변은 더 신뢰할 수 있게 되었습니다.

채팅 + 검색: 쉬운 부분들

에이전트가 검색 모드 (retrieval mode)로 구성되면, 나머지는 프론트엔드 연결 (frontend wiring) 작업이었습니다. 검색창을 채팅으로 연결하고, 검색 결과가 없을 때 AI로 연결하며, 채팅 결과를 다시 검색으로 연결하는 과정입니다. 다행히 InstantSearch 팀도 이러한 트렌드를 계속 따라오고 있었으며, 대부분의 기능은 업데이트된 <Chat> 위젯 내의 속성 (properties)으로 이미 존재하고 있었습니다.

AI 모드: 검색창이 두 가지 검색 모드 모두의 진입점이 됩니다.

aiMode는 검색 필드 내에 "AI에게 물어보기 (Ask AI)" 버튼을 추가합니다. 이 버튼을 클릭하면 현재 쿼리를 가져와 해당 쿼리가 미리 로드된 상태로 채팅 위젯을 엽니다. "가치 있는 카드 (valuable cards)"를 검색했지만 원하는 것을 찾지 못한 사용자는 버튼 하나만 누르면 자신의 검색어가 이미 시작 프롬프트로 입력된 채팅 컨텍스트로 이동할 수 있습니다.

<SearchBox placeholder="Search for cards" aiMode />

주의할 점이 하나 있습니다. 만약 검색창에 이미 커스텀 스타일링 (custom styling)이 적용되어 있다면, CSS 작업 시간을 따로 확보해 두세요. AI 모드 버튼은 input 내부가 아니라 form 내부의 flex 형제 요소 (flex sibling)로 렌더링되므로, 기존에 설정해 둔 일부 스타일링 가정이 깨질 수 있습니다 (저의 경우도 그랬습니다).

사이드 패널 레이아웃: 검색 컨텍스트를 벗어나지 않고 채팅이 열립니다.

ChatSidePanelLayout은 채팅을 페이지 오른쪽 가장자리에서 슬라이드 인 패널 (slide-in panel) 형태로 엽니다. 결정적으로, 이 레이아웃은 콘텐츠를 덮어쓰는 대신 페이지 콘텐츠를 왼쪽으로 밀어냅니다. 따라서 대화가 진행되는 동안에도 검색 결과가 계속 보이게 됩니다. 두 영역은 서로 배타적인 것이 아니라 대화(dialogue)를 나누는 관계가 됩니다. 이는 팝업 형태의 채팅 모달 (chat modal)보다 훨씬 더 일관성 있게 느껴집니다.

import { Chat, ChatSidePanelLayout } from 'react-instantsearch';

<Chat agentId={agentId} layoutComponent={ChatSidePanelLayout} />

채팅 인사말(Chat greeting): 일반적인 챗봇이 아닌 검색 어시스턴트로서의 기대치를 설정합니다.

ChatGreeting은 채팅에 메시지가 없을 때 렌더링됩니다. 여기서 어떤 말을 하느냐가 중요합니다. 에이전트를 검색 어시스턴트(search assistant)로 포지셔닝하는 인사말은 일반적인 "무엇을 도와드릴까요?"와는 다릅니다. 라이브러리의 컴포넌트는 emptyComponent를 통해 전달되는 헤딩(heading), 서브헤딩(subheading), 배너 이미지(banner image)를 처리합니다. 저의 경우에는 인사말에도 클릭 가능한 스타터 프롬프트(starter prompts)를 포함하고 싶었기 때문에, 라이브러리가 제공하는 표준 경로를 벗어나야 했습니다. 이에 대한 자세한 내용은 아래의 '틈새(seams)' 부분에서 다루겠습니다.

피드백(Feedback): 검색 품질에 대한 피드백 루프를 완성합니다.

위젯에는 모든 어시스턴트 메시지에 대해 엄지 척/엄지 아래(thumbs up/down) 버튼을 포함할 수 있으며, feedback 프롭(prop)을 포함함으로써 Agent Studio 피드백 API에 연결할 수 있습니다. 이는 위젯이 검색 클라이언트의 자격 증명(credentials)으로 요청에 서명해야 하므로 선택 사항(opt-in)으로 제공됩니다. 이를 통해 기존의 키워드 분석(keyword analytics)과 일치하도록 에이전트 상호작용에 대한 피드백을 제공할 수 있습니다.

이 분야는 매우 빠르게 움직이고 있습니다. 라이브러리는 문서에 드러난 것보다 더 많은 통합 계층(integration layer)을 출시했으므로, 이러한 연결을 처음부터 직접 구축하기 전에 패키지 내보내기(package exports)를 주의 깊게 살펴보시기 바랍니다.

남은 틈새 (The remaining seams)

v2를 구축하면서, 몇몇 인계(handoff) 시점에는 아직 라이브러리 수준의 깔끔한 솔루션이 마련되어 있지 않았습니다.

Enter 키를 누르면 채팅이 열려야 합니다. 타이핑 중 검색(Search-as-you-type)은 모든 키 입력마다 실행되므로, 폼 제출(form submission)은 막다른 길입니다. 하지만 비어 있지 않은 쿼리(query)와 함께 Enter를 누르는 것은 대화형 에이전트가 있는 모든 인터페이스에서 자연스러운 동작입니다. SearchBoxonSubmit 프롭을 노출하지만, openChat 콜백은 없습니다. AI 모드 버튼의 클릭 핸들러는 내부적으로 Chat의 setOpen에 연결되어 있으며 외부로 노출되지 않기 때문입니다. 해결 방법은 onSubmit을 통해 .ais-AiModeButton에 대한 DOM 쿼리를 수행하고, 제출 이벤트가 쿼리를 전달하지 않으므로 useSearchBox에서 현재 쿼리를 읽어오는 것입니다. 다소 취약한 방식이지만, aiModeChat이 트리(tree)에 있을 때 버튼은 항상 마운트(mounted)되어 있으므로 작동은 합니다.

결과 없음(no-results) 상태는 가장 중요한 전환점(handoff point)입니다. 자연어 질문에 대해 검색 결과가 0건일 때, "검색어나 필터를 조정해 보세요"라고 조언하는 것은 잘못된 방식입니다. 검색 엔진이 고장 난 것이 아니라, 해당 쿼리에 대해 검색 모드(retrieval mode)가 적절하지 않았을 뿐이기 때문입니다. 올바른 대응은 모드 전환을 인지시켜 주는 것입니다:

찾은 카드가 없습니다. AI에게 물어보세요. 가용성, 가격 및 추천에 관한 질문에 답변할 수 있습니다.

현재 쿼리가 미리 로드된 상태로 채팅을 여는 버튼과 함께 제공해야 합니다. 이것이 키워드 검색(keyword search)이 실패하고 대화형 검색(conversational search)이 역할을 이어받는 지점입니다. 이는 막다른 길(dead end)이 아니라, 자연스러운 인계(hand-off)처럼 느껴져야 합니다.

A zero-results search for

한 가지 복잡한 점이 있었습니다: AiModeButton이 독립된 컴포넌트(component)로 내보내기(export) 되어 있지 않았습니다. 결과 없음 상태나 검색창 이외의 곳에서 채팅 진입점이 필요한 경우, InstantSearch 소스에서 반짝임(sparkle) SVG를 추출하여 얇은 래퍼(wrapper)를 만드세요. 유지보수 범위(maintenance surface)는 작습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0