【AI】利用Azure AI的元数据过滤器提升 RAG 性能并增强向量搜索案例

2025-03-15T08:50:09+08:00 | 7分钟阅读 | 更新于 2025-03-15T08:50:09+08:00

Macro Zhao

【AI】利用Azure AI的元数据过滤器提升 RAG 性能并增强向量搜索案例

在检索增强生成 (RAG) 设置中,用户指定的过滤器(无论是隐含的还是明确的)通常在向量搜索中被忽视,因为向量搜索主要关注语义相似性。 在某些场景中,确保特定的查询仅使用预定义的(子)文档集来回答至关重要。通过使用“元数据”或标签,您可以强制执行每种类型用户查询应使用的文档类型。这甚至可以变成一种安全覆盖策略,其中每个用户的查询都带有他们的凭据/权限级别标签,以便使用与其权限级别相对应的文档来回答他们的查询。 当 RAG 数据由许多单独的数据对象(例如,文件)组成时,每个数据对象都可以用一组预定义的元数据标记。然后,这些标签可以在向量或混合搜索中用作过滤器。元数据可以与向量嵌入一起集成到搜索索引中,然后用作过滤器。 在本博客中,我们将演示一个示例实现……

推荐超级课程:

@TOC

实施步骤

为了演示目的,在本博客文章中,我们将使用电影维基百科文章作为我们的文档。然后,我们将使用诸如 genrereleaseYeardirector 等元数据对这些电影文件进行标记,并稍后使用这些元数据对 RAG 生成进行过滤。 请注意,大型语言模型 (LLM) 也可以在文档上传到搜索索引之前用于“分类”文档,以便在大规模部署。当用户输入提示时,我们可以使用额外的 LLM 调用来对用户提示进行分类(匹配一组元数据),然后稍后使用它来过滤结果。博客文章演示了一个更简单的用例,其中 RAG 文档(保存为 pdf 文件的维基百科页面)带有预先标记的电影元数据…

1. 分类文档并标记元数据

movies = [
    {"id": "1", "title": "肖申克的救赎", "genre": "剧情", "releaseYear": 1994, "director": "弗兰克·德拉邦特"},
    {"id": "2", "title": "教父", "genre": "犯罪", "releaseYear": 1972, "director": "弗朗西斯·福特·科波拉"},
    {"id": "3", "title": "黑暗骑士", "genre": "动作", "releaseYear": 2008, "director": "克里斯托弗·诺兰"},
    {"id": "4", "title": "辛德勒的名单", "genre": "传记", "releaseYear": 1993, "director": "史蒂文·斯皮尔伯格"},
    {"id": "5", "title": "低俗小说", "genre": "犯罪", "releaseYear": 1994, "director": "昆汀·塔伦蒂诺"},
    {"id": "6", "title": "指环王:王者归来", "genre": "奇幻", "releaseYear": 2003, "director": "彼得·杰克逊"},
    {"id": "7", "title": "黄金三镖客", "genre": "西部", "releaseYear": 1966, "director": "塞尔吉奥·莱昂内"},
    {"id": "8", "title": "搏击俱乐部", "genre": "剧情", "releaseYear": 1999, "director": "大卫·芬奇"},
    {"id": "9", "title": "阿甘正传", "genre": "剧情", "releaseYear": 1994, "director": "罗伯特·泽米吉斯"},
    {"id": "10", "title": "盗梦空间", "genre": "科幻", "releaseYear": 2010, "director": "克里斯托弗·诺兰"}
]

2. 创建 Azure AI 搜索索引…

我们需要创建一个 Azure AI 搜索索引,该索引将元数据字段作为“可搜索”和“可过滤”字段。下面是我们将使用的模式定义。 首先在 JSON 中定义模式….

{
    "name": "movies-index",
    "fields": [
      { "name": "id", "type": "Edm.String", "key": true, "filterable": false, "sortable": false },
      { "name": "title", "type": "Edm.String", "filterable": true, "searchable": true },
      { "name": "genre", "type": "Edm.String", "filterable": true, "searchable": true },
      { "name": "releaseYear", "type": "Edm.Int32", "filterable": true, "sortable": true },
      { "name": "director", "type": "Edm.String", "filterable": true, "searchable": true },
      { "name": "content", "type": "Edm.String", "filterable": false, "searchable": true },
      {
        "name": "contentVector",
        "type": "Collection(Edm.Single)",
        "searchable": true,
        "retrievable": true,
        "dimensions": 1536,
        "vectorSearchProfile": "my-default-vector-profile"
    }
    ],
    "vectorSearch": {
        "algorithms": [
            {
                "name": "my-hnsw-config-1",
                "kind": "hnsw",
                "hnswParameters": {
                    "m": 4,
                    "efConstruction": 400,
                    "efSearch": 500,
                    "metric": "cosine"
                }
            }
        ],
        "profiles": [
            {
                "name": "my-default-vector-profile",
                "algorithm": "my-hnsw-config-1"
            }
        ]
    }
}

然后运行以下脚本来使用 REST API 调用创建索引 Azure AI 搜索服务…

RESOURCE_GROUP="[your-resource-group]"
SEARCH_SERVICE_NAME="[search-index-name]"
API_VERSION="2023-11-01"
API_KEY="[your-AI-Search-API-key"
SCHEMA_FILE="movies-index-schema.json"
curl -X POST "https://${SEARCH_SERVICE_NAME}.search.windows.net/indexes?api-version=${API_VERSION}" \
     -H "Content-Type: application/json" \
     -H "api-key: ${API_KEY}" \
     -d @${SCHEMA_FILE}

一旦创建了 Azure AI 搜索索引,请在门户中确认元数据字段已标记为可过滤和可搜索… ![](images/946568474923930764.png =796x)

3. 嵌入并将文档块及其元数据上传到 Azure AI 搜索索引

我们将使用的文档是保存为 pdf 文件的电影的维基百科页面。为了将文档集成到 RAG 模式中的 LLM,我们首先将“预处理”文档。下面代码首先使用 extract_text_from_pdf 函数打开指定的 PDF 文件,使用 PdfReader 类读取其内容,并从每页中提取文本,将所有文本合并成一个字符串。normalize_text 函数接受一个文本字符串并删除任何不必要的空白,确保文本被规范化成一个连续的字符串,其中只包含空格。chunk_text 函数然后接受这个规范化文本并将其分成更小的块,每个块的大小不超过指定的大小(默认为 6000 个字符)。这是通过将文本标记成句子并将它们分组到块中,同时确保每个块不超过指定的大小来完成的,这使得文本更容易管理,并以更小的片段进行处理。

# 从 PDF 中提取文本的函数
def extract_text_from_pdf(pdf_path):
    text = ""
    with open(pdf_path, "rb") as file:
        reader = PdfReader(file)
        for page in reader.pages:
            text += page.extract_text() + "\n"
    return text
# 规范化文本的函数
def normalize_text(text):
    return ' '.join(text.split())
# 将文本分成更小的块的函数
def chunk_text(text, chunk_size=6000):
    sentences = sent_tokenize(text)
    chunks = []
    current_chunk = []
    current_length = 0
    for sentence in sentences:
        if current_length + len(sentence) > chunk_size:
            chunks.append(' '.join(current_chunk))
            current_chunk = [sentence]
            current_length = len(sentence)
        else:
            current_chunk.append(sentence)
            current_length += len(sentence)
    if current_chunk:
        chunks.append(' '.join(current_chunk))
    return chunks

然后嵌入每个块并将嵌入及其文档元数据上传到之前创建的 Azure AI 搜索索引中。

4. 无过滤向量搜索

首先,让我们用一个相对通用的提示进行向量搜索,该提示将与多个文档块匹配…请注意显式过滤语句“这部电影于 2010 年上映”。请注意,向量搜索无法成功解释所述过滤器,并且搜索结果中也会返回错误的结果(早在 2010 年之前上映的电影)。


# 为情节提示生成嵌入

plot_prompt = "一个人面临无法克服的困难,经历了一段转变性的旅程,揭示了隐藏的力量,并形成了意想不到的同盟。通过坚韧和机智,他们在充满腐败、背叛和正义斗争的世界中导航,最终发现了他们的真正目的。这部电影于 2010 年上映"
prompt_embedding_vector = generate_embeddings(plot_prompt)
payload = {
    "count": True,
    "select": "title, content, genre",
    "vectorQueries": [
        {
            "kind": "vector",
            "vector": prompt_embedding_vector,
            "exhaustive": True,
            "fields": "contentVector",
            "k": 5
        }
    ],
#    "filter": "genre eq 'Drama' and releaseYear ge 1990 and director eq 'Christopher Nolan'"
}
response = requests.post(url, headers=headers, data=json.dumps(payload))
if response.status_code == 200:
    results = response.json()
    print("Results with pre-filter:")
    for result in results['value']:
        print(result)
else:
    print(f"Error: {response.status_code}")
    print(response.json())
Results without pre-filter:
{'@search.score': 0.83729386, 'title': '肖申克的救赎', 'genre': '剧情', 'content': '...'}
{'@search.score': 0.83415884, 'title': '肖申克的救赎', 'genre': '剧情', 'content': '...'}
{'@search.score': 0.8314112, 'title': '盗梦空间', 'genre': '科幻', 'content': '...'}
{'@search.score': 0.8308051, 'title': '指环王:王者归来', 'genre': '奇幻', 'content': '...'}

5. (预)过滤向量搜索

接下来,向向量搜索添加一个过滤器…过滤器定义为任何文档块,其 releaseYear 元数据值(int32)大于 2010。在这种情况下,只有正确的搜索结果,即来自电影“盗梦空间”的文档块被返回。

payload_with_release_year_filter = {
    "count": True,
    "select": "title, content, genre, releaseYear, director",
    "filter": "releaseYear eq 2010",
    "vectorFilterMode": "preFilter",
    "vectorQueries": [
        {
            "kind": "vector",
            "vector": prompt_embedding_vector,
            "exhaustive": True,
            "fields": "contentVector",
            "k": 5
        }
    ]
}
Results with pre-filter:
{'@search.score': 0.8314112, 'title': '盗梦空间', 'genre': '科幻', 'releaseYear': 2010, 'director': '克里斯托弗·诺兰', 'content': '...'}
{'@search.score': 0.83097535, 'title': '盗梦空间', 'genre': '科幻', 'releaseYear': 2010, 'director': '克里斯托弗·诺兰', 'content':'...'}
{'@search.score': 0.83029956, 'title': '盗梦空间', 'genre': '科幻', 'releaseYear': 2010, 'director': '克里斯托弗·诺兰', 'content': '...'}
{'@search.score': 0.82646775, 'title': '盗梦空间', 'genre': '科幻', 'releaseYear': 2010, 'director': '克里斯托弗·诺兰', 'content': '...'}
{'@search.score': 0.8255407, 'title': '盗梦空间', 'genre': '科幻', 'releaseYear': 2010, 'director': '克里斯托弗·诺兰', 'content': '...'}

结论

本博客展示了将文档块嵌入并上传到 Azure 搜索索引中,其中文档元数据作为可搜索和可过滤字段的情况。 这个概念可以扩展,以便可以使用额外的 LLM 查询步骤来“分类”用户提示并推断将应用于预/后过滤向量搜索匹配块的元数据。同样,文档本身可以使用 LLM 调用标记元数据,而不是像本例中演示的那样依赖于静态人工注释。

参考

  • 向量查询中的过滤器 文档
  • 在 Azure AI 搜索中创建向量查询 文档

© 2011 - 2025 Macro Zhao的分享站

关于我

如遇到加载502错误,请尝试刷新😄

Hi,欢迎访问 Macro Zhao 的博客。Macro Zhao(或 Macro)是我在互联网上经常使用的名字。

我是一个热衷于技术探索和分享的IT工程师,在这里我会记录分享一些关于技术、工作和生活上的事情。

我的CSDN博客:
https://macro-zhao.blog.csdn.net/

欢迎你通过评论或者邮件与我交流。
Mail Me

推荐好玩(You'll Like)
  • AI 动·画
    • 这是一款有趣·免费的能让您画的画中的角色动起来的AI工具。
    • 支持几十种动作生成。
我的项目(My Projects)
  • 爱学习网

  • 小乙日语App

    • 这是一个帮助日语学习者学习日语的App。
      (当然初衷也是为了自用😄)
    • 界面干净,简洁,漂亮!
    • 其中包含 N1 + N2 的全部单词和语法。
    • 不需注册,更不需要订阅!完全免费!
  • 小乙日文阅读器

    • 词汇不够?照样能读日语名著!
    • 越读积累越多,积跬步致千里!
    • 哪里不会点哪里!妈妈再也不担心我读不了原版读物了!
赞助我(Sponsor Me)

如果你喜欢我的作品或者发现它们对你有所帮助,可以考虑给我买一杯咖啡 ☕️。这将激励我在未来创作和分享更多的项目和技术。🦾

👉 请我喝一杯咖啡

If you like my works or find them helpful, please consider buying me a cup of coffee ☕️. It inspires me to create and share more projects in the future. 🦾

👉 Buy me a coffee