查询和过滤

使用RedisVL进行查询和过滤

在本文档中,您将探索可以使用RedisVL执行的更复杂的查询。

注意:
本文档是这个Jupyter笔记本的转换形式。

在开始之前,请确保以下事项:

  1. 您已经安装了RedisVL并激活了该环境。
  2. 您有一个运行中的Redis实例,具备Redis查询引擎功能。

示例二进制数据在GitHub上的这个文件中。

import pickle
from jupyterutils import table_print, result_print

# load in the example data and printing utils
data = pickle.load(open("hybrid_example_data.pkl", "rb"))
table_print(data)
用户年龄工作信用评分办公室位置用户嵌入
john18工程师-122.4194,37.7749b'\xcd\xcc\xcc=\xcd\xcc\xcc=\x00\x00\x00?'
derrick14医生-122.4194,37.7749b'\xcd\xcc\xcc=\xcd\xcc\xcc=\x00\x00\x00?'
nancy94医生-122.4194,37.7749b'333?\xcd\xcc\xcc=\x00\x00\x00?'
tyler100工程师-122.0839,37.3861b'\xcd\xcc\xcc=\xcd\xcc\xcc>\x00\x00\x00?'
tim12皮肤科医生-122.0839,37.3861b'\xcd\xcc\xcc>\xcd\xcc\xcc>\x00\x00\x00?'
taimur15首席执行官-122.0839,37.3861b'\x9a\x99\x19?\xcd\xcc\xcc=\x00\x00\x00?'
joe35牙医中等-122.0839,37.3861b'fff?fff?\xcd\xcc\xcc='
schema = {
    "index": {
        "name": "user_queries",
        "prefix": "user_queries_docs",
        "storage_type": "hash", # default setting -- HASH
    },
    "fields": [
        {"name": "user", "type": "tag"},
        {"name": "credit_score", "type": "tag"},
        {"name": "job", "type": "text"},
        {"name": "age", "type": "numeric"},
        {"name": "office_location", "type": "geo"},
        {
            "name": "user_embedding",
            "type": "vector",
            "attrs": {
                "dims": 3,
                "distance_metric": "cosine",
                "algorithm": "flat",
                "datatype": "float32"
            }

        }
    ],
}
from redisvl.index import SearchIndex

# construct a search index from the schema
index = SearchIndex.from_dict(schema)

# connect to local redis instance
index.connect("redis://localhost:6379")

# create the index (no data yet)
index.create(overwrite=True)
# inspect the newly-created index
$ rvl index listall

18:26:34 [RedisVL] INFO   Indices:
18:26:34 [RedisVL] INFO   1. user_queries
keys = index.load(data)

混合查询

混合查询是结合了多种类型过滤器的查询。例如,您可能想要搜索一个特定年龄、有特定工作并且距离某个位置一定距离的用户。这是一个结合了数值、标签和地理过滤器的混合查询。

标签过滤器

标签过滤器是应用于标签字段的过滤器。这些字段不被分词,用于存储单个分类值。

from redisvl.query import VectorQuery
from redisvl.query.filter import Tag

t = Tag("credit_score") == "high"

v = VectorQuery([0.1, 0.1, 0.5],
                "user_embedding",
                return_fields=["user", "credit_score", "age", "job", "office_location"],
                filter_expression=t)

results = index.query(v)
result_print(results)
vector_distance用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
0.158808946609tim12皮肤科医生-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749
# negation
t = Tag("credit_score") != "high"

v.set_filter(t)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0德里克14医生-122.4194,37.7749
0.217882037163taimur15首席执行官-122.0839,37.3861
0.653301358223joe中等35牙医-122.0839,37.3861
# use multiple tags as a list
t = Tag("credit_score") == ["high", "medium"]

v.set_filter(t)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
0.158808946609tim12皮肤科医生-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749
0.653301358223joe中等35牙医-122.0839,37.3861
# use multiple tags as a set (to enforce uniqueness)
t = Tag("credit_score") == set(["high", "high", "medium"])

v.set_filter(t)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
0.158808946609tim12皮肤科医生-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749
0.653301358223joe中等35牙医-122.0839,37.3861

如果你想要动态生成一个标签列表的场景怎么办?RedisVL 允许你优雅地完成这个操作,而不需要检查空的情况。空的情况是指当你尝试在一个没有定义匹配值的字段上运行标签过滤器时。例如:

Tag("credit_score") == []

像上面这样的空过滤器将产生一个* Redis查询过滤器,这意味着基本情况:没有过滤器。

# gracefully fallback to "*" filter if empty case
empty_case = Tag("credit_score") == []

v.set_filter(empty_case)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0德里克14医生-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
0.158808946609tim12皮肤科医生-122.0839,37.3861
0.217882037163taimur15首席执行官-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749
0.653301358223joe中等35牙医-122.0839,37.3861

数值过滤器

数值过滤器是应用于数值字段的过滤器,可用于隔离给定字段的数值范围。

from redisvl.query.filter import Num

numeric_filter = Num("age") > 15

v.set_filter(numeric_filter)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749
0.653301358223joe中等35牙医-122.0839,37.3861
# exact match query
numeric_filter = Num("age") == 14

v.set_filter(numeric_filter)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0德里克14医生-122.4194,37.7749
# negation
numeric_filter = Num("age") != 14

v.set_filter(numeric_filter)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
0.158808946609tim12皮肤科医生-122.0839,37.3861
0.217882037163taimur15首席执行官-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749
0.653301358223joe中等35牙医-122.0839,37.3861

文本过滤器

文本过滤器是应用于文本字段的过滤器。这些过滤器应用于整个文本字段。例如,如果您有一个包含文本“The quick brown fox jumps over the lazy dog”的文本字段,文本过滤器“quick”将匹配此文本字段。

from redisvl.query.filter import Text

# exact match filter -- document must contain the exact word doctor
text_filter = Text("job") == "doctor"

v.set_filter(text_filter)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0德里克14医生-122.4194,37.7749
0.266666650772nancy94医生-122.4194,37.7749
# negation -- document must not contain the exact word doctor
negate_text_filter = Text("job") != "doctor"

v.set_filter(negate_text_filter)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
0.158808946609tim12皮肤科医生-122.0839,37.3861
0.217882037163taimur15首席执行官-122.0839,37.3861
0.653301358223joe中等35牙医-122.0839,37.3861
# wildcard match filter
wildcard_filter = Text("job") % "doct*"

v.set_filter(wildcard_filter)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0德里克14医生-122.4194,37.7749
0.266666650772nancy94医生-122.4194,37.7749
# fuzzy match filter
fuzzy_match = Text("job") % "%%engine%%"

v.set_filter(fuzzy_match)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
# conditional -- match documents with job field containing engineer OR doctor
conditional = Text("job") % "engineer|doctor"

v.set_filter(conditional)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0德里克14医生-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749
# gracefully fallback to "*" filter if empty case
empty_case = Text("job") % ""

v.set_filter(empty_case)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0德里克14医生-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
0.158808946609tim12皮肤科医生-122.0839,37.3861
0.217882037163taimur15首席执行官-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749
0.653301358223joe中等35牙医-122.0839,37.3861

地理过滤器

地理过滤器是应用于地理字段的过滤器。这些过滤器用于查找与给定点在一定距离内的结果。距离可以以公里、英里、米或英尺为单位指定。还可以指定半径以查找在给定点一定半径范围内的结果。

from redisvl.query.filter import Geo, GeoRadius

# within 10 km of San Francisco office
geo_filter = Geo("office_location") == GeoRadius(-122.4194, 37.7749, 10, "km")

v.set_filter(geo_filter)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0德里克14医生-122.4194,37.7749
0.266666650772nancy94医生-122.4194,37.7749
# within 100 km Radius of San Francisco office
geo_filter = Geo("office_location") == GeoRadius(-122.4194, 37.7749, 100, "km")

v.set_filter(geo_filter)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0德里克14医生-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
0.158808946609tim12皮肤科医生-122.0839,37.3861
0.217882037163taimur15首席执行官-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749
0.653301358223joe中等35牙医-122.0839,37.3861
# not within 10 km Radius of San Francisco office
geo_filter = Geo("office_location") != GeoRadius(-122.4194, 37.7749, 10, "km")

v.set_filter(geo_filter)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0.109129190445tyler100工程师-122.0839,37.3861
0.158808946609tim12皮肤科医生-122.0839,37.3861
0.217882037163taimur15首席执行官-122.0839,37.3861
0.653301358223joe中等35牙医-122.0839,37.3861

组合过滤器

在这个例子中,您将结合一个数字过滤器和一个标签过滤器,搜索年龄在20到30岁之间且职位为“engineer”的用户。

交集("与")

t = Tag("credit_score") == "high"
low = Num("age") >= 18
high = Num("age") <= 100

combined = t & low & high

v = VectorQuery([0.1, 0.1, 0.5],
                "user_embedding",
                return_fields=["user", "credit_score", "age", "job",  "office_location"],
                filter_expression=combined)


result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749

联合("或")

两个查询的联合是由这两个查询中的任何一个返回的所有结果的集合。两个查询的联合是使用|操作符执行的。

low = Num("age") < 18
high = Num("age") > 93

combined = low | high

v.set_filter(combined)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0德里克14医生-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
0.158808946609tim12皮肤科医生-122.0839,37.3861
0.217882037163taimur15首席执行官-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749

动态组合

在某些情况下,您可能希望或不希望在给定的查询中使用过滤器。如上所示,过滤器将接受None类型,并恢复为返回所有结果的通配符过滤器。

过滤器组合也是如此,它可以在具有不同参数的请求中快速重用过滤器,如下所示。这消除了需要多个“if-then”条件来测试空情况的需求。

def make_filter(age=None, credit=None, job=None):
    flexible_filter = (
        (Num("age") > age) &
        (Tag("credit_score") == credit) &
        (Text("job") % job)
    )
    return flexible_filter
# all parameters
combined = make_filter(age=18, credit="high", job="engineer")
v.set_filter(combined)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0.109129190445tyler100工程师-122.0839,37.3861
# just age and credit_score
combined = make_filter(age=18, credit="high")
v.set_filter(combined)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0.109129190445tyler100工程师-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749
# just age
combined = make_filter(age=18)
v.set_filter(combined)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0.109129190445tyler100工程师-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749
0.653301358223joe中等35牙医-122.0839,37.3861
# no filters
combined = make_filter()
v.set_filter(combined)
result_print(index.query(v))
向量距离用户信用评分年龄工作办公地点
0john18工程师-122.4194,37.7749
0德里克14医生-122.4194,37.7749
0.109129190445tyler100工程师-122.0839,37.3861
0.158808946609tim12皮肤科医生-122.0839,37.3861
0.217882037163taimur15首席执行官-122.0839,37.3861
0.266666650772nancy94医生-122.4194,37.7749
0.653301358223joe中等35牙医-122.0839,37.3861

过滤查询

在某些情况下,您可能不想运行向量查询,而是只想使用类似于SQL查询的FilterExpressionFilterQuery类实现了这一功能。它与VectorQuery类类似,但仅接受FilterExpression

from redisvl.query import FilterQuery

has_low_credit = Tag("credit_score") == "low"

filter_query = FilterQuery(
    return_fields=["user", "credit_score", "age", "job", "location"],
    filter_expression=has_low_credit
)

results = index.query(filter_query)

result_print(results)
用户信用评分年龄工作
德瑞克14医生
泰穆尔15首席执行官

计数查询

在某些情况下,您可能需要使用FilterExpression来执行CountQuery,该查询仅返回相关集合中实体的数量。它与FilterQuery类类似,但不返回基础数据的值。

from redisvl.query import CountQuery

has_low_credit = Tag("credit_score") == "low"

filter_query = CountQuery(filter_expression=has_low_credit)

count = index.query(filter_query)

print(f"{count} records match the filter expression {str(has_low_credit)} for the given index.")

2 records match the filter expression @credit_score:{low} for the given index.

范围查询

范围查询对于执行向量搜索非常有用,其中只返回向量distance_threshold内的结果。这使得用户能够找到数据集中与查询向量相似的所有记录,其中“相似”由定量值定义。

from redisvl.query import RangeQuery

range_query = RangeQuery(
    vector=[0.1, 0.1, 0.5],
    vector_field_name="user_embedding",
    return_fields=["user", "credit_score", "age", "job", "location"],
    distance_threshold=0.2
)

# same as the vector query or filter query
results = index.query(range_query)

result_print(results)
vector_distance用户信用评分年龄工作
0john18工程师
0derricklow14医生
0.109129190445tyler100工程师
0.158808946609tim12皮肤科医生

您还可以在使用之间更改查询对象的距离阈值。在这里,您将设置distance_threshold==0.1。这意味着查询对象将返回所有与查询对象距离在0.1以内的匹配项。这是一个很小的距离,因此预计会比之前获得更少的匹配项。

range_query.set_distance_threshold(0.1)

result_print(index.query(range_query))
向量距离用户信用评分年龄工作
0john18工程师
0derricklow14医生

范围查询也可以像其他查询类型一样与过滤器一起使用。以下将结果限制为仅包含jobengineer的记录,同时这些记录也在向量范围内(即距离)。

is_engineer = Text("job") == "engineer"

range_query.set_filter(is_engineer)

result_print(index.query(range_query))
vector_distance用户信用评分年龄工作
0john18工程师

其他 Redis 查询

在某些情况下,RedisVL 可能无法覆盖查询所需的明确功能,这可能是由于客户端尚未实现的新版本,或者是因为非常特定的用例。在这些情况下,可以使用 SearchIndex.search 方法通过 redis-py 的 Query 对象或原始 Redis 字符串来执行查询。

redis-py

# Manipulate the redis-py Query object
redis_py_query = v.query

# choose to sort by age instead of vector distance
redis_py_query.sort_by("age", asc=False)

# run the query with the ``SearchIndex.search`` method
result = index.search(redis_py_query, v.params)
result_print(result)
向量距离年龄用户信用评分工作办公地点
0.109129190445100tyler工程师-122.0839,37.3861
0.26666665077294nancy医生-122.4194,37.7749
0.65330135822335joe中等牙医-122.0839,37.3861
018约翰工程师-122.4194,37.7749
0.21788203716315taimur首席执行官-122.0839,37.3861
014德里克医生-122.4194,37.7749
0.15880894660912tim皮肤科医生-122.0839,37.3861

原始Redis查询字符串

一种情况可能是您希望有一个仅对标签字段进行过滤且不需要其他功能的搜索。相反,您可能需要一个比RedisVL当前支持的更复杂的查询。在这些情况下,您可以使用SearchIndex.search方法与原始的Redis查询字符串。

t = Tag("credit_score") == "high"

str(t)

'@credit_score:{high}'
results = index.search(str(t))
for r in results.docs:
    print(r.__dict__)

    {'id': 'user_queries_docs:0e511391dcf346639669bdba70a189c0', 'payload': None, 'user': 'john', 'age': '18', 'job': 'engineer', 'credit_score': 'high', 'office_location': '-122.4194,37.7749', 'user_embedding': '==\x00\x00\x00?'}
    {'id': 'user_queries_docs:d204e8e5df90467dbff5b2fb6f800a78', 'payload': None, 'user': 'nancy', 'age': '94', 'job': 'doctor', 'credit_score': 'high', 'office_location': '-122.4194,37.7749', 'user_embedding': '333?=\x00\x00\x00?'}
    {'id': 'user_queries_docs:7cf3d6b1a4044966b4f0c5d3725a5e03', 'payload': None, 'user': 'tyler', 'age': '100', 'job': 'engineer', 'credit_score': 'high', 'office_location': '-122.0839,37.3861', 'user_embedding': '=>\x00\x00\x00?'}
    {'id': 'user_queries_docs:f6581edaaeaf432a85c1d1df8fdf5edc', 'payload': None, 'user': 'tim', 'age': '12', 'job': 'dermatologist', 'credit_score': 'high', 'office_location': '-122.0839,37.3861', 'user_embedding': '>>\x00\x00\x00?'}

检查查询

在这个例子中,你将学习如何检查由RedisVL生成的查询。这对于调试目的或理解查询是如何执行的非常有用。

考虑一个结合了数字过滤器和标签过滤器的查询示例。这将搜索年龄在18到100岁之间、信用评分高的用户,并按与查询向量的最近向量距离对结果进行排序。

t = Tag("credit_score") == "high"
low = Num("age") >= 18
high = Num("age") <= 100

combined = t & low & high

v.set_filter(combined)

# Using the str() method, you can see what Redis Query this will emit.
str(v)

'((@credit_score:{high} @age:[18 +inf]) @age:[-inf 100])=>[KNN 10 @user_embedding $vector AS vector_distance] RETURN 6 user credit_score age job office_location vector_distance SORTBY vector_distance ASC DIALECT 2 LIMIT 0 10'
# Cleanup
index.delete()
RATE THIS PAGE
Back to top ↑