五分钟——用ML Studio创建基于FAISS的RAG聊天

   我们已经尝试过集成认知搜索的Azure OpenAI操场集成认知搜索的Azure ML工作室提示流,也尝试了集成在线搜索的RAG。现在来看看FAISS以及同时集成离线在线RAG的ChatGPT吧。

创建FAISS矢量索引

   在Azure ML Studio的提示流中,除了通过Azure Cognitive Search认知搜索来创建矢量搜索之外,还可以通过FAISS(Facebook AI Similarity Search) [1] 来创建矢量索引。

   仍然是在提示流的页面,在矢量索引页签点击“创建”下拉按钮,打开创建向导。为了比较,我们还是上传一样的文件夹,一样的PDF文件。然后矢量存储选择“Faiss 索引”。 56def4fce995689e312c769b71ebc80b.png

   如同之前创建 Azure 认知搜索的矢量索引一样,需要Azure OpenAI连接来完成 Embedding 工作。7f5473c650713b5ce5d81715b4907130.png

1f66b5e6a6f44acf55e50ea3b3e6a3ca.png

   然后依然选择使用无服务器计算来运行后续的作业(Job)。审阅输入的信息后,完成向导,开始运行创建FAISS矢量索引的作业。b19a6bfa439acf15e00238b2b1d8411a.png

   可以看到,创建FAISS矢量索引的作业和我们之前创建ACS(Azure认知搜索)的矢量索引的作业不能说是一模一样,起码也是毫无二致……要非说有区别,可能就在于创建FAISS矢量索引的时候,使用无服务器计算就成功的完成了作业流水线。

   因为这个配置过程和生成过程和创建ACS矢量索引没什么区别,所以就不浪费时间了,如有需要可以查看前一篇的介绍。

验证提示流管道线

   基于FAISS的矢量索引创建完成后,就可以通过自动创建的提示流来测试效果。86637a7ac54b3c4e8e15f0ccf2aaca26.png

    由于前一篇我们已经验证过基于ACS的矢量索引的提示流,所以这里我们直接跳到结果。可以看到提示流同样对提问进行嵌入,然后利用矢量搜索矢量索引,找到近似的文本后,将内容和来源URL通过提示传递给GPT模型,然后生成问题的答复。

   为什么有基于ACS的矢量索引,还要支持FAISS的矢量索引呢?其实这两种服务的适用场景是有所不同的。不妨参考官网文档 [2] 比较一下:

Faiss

优点缺点
创建索引没有成本:只有存储成本查询功能有限,嵌入向量相似性搜索
可在内存中生成和查询需要用户进行设置,才能在应用程序中托管用于访问
易于共享副本供个人使用
使用基础计算加载索引进行缩放

Azure 认知搜索

优点缺点
支持向量搜索、语义搜索、筛选器月度订阅费用
可根据需要通过副本和分区进行缩放初始设置很复杂:需要预配和管理 Azure 资源,需要授予对本地开发使用情况/管理机密的访问权限
提供了关于服务功能/限制的支持和详细文档对可存储在索引中的向量数设有限制

   在我看来如果矢量索引需求很简单,不妨用FAISS,成本低,主要专注在向量搜索上。如果需求比较复杂,除了向量搜索之外,还希望实现更多搜索技能,例如Rank调整排名、语义搜索、对接认知服务技能等等,可以考虑ACS。

对过程的探索

   虽然实现FAISS矢量索引的RAG很快就结束了,但同样会在这个过程中去思考和探索。例如FAISS和ACS的实现真的是一致的吗?作业使用的那些管道线组件模块是如何定义的呢?好在AML机器学习工作室提供了“输出+日志”和“代码”等查看方式,能够帮助我们了解图形界面之后到底发生了什么。

886c9133c3ab68432cb2ff0737528780.png

   通过查看FAISS矢量索引生成的作业,对比之前ACS矢量索引生成的代码,能够看到两者的代码是完全一致的。实际上,微软已经把这部分管道线组件模块定义和使用的代码发布到Github上了。我们这几篇文章讨论的RAG创建矢量索引所涉及的组件和代码都可以在 Azure ML Assets 代码库[3] 中。实际上,在这个大宝库里,除了组件模块定义和代码,还可以看到运行环境的YAML定义和容器的YAML定义。

    我们之前运行矢量索引所选择的无服务器计算或计算群集,也都是使用这些定义来准备运行环境的。

   因为FAISS支持本地运行,而我又希望知道提示流是如何做的,所以想看看代码是怎么运行的。可是我确实没找到代码,不过我找到了微软的示例笔记本 [4],即用了微软自己的库,又用了langchain,挺有意思的。

同时使用离线和在线RAG

   如果我们把之前使用基于在线搜索Bing Search的RAG称为在线的话,那么基于“本地”文件实现的矢量搜索是不是可以称之为离线RAG?那是不是可以借助提示流,实现RAG提问同时搜索互联网和我们指定的文件数据呢?试试看。

85af0158252b32f891c4e802924f2121.png

   初步的思路就是我们把提示流“Bring Your Own Data QnA“缝合到提示流“Chat With Bing Search”。将两个原本的提示流的网上搜索结果和矢量搜索结果都发送给“process_search_result”这个Python代码组件模块,处理后使用提示工程让LLM生成合适的回复。例如在每个回复内容之后显示引用是内部(基于数据生成的矢量搜索)还是外部(直接使用Bing Search的搜索结果),然后再注明来源(内部使用文件URL,外部使用网站URL)。

   为此我们要做几件事情:

  1. 修改在线查找RAG提示流,增加矢量搜索的组件模块。把提示流管道线改成如图模样;

  2. 修改“search_result_from_offline”和“search_result_from_online”(是的,我把组件模块改名了,猜猜原本叫什么?也可以看看之前的文章)组件模块的代码,使得输出的数据能够适配相同的“process_search_result”组件模块;

  3. 修改“process_search_result”组件模块代码,同时处理网络搜索结果和矢量搜索结果,并让字典能够多一个参数 Citation,用来表示内部或外部引用;

  4. 修改“augmented_chat”组件模块的提示,让LLM能够按照我们的要求,在生成的内容之后表明是内部还是外部应引用,引用的文件或者网页URL。

   首先,看一下“search_result_from_offline”和“search_result_from_online”这两个组件模块。“search_result_from_offline”原本的代码是从包括矢量搜索结果中抽取Content和Source然后存放到Dict字典,再从字典转换成List列表元素组成的字符串,传递给后续的组件模块。从输出的数据看格式为:"Content: XXX. Source: XXX. Content: YYY. Source: YYY"。这显然方便之前发送给LLM做上下文提示,而不方便我们做下一步处理。所以我们取消转换成字符串的代码,让这个Python代码组件模块直接返回List[Dict]就行。

from typing import List
 from promptflow import tool
 from embeddingstore.core.contracts import SearchResultEntity
 
 @tool
 #def generate_prompt_context(search_result: List[dict]) -> str:
 def generate_prompt_context(search_result: List[dict]):
     def format_doc(doc: dict):
         return f"Content: {doc['Content']}\nSource: {doc['Source']}"
     
     SOURCE_KEY = "source"
     URL_KEY = "url"
     
     retrieved_docs = []
     for item in search_result:
         entity = SearchResultEntity.from_dict(item)
         content  = entity.text or ""    
         source = ""
         if entity.metadata is not None:
             if SOURCE_KEY in entity.metadata:
                 if URL_KEY in entity.metadata[SOURCE_KEY]:
                     source = entity.metadata[SOURCE_KEY][URL_KEY] or ""
         retrieved_docs.append({
             "Content": content,
             "Source": source
         })
 #    doc_string = "\n\n".join([format_doc(doc) for doc in retrieved_docs])
 #    return doc_string
     return retrieved_docs

   请注意以上代码被注释之后修改的部分。

   而“search_result_from_online”原来的代码是处理完Bing搜索到的页面之后,生成包含网页URL和内容的List。我们打算让后一个组件模块都处理List,这部分代码就不用调整了。

   接下来我们看看“process_search_result”组件模块的代码。我们需要修改Dict的定义,除了原来的Content和Source之外,增加Citation。同时,我们需要修改模块定义的输入,从原来处理网络搜索的一个,变为网络搜索和矢量搜索的两个(注意被注释的部分)。

from promptflow import tool
 
 @tool
 def process_search_result(search_result_external, search_result_internal):
 #def process_search_result(search_result_internal):
     def format(doc: dict):
         return f"Content: {doc['Content']}\nCitation: {doc['Citation']}\nSource: {doc['Source']}"
 
     try:
         context = []
         for i in search_result_internal:
             context.append({
                 "Content": i['Content'],
                 "Source": i['Source'],
                 "Citation": "Internal"
             })
         for url, content in search_result_external:
             context.append({
                 "Content": content,
                 "Source": url,
                 "Citation": "External"
             })
         context_str = "\n\n".join([format(c) for c in context])
         return context_str
 
     except Exception as e:
         print(f"Error: {e}")
         return ""

   然后我们需要在代码定义内部,增加对矢量搜索结果的处理。前文说了我们将返回的字符串改为List,这里就要对List里面包含的Content和Source进行枚举,然后添加到字典里,同时增加值为Internal的Citation项目。原本处理网络搜索List的代码我们也加上值为External的Citation项目。最后再把生成的字典按条目使用\n\n隔离,再转换为字符串。

   终于到最后一步了。我们需要对原来的提示进行修改,使得 GPT 模型聊天时,能够按照我们的要求在内容之后标注引用是内部还是外部,来源的文件或网页URL地址。传统上做这件事使用代码的话会很复杂,而现在借助 LLM 的能力,我们只需要使用提示工程的方法就能很容易的实现。以下就是一个示例提示:

system:
 You are a chatbot having a conversation with a human.
 Given the following extracted parts of a long document and a question, create a final answer with references ("Citation: Internal" or "Citation: External") and ("Source").
 If you don't know the answer, just say that you don't know. Don't try to make up an answer.
 ALWAYS return "Citation" and "Source" parts in your answer after reference content and one url per line.
 
 {{contexts}}
 
 {% for item in chat_history %}
 user:
 {{item.inputs.question}}
 assistant:
 {{item.outputs.answer}}
 {% endfor %}
 
 user:
 {{question}}

   可以把这个提示与提示流默认示例的提示进行比较。ac281672e583a69a9a78f03285115583.png

   至此,我们的工作就完成了,来试一试和同时支持离线/在线RAG的GPT聊天吧。

[1]: FAISS, Facebook AI Similarity Search,FaceBook的AI团队针对大规模相似度检索问题开发的一个工具,使用C++编写,有python接口,对10亿量级的索引可以做到毫秒级检索的性能。- https://github.com/facebookresearch/faiss

[2]: Azure 机器学习中的向量存储 - https://learn.microsoft.com/zh-cn/azure/machine-learning/concept-vector-stores?view=azureml-api-2&WT.mc_id=AI-MVP-33253

[3]: Azure ML Assets - https://github.com/Azure/azureml-assets/tree/main/assets/large_language_models/rag

[4]: Azure ML Samples - create faiss index - https://github.com/Azure/azureml-examples/blob/main/sdk/python/generative-ai/promptflow/create_faiss_index.ipynb

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值