키워드 검색

키워드 검색 (BM25)

단어 그대로 정확히 일치하는 텍스트를 찾고 싶을 때가 있어요. Weaviate의 키워드 검색은 BM25(Best Match 25) 계열 알고리즘—정확히는 BM25F—로 점수를 매겨, 가장 높은 BM25F 점수를 가진 객체부터 반환합니다. "스파스 벡터 검색"이라고도 불러요.

키워드 검색은 벡터 유사도와는 달리 토큰 일치를 기반으로 동작해서, 전문 용어·코드 식별자·정확한 단어가 중요할 때 특히 유용합니다. 하이브리드 검색의 키워드 다리 역할을 하기도 하고요.

출처: 공식문서

기본 BM25 검색

검색 문자열을 정의하는 것만으로 시작할 수 있어요.

jeopardy = client.collections.use("JeopardyQuestion")
response = jeopardy.query.bm25(
    query="food",
    limit=3
)

검색 연산자

검색 연산자는 쿼리 토큰 중 몇 개가 매칭되어야 하는지, 그 토큰들이 한 프로퍼티 안에 전부 있어야 하는지를 정의해요. 옵션은 or(기본값), and, and_cross입니다.

or

or 연산자는 검색 문자열의 토큰 중 최소 minimum match 개수 이상을 포함하는 객체를 반환합니다.

from weaviate.classes.query import BM25Operator

response = jeopardy.query.bm25(
    query="African desert wind",
    operator=BM25Operator.or_(minimum_match=1),
    limit=3,
)

GraphQL로는 이렇게 씁니다.

{
  Get {
    JeopardyQuestion(
      limit: 3
      bm25: {
        query: "Australian mammal cute"
        searchOperator: {
          operator: Or,
          minimumOrTokensMatch: 2
        }
      }
    ) {
      question
      answer
    }
  }
}

and

and 연산자는 검색 문자열의 모든 토큰이 하나의 검색 프로퍼티 안에 함께 나타나는 객체를 반환합니다.

response = jeopardy.query.bm25(
    query="African desert wind",
    operator=BM25Operator.and_(),  # 모든 토큰(african, desert, wind) 포함 필수
    limit=3,
)

and_cross

:::info v1.38.8에서 추가 :::

and_cross 연산자는 검색 문자열의 모든 토큰이 각각 최소 하나의 검색 프로퍼티에 매칭되면 됩니다. 토큰들이 같은 프로퍼티에 있을 필요는 없어요. 한 객체에서 title이 한 토큰을, body가 나머지 토큰을 매칭하면 and_cross에는 매치, and에는 매치되지 않는 경우죠.

단일 프로퍼티 요구를 완화하기 때문에 and_crossand가 반환하는 모든 객체를 반환하고, 보통 더 많은 결과를 줍니다.

:::caution 모든 검색 프로퍼티는 동일하게 설정돼야 함 and_cross는 모든 검색 프로퍼티가 같은 토큰화와 같은 analyzer 설정(토크나이저, 악센트 폴딩과 예외, 스톱워드 프리셋)을 공유해야 해요. 다르면 결과를 줄이는 대신 쿼리가 오류로 실패합니다:

OPERATOR_AND_CROSS requires all searched properties to share the same tokenization and analyzer settings

:::

아래 예시는 and_cross가 Python·GraphQL에서 동작하는 모습이에요. (JeopardyQuestion 컬렉션이 프로퍼티마다 토큰화를 섞어 쓰므로 검색을 questionanswer로 한정합니다.)

response = jeopardy.query.bm25(
    query="African desert wind",
    query_properties=["question", "answer"],
    operator=BM25Operator.and_cross(),
    limit=3,
)

BM25F 점수 가져오기

각 반환 객체의 BM25F score 값을 조회할 수 있어요.

from weaviate.classes.query import MetadataQuery

response = jeopardy.query.bm25(
    query="food",
    return_metadata=MetadataQuery(score=True),
    limit=3
)
for o in response.objects:
    print(o.properties)
    print(o.metadata.score)

선택한 프로퍼티만 검색

키워드 검색이 객체 프로퍼티의 일부만 검색하도록 지시할 수 있어요. 아래 예시는 BM25F 점수 생성에 question 프로퍼티만 사용합니다.

response = jeopardy.query.bm25(
    query="safety",
    query_properties=["question"],
    return_metadata=MetadataQuery(score=True),
    limit=3
)

가중치로 프로퍼티 부스트

각 프로퍼티가 전체 BM25F 점수에 영향을 주는 정도를 가중할 수 있어요. 아래는 question 프로퍼티를 2배로 부스트하고 answer는 그대로 두는 예시입니다.

response = jeopardy.query.bm25(
    query="food",
    query_properties=["question^2", "answer"],
    limit=3
)

토큰화 설정

BM25 쿼리 문자열은 역파일(inverted index)로 객체를 검색하기 전에 토큰화됩니다. 토큰화 방식은 컬렉션 정의에서 각 프로퍼티별로 지정해야 해요.

악센트 폴딩 (Accent folding)

텍스트 프로퍼티는 textAnalyzer.asciiFold로 악센트 폴딩을 켜서, 인덱싱·쿼리 시 악센트 문자를 ASCII 동등 문자로 정규화할 수 있어요. 예를 들어 "Café Crème"이 "cafe creme"으로도 검색됩니다. 다국어 콘텐츠에서 정확한 악센트 문자를 입력하지 않아도 BM25 재현율을 높여주는 기능입니다.

스톱워드

기본적으로 Weaviate는 영어 스톱워드("a", "the", "is" 등)를 BM25 점수에서 걸러내요. 다음과 같이 커스터마이즈할 수 있습니다.

  • 커스텀 프리셋: invertedIndexConfig.stopwordPresets로 컬렉션별로 이름 붙은 스톱워드 리스트 정의 (비영어 또는 도메인 특화 용어에 유용).
  • 프로퍼티별 오버라이드: textAnalyzer.stopwordPreset으로 다국어 컬렉션에서 프로퍼티마다 다른 스톱워드 프리셋 할당.

스톱워드는 여전히 인덱싱되고 쿼리 시점에만 걸러지므로, 설정을 바꿔도 재인덱싱이 필요 없습니다.

limit & offset

limit으로 반환할 최대 객체 수를 정하고, 선택적으로 offset으로 페이지네이션합니다.

response = jeopardy.query.bm25(
    query="safety",
    limit=3,
    offset=1
)

그룹핑(Group by)

결과를 특정 기준으로 묶을 수 있어요.

from weaviate.classes.query import GroupBy

group_by = GroupBy(
    prop="round",
    objects_per_group=3,
    number_of_groups=2,
)
response = jeopardy.query.bm25(
    query="California",
    group_by=group_by
)

필터 적용

더 구체적인 결과를 원하면 filter로 검색을 좁힙니다.

from weaviate.classes.query import Filter

response = jeopardy.query.bm25(
    query="food",
    filters=Filter.by_property("round").equal("Double Jeopardy!"),
    return_properties=["answer", "question", "round"],
    limit=3
)

퍼지 매칭 (Fuzzy matching)

BM25 검색에서 오타 허용과 퍼지 매칭을 켜려면 trigram 토큰화를 사용하세요. 텍스트를 겹치는 3-문자 시퀀스로 쪼개서, 철자 오류나 변형이 있어도 매칭을 찾아냅니다.

예를 들어 "Morgn""Morgan""org", "rga", "gan" 같은 트라이그램을 공유해 매칭됩니다.

:::tip 모범 사례

  • 퍼지 매칭이 필요한 필드에만 trigram 토큰화를 선택적으로 적용하세요. 필터링 동작이 (전체 단어가 아닌) 트라이그램 토큰화 텍스트 기반으로 크게 바뀝니다.
  • 정확 일치가 필요한 필드는 정밀도를 위해 word 또는 field 토큰화로 유지하세요. :::

Boost로 부드럽게 순위 조정

키워드(BM25) 쿼리도 v1.38부터 선택적으로 boost 인자를 받아요. 매칭 문서를 제거하지 않고 위로 끌어올립니다. 최신성, 인기도, 소프트 필터 같은 신호로 결과를 치우칠 때 유용합니다. 매칭 문서는 위로 올라가고, 나머지도 결과에 남되 더 아래로 순위가 내려갑니다.

지원 조건 타입(filter, 프로퍼티 값, 시간 감쇠, 숫자 감쇠), 곡선 선택, 블렌딩 의미, 깊이 조절은 Boost를 참고하세요.

더 알아보기 (Learn more)