Web3 개발자를 위한 벡터 검색: Qdrant를 이용한 NFT 메타데이터 검색
요약
Web3 개발자가 Qdrant와 FastEmbed를 사용하여 NFT 메타데이터에 의미론적 벡터 검색을 구현하는 방법을 소개합니다. 복잡한 설정 없이 Python 코드만으로 텍스트 기반의 정확한 일치 검색을 넘어 '분위기'와 같은 의미론적 검색을 구현할 수 있습니다.
핵심 포인트
- 정확한 일치(Exact-match) 방식의 한계를 벡터 검색으로 해결
- Qdrant와 FastEmbed를 활용한 로컬 기반의 간편한 스택 구성
- 임베딩을 통해 단어가 일치하지 않아도 의미가 유사한 NFT 검색 가능
- API 키나 Docker 없이 약 40줄의 코드로 구현 가능
온체인(on-chain)에서 무언가를 구축해 본 적이 있다면, 현재 NFT 검색이 어떻게 작동하는지 알고 있을 것입니다. 바로 정확한 일치(exact-match) 필터 방식입니다. Background = Neon City. Rarity = Legendary. Eyes = Laser. 마켓플레이스는 기본적으로 패싯 데이터베이스(faceted databases)와 같습니다. 특성(traits)을 선택하면 그리드(grid)가 나타나는 방식이죠.
원하는 것을 정확히 알고 있을 때는 완벽합니다. 하지만 사용자가 속성(attributes) 대신 '분위기(vibes)'로 생각하는 순간 이 방식은 무너집니다.
"전기 빛으로 빛나는 침울한 전사를 보여줘."
vibe = brooding(침울한)이라는 특성은 존재하지 않습니다. "brooding", "glowing", "electric"이라는 단어들이 단 하나의 NFT 메타데이터에도 나타나지 않을 수 있습니다. 정확한 일치 검색은 아무것도 반환하지 못합니다.
이것이 바로 **벡터 검색 (vector search)**이 채워주는 간극이며, 대부분의 Web3 개발자들이 아직 활용하지 못한 도구입니다. 저는 API 키, Docker, 클라우드 계정 없이 약 40줄의 Python 코드로 NFT 메타데이터에 의미론적 검색 (semantic search)을 추가하는 방법을 보여드릴 것입니다. 그런 다음 마켓플레이스에서 실제로 중요한 부분, 즉 의미론적 검색을 이미 사용 중인 특성 필터와 결합하는 방법을 보여드리겠습니다.
간략한 자기소개: 저는 지난 몇 년 동안 블록체인 분야에서 개발자 관계(developer relations) 업무를 수행했습니다. 현재는 AI 인프라 분야에서 일하고 있으며, 이 두 세계의 교집합은 양측이 인식하는 것보다 더 큽니다. 이 포스트는 그 하나의 예시입니다.
한 문장으로 요약한 아이디어
각 NFT의 텍스트를 그 의미를 포착하는 숫자 리스트(임베딩 (embedding))로 변환하여, 이 숫자들을 벡터 데이터베이스 (vector database)에 저장하고, 정확한 문자열 일치 대신 '의미'로 검색하는 것입니다.
"임베딩 (embeddings)"이나 "벡터 (vectors)"라는 말이 들릴 때 그냥 넘기셨다면 — 그것이 개념의 전부입니다. 모델이 "솜사탕 구름 속의 솜털 같은 라벤더색 토끼"라는 문장을 읽고 384개의 숫자로 이루어진 지문(fingerprint)을 생성합니다. 의미가 유사한 두 NFT는 단어를 공유하지 않더라도 유사한 지문을 갖게 됩니다. 검색은 "가장 유사한 지문을 찾아라"가 됩니다.
스택 (그리고 왜 마찰이 없는가)
- Qdrant — Rust로 작성된 오픈 소스 벡터 데이터베이스 (vector database)입니다. 인메모리 (in-memory) 방식으로 실행하므로 별도로 설치하거나 호스팅할 필요가 없습니다.
- FastEmbed — 임베딩 모델 (embedding model)을 로컬에서 실행합니다. OpenAI 키도, 속도 제한 (rate limits)도, 호출당 비용도 발생하지 않습니다.
이 조합이 중요한 이유가 있습니다. 입문자로서 시도했던 모든 "벡터 검색 입문" 튜토리얼들은 단 하나의 결과라도 보기 전에 OpenAI 키, Pinecone 계정, 그리고 Docker 데몬 (Docker daemon)을 요구했습니다. 여기서는 클론(clone)해서 바로 실행하면 됩니다.
pip install "qdrant-client[fastembed]"
Step 1: 데이터
실제 NFT 메타데이터는 IPFS에 저장되어 있거나 The Graph와 같은 인덱서 (indexer)로부터 가져옵니다. 이번 데모에서는 data/nfts.json에 사이버펑크 사무라이, 카와이(kawaii) 동물, 신비로운 유물 등 세 가지 컬렉션에 걸친 15개의 NFT가 들어 있으며, 각 NFT는 표준 마켓플레이스 메타데이터 형식을 따릅니다.
{
"token_id": 1,
"name": "Neon Ronin #001",
...
Step 2: 메타데이터를 검색 가능한 형태로 변환하기
임베딩 모델 (embedding models)은 텍스트를 읽기 때문에, 구조화된 메타데이터를 하나의 문자열로 평탄화 (flatten)합니다. 설명 (description)은 분위기를 전달하고, 특성 (traits)은 구체적인 세부 정보를 더합니다.
def nft_to_text(nft: dict) -> str:
traits = ", ".join(f"{k}: {v}" for k, v in nft["traits"].items())
return f"{nft['name']}. {nft['description']} Traits: {traits}."
Step 3: 임베딩 및 인덱싱
from fastembed import TextEmbedding
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
...
우리는 **전체 메타데이터 (full metadata)**를 페이로드 (payload)로 저장합니다. 덕분에 풍부한 결과를 반환할 수 있고, 잠시 후에 특성 (traits)을 기준으로 필터링할 수도 있습니다.
Step 4: 의미 기반 검색
def search(query, limit=3, query_filter=None):
qv = next(embedder.embed([query]))
return client.query_points(
...
이제 결과가 나타납니다. 기억하세요: 쿼리 단어 중 그 어느 것도 메타데이터에 그대로(verbatim) 나타나지 않습니다.
쿼리: "a brooding warrior glowing with electric light"
0.691 Neon Ronin #103 A wandering swordsman bathed in soft teal light...
0.670 Neon Ronin #014 A cybernetic warrior with a chrome jaw and glowing red optics...
...
세 가지 분위기 기반(vibe-based) 쿼리가 컬렉션 전반에 걸쳐 깔끔하게 분리되었습니다. 모델은 단 하나의 공유 키워드 없이도 "brooding warrior"가 사무라이로, "fluffy companion"이 귀여운 동물로, 그리고 "cursed artifact"가 흑요석 네크로틱 단검(obsidian necrotic dagger)으로 매핑된다는 것을 이해했습니다.
5단계: 마켓플레이스에서 중요한 부분
순수 시맨틱 검색(Semantic search)은 멋진 데모입니다. 하지만 마켓플레이스는 특성 필터(trait filters)를 기반으로 운영되며, 사용자들은 이를 포기하지 않을 것입니다. 좋은 소식은 둘 중 하나를 선택할 필요가 없다는 것입니다. Qdrant는 단 한 번의 쿼리로 특성에 따라 후보군을 필터링(filter)함과 동시에 시맨틱 유사도(semantic similarity)에 따라 순위를 매깁니다(rank).
from qdrant_client.models import Filter, FieldCondition, MatchValue
legendary_only = Filter(
...
쿼리: "powerful and regal" + 필터 Rarity = Legendary
0.532 Pastel Critter #251 A chubby peach-colored hamster wearing a tiny crown...
0.519 Neon Ronin #156 An armored general clad in glowing crimson nano-plates...
...
여기서 두 가지 일이 일어났습니다. 첫째, 필터가 제 역할을 수행하여 오직 Legendary 등급의 NFT만 반환되었습니다. 둘째, 그리고 이것이 제가 가장 좋아하는 결과입니다: 최상위 검색 결과가 왕관을 쓴 햄스터라는 점입니다. 모델은 공유된 단어가 전혀 없음에도 불구하고, '귀여움'과 '사나움'이라는 경계를 넘어 "regal"을 "wearing a tiny crown"과 연결했습니다. 이것이 문자열 매칭(matching strings)과 의미 매칭(matching meaning)의 차이입니다.
이것이 Web3 개발자가 가져야 할 사고 모델(mental model)의 변화입니다. 기존의 특성 필터는 구조화된(structured) 레이어가 되고, 벡터 검색은 그 위에 시맨틱(semantic) 레이어를 추가합니다. 동일한 쿼리로 두 세계를 모두 활용하는 것입니다.
프로덕션 환경으로 전환하기
변경되는 유일한 코드는 클라이언트 부분입니다:
# 로컬 개발 환경:
client = QdrantClient(":memory:")
...
인덱싱(Indexing), 검색(search), 그리고 필터링(filtering)은 동일합니다.
보너스: 실제 컬렉션에 적용하기
샘플 데이터는 데모가 즉시 실행되도록 큐레이션되었지만, 실제로는 실제 메타데이터를 사용하고 싶을 것입니다. Web3 개발자로서 제가 좋아하는 부분은 다음과 같습니다. OpenSea의 API, Alchemy 키, 심지어 web3.py조차 필요하지 않습니다. NFT 메타데이터는 온체인(on-chain)에 존재합니다. 단순한 JSON-RPC 호출로 컨트랙트에서 tokenURI를 읽기만 하면 됩니다.
import requests
SELECTOR_TOKEN_URI = "0xc87b56dd" # keccak256("tokenURI(uint256)")[:4]
...
URI를 해석하고(이는 ipfs://, HTTPS 게이트웨이, 또는 온체인 data: URI가 될 것입니다), JSON을 가져온 다음, attributes를 평탄화(flatten)하여 이전과 똑같이 인덱싱(indexing)하면 됩니다. 리포지토리의 fetch_nfts.py는 이 모든 과정을 수행한 다음 실제 Azuki 토큰에 대해 동일한 검색을 실행합니다.
Query: "someone holding a sword or katana"
0.593 Azuki #7 Hair: Orange Samurai, Headgear: Full Bandana...
0.578 Azuki #10 Hair: Green Samurai, Headgear: Black Bucket Hat...
쿼리는 "katana(카타나)"를 말했지만, 결과는 Samurai(사무라이) 머리를 한 Azuki들입니다. 공유된 단어는 없지만, 모델이 그 연결 고리를 이해한 것입니다. 알아두어야 할 솔직한 주의 사항이 하나 있습니다. 실제 PFP 컬렉션은 보통 description을 비워두고 모든 것을 attributes에 넣기 때문에, 시맨틱 검색(semantic search)은 산문(prose)보다는 특성(trait)의 조합("카타나를 들고 있는 분홍색 머리의 캐릭터")에 대해 실행됩니다. 이것이 NFT 메타데이터의 실제 형태이며, 벡터 검색(vector search)은 이를 깔끔하게 처리합니다.
다음 단계
NFT 메타데이터는 친숙한 진입로(on-ramp)이지만, 동일한 패턴은 정확한 일치(exact-match) 검색으로는 건드릴 수 없는 많은 Web3 문제들을 해결할 수 있게 해줍니다.
- "이것과 유사한 NFT" 추천 — 기존 토큰의 벡터(vector)로 검색.
- 자연어 마켓플레이스 검색 — 사용자가 원하는 것을 설명할 수 있도록 허용.
- 온체인(On-chain) 텍스트 검색 — ENS 프로필, DAO 제안, 거버넌스 스레드.
- 자전거래(Wash-trading) / 이상 탐지(anomaly detection) — 벡터 거리(vector distance)를 통해 이상치(outliers)를 발견.
전체 실행 가능한 코드는 GitHub에서 확인할 수 있습니다: github.com/midegdugarova/web3-nft-vector-search.
이를 클론(Clone)하고 실제 컬렉션의 메타데이터를 연결하기만 하면, 단 한나절 만에 의미론적(semantic) NFT 검색 시스템을 구축할 수 있습니다.
만약 Web3와 AI의 교차점에서 무언가를 구축하고 계신다면, 여러분이 어떤 작업을 하고 있는지 진심으로 듣고 싶습니다. github.com/midegdugarova에서 저를 찾아주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기