成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

基于LangChain自查詢檢索器的RAG系統開發實戰 原創

發布于 2024-5-22 09:23
瀏覽
0收藏

本文介紹RAG(檢索增強生成)技術并基于LangChain框架的自查詢檢索器來開發一個實戰型電影推薦系統。

引言

最近,我在瀏覽Max.com網站時想找一部電影看。通常,這個過程包括瀏覽系統呈現給我的各種列表,閱讀一些相關描述,然后挑選一些看起來有趣的電影。如果我知道我想看的電影的片名或我喜歡的演員的名字,我通常只會點擊搜索功能。否則,搜索就沒有多大用處了。

現在,我突然想到了一個新的想法:為什么我不能用自然語言來查找一部電影,更多地基于電影的氛圍或實質,而不僅僅是標題或演員呢?例如,為什么我不能啟動Max、Netflix或Hulu等流媒體播放平臺,并在搜索欄中鍵入類似于以下查詢之一呢:

  • 給我找一部長度不到2小時、以寵物為主角的英語戲劇電影。
  • 推薦僵尸電影,但要確保它們很有趣。
  • 我喜歡《瞬息全宇宙》。給我一部類似的電影,但場景、氛圍或者人物性格更加陰暗、沉重一些。

這種方法的美妙之處超出了更自然的電影搜索方式,還保護了用戶的隱私。該系統根本不會使用用戶數據,不是挖掘用戶的行為、喜歡和不喜歡來提供給推薦系統。唯一需要的就是一個查詢。

為此,我開發了本文中要展示給大家的一個電影搜索程序。這是一個基于RAG(檢索增強生成)的系統,它可以接受用戶的查詢,嵌入查詢,并進行相似性搜索,以找到相似的電影。不過,這個程序超越了普通的RAG系統。這個系統使用了所謂的自查詢檢索器。該技術允許在進行相似性搜索之前,根據電影的元數據對其進行過濾。因此,如果用戶有一個類似“推薦1980年后拍攝的以大量爆炸為特征的恐怖電影”的查詢,搜索算法將首先過濾掉所有不是“1980年后制作的恐怖片”的電影,然后再對“以大量爆炸為主”的電影進行相似性搜索。

在本文中,我將提供一個關于我如何創建此系統的總體概述。如果您想深入了解這個程序,完整的源代碼將在文后的鏈接參考處提供。

接下來,讓我們繼續作深入介紹。

檢索數據

首先,該項目的數據來自電影數據庫(TMDB:https://developer.themoviedb.org/docs/getting-started),并得到了所有者的許可。他們的API使用簡單,維護良好,并且沒有嚴格的費率限制。我從他們的API中提取了以下電影屬性:

  • 標題
  • 運行時間(分鐘)
  • 語言
  • 概述
  • 發布年份
  • 體裁
  • 描述電影的關鍵詞
  • 演員
  • 董事
  • 流式傳輸的位置
  • 購買地點
  • 出租場所
  • 生產公司名單

以下是如何使用TMDB API和Python的響應庫提取數據的片段:

def get_data(API_key, Movie_ID, max_retries=5):
    """
函數以JSON格式提取感興趣的電影的詳細信息。

    parameters:
    API_key (str): Your API key for TMBD
    Movie_ID (str): TMDB id for film of interest

    returns:
    dict: JSON格式的字典,包含您的電影的所有細節
興趣
    """

    query = 'https://api.themoviedb.org/3/movie/' + Movie_ID + \
        '?api_key='+API_key + '&append_to_response=keywords,' + \
            'watch/providers,credits'
    for i in range(max_retries):
        response = requests.get(query)
        if response.status_code == 429:
            # If the response was a 429, wait and then try again
            print(
                f"Request limit reached. Waiting and retrying ({i+1}/{
                    max_retries})")
            time.sleep(2 ** i)  # Exponential backoff
        else:
            dict = response.json()
            return dict

請注意,該查詢需要電影ID(也是使用TMDB獲得的)以及append_to_response,這允許我提取幾種類型的數據,例如關鍵字、影片提供商、演員(導演和演員)以及有關電影的一些基本信息。還有一些基本的框架類代碼,以防我達到速率限制,盡管我注意到從未發生這種情況。

然后,我們必須解析JSON響應。以下的代碼片段展示了如何解析電影中的演員和導演:

credits = dict['credits']
    actor_list, director_list = [], []

# 分析演員表
cast = credits['cast']
NUM_ACTORS = 5
for member in cast[:NUM_ACTORS]:
    actor_list.append(member["name"])

# 分析劇組
crew = credits['crew']
for member in crew:
    if member['job'] == 'Director':
        director_list.append(member["name"])

actor_str = ', '.join(list(set(actor_list)))
director_str = ', '.join(list(set(director_list)))

請注意,我將演員數量限制在一部電影的前五名。我還必須說明,我只對導演感興趣,因為系統的響應還包括其他類型的劇組成員,如編輯、服裝設計師等。

所有這些數據隨后被編譯成CSV文件。上面列出的每個屬性都被轉換成了一列,現在每一行都代表一部特定的電影。以下是通過程序創建的2008_movie_collection_data.csv文件中的短片。在這個項目中,我獲得了大約100部1920年至2023年的頂級電影。

基于LangChain自查詢檢索器的RAG系統開發實戰-AI.x社區

用于演示目的的電影數據片段(作者本人提供)

信不信由你,我還沒看過《功夫熊貓》。也許我必須完成這個項目。

將文檔上載到pinecone網站

接下來,我必須將csv數據上傳到https://www.pinecone.io/網站([譯者注]。Pinecone是一個非開源型的向量數據庫。Pinecone支持在大規模向量集上進行快速且實時的搜索,具有亞秒級的查詢響應時間,適用于需要高性能和實時性的大型應用,特別適合于構建實時推薦系統、電商搜索引擎和社交媒體內容過濾等)。通常,分塊在RAG系統中很重要,但這里每個“文檔”(CSV文件的行)都很短,所以分塊不是一個問題。我首先必須將每個CSV文件轉換為LangChain文檔,然后指定哪些字段應該是主要內容,哪些字段應該作為元數據。

以下是用于構建這些文檔的代碼片段:

# 從所有csv文件加載數據
loader = DirectoryLoader(
    path="./data",
    glob="*.csv",
    loader_cls=CSVLoader,
    show_progress=True)

docs = loader.load()

metadata_field_info = [
    AttributeInfo(
        name="Title", description="The title of the movie", type="string"),
    AttributeInfo(name="Runtime (minutes)",
                  description="The runtime of the movie in minutes", type="integer"),
    AttributeInfo(name="Language",
                  description="The language of the movie", type="string"),
    ...
]

for doc in docs:
    #將page_content字符串解析到字典中
    page_content_dict = dict(line.split(": ", 1)
                             for line in doc.page_content.split("\n") if ": " in line)
    
    doc.page_content = 'Overview: ' + page_content_dict.get(
        'Overview') + '. Keywords: ' + page_content_dict.get('Keywords')
    doc.metadata = {field.name: page_content_dict.get(
        field.name) for field in metadata_field_info}

    #將字段從字符串轉換為字符串列表
    for field in fields_to_convert_list:
        convert_to_list(doc, field)      

    # 將字段從字符串轉換為整數
    for field in fields_to_convert_int:
        convert_to_int(doc, field)

LangChain的DirectoryLoader負責將所有csv文件加載到文檔中。然后,我需要指定什么應該是page_content,什么應該是metadata;這是一個重要的決定。page_content將在檢索階段嵌入并用于相似性搜索。在進行相似性搜索之前,metadata將僅用于過濾目的。我決定采用overview和keywords屬性并嵌入它們,其余的屬性將是元數據。應該做進一步的調整,看看title是否也應該包括在page_content中,但我發現這種配置對大多數用戶查詢都很有效。

接下來,文件必須上傳到pinecone網站。這是一個相當簡單的過程:

# 如果尚未創建索引,則取消注釋
pc.create_index(
    name=PINECONE_INDEX_NAME,
    dimension=1536,
    metric="cosine",
    spec=PodSpec(
        environment="gcp-starter"
    )
)

# 目標索引和檢查狀態
pc_index = pc.Index(PINECONE_INDEX_NAME)
print(pc_index.describe_index_stats())

embeddings = OpenAIEmbeddings(model='text-embedding-ada-002')

vectorstore = PineconeVectorStore(
    pc_index, embeddings
)

# 創建記錄管理器
namespace = f"pinecone/{PINECONE_INDEX_NAME}"
record_manager = SQLRecordManager(
    namespace, db_url="sqlite:///record_manager_cache.sql"
)

record_manager.create_schema()

# 將文檔上載到松果網站
index(docs, record_manager, vectorstore,
      cleanup="full", source_id_key="Website")

我只想在這里強調幾個事情:

  • 如果多次運行此代碼,那么使用SQLRecordManager可確保不會將重復的文檔上載到Pinecone。如果修改了文檔,則在矢量存儲中僅修改該文檔。
  • 我們使用OpenAI的經典text-embedding-ada-002作為我們的嵌入模型。

創建自查詢檢索器

自查詢檢索器將允許我們通過我們之前定義的元數據來過濾RAG期間檢索到的電影。這將大大提高我們電影推薦人的實用性。

在選擇矢量存儲時,一個重要的考慮因素是確保它支持按元數據過濾,因為并非所有數據庫都支持這種技術。鏈接https://python.langchain.com/docs/integrations/retrievers/self_query處提供了LangChain支持自查詢檢索的數據庫列表。另一個重要的考慮因素是對于每個矢量存儲允許什么類型的比較器。比較器是我們通過元數據進行過濾的方法。例如,我們可以使用eq比較器來確保我們的電影屬于科幻類型:eq('Genre', 'Science Fiction')。并非所有矢量存儲都允許所有比較器。舉個例子,有興趣的讀者可以觀察一下開源的嵌入式數據庫Chroma中支持的比較器(https://docs.trychroma.com/usage-guide#using-where-filters),以及它們與Pinecone網站中支持的比較器(https://docs.pinecone.io/guides/data/filtering-with-metadata#metadata-query-language)有何不同。我們需要告訴模型允許使用哪些比較器,以防止它意外地寫入禁止的查詢。

除了告訴模型存在哪些比較器之外,我們還可以提供用戶查詢和相應過濾器的模型示例。這被稱為小樣本學習(Few-shot Learning),這對指導您的模型是非常寶貴的。

要具體地了解這一技巧有何幫助,您可以嘗試查看以下兩個用戶查詢:

  • “推薦一些約戈斯·蘭蒂莫斯的電影。”
  • “類似于約戈斯·蘭圖米奧斯電影的電影。”

我的元數據過濾模型很容易為這些示例中的每一個編寫相同的過濾查詢,盡管我希望對它們進行不同的處理。第一部應該只推薦蘭蒂莫斯執導的電影,而第二部應該推薦與蘭蒂莫斯電影有相似氛圍的電影。為了確保這種行為,我一點點細致地提供了我想要的行為的模型示例。語言模型的美妙之處在于,它們可以利用自己的“推理”能力和世界知識,將這些小樣本學習示例推廣到其他用戶查詢中。

document_content_description = "Brief overview of a movie, along with keywords"

        # 定義允許的比較器列表
        allowed_comparators = [
            "$eq",  # Equal to (number, string, boolean)
            "$ne",  # Not equal to (number, string, boolean)
            "$gt",  # Greater than (number)
            "$gte",  # Greater than or equal to (number)
            "$lt",  # Less than (number)
            "$lte",  # Less than or equal to (number)
            "$in",  # In array (string or number)
            "$nin",  # Not in array (string or number)
            "$exists", # Has the specified metadata field (boolean)
        ]

        examples = [
            (
                "Recommend some films by Yorgos Lanthimos.",
                {
                    "query": "Yorgos Lanthimos",
                    "filter": 'in("Directors", ["Yorgos Lanthimos]")',
                },
            ),
            (
                "Films similar to Yorgos Lanthmios movies.",
                {
                    "query": "Dark comedy, absurd, Greek Weird Wave",
                    "filter": 'NO_FILTER',
                },
            ),
            ...
        ]

        metadata_field_info = [
            AttributeInfo(
                name="Title", description="The title of the movie", type="string"),
            AttributeInfo(name="Runtime (minutes)",
                          description="The runtime of the movie in minutes", type="integer"),
            AttributeInfo(name="Language",
                          description="The language of the movie", type="string"),
            ...
        ]

        constructor_prompt = get_query_constructor_prompt(
            document_content_description,
            metadata_field_info,
            allowed_comparators=allowed_comparators,
            examples=examples,
        )

        output_parser = StructuredQueryOutputParser.from_components()
        query_constructor = constructor_prompt | query_model | output_parser

        retriever = SelfQueryRetriever(
            query_constructor=query_constructor,
            vectorstore=vectorstore,
            structured_query_translator=PineconeTranslator(),
            search_kwargs={'k': 10}
        )

除了示例之外,模型還必須知道每個元數據字段的描述。這有助于它了解什么是元數據過濾。

最后,我們來構建我們的鏈。這里的query_model是使用OpenAI API的GPT-4 Turbo的一個實例。我建議使用GPT-4而不是3.5來編寫這些元數據過濾器查詢,因為這是一個關鍵步驟,理由是3.5會更頻繁地出錯。search_kwargs={'k':10}告訴檢索器根據用戶查詢找出十部最相似的電影。

創建聊天模型

最后,在構建了自查詢檢索器之后,我們可以在此基礎上構建標準的RAG模型。我們首先定義我們的聊天模型。這就是我所說的摘要模型,因為它采用上下文(檢索到的電影+系統消息),并以每個推薦的摘要作為響應。如果你想降低成本,這個模型可以是GPT-3.5 Turbo;當然,如果你想獲得絕對最佳的結果,這個模型也可以是GPT-4 Turbo。

在系統消息中,我告訴機器人它的目標是什么,并提供了一系列建議和限制,其中最重要的是不要推薦自我查詢檢索器沒有提供給它的電影。在測試中,當用戶查詢沒有從數據庫中得到電影時,我遇到了問題。例如,查詢“推薦一些由韋斯·安德森執導的馬特·達蒙主演的1980年之前拍攝的恐怖電影”會導致自我查詢檢索器無法檢索到任何電影(因為盡管聽起來很棒,但這部電影并不存在)。在沒有電影數據的情況下,該模型會使用自己的(錯誤的)內存來嘗試推薦一些電影。這是不好的行為。我不希望Netflix的推薦人討論數據庫中沒有的電影。下面的系統消息成功阻止了此行為。我確實注意到GPT-4比GPT-3.5更善于遵循指令,這是在意料之中的事情。

chat_model = ChatOpenAI(
    model=SUMMARY_MODEL_NAME,
    temperature=0,
    streaming=True,
)

prompt = ChatPromptTemplate.from_messages(
    [
        (
            'system',
            """
            Your goal is to recommend films to users based on their 
            query and the retrieved context. If a retrieved film doesn't seem 
            relevant, omit it from your response. If your context is empty
            or none of the retrieved films are relevant, do not recommend films
            , but instead tell the user you couldn't find any films 
            that match their query. Aim for three to five film recommendations,
            as long as the films are relevant. You cannot recommend more than 
            five films. Your recommendation should be relevant, original, and 
            at least two to three sentences long.
            
            YOU CANNOT RECOMMEND A FILM IF IT DOES NOT APPEAR IN YOUR 
            CONTEXT.

            # TEMPLATE FOR OUTPUT
            - **Title of Film**:
                - Runtime:
                - Release Year:
                - Streaming:
                - (Your reasoning for recommending this film)
            
            Question: {question} 
            Context: {context} 
            """
        ),
    ]
)

def format_docs(docs):
    return "\n\n".join(f"{doc.page_content}\n\nMetadata: {doc.metadata}" for doc in docs)

# Create a chatbot Question & Answer chain from the retriever
rag_chain_from_docs = (
    RunnablePassthrough.assign(
        context=(lambda x: format_docs(x["context"])))
    | prompt
    | chat_model
    | StrOutputParser()
)

rag_chain_with_source = RunnableParallel(
    {"context": retriever, "question": RunnablePassthrough()}
).assign(answer=rag_chain_from_docs)

上述代碼中,formatdocs用于格式化提供給模型的信息,使其易于理解和解析。我們向模型提供page_content(概述和關鍵字)以及元數據(所有其他電影屬性);任何它可能需要用來更好地向用戶推薦電影的信息。

rag_chain_from_docs是一個鏈,它獲取檢索到的文檔,并使用format_docs對其進行格式化,然后將格式化的文檔饋送到模型用來回答問題的上下文中。最后,我們創建了rag_chain_with_source,這是一個RunnableParallel,顧名思義,它并行運行兩個操作:自查詢檢索器啟動以檢索類似的文檔,而查詢只是通過RunnablePassthrough()函數傳遞給模型。然后將來自這兩個并行組件的結果進行組合,并使用rag_chain_from_docs生成答案。這里的source指的是檢索器,它可以訪問所有的“source”文檔。

因為我希望答案是流式的(例如,像ChatGPT這樣一塊一塊地呈現給用戶),所以我們使用了以下代碼:

for chunk in rag_chain_with_source.stream(query):
    for key in chunk:
        if key == 'answer':
            yield chunk[key]

程序展示

現在進入有趣的部分:與模型一起玩。Streamlit軟件是一個用于創建前端和托管應用程序的優秀工具。當然,我不會在本文中討論所開發軟件的用戶界面相關的代碼;有關此用戶界面實現的詳細信息,請參閱文后所附的原始代碼。當然,這些代碼也相當簡單,Streamlit網站(https://docs.streamlit.io/knowledge-base/tutorials/build-conversational-apps)上還有很多其他的例子可供參考。

基于LangChain自查詢檢索器的RAG系統開發實戰-AI.x社區

電影搜索實例程序的用戶界面(作者本人提供圖片)

您可以使用軟件中提供的好幾個方面的建議,但首先讓我們嘗試使用自己的查詢:

基于LangChain自查詢檢索器的RAG系統開發實戰-AI.x社區

示例查詢和模型響應情況(作者本人提供圖片)

在底層的代碼實現中,這個自我查詢的檢索器確保過濾掉任何不是法語的電影。然后,它對“成長故事”進行了相似性搜索,得出了十部在此背景下的電影。最后,機器人選擇了五部電影進行推薦。請注意建議的電影范圍:有些電影的上映日期最早在1959年,最晚在2012年。為了方便起見,我確保機器人提供的信息中包含電影的運行時間、上映年份、流媒體提供商以及機器人手工制作的簡短推薦。

(旁注:如果你還沒有看過《400拳》( The 400 Blows:https://en.wikipedia.org/wiki/The_400_Blows),請停止你正在做的任何事情,立即去看一看吧。)

值得注意的是,以前在大型語言模型中通常被視為負面的性質,例如其響應的不確定性,現在被系統認為是正面的性質。向模型提出同樣的問題兩次,你可能會得到略微不同的建議。

重要的是,要注意當前實施的一些局限性:

  • 無法保存建議。用戶可能希望重新訪問舊的推薦。
  • 手動更新電影數據庫中的原始數據。將其自動化并每周更新是個好主意。
  • 自查詢檢索過濾的元數據不正確。例如,“本·阿弗萊克電影”的查詢可能會有問題。這可能意味著查詢本·阿弗萊克主演的電影或本·阿弗萊克執導的電影。這是一個對查詢進行澄清會有所幫助的例子。

最后,您可能對本文項目作出的改進之一是,在檢索后對文檔(https://python.langchain.com/docs/integrations/retrievers/cohere-reranker)進行重新排序。另外,提供一個聊天模型也可能很有趣,因為你可以在多回合的對話中與之交談,而不僅僅是一個QA機器人。此外,你還可以創建一個推薦器代理(https://python.langchain.com/docs/integrations/tools/human_tools),以便在查詢不清楚的情況下向用戶提示一個清晰的問題。

最后,祝您的電影搜索玩得開心!

鏈接參考

  • 自己嘗試電影搜索(需要OpenAI API密鑰):https://platform.openai.com/api-keys
  • 本文位于GitHub的示例代碼鏈接:https://github.com/EdIzaguirre/FilmSearchOpen

譯者介紹

朱先忠,51CTO社區編輯,51CTO專家博客、講師,濰坊一所高校計算機教師,自由編程界老兵一枚。

原文標題:How to Build a RAG System with a Self-Querying Retriever in LangChain,作者:Ed Izaguirre

鏈接:

https://towardsdatascience.com/how-to-build-a-rag-system-with-a-self-querying-retriever-in-langchain-16b4fa23e9ad。

?著作權歸作者所有,如需轉載,請注明出處,否則將追究法律責任
收藏
回復
舉報
回復
相關推薦
主站蜘蛛池模板: 99精品在线 | 欧美日韩在线观看一区 | 亚洲欧洲中文 | 午夜一区 | 男人阁久久| 成人不卡 | 高清国产一区二区 | 日韩精品久久久久 | 看一级毛片 | 国产免费一区二区 | 精品麻豆剧传媒av国产九九九 | 超碰日本 | 亚洲一av| 91精品国产一区二区三区 | 国产精品99精品久久免费 | 欧美国产精品一区二区三区 | 成人精品国产免费网站 | 狠狠干天天干 | 羞羞视频在线观免费观看 | 国产免费一区二区三区 | 亚洲高清在线 | 国产精品永久在线观看 | 国产精品免费小视频 | 91看片网址| 天天操综合网 | 欧美视频 亚洲视频 | 日本免费视频 | 可以在线看的黄色网址 | 在线色网站 | 日韩欧美三级在线 | 欧美日韩在线免费 | 国产特级毛片aaaaaa | 亚洲成人高清 | 成人福利在线 | 国产成人精品久久二区二区91 | 蜜桃传媒一区二区 | 亚洲成人国产综合 | 日韩中文一区二区 | 91久久看片 | 亚洲 中文 欧美 | 欧美久久久久 |