你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

Azure OpenAI 助手文件搜索工具(预览版)

文件搜索利用其模型外部的知识(如专有产品信息或用户提供的文档)对助手进行增强。 OpenAI 会自动分析和分块文档、创建和存储嵌入内容,并使用矢量和关键字搜索来检索相关内容来回答用户查询。

重要

  • 除了与使用 Azure OpenAI 相关的基于令牌的费用之外,文件搜索还会产生其他费用

注意

  • 文件搜索可为每个助手最多引入 10,000 个文件 - 比之前多 500 倍。 它速度快,支持通过多线程搜索进行并行查询,并具有增强的重新排序和查询重写功能。
    • 矢量存储是 API 中的新对象。 文件一旦添加到矢量存储中,就会自动进行分析、分块和嵌入,以便随时搜索。 矢量存储可跨助手和线程使用,简化了文件管理和计费。
  • 我们添加了对 tool_choice 参数的支持,该参数可用于在特定运行中强制使用特定工具(如文件搜索、代码解释器或函数)。

文件搜索支持

支持的区域

文件搜索在支持助手的区域可用。

API 版本

  • 2024-05-01-preview

支持的文件类型

注意

对于文本/MIME 类型,编码必须是 utf-8、utf-16 或 ASCII。

文件格式 MIME 类型
c. text/x-c
.cs text/x-csharp
.cpp text/x-c++
.doc application/msword
.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
.html text/html
.java text/x-java
.json application/json
.md text/markdown
.pdf application/pdf
.php text/x-php
.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
.py text/x-python
.py text/x-script.python
.rb text/x-ruby
.tex text/x-tex
.txt text/plain
.css text/css
.js text/javascript
.sh application/x-sh
.ts application/typescript
from openai import AzureOpenAI
    
client = AzureOpenAI(
    api_key=os.getenv("AZURE_OPENAI_API_KEY"),  
    api_version="2024-05-01-preview",
    azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
    )

assistant = client.beta.assistants.create(
  name="Financial Analyst Assistant",
  instructions="You are an expert financial analyst. Use your knowledge base to answer questions about audited financial statements.",
  model="gpt-4-turbo",
  tools=[{"type": "file_search"}],
)

为了访问文件,文件搜索工具会使用矢量存储对象。 上传文件并创建矢量存储以包含这些文件。 创建矢量存储后,应轮询其状态,直到所有文件都退出 in_progress 状态,以确保所有内容都已完成处理。 SDK 提供用于上传和轮询的帮助程序。

from openai import AzureOpenAI
    
client = AzureOpenAI(
    api_key=os.getenv("AZURE_OPENAI_API_KEY"),  
    api_version="2024-05-01-preview",
    azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
    )

# Create a vector store called "Financial Statements"
vector_store = client.beta.vector_stores.create(name="Financial Statements")
 
# Ready the files for upload to OpenAI
file_paths = ["mydirectory/myfile1.pdf", "mydirectory/myfile2.txt"]
file_streams = [open(path, "rb") for path in file_paths]
 
# Use the upload and poll SDK helper to upload the files, add them to the vector store,
# and poll the status of the file batch for completion.
file_batch = client.beta.vector_stores.file_batches.upload_and_poll(
  vector_store_id=vector_store.id, files=file_streams
)
 
# You can print the status and the file counts of the batch to see the result of this operation.
print(file_batch.status)
print(file_batch.file_counts)

更新助手以使用新的矢量存储

若要使助手可以访问这些文件,请使用新 vector_store ID 更新助手的 tool_resources

assistant = client.beta.assistants.update(
  assistant_id=assistant.id,
  tool_resources={"file_search": {"vector_store_ids": [vector_store.id]}},
)

创建线程

还可以将文件作为邮件附件附加到线程上。 这样做将创建另一个与线程关联的 vector_store,或者,如果已有一个附加到此线程的矢量存储,请将新文件附加到现有的线程矢量存储。 在此线程上创建“运行”时,文件搜索工具将从助手的 vector_store 和线程上的 vector_store 进行查询。

# Upload the user provided file to OpenAI
message_file = client.files.create(
  file=open("mydirectory/myfile.pdf", "rb"), purpose="assistants"
)
 
# Create a thread and attach the file to the message
thread = client.beta.threads.create(
  messages=[
    {
      "role": "user",
      "content": "How many company shares were outstanding last quarter?",
      # Attach the new file to the message.
      "attachments": [
        { "file_id": message_file.id, "tools": [{"type": "file_search"}] }
      ],
    }
  ]
)
 
# The thread now has a vector store with that file in its tool resources.
print(thread.tool_resources.file_search)

矢量存储是使用邮件附件创建的,其默认过期策略在上次处于活动状态后七天(定义为矢量存储上次运行的时间)。 此默认值可用于帮助你管理矢量存储成本。 可以随时替代这些过期策略。

创建运行并检查输出

创建运行并观察模型是否使用文件搜索工具来响应用户的问题。

from typing_extensions import override
from openai import AssistantEventHandler, OpenAI
 
client = OpenAI()
 
class EventHandler(AssistantEventHandler):
    @override
    def on_text_created(self, text) -> None:
        print(f"\nassistant > ", end="", flush=True)

    @override
    def on_tool_call_created(self, tool_call):
        print(f"\nassistant > {tool_call.type}\n", flush=True)

    @override
    def on_message_done(self, message) -> None:
        # print a citation to the file searched
        message_content = message.content[0].text
        annotations = message_content.annotations
        citations = []
        for index, annotation in enumerate(annotations):
            message_content.value = message_content.value.replace(
                annotation.text, f"[{index}]"
            )
            if file_citation := getattr(annotation, "file_citation", None):
                cited_file = client.files.retrieve(file_citation.file_id)
                citations.append(f"[{index}] {cited_file.filename}")

        print(message_content.value)
        print("\n".join(citations))


# Then, we use the stream SDK helper
# with the EventHandler class to create the Run
# and stream the response.

with client.beta.threads.runs.stream(
    thread_id=thread.id,
    assistant_id=assistant.id,
    instructions="Please address the user as Jane Doe. The user has a premium account.",
    event_handler=EventHandler(),
) as stream:
    stream.until_done()

工作原理

文件搜索工具现身实现多个检索最佳做法,以帮助你从文件中提取正确的数据,并增强模型响应。 file_search 工具:

  • 重写用户查询以优化其搜索。
  • 将复杂的用户查询分解为可以并行运行的多个搜索。
  • 在助手和线程矢量存储中同时运行关键字和语义搜索。
  • 在生成最终响应之前,请重新调整搜索结果以选取最相关的搜索结果。
  • 默认情况下,文件搜索工具使用以下设置:
    • 区块大小:800 个令牌
    • 区块重叠:400 个令牌
    • 嵌入模型:text-embedding-3-large,尺寸为 256
    • 添加到上下文的最大区块数:20

矢量存储

矢量存储对象使文件搜索工具能够搜索文件。 将文件添加到矢量存储会自动分析、分块、嵌入文件并将其存储在能够进行关键字和语义搜索的矢量数据库中。 每个矢量存储最多可以容纳 10,000 个文件。 矢量存储可以同时附加到助手和线程。 目前,最多可以将一个矢量存储附加到助手,最多可将一个矢量存储附加到线程。

创建矢量存储并添加文件

可以在单个 API 调用中创建矢量存储并将文件添加到其中:

vector_store = client.beta.vector_stores.create(
  name="Product Documentation",
  file_ids=['file_1', 'file_2', 'file_3', 'file_4', 'file_5']
)

将文件添加到矢量存储是一项异步操作。 为了确保操作完成,我们建议在官方 SDK 中使用“创建和轮询”帮助程序。 如果不使用 SDK,则可以检索 vector_store 对象并监视其 file_counts 属性以查看文件引入操作的结果。

创建矢量存储文件后,还可以将文件添加到矢量存储中。

file = client.beta.vector_stores.files.create_and_poll(
  vector_store_id="vs_abc123",
  file_id="file-abc123"
)

或者,可以通过创建最多 500 个文件的批处理,将多个文件添加到矢量存储中。

batch = client.beta.vector_stores.file_batches.create_and_poll(
  vector_store_id="vs_abc123",
  file_ids=['file_1', 'file_2', 'file_3', 'file_4', 'file_5']
)

同样,可以通过以下任一方法从矢量存储中删除这些文件:

  • 删除矢量存储文件对象或
  • 通过删除基础文件对象(从组织中所有助手和线程的所有 vector_store 中删除该文件和 code_interpreter 配置)

最大文件大小为 512 MB。 每个文件应包含每个文件的令牌不超过 5,000,000 个(会在附加文件时自动计算)。

附加矢量存储

可以使用 tool_resources 参数将矢量存储附加到助手或 Thread。

assistant = client.beta.assistants.create(
  instructions="You are a helpful product support assistant and you answer questions based on the files provided to you.",
  model="gpt-4-turbo",
  tools=[{"type": "file_search"}],
  tool_resources={
    "file_search": {
      "vector_store_ids": ["vs_1"]
    }
  }
)

thread = client.beta.threads.create(
  messages=[ { "role": "user", "content": "How do I cancel my subscription?"} ],
  tool_resources={
    "file_search": {
      "vector_store_ids": ["vs_2"]
    }
  }
)

还可以通过将矢量存储附加到线程或助手,方法是使用正确的 tool_resources 更新它们。

在创建运行之前确保矢量存储就绪情况

强烈建议在创建运行之前确保已完全处理 vector_store 中的所有文件。 这可以确保矢量存储中的所有数据都可搜索。 可以使用 SDK 中的轮询帮助程序或手动轮询 vector_store 对象来确保状态已完成,来检查矢量存储就绪情况。

作为回退,当线程的矢量存储包含仍在处理的文件时,最多在 Run 对象中等待 60 秒。 这是为了确保用户在运行继续之前完全可搜索的线程中上传的任何文件。 此回退等待不适用于助手的矢量存储。

使用过期策略管理成本

file_search 工具使用 vector_stores 对象作为其资源,将根据创建的 vector_store 对象的大小计费。 矢量存储对象的大小是文件及其相应嵌入的所有已分析区块的总和。

为了帮助你管理与这些 vector_store 对象相关的成本,我们在 vector_store 对象中添加了对过期策略的支持。 创建或更新 vector_store 对象时,可以设置这些策略。

vector_store = client.beta.vector_stores.create_and_poll(
  name="Product Documentation",
  file_ids=['file_1', 'file_2', 'file_3', 'file_4', 'file_5'],
  expires_after={
	  "anchor": "last_active_at",
	  "days": 7
  }
)

线程矢量存储具有默认过期策略

使用线程帮助程序(如 tool_resources.file_search.vector_stores 线程或 message.attachments 消息中创建的矢量存储)的默认过期策略在上次处于活动状态后七天(定义为最后一次矢量存储是运行的一部分)。

当矢量存储过期时,在该线程上运行将失败。 若要解决此问题,可以使用同一文件重新创建一个新的 vector_store,并将其重新附加到线程。

all_files = list(client.beta.vector_stores.files.list("vs_expired"))

vector_store = client.beta.vector_stores.create(name="rag-store")
client.beta.threads.update(
    "thread_abc123",
    tool_resources={"file_search": {"vector_store_ids": [vector_store.id]}},
)

for file_batch in chunked(all_files, 100):
    client.beta.vector_stores.file_batches.create_and_poll(
        vector_store_id=vector_store.id, file_ids=[file.id for file in file_batch]
    )