Skip to main content
Azure AI 搜索(前身为 Azure SearchAzure Cognitive Search)是一项云搜索服务,为开发者提供基础设施、API 和工具,用于大规模执行向量、关键词和混合查询的信息检索。 您需要使用 pip install -qU langchain-community 安装 langchain-community 才能使用此集成。

安装 Azure AI 搜索 SDK

使用 azure-search-documents 包版本 11.4.0 或更高版本。
pip install -qU  azure-search-documents
pip install -qU  azure-identity

导入所需库

假设使用 OpenAIEmbeddings,但如果您使用的是 Azure OpenAI,请改为导入 AzureOpenAIEmbeddings
import os

from langchain_community.vectorstores.azuresearch import AzureSearch
from langchain_openai import AzureOpenAIEmbeddings, OpenAIEmbeddings

配置 OpenAI 设置

为您的 OpenAI 提供商设置变量。您需要一个 OpenAI 帐户 或一个 Azure OpenAI 帐户 来生成嵌入。
# 选项 1:使用 OpenAI 帐户
openai_api_key: str = "YOUR API KEY 占位符"
openai_api_version: str = "2023-05-15"
model: str = "text-embedding-ada-002"
# 选项 2:使用部署了嵌入模型的 Azure OpenAI 帐户
azure_endpoint: str = "YOUR AZURE OPENAI ENDPOINT 占位符"
azure_openai_api_key: str = "YOUR AZURE OPENAI KEY 占位符"
azure_openai_api_version: str = "2023-05-15"
azure_deployment: str = "text-embedding-ada-002"

配置向量存储设置

您需要一个 Azure 订阅Azure AI 搜索服务 才能使用此向量存储集成。对于小型和有限的工作负载,提供免费版本。 为您的 Azure AI 搜索 URL 和管理员 API 密钥设置变量。您可以从 Azure 门户 获取这些变量。
vector_store_address: str = "YOUR_AZURE_SEARCH_ENDPOINT"
vector_store_password: str = "YOUR_AZURE_SEARCH_ADMIN_KEY"

创建嵌入和向量存储实例

创建 OpenAIEmbeddings 和 AzureSearch 类的实例。完成此步骤后,您应该在 Azure AI 搜索资源上有一个空的搜索索引。集成模块提供了一个默认架构。
# 选项 1:使用 OpenAI 帐户的 OpenAIEmbeddings
embeddings: OpenAIEmbeddings = OpenAIEmbeddings(
    openai_api_key=openai_api_key, openai_api_version=openai_api_version, model=model
)
# 选项 2:使用 Azure 帐户的 AzureOpenAIEmbeddings
embeddings: AzureOpenAIEmbeddings = AzureOpenAIEmbeddings(
    azure_deployment=azure_deployment,
    openai_api_version=azure_openai_api_version,
    azure_endpoint=azure_endpoint,
    api_key=azure_openai_api_key,
)

创建向量存储实例

使用上面的嵌入创建 AzureSearch 类的实例
index_name: str = "langchain-vector-demo"
vector_store: AzureSearch = AzureSearch(
    azure_search_endpoint=vector_store_address,
    azure_search_key=vector_store_password,
    index_name=index_name,
    embedding_function=embeddings.embed_query,
)
# 为 Azure 客户端指定其他属性,例如以下 https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/core/azure-core/README.md#configurations
vector_store: AzureSearch = AzureSearch(
    azure_search_endpoint=vector_store_address,
    azure_search_key=vector_store_password,
    index_name=index_name,
    embedding_function=embeddings.embed_query,
    # 配置 Azure 客户端的最大重试次数
    additional_search_client_options={"retry_total": 4},
)

将文本和嵌入插入向量存储

此步骤加载、分块并矢量化示例文档,然后将内容索引到 Azure AI 搜索的搜索索引中。
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter

loader = TextLoader("../../how_to/state_of_the_union.txt", encoding="utf-8")

documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)

vector_store.add_documents(documents=docs)
['M2U1OGM4YzAtYjMxYS00Nzk5LTlhNDgtZTc3MGVkNTg1Mjc0',
 'N2I2MGNiZDEtNDdmZS00YWNiLWJhYTYtYWEzMmFiYzU1ZjZm',
 'YWFmNDViNTQtZTc4MS00MTdjLTkzZjQtYTJkNmY1MDU4Yzll',
 'MjgwY2ExZDctYTUxYi00NjE4LTkxMjctZDA1NDQ1MzU4NmY1',
 'NGE4NzhkNTAtZWYxOC00ZmI5LTg0MTItZDQ1NzMxMWVmMTIz',
 'MTYwMWU3YjAtZDIzOC00NTYwLTgwMmEtNDI1NzA2MWVhMDYz',
 'NGM5N2NlZjgtMTc5Ny00OGEzLWI5YTgtNDFiZWE2MjBlMzA0',
 'OWQ4M2MyMTYtMmRkNi00ZDUxLWI0MDktOGE2NjMxNDFhYzFm',
 'YWZmZGJkOTAtOGM3My00MmNiLTg5OWUtZGMwMDQwYTk1N2Vj',
 'YTc3MTI2OTktYmVkMi00ZGU4LTgyNmUtNTY1YzZjMDg2YWI3',
 'MTQwMmVlYjEtNDI0MS00N2E0LWEyN2ItZjhhYWU0YjllMjRk',
 'NjJjYWY4ZjctMzgyNi00Y2I5LTkwY2UtZjRkMjJhNDQxYTFk',
 'M2ZiM2NiYTMtM2ZiMS00YWJkLWE3ZmQtNDZiODcyOTMyYWYx',
 'MzNmZTNkMWYtMjNmYS00Y2NmLTg3ZjQtYTZjOWM1YmJhZTRk',
 'ZDY3MDc1NzYtY2YzZS00ZjExLWEyMjAtODhiYTRmNDUzMTBi',
 'ZGIyYzA4NzUtZGM2Ni00MDUwLWEzZjYtNTg3MDYyOWQ5MWQy',
 'NTA0MjBhMzYtOTYzMi00MDQ2LWExYWQtMzNiN2I4ODM4ZGZl',
 'OTdjYzU2NGUtNWZjNC00N2ZmLWExMjQtNjhkYmZkODg4MTY3',
 'OThhMWZmMjgtM2EzYS00OWZkLTk1NGEtZTdkNmRjNWYxYmVh',
 'ZGVjMTQ0NzctNDVmZC00ZWY4LTg4N2EtMDQ1NWYxNWM5NDVh',
 'MjRlYzE4YzItZTMxNy00OGY3LThmM2YtMjM0YmRhYTVmOGY3',
 'MWU0NDA3ZDQtZDE4MS00OWMyLTlmMzktZjdkYzZhZmUwYWM3',
 'ZGM2ZDhhY2MtM2NkNi00MzZhLWJmNTEtMmYzNjEwMzE3NmZl',
 'YjBmMjkyZTItYTNlZC00MmY2LThiMzYtMmUxY2MyNDlhNGUw',
 'OThmYTQ0YzEtNjk0MC00NWIyLWE1ZDQtNTI2MTZjN2NlODcw',
 'NDdlOGU1ZGQtZTVkMi00M2MyLWExN2YtOTc2ODk3OWJmNmQw',
 'MDVmZGNkYTUtNWI2OS00YjllLTk0YTItZDRmNWQxMWU3OTVj',
 'YWFlNTVmNjMtMDZlNy00NmE5LWI0ODUtZTI3ZTFmZWRmNzU0',
 'MmIzOTkxODQtODYxMi00YWM2LWFjY2YtNjRmMmEyM2JlNzMw',
 'ZmI1NDhhNWItZWY0ZS00NTNhLWEyNDEtMTE2OWYyMjc4YTU2',
 'YTllYTc5OTgtMzJiNC00ZjZjLWJiMzUtNWVhYzFjYzgxMjU2',
 'ODZlZWUyOTctOGY4OS00ZjA3LWIyYTUtNDVlNDUyN2E4ZDFk',
 'Y2M0MWRlM2YtZDU4Ny00MjZkLWE5NzgtZmRkMTNhZDg2YjEy',
 'MDNjZWQ2ODEtMWZiMy00OTZjLTk3MzAtZjE4YjIzNWVhNTE1',
 'OTE1NDY0NzMtODNkZS00MTk4LTk4NWQtZGVmYjQ2YjFlY2Q0',
 'ZTgwYWQwMjEtN2ZlOS00NDk2LWIxNzUtNjk2ODE3N2U0Yzlj',
 'ZDkxOTgzMGUtZGExMC00Yzg0LWJjMGItOWQ2ZmUwNWUwOGJj',
 'ZGViMGI2NDEtZDdlNC00YjhiLTk0MDUtYjEyOTVlMGU1Y2I2',
 'ODliZTYzZTctZjdlZS00YjBjLWFiZmYtMDJmNjQ0YjU3ZDcy',
 'MDFjZGI1NzUtOTc0Ni00NWNmLThhYzYtYzRlZThkZjMwM2Vl',
 'ZjY2ZmRiN2EtZWVhNS00ODViLTk4YjYtYjQ2Zjc4MDdkYjhk',
 'ZTQ3NDMwODEtMTQwMy00NDFkLWJhZDQtM2UxN2RkOTU1MTdl']

执行向量相似性搜索

使用 similarity_search() 方法执行纯向量相似性搜索:
# 执行相似性搜索
docs = vector_store.similarity_search(
    query="总统对凯坦吉·布朗·杰克逊说了什么",
    k=3,
    search_type="similarity",
)
print(docs[0].page_content)
今晚。我呼吁参议院:通过《自由投票法案》。通过《约翰·刘易斯投票权法案》。并且,在你们这样做的时候,通过《披露法案》,以便美国人能够知道谁在资助我们的选举。

今晚,我想向一位毕生致力于为这个国家服务的人致敬:斯蒂芬·布雷耶大法官——一位陆军退伍军人、宪法学者,以及即将退休的美国最高法院大法官。布雷耶大法官,感谢您的服务。

总统最严肃的宪法职责之一是提名某人在美国最高法院任职。

而我在四天前就这样做了,当时我提名了巡回上诉法院法官凯坦吉·布朗·杰克逊。她是我们国家顶尖的法律头脑之一,将继续布雷耶大法官卓越的遗产。

执行带有相关性分数的向量相似性搜索

使用 similarity_search_with_relevance_scores() 方法执行纯向量相似性搜索。不满足阈值要求的查询将被排除。
docs_and_scores = vector_store.similarity_search_with_relevance_scores(
    query="总统对凯坦吉·布朗·杰克逊说了什么",
    k=4,
    score_threshold=0.80,
)
from pprint import pprint

pprint(docs_and_scores)
[(Document(page_content='今晚。我呼吁参议院:通过《自由投票法案》。通过《约翰·刘易斯投票权法案》。并且,在你们这样做的时候,通过《披露法案》,以便美国人能够知道谁在资助我们的选举。 \n\n今晚,我想向一位毕生致力于为这个国家服务的人致敬:斯蒂芬·布雷耶大法官——一位陆军退伍军人、宪法学者,以及即将退休的美国最高法院大法官。布雷耶大法官,感谢您的服务。 \n\n总统最严肃的宪法职责之一是提名某人在美国最高法院任职。 \n\n而我在四天前就这样做了,当时我提名了巡回上诉法院法官凯坦吉·布朗·杰克逊。她是我们国家顶尖的法律头脑之一,将继续布雷耶大法官卓越的遗产。', metadata={'source': '../../how_to/state_of_the_union.txt'}),
  0.84402436),
 (Document(page_content='一位前私人执业顶级诉讼律师。一位前联邦公设辩护人。并且来自一个公立学校教育工作者和警察的家庭。一位共识建立者。自从她被提名以来,她获得了广泛的支持——从警察兄弟会到民主党和共和党任命的前法官。 \n\n如果我们想要推进自由和正义,我们需要确保边境安全并修复移民系统。 \n\n我们可以两者兼顾。在我们的边境,我们安装了新技术,如尖端扫描仪,以更好地检测毒品走私。 \n\n我们与墨西哥和危地马拉建立了联合巡逻队,以抓捕更多的人口贩子。 \n\n我们正在设立专门的移民法官,以便逃离迫害和暴力的家庭能够更快地得到案件审理。 \n\n我们正在确保承诺并支持南美洲和中美洲的合作伙伴,以收容更多难民并确保他们自己的边境安全。', metadata={'source': '../../how_to/state_of_the_union.txt'}),
  0.82128483),
 (Document(page_content='对于我们的 LGBTQ+ 美国人,让我们最终将两党支持的《平等法案》送到我的办公桌。针对跨性别美国人及其家人的州法律攻击是错误的。 \n\n正如我去年所说,特别是对我们年轻的跨性别美国人,作为你们的总统,我将永远支持你们,这样你们就可以做自己并发挥你们上帝赋予的潜力。 \n\n虽然我们似乎从未达成一致,但事实并非如此。去年我签署了 80 项两党法案成为法律。从防止政府关门到保护亚裔美国人免受仍然过于常见的仇恨犯罪,再到改革军事司法。 \n\n很快,我们将加强我三十年前首次撰写的《针对妇女暴力法》。向国家表明我们可以团结一致并完成大事对我们很重要。 \n\n所以今晚我提出一个国家团结议程。我们可以一起做的四件大事。 \n\n首先,击败阿片类药物流行病。', metadata={'source': '../../how_to/state_of_the_union.txt'}),
  0.8151042),
 (Document(page_content='今晚,我宣布对这些向美国企业和消费者多收费的公司进行打击。 \n\n随着华尔街公司接管更多的养老院,这些养老院的质量下降,成本上升。 \n\n这在我的任期内将结束。 \n\n医疗保险将为养老院设定更高的标准,并确保您所爱的人得到他们应得和期望的护理。 \n\n我们还将通过给予工人公平的机会、提供更多培训和学徒机会、根据他们的技能而非学位来雇用他们,从而降低成本并保持经济强劲。 \n\n让我们通过《薪酬公平法案》和带薪休假。 \n\n将最低工资提高到每小时 15 美元,并延长儿童税收抵免,这样就没有人必须在贫困中抚养家庭。 \n\n让我们增加佩尔助学金,增加我们对历史性黑人大学和学院的支持,并投资于吉尔——我们的第一夫人全职教学——所说的美国最保守的秘密:社区学院。', metadata={'source': '../../how_to/state_of_the_union.txt'}),
  0.8148832)]

执行混合搜索

使用 search_type 或 hybrid_search() 方法执行混合搜索。向量和非向量文本字段并行查询,结果被合并,并返回统一结果集中的最佳匹配项。
# 使用 search_type 参数执行混合搜索
docs = vector_store.similarity_search(
    query="总统对凯坦吉·布朗·杰克逊说了什么",
    k=3,
    search_type="hybrid",
)
print(docs[0].page_content)
今晚。我呼吁参议院:通过《自由投票法案》。通过《约翰·刘易斯投票权法案》。并且,在你们这样做的时候,通过《披露法案》,以便美国人能够知道谁在资助我们的选举。

今晚,我想向一位毕生致力于为这个国家服务的人致敬:斯蒂芬·布雷耶大法官——一位陆军退伍军人、宪法学者,以及即将退休的美国最高法院大法官。布雷耶大法官,感谢您的服务。

总统最严肃的宪法职责之一是提名某人在美国最高法院任职。

而我在四天前就这样做了,当时我提名了巡回上诉法院法官凯坦吉·布朗·杰克逊。她是我们国家顶尖的法律头脑之一,将继续布雷耶大法官卓越的遗产。
# 使用 hybrid_search 方法执行混合搜索
docs = vector_store.hybrid_search(
    query="总统对凯坦吉·布朗·杰克逊说了什么", k=3
)
print(docs[0].page_content)
今晚。我呼吁参议院:通过《自由投票法案》。通过《约翰·刘易斯投票权法案》。并且,在你们这样做的时候,通过《披露法案》,以便美国人能够知道谁在资助我们的选举。

今晚,我想向一位毕生致力于为这个国家服务的人致敬:斯蒂芬·布雷耶大法官——一位陆军退伍军人、宪法学者,以及即将退休的美国最高法院大法官。布雷耶大法官,感谢您的服务。

总统最严肃的宪法职责之一是提名某人在美国最高法院任职。

而我在四天前就这样做了,当时我提名了巡回上诉法院法官凯坦吉·布朗·杰克逊。她是我们国家顶尖的法律头脑之一,将继续布雷耶大法官卓越的遗产。

自定义架构和查询

本节向您展示如何用自定义架构替换默认架构。

使用自定义可筛选字段创建新索引

此架构显示字段定义。它是默认架构,加上几个标记为可筛选的新字段。因为它使用默认的向量配置,所以您不会在这里看到向量配置或向量配置文件覆盖。默认向量配置文件的名称是 “myHnswProfile”,它使用分层可导航小世界 (HNSW) 的向量配置来索引和查询 content_vector 字段。 此步骤中此架构没有数据。当您执行单元格时,您应该在 Azure AI 搜索上获得一个空索引。
from azure.search.documents.indexes.models import (
    ScoringProfile,
    SearchableField,
    SearchField,
    SearchFieldDataType,
    SimpleField,
    TextWeights,
)

#  如果 Azure OpenAI 是您的提供商,请将 OpenAIEmbeddings 替换为 AzureOpenAIEmbeddings。
embeddings: OpenAIEmbeddings = OpenAIEmbeddings(
    openai_api_key=openai_api_key, openai_api_version=openai_api_version, model=model
)
embedding_function = embeddings.embed_query

fields = [
    SimpleField(
        name="id",
        type=SearchFieldDataType.String,
        key=True,
        filterable=True,
    ),
    SearchableField(
        name="content",
        type=SearchFieldDataType.String,
        searchable=True,
    ),
    SearchField(
        name="content_vector",
        type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
        searchable=True,
        vector_search_dimensions=len(embedding_function("Text")),
        vector_search_profile_name="myHnswProfile",
    ),
    SearchableField(
        name="metadata",
        type=SearchFieldDataType.String,
        searchable=True,
    ),
    # 用于存储标题的附加字段
    SearchableField(
        name="title",
        type=SearchFieldDataType.String,
        searchable=True,
    ),
    # 用于按文档来源筛选的附加字段
    SimpleField(
        name="source",
        type=SearchFieldDataType.String,
        filterable=True,
    ),
]

index_name: str = "langchain-vector-demo-custom"

vector_store: AzureSearch = AzureSearch(
    azure_search_endpoint=vector_store_address,
    azure_search_key=vector_store_password,
    index_name=index_name,
    embedding_function=embedding_function,
    fields=fields,
)

添加数据并执行包含筛选器的查询

此示例根据自定义架构向向量存储添加数据。它将文本加载到 title 和 source 字段中。source 字段是可筛选的。本节中的示例查询根据 source 字段中的内容筛选结果。
# 元数据字典中与索引中对应字段匹配的数据将被添加到索引中。
# 在此示例中,元数据字典包含一个 title、一个 source 和一个 random 字段。
# title 和 source 作为单独的字段添加到索引中,但 random 值被忽略,因为它未在架构中定义。
# random 字段仅存储在 metadata 字段中。
vector_store.add_texts(
    ["Test 1", "Test 2", "Test 3"],
    [
        {"title": "Title 1", "source": "A", "random": "10290"},
        {"title": "Title 2", "source": "A", "random": "48392"},
        {"title": "Title 3", "source": "B", "random": "32893"},
    ],
)
['ZjhmMTg0NTEtMjgwNC00N2M0LWFiZGEtMDllMGU1Mzk1NWRm',
 'MzQwYWUwZDEtNDJkZC00MzgzLWIwMzItYzMwOGZkYTRiZGRi',
 'ZjFmOWVlYTQtODRiMC00YTY3LTk2YjUtMzY1NDBjNjY5ZmQ2']
res = vector_store.similarity_search(query="Test 3 source1", k=3, search_type="hybrid")
res
[Document(page_content='Test 3', metadata={'title': 'Title 3', 'source': 'B', 'random': '32893'}),
 Document(page_content='Test 1', metadata={'title': 'Title 1', 'source': 'A', 'random': '10290'}),
 Document(page_content='Test 2', metadata={'title': 'Title 2', 'source': 'A', 'random': '48392'})]
res = vector_store.similarity_search(
    query="Test 3 source1", k=3, search_type="hybrid", filters="source eq 'A'"
)
res
[Document(page_content='Test 1', metadata={'title': 'Title 1', 'source': 'A', 'random': '10290'}),
 Document(page_content='Test 2', metadata={'title': 'Title 2', 'source': 'A', 'random': '48392'})]

使用评分配置文件创建新索引

这是另一个包含评分配置文件定义的自定义架构。评分配置文件用于非向量内容的相关性调整,这在混合搜索场景中很有帮助。
from azure.search.documents.indexes.models import (
    FreshnessScoringFunction,
    FreshnessScoringParameters,
    ScoringProfile,
    SearchableField,
    SearchField,
    SearchFieldDataType,
    SimpleField,
    TextWeights,
)

#  如果 Azure OpenAI 是您的提供商,请将 OpenAIEmbeddings 替换为 AzureOpenAIEmbeddings。
embeddings: OpenAIEmbeddings = OpenAIEmbeddings(
    openai_api_key=openai_api_key, openai_api_version=openai_api_version, model=model
)
embedding_function = embeddings.embed_query

fields = [
    SimpleField(
        name="id",
        type=SearchFieldDataType.String,
        key=True,
        filterable=True,
    ),
    SearchableField(
        name="content",
        type=SearchFieldDataType.String,
        searchable=True,
    ),
    SearchField(
        name="content_vector",
        type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
        searchable=True,
        vector_search_dimensions=len(embedding_function("Text")),
        vector_search_profile_name="myHnswProfile",
    ),
    SearchableField(
        name="metadata",
        type=SearchFieldDataType.String,
        searchable=True,
    ),
    # 用于存储标题的附加字段
    SearchableField(
        name="title",
        type=SearchFieldDataType.String,
        searchable=True,
    ),
    # 用于按文档来源筛选的附加字段
    SimpleField(
        name="source",
        type=SearchFieldDataType.String,
        filterable=True,
    ),
    # 用于最后文档更新的附加数据字段
    SimpleField(
        name="last_update",
        type=SearchFieldDataType.DateTimeOffset,
        searchable=True,
        filterable=True,
    ),
]
# 添加带有新鲜度函数的自定义评分配置文件
sc_name = "scoring_profile"
sc = ScoringProfile(
    name=sc_name,
    text_weights=TextWeights(weights={"title": 5}),
    function_aggregation="sum",
    functions=[
        FreshnessScoringFunction(
            field_name="last_update",
            boost=100,
            parameters=FreshnessScoringParameters(boosting_duration="P2D"),
            interpolation="linear",
        )
    ],
)

index_name = "langchain-vector-demo-custom-scoring-profile"

vector_store: AzureSearch = AzureSearch(
    azure_search_endpoint=vector_store_address,
    azure_search_key=vector_store_password,
    index_name=index_name,
    embedding_function=embeddings.embed_query,
    fields=fields,
    scoring_profiles=[sc],
    default_scoring_profile=sc_name,
)
# 添加具有不同 last_update 的相同数据以显示评分配置文件效果
from datetime import datetime, timedelta

today = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S-00:00")
yesterday = (datetime.utcnow() - timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%S-00:00")
one_month_ago = (datetime.utcnow() - timedelta(days=30)).strftime(
    "%Y-%m-%dT%H:%M:%S-00:00"
)

vector_store.add_texts(
    ["Test 1", "Test 1", "Test 1"],
    [
        {
            "title": "Title 1",
            "source": "source1",
            "random": "10290",
            "last_update": today,
        },
        {
            "title": "Title 1",
            "source": "source1",
            "random": "48392",
            "last_update": yesterday,
        },
        {
            "title": "Title 1",
            "source": "source1",
            "random": "32893",
            "last_update": one_month_ago,
        },
    ],
)
['NjUwNGQ5ZDUtMGVmMy00OGM4LWIxMGYtY2Y2MDFmMTQ0MjE5',
 'NWFjN2YwY2UtOWQ4Yi00OTNhLTg2MGEtOWE0NGViZTVjOGRh',
 'N2Y2NWUyZjctMDBjZC00OGY4LWJlZDEtNTcxYjQ1MmI1NjYx']
res = vector_store.similarity_search(query="Test 1", k=3, search_type="similarity")
res
[Document(page_content='Test 1', metadata={'title': 'Title 1', 'source': 'source1', 'random': '32893', 'last_update': '2024-01-24T22:18:51-00:00'}),
 Document(page_content='Test 1', metadata={'title': 'Title 1', 'source': 'source1', 'random': '48392', 'last_update': '2024-02-22T22:18:51-00:00'}),
 Document(page_content='Test 1', metadata={'title': 'Title 1', 'source': 'source1', 'random': '10290', 'last_update': '2024-02-23T22:18:51-00:00'})]