New course with Neo4J: Knowledge Graphs for RAG [![KGRAG](https://hackmd.io/_uploads/Bk1o5NeRa.png)](https://learn.deeplearning.ai/courses/knowledge-graphs-rag/lesson/1/introduction) 這段影片的主要介紹 ## Introduction 我們今天要介紹的是「知識抽取擴充生成」(Knowledge Draft Rag),這項技術是與NEO4J合作開發的,由Andreas Kiger來教學。知識抽取在許多大型企業裡,包括了像是大型網路搜尋引擎和電子商務網站,都扮演了很重要的角色。雖然在學術AI領域可能被低估了,但學習這項技術絕對是值得的。這門課程會教你如何結合大型語言模型來運用知識抽取。 所謂的知識抽取,是指一種儲存與組織數據的方式,它特別強調事物之間的關聯。舉個例子,如果有個存放電影資訊的知識圖譜,它可能會將演員和電影分別作為圖譜中不同類型的節點,並透過邊來表示演員和他們所演的電影之間的關係。這裡的「圖」指的是計算機科學中的數據結構概念,和我們高中數學課本上的「圖表」是不同的。 如果你在建立一個擴充檢索生成(RAG)系統,想要回答關於電影和演員的問題,這種數據結構可以幫你快速找到相關資訊,作為大型語言模型的上下文依據。這門課程中,你還會學到如何為金融投資公司建立一個知識圖譜,這個圖譜會顯示出哪些投資公司投資了哪些其他公司。 知識圖譜對網路搜尋引擎來說至關重要,它們是這些搜尋引擎找出相關資訊的核心。當你在Google或Bing搜尋時,側邊欄上出現的卡片大多來自於知識圖譜。所以,看到知識圖譜現在被用於RAG和OM等應用,真是太令人興奮了。 Andreas Kiger是Gen AI和NEO4J的開發傳道者,NEO4J是領先的圖形數據庫公司,他加入該公司時是第十名員工,現在公司已發展至近千人。他最近專注於將Gen AI工具整合進知識圖譜中。這門課程的目的,就是要幫助你對這個在AI領域中既重要又被低估的工具建立直覺理解。 在課程中,你會學到如何建立和查詢知識圖譜。從學習如何使用Cypher(NE 4J的查詢語言)來查詢有趣的電影資料庫開始,如果你之前有使用過SQL,Cypher這個語言的某些部分對你來說會很熟悉。接著,你會學習使用Cypher、Lang chain和嵌入模型,來構建一個包含公司向證券交易委員會(SEC)提交的金融文件的知識圖譜。證券交易委員會是美國政府的一個機構,負責監管和監督市場,保護投資者。 建立好這個數據庫之後,你會利用Lang chain結合這個數據庫來詢問SEC數據,像是「這家公司主要做什麼」以及「它的主要投資者是誰」。這種分析公開文件的方式,其實是知識圖譜非常常見的應用場景,而加入大型語言模型可以大大幫助這項研究。 我相信這門課程會讓你享受學習過程,並且能夠利用知識圖譜來打造出真正卓越的東西。 這就是影片開頭部分的內容,希望這樣的整理和說明對您有所幫助! 課程的SOURCE CODE [ https://github.com/ww-jermaine/Knowlege-Graphs-for-RAG/blob/main/README.md](https://github.com/ww-jermaine/Knowlege-Graphs-for-RAG/blob/main/README.md) ## Lesson 2: Querying Knowledge Graphs with Cypher 現在您已經探索了知識圖譜的基本概念,讓我們來看看它們是如何實際運用的。在本課程中,您將使用 Cypher 查詢語言與一個有趣的知識圖譜互動,其中包含了關於演員和電影的數據。讓我們開始吧。 好的,為了讓這個notebook開始運行,我們將導入一些需要的包,以便能夠訪問Neo4j和設置環境。這些都是一些經典的包,我們將從OS和大家的朋友dot env中獲取。 此外,我們還將從LangChain中載入Neo4jGraph類本身,這就是我們將如何訪問Neo4j的方式。 通過導入這些包,我們將設置Neo4j本身,方法是獲取一些環境變量。當然,您知道從dot env加載,然後設置一些變量。 現在,我們正在設置的變量都當然是以Neo4j命名的。第一個Neo4jURI基本上是連接字串,用於指定Neo4j的位置、端口等典型內容。我們當然需要一個用戶名和密碼,並將指定要使用DBMS中的哪個數據庫。 好的,現在我們已經設置好了環境,我們將創建一個Neo4jGraph類的實例,它基本上將作為與數據庫的驅動程序或連接,我們可以在Python中使用它。 因為今天我們要構建一個知識圖譜,所以我們將這個Neo4jGraph實例稱為KG,這是knowledge graph的縮寫。 ![image](https://hackmd.io/_uploads/HymJN8Kk0.png) 好了,現在notebook準備好開始對這個知識圖譜發送Cypher查詢了。但是我們實際要使用的知識圖譜是什麼呢?讓我們先看看那個。 我們知道它包含電影和人物。這是它的樣子的插圖。在左邊,我們有一些人物節點。我們知道他們會出演一些電影,對吧?所以演員之所以成為演員,是因為他們曾出演過某些作品。如果你能將其讀出作為一個句子,當然就是一個人在一部電影中出演。這就是我們在處理這個數據集時要尋找的基本模式。 對於這些人物和電影節點,我們知道它們都有屬性。人物可用的屬性是他們都有名字,還有出生年份,只是一個整數,表示他們出生的年份。電影則有標題和標語,兩者都是字符串,還有一個發行日期,也只是電影發行年份的整數。 最後,就像我提到的,一個人在一部電影中出演,還有更多人與電影之間的關係。這裡列出了人物和電影之間的所有不同關係。我們知道他們已經出演過那部電影。一個人也可能執導過那部電影。有時對於某些電影,我們知道一個人既出演又執導了一部電影。此外,他們可能撰寫過那部電影,所以一個人可能撰寫了那部電影。一個人可能制作過那部電影。最後,有人可能評論過那部電影。這就是人物和電影之間在這個數據集中的所有不同關係。 但是人物本身也與其他人物有關係。這裡的想法是,如果某個人評論了一部電影,另一個人可能是那位評論者的追隨者。所以這就是存在的所有關係。 有趣的是,我們只需要說這些被標記為人物的節點就是人物。這些人物的角色、與電影和其他人物的關係,真正決定了他們在數據集中的類型或職業或行為。 當然值得觀察的是,這些都是潛在的關係,實際的關係本身是基於特定人物與特定電影之間的動態關係。 現在您對圖形的樣子有了一些瞭解。我們知道我們有人物,我們知道我們有電影和與這些電影的關係。我們知道我們有電影,以及這些人物與電影之間的關係。讓我們開始在Python notebook中查詢。 我們要進行的查詢是基於Cypher的。Cypher是Neo4j的查詢語言,它使用模式匹配來在圖形中查找東西。 這是它的樣子。我將在Python中使用多行字符串,並將該字符串分配給這個名為Cypher的變量。Cypher本身以match子句開頭。Match是模式匹配。 而您可以查找的最小模式匹配是單個節點模式。在這裡,我將模式的結果分配給一個我稱為n的變量。然後下一行,我將說返回您找到的那些n的計數。 所以運行此查詢的結果應該是我們在此圖形中擁有的節點數量,無論這些節點是人物、電影還是其他內容,所有節點都應該在此查詢中被計數。 ![image](https://hackmd.io/_uploads/rkfQVUY1R.png) 所以讓我們分配該變量並運行查詢本身。我們將利用之前設置的LangChain集成。我們有kg.query函數,它實際上將採用我們之前定義的Cypher,運行該Cypher並返回結果。然後我們將只顯示結果的實際樣子。 您可以看到,結果實際上是來自Neo4j查詢的行列表。在該查詢中,每個行都是一個字典,其中字典鍵是基於您從Cypher的return子句中返回的內容。 所以這裡我們一直在返回count n。因此我們得到了一個count n的鍵,它的值是171。所以在這個圖形中總共有171個節點。 現在,count n作為變量名稱或作為字典中的鍵名並不是最好的。因此,您也可以使用別名重命名這些內容。 所以我們可以說將count n稱為number of nodes。這就友好多了,對吧?所以我們將重新定義該Cypher,然後重新運行查詢本身。現在看起來就好多了。我們有一個number of nodes,顯然結果仍然是171。 很好。現在我們知道我們得到了這個結果,而不是查看原始結果,我們可以利用一些不錯的打印語句。 好的,通過我們剛才進行的那個Cypher Match,我們查找了圖形中所有節點的模式並返回了計數。但如果我們不想找到圖形中的所有節點,而只是電影或人物呢? 電影和人物將顯示為節點上的標籤。所以我們需要做的就是修改之前的match,並添加一個電影的標籤。 所以讓我們這樣做。我將從之前相同的Cypher查詢開始。這個小改動是,不僅僅是n本身,我們可以添加一個冒號,然後說movie。Movie是所有電影節點的標籤名稱。 我們將返回一個count,而不是number of nodes。我們將直接稱其為number of movies。運行它,當然就顯示了該數據集中電影的數量是38部。很好,這足夠讓我們嘗試了。 太棒了。我們得到了預期的結果,這真的很好。 還有一個小改動我們可以對此查詢做,以提高查詢的可讀性。我們一直在使用這個變量n來捕獲匹配的所有這些節點的模式。但是我們知道我們在抓取電影,而電影是以字母M開頭的,而不是N。 所以讓我們說我們在獲取電影。因此,我們在獲取電影,而電影以字母M而不是N開頭。 好的,運行這個又得到了相同的結果。我們所做的只是更改了變量的名稱,以幫助提高查詢的可讀性。 我們也可以對people做同樣的事情。我們只需修改這裡的查詢,而不是匹配標籤為movie的節點。我們將其更改為person,並更改這裡的名稱。 讓我們說這是people。所以我們將匹配標籤為person的人物節點。我們將再次對這些節點執行計數,並將結果重命名為number of people。 太棒了。完美。 所以現在對於圖形中的任何一組節點,您都知道如何匹配這些節點,然後獲得它們的計數。 ![image](https://hackmd.io/_uploads/HJGrEUKJA.png) 因此,這是探索新圖形時的一個很好的起點。在探索圖形時,您當然可以添加更多匹配條件。例如,尋找一個非常特定的值。 基於值的條件是在大括號內使用類似對象表示法引入的。 然後是屬性名稱和要查找的確切匹配。 例如,如果我們不只是想找到所有人,而是想找一個特定的人,我們將複製這個並加以修改。 所以不僅僅是所有帶有person標籤的人,讓我們尋找湯姆·漢克斯。 所以我們將為Tom匹配一個person,並在這裡引入大括號,我們希望這個人的名字是湯姆·漢克斯。 我們不需要計數湯姆·漢克斯的實例數。讓我們返回Tom本人。 所以我們可以匹配一個名為湯姆·漢克斯的person。讓我們返回Tom。 運行該查詢,我們就有了一個結果列表,因為在這個電影數據集中沒有其他名為湯姆·漢克斯的人。 我們找到了湯姆·漢克斯。我們可以看到他的出生年份,以及這個特定節點上的所有屬性。 您當然也可以對於您碰巧知道要尋找的電影做同樣的事情。所以讓我們複製所有這些,然後我們將進行更改。不是尋找一個人,而是尋找一部電影。 我想這裡應該有一部老電影,但是很出色的電影,叫做《雲圖》。 它不是一個名字,但它的標題是《雲圖》。然後我們返回該電影節點。 這裡是《雲圖》。我們可以看到它有一個標語。"一切都是相連的,為什麼這麼貼近我的心?"當然,標題是《雲圖》,它是在2012年發行的。 在最後這幾個查詢中,我們一直在尋找特定的節點,可能是基於一些確切的匹配條件。如果我們不想返回整個《雲圖》節點,只是它的發行日期,我們可以只返回那個。 讓我們複製這個查詢,然後只修改return子句。 所以這裡CloudAtlas將是所有具有此標題《雲圖》的節點。我們知道它們有一個標題,它們還有一個發行日期。 所以我們將在這裡添加發行日期,然後運行它。 現在返回的值僅為發行日期號碼本身,而不是所有其他屬性。 當然,如果您願意,您也可以返回多個值。 所以我們再對此進行一個修改,我們將返回兩件事。 除了發行日期之外,我們還將返回標語,比如說,CloudAtlas。標語。 現在我們在這個列表中得到一個字典,其中只有我們感興趣的這兩個字段。 到目前為止我們描述的模式,我們一直在進行確切匹配,以查找具有特定標籤的節點。然後我們對這些節點內的特定值進行了確切匹配。 如果您不知道要查找的確切值,您也可以進行範圍類型查詢。您可以在屬性匹配中進行條件匹配。 讓我們看一下。假設對於這些電影,您知道,這是一組經典電影。讓我們找到所有來自90年代的電影。 在代碼中,Cypher查詢看起來像這樣。 第一部分與我們之前做的完全一樣。我們有一個match子句,只有一個單節點模式。 現在我們將使用where子句來限定我們正在進行的那個模式匹配。 所以90s將包含所有帶有movie標籤的電影。然後where子句將為我們實際關心的屬性進行條件匹配。 在這裡,我們將對發行日期進行範圍值比較,既大於1990又小於2000。 對於那些90年代的電影,我們將返回它們的標題。 這就是查詢。我們將獲取該集合。 像之前一樣,我們將使用LangChain集成來針對數據庫運行此查詢。 這裡就是那些經典的、出色的90年代電影。 好的,我們可以用單個節點模式做更多事情。但讓我們通過引入關係來探索一些更多的圖形相關內容。 我們知道我們有人物出演過電影。因此讓我們看一些例子,看看演員的名字和他們出演過的電影。 我們將使用稍大一些的模式來做到這一點。 Cypher查詢本身開始時與之前非常相似。我們有一個match子句。它將匹配一些演員,我們知道他們具有人物標籤。 現在有趣的部分是,我們在這裡引入了關係。這是一個出演關係,與一些我們不知道的電影相關聯。但是我們知道這些電影節點帶有電影標籤。 對於數據庫中匹配的那些演員和電影模式,我們只是返回演員的名字和電影的標題,並將結果限制為10條。 很酷,現在我們有一堆演員名字和他們出演過的電影。當然,由於我們熱愛矩陣,所以這裡充斥著大量矩陣相關的內容。 您可能認識其中一些名字,但也許第一個名字不太對。我不確定這完全正確。 讓我們暫時回到這個問題。 如果我們關心某個特定的演員,並想找出他們出演過的電影,我們也可以做到這一點,使用我們之前研究過的模式匹配。 所以讓我們假設我們記得數據集中當然有湯姆·漢克斯。 讓我們看看一個將他重新引入的 Cypher 查詢長什麼樣。 這裡是 Cypher 的樣子。我們有一個 match 子句。我們將說有一個名叫湯姆·漢克斯的person,他出演了一些湯姆·漢克斯電影。 讓我們返回那個person的名字Tom.name,以及那些湯姆·漢克斯電影的titles。 太棒了。現在我們看到湯姆·漢克斯以及他在這個數據庫中出演過的所有電影。 當我們在處理湯姆·漢克斯的時候,延伸這個模式稍微更進一步可能會很有趣。 我們目前拥有的模式是从一个人出演一部电影。我们可以思考,嗯,在那部电影中还有谁一起出演? 因此,模式现在将是从一个人出演一部电影到在那部电影中的其他一些人。这变得非常有趣。让我们看看那个查询是什么样子的。 在 Cypher 中,这最终会变得有点长,但我们可以一起通读。 因此,match 子句现在是 match Tom 是一个人,其中 name 是 Tom Hanks。 Tom 出演了一些电影,我们暂且叫它们 m。我们甚至不会在这里使用电影的标签。 我们知道,一个人出演了某些作品,就会出现在一些电影中。 然后从另一个方向来看,我们还有一个新的关系 also acted in。这将被分配给一些协演者节点。 所以在这个右手边,我们知道还是将会有人在电影中出演。 我们真正关心的是那些最终的协演者。我们将返回协演者的名字和他们与汤姆·汉克斯合作出演的电影的标题。 像以前一样,kg.query 来运行这段 Cypher。 太酷了。这里是所有与汤姆·汉克斯在各种电影中合作的人。这是一个很长的名单,可能还不到凯文·贝肯那么长,但很长。 您可能还记得之前我们注意到在《黑客帝国》电影中有一位名叫埃米尔·埃夫拉姆的演员。 埃米尔并不是《黑客帝国》的演员,他实际上是 Neo4j 的创始人。 我们可以将他留在知识图谱中,因为他是一个人。让我们删除他被当演员的事实。 但在此之前,让我们首先找到埃米尔可能还有哪些其他声称,方法与我们对汤姆·汉克斯所做的类似。 我们将对模式进行匹配,我们有埃米尔,他是一个名为埃米尔·埃夫拉姆的人。而且他出演了一些电影。让我们返回埃米尔的名字和那些电影的标题。 好的,他只声称出演过《黑客帝国》。 现在我们将运行一个非常相似的查询。但是不是返回一些结果,而是删除那个出演的关系。 您可以拿我们刚才的查询,因为匹配是完美的。但是不是返回一些数据,我们将使用一个新的子句,叫做 delete,delete。 delete 将从数据库中删除我们想要删除的任何内容。在这里,我们要删除这些 acted in 关系。 所以我们将说 delete acted in。 我们没有从这个查询中返回任何结果。所以结果本身实际上是空的。 让我们通过再次运行之前的查询来验证埃米尔现在已经不再是一位演员,也不再出现在电影《黑客帝国》中。 好的,现在我们甚至对这种匹配模式都没有结果了,因为当然在数据集中找不到满足这种人名为埃米尔的模式并曾出演一些电影的内容。 正是我们想看到的。好的,让我们看看如何创建数据。 创建数据与对单个节点进行匹配非常相似。 让我们看一下,例如创建一个新的人。我将创建我自己。如果埃米尔要出现在这个知识图谱中,我也可以出现。 所以新建查询看起来像这样,不是一个 match 子句。我们现在将有一个 create 子句。我们将创建一个Andreas。 我们将给它一个person标签,并给Andreas一个name属性,其值为Andreas,然后我们将返回我们刚刚创建的节点。 就这样,我也成为了知识图谱的一部分。 我们也希望能够创建关系。我们可以再进一步。 好的,现在我们已经将我 Andreas 添加到了知识图谱中,还有埃米尔也在知识图谱中,让我们在我 Andreas 和埃米尔之间添加一个关系。 添加关系与添加节点有些不同,因为关系包含两个节点。 所以第一步是有一个 Cypher 查询,您将在其中找到要创建关系的节点。 所以这个模式与我们之前看到的有点不同,让我们仔细看看。 我们将匹配一个名为 Andreas 的人,其中 name 是 Andreas。 然后我们将在这里使用一个逗号,而不是关系,因为还不存在关系。 我们还将找到一个名为艾米尔·艾夫雷姆的人。 找到这两个节点后,我们将合并一个新的关系。 merge 与 create 非常相似,除了如果关系已经存在,它不会再次创建。 所以 merge 实际上结合了 match 和 create 子句,并根据数据是否存在做出正确的处理。 所以我们将合并 Andreas,说他有一个关系类型为 works with,并与 Emil works with。 让我们返回那个 Andreas、关系和 Emil。 太棒了。所以我们可以看到有 Andreas 节点。有一个关系,其中关系类型是 works with,然后是 Emil Efrem 节点。 Emil 碰巧比我们添加给 Andreas 的属性多一些,但在知识图谱中这完全没问题。 在本课中,您学习了使用 Cypher 语言查询知识图谱的基础知识。 您正在努力构建一个RAG应用程序,这需要某种文本嵌入。 在下一课中,您将看到如何将图形中的任何文本字段转换为向量嵌入,并将它们添加到图形中,以启用向量相似性搜索。 让我们继续下一个视频,开始吧。 ## Lesson 3 當然,以下是您提供的會議記錄內容的逐字翻譯,並已去除時間標註: RAG系統首先使用文本的向量表示來匹配你的提示與非結構化數據中的相關部分。因此,為了能夠以相同的方式在知識圖譜中找到相關文本,你需要為圖譜中的文本字段創建嵌入向量。讓我們看看如何做到這一點。 首先,你將像在上一個筆記本中那樣導入一些包,我們還將設置Neo4j。你將加載與上一個筆記本中相同的環境變數,但現在包括一個新變數,稱為OpenAI API鍵,我們將使用它來呼叫OpenAI嵌入模型。最後,像之前一樣,我們將使用Neo4j圖形類來創建與知識圖譜的連接,這樣我們就可以向它發送一些查詢。 啟用向量搜索的第一步是創建一個向量索引。在這非常第一行,我們正在創建一個向量索引。我們將為其命名,電影標語嵌入,並將新增條款說明應在該索引尚不存在時才創建它。我們將為我們將稱之為M的節點創建索引,這些節點必須有電影標籤,並在這些節點上針對電影的標語屬性。我們將創建嵌入向量並儲存這些向量。在設置索引時,我們還有一些選項,我們正在以索引配置對象的形式傳遞進來。 這裡有兩件事情當然是重要的。它們是向量本身有多大,向量的維度是多少。這裡是1536,這是OpenAI的嵌入模型的默認大小。OpenAI還建議使用餘弦相似度。我們在這裡指定了它作為相似性函數。 現在,為了驗證索引是否已經創建,你可以要求Neo4j直接顯示向量索引。這是如何要求的,很簡單直接。看起來像這樣。我們可以看到我們之前指定的名稱。我們可以看到它已準備就緒並且它是一個向量索引。 太棒了。現在你有了一個向量索引,你可以開始用數據填充它。我們將在一個三步查詢中做到這一點。在這第一行,我們有我們熟悉的匹配子句。我們將匹配帶有電影標籤的電影,並且電影的標語不為空。我們只想找到有標語的電影。 在下一行,我們將拿電影,並且為標語計算嵌入向量 。我們將通過呼叫這個稱為genai.vector.encode的函數來做到這一點。在這個函數中,我們將做幾件事。我們傳遞的參數是我們想要編碼的值。這裡是movie.tagline。我們將指定我們想要使用的嵌入模型。這是OpenAI的。因為OpenAI需要一個鍵,所以我們也將在這裡傳入一些配置,它說這裡是OpenAI的令牌。 它將是這個OpenAI API鍵。這裡使用這個美元符號是我們稱之為查詢參數的某種標記。查詢參數可以用於替代賽弗語句中任何位置的字面硬編碼值。回顧一下我們用於填充向量索引所做的事情,你可以看到這最後一部分調用kg.query。我們傳遞了一個名為params的字典,並指定了一個名為OpenAI API鍵的鍵。 我們將這個的值設置為我們之前設置的環境變數,它也被稱為OpenAI API鍵。這在查詢運行之前最終會被替換到這個查詢中,就在這裡,這樣API鍵就被傳遞到了對OpenAI的調用中。 好的,那麼讓我們運行這個查詢。這個查詢可能需要幾秒鐘來運行,因為它呼叫了OpenAI API,為數據集中的每部電影計算向量嵌入。現在你可以查看標語以及計算出來的文本嵌入,看看剛才運行的查詢發生了什麼。所以,讓我們從那個結果中提取出僅有的標語本身,看看它是什麼。 既然我們只為一部電影做了標語,那就是「歡迎來到真實世界」。 當然,以下是您提供的會議記錄的後續內容的逐字翻譯,時間標註已被移除: 超棒。讓我們也來看看嵌入向量看起來是什麼樣子。我不會顯示整個嵌入向量。我們只取出前10個值。好的,太好了。對我來說,這看起來像一個很好的嵌入向量。 在驗證我們獲得的嵌入向量的最後一步中,我們要確保這些嵌入向量的大小是正確的。我們預期它們為1536。所以我們將實際取出那個結果並計算它們的長度。太好了,向量大小正如我們預期的1536。 所以你只看了一部有標語和標語嵌入向量的電影,但我們之前運行的查詢為數據庫中的每部電影計算了一個嵌入向量。所以現在我們實際上可以查詢數據庫,並對這些電影進行向量相似性搜索。 我們將從指定我們想要問的問題開始,並找到可能符合這個問題的相似電影。所以像是,哪些電影是關於愛情的?記住,我們對標語進行了向量索引。所以這將對那些標語進行相似性搜索。 這裡,我們將開始呼叫計算嵌入向量的函數,使用我們之前有的那個函數。我們將通過說這個函數呼叫,JNI vector encode,和一個我們將傳入的問題參數,來這樣做。我們想要使用OpenAI模型來計算嵌入向量。而OpenAI當然需要一個API鍵。所以我們也將傳入這個。 我們將這個函數呼叫的結果賦值給我們稱為問題嵌入向量的東西。然後我們將呼叫另一個實際進行向量相似性搜索的函數。所以我們將呼叫DB index vector query nodes。我們想要查詢電影標語嵌入向量。那是我們之前創建的索引的名稱。 這是另一個有趣的參數。我們只想要前K個結果。所以我們不想返回所有結果,我們只想對我們稍後將指定的前K個進行相似性查找。然後,當然,我們將傳入我們剛剛計算的嵌入向量。所以相似性搜索將會說在你所有標語的這個索引中,這是我們為問題計算的嵌入向量。進行相似性搜索,實際給我們這些結果。 現在,從結果中,我們想要能夠產生我們找到的節點,我們將重命名它們為電影,以及相似性得分是什麼。有了這個,我們將返回電影標題、電影標語和分數。 我們傳入一些查詢參數,用於OpenAI API鍵本身。我們所問的問題,將被計算成嵌入向量。這裡的前K個是五。所以我們只想要最接近的五個嵌入向量。 酷。所以我們得到了像《喬對抗火山》這樣的電影標題,這是一個關於愛情、岩漿和熾熱慾望的故事。你可以看到通過所有這些標語,這對於關於愛情的電影來說是一個很好的匹配。 所以現在我們已經建立了一個可以進行向量相似性搜索的查詢,我們已經提出了一個問題,這是一個很好的時機,通過問不同的問題來探索電影數據集,看看這裡還有什麼其他電影。例如,讓我們嘗試有關冒險的電影。 我們將保存這個問題並再次運行這個查詢。哦,是的。《荒島驚魂》、《忍者刺客》,這聽起來像是冒險的東西。《喬對抗火山》,顯然它是關於愛情和冒險的。也許這是一個好的選擇,可以加入你的Netflix列表。 這是一個好的時刻來實際暫停視頻,嘗試只改變那個問題來探索電影數據集本身,問不同的電影有不同的特點,看看你得到什麼樣的結果。 這就是如何創建文本嵌入向量並將它們添加到知識圖譜中。現在,在所有的例子中,你一直在使用一個現有的數據庫。但是要建立自己的RAG應用程序,你需要從頭開始建立一個,以表示和儲存你的數據。讓我們在下一課中看看如何做到這一點。 ## Lesson 5 在這堂課中,你將使用迄今所學來開始建立一個知識圖譜,其中包含公司必須向證券交易委員會提交的一些財務文件。讓我們深入瞭解。 那麼這些財務文件是什麼呢?公司每年都必須向SEC提交許多財務報告。一個重要的表格是10-K表格,它是公司活動的年度報告。這些表格是公開記錄,可以通過SEC的Edgar數據庫訪問。讓我們看一下一個例子。 這裡我們在sec.gov網站和Edgar的所有這些財務表格數據庫中。我們可以尋找一家公司,比如說高通,然後只過濾他們的年度報告。這是高通的10-K表格。我們可以點擊它來打開並查看一個更大的視圖。 你可以看到,這裡有許多部分需要查看,大量的文本,這些表格中有很多有趣的信息。行業趨勢、技術概述。這很棒。所以這就是我們將拉入知識圖譜的數據類型,這樣我們就可以稍微與這些財務信息交流一下。 這些表格可以下載,下載後實際上是XML文件。因此,在真正開始導入之前,你必須解析XML文件,並提取出你實際想要使用的數據。我們的處理方式是從進行一些正則表達式清理開始,通過XML尋找我們實際想要的塊。 然後我們使用Beautiful Soup將其中一些XML轉換為漂亮的Python數據結構。從那裡,我們還提取了一些關鍵信息,例如一個稱為CIK的標識符,這是一個中央索引鍵,是SEC中識別公司的方式。對於大文本塊,我們接著查看了第1、1A、7和7A項。這些是我們將要進行對話的大量文本。 如果你想在這個筆記本的數據目錄中看看,你會看到做完所有這些清理後的一些結果文件。在做完所有這些工作之後,我們將它們轉換成JSON,這樣就容易導入並開始創建知識圖譜。 好的,我們幾乎準備好回到筆記本了。但在我們這麼做之前,讓我們想想我們的攻擊計劃將是什麼。我們看到每個表格都有不同的文本部分,我們將把它們分割成塊。我們將使用Lang chain進行這項工作。一旦我們準備好所有這些塊,每個塊都將成為圖譜中的一個節點。節點將 具有單獨文本和一些元數據作為屬性。一旦就位,我們將創建一個向量索引。然後在這個向量索引中,我們將計算文本嵌入向量,為每個文本塊填充索引。 最後,完成所有這些工作後,我們將能夠進行相似性搜索。所以讓我們回到筆記本,我們可以開始工作。首先,我們將加載一些有用的Python包,包括Lang chain的一些很棒的東西。我們還將從環境中加載一些全局變數,並設置一些我們稍後在創建知識圖譜部分想要使用的常量。 在這堂課中,你將使用單一的10-K文件。在實踐中,你可能有數百或數千個文件。這裡的步驟需要對所有文件重複進行。讓我們首先設置文件名,然後加載這一個JSON文件。首先,讓我們只抓取我們將要使用的文件名,我們將其稱為first file name。然後使用first file name,我們將加載該JSON並將其儲存為Python中的變數。 你可以看一下,以確保它看起來像一個合適的字典。類型是Python中的字典。這非常完美。讓我們看看有哪些可用的鍵。我只是要複製一個迴圈,它會遍歷字典並打印出對象中的鍵和值的類型。所以你可以看到這些是來自10-K表格的熟悉字段,不同部分稱為項目1、項目1A等。然後還有特殊的標識符,公司的名稱和原始來源,這是我們從SEC獲得文件的來源鏈接。 讓我們看一下第1項的文本是什麼樣的。讓我們從對象中獲取第1項。因為我知道那裡有很多文本,我們只看一點點。讓我們看一下,你知道,前1500個字符。因為文本如此之大,這就是進行分塊的全部目的。我們不會將整個文本儲存在單個記錄中。我們將使用Lang chain的文本分割器來實際將其分解。所以我將使用一個叫做遞歸字符文本分割器的東西。這個文本分割器設置了一個2000字符的塊大小。我們將有一個200字符的重疊。 當然,這是您提供的會議記錄的逐字翻譯,時間標註已移除: 所以我們將運行這個,並將結果存儲為item one text chunks。像以前一樣,我們來看看它的類型是什麼。好的,我們可以看到它是列表,應該是字符串的列表。好的,讓我們也來看看這個列表有多長。好的,原始文本有254個塊。最後,讓我們實際上看看其中一個塊的文本內容是什麼。那看起來很像我們之前看到的,這很完美。 現在文本分割器準備好了,我們可以設置一個幫助函數,可以遍歷一個文件,並遍歷文件中的每個部分,並從中創建塊,然後將這些全部轉換成我們可以用來將數據加載到圖形中的對象。好的,這是一個很大的函數。讓我們一步一步來解讀它。這個函數叫做split form 10k data from file。所以它當然需要一個文件。 我們首先要做的是,我們會預留一個列表,在那裡我們會積累我們創建的所有塊,然後我們會加載文件,打開JSON,並將其存儲到一個變數中。現在在那個文件中,我們知道我們有不同的部分要提取出來。所以我們將遍歷每個部分名稱,如項目1、項目1A等,對於每個項目。我們將從對象中提取出項目文本。所以我們只有項目文本這個變數。我們將使用你剛剛看到的文本分割器來分割它。 現在我們有了文本塊,我們將遍歷所有這些塊並為每一個創建數據記錄。我們將提取文件名的最後一部分,並將其用作表格的ID。然後對於帶有元數據的數據記錄,我們將提取不同的信息。首先是直接從塊中提取的文本。還有我們正在處理的當前項目。還有我們將循環的塊序列ID。然後還有元數據,我們也將添加我們剛剛創建的表格ID,塊ID也將從表格ID加上序列來構建。最後,來自SEC的名稱和特殊標識符的一些元數據。 所有這些都將進入一個數據記錄,然後被追加到我們的帶有元數據的塊集合中。現在我們可以在文件上調用該幫助函數。你可以看到它處理了項目1和項目1A等。我們將從那次呼叫的輸出中取出結果,並將其存儲為first file chunks,這將是記錄的列表。每個記錄代表一個塊及其元數據。 讓我們看一下該列表中的第一個記錄。你可以看到原始文本,正如我們所預期的,還有所有的元數據。太好了。在調用幫助函數創建知識圖譜之前,我們將採取一個額外的步驟,以確保我們不會重複數據。在前一課中,你創建了一個向量索引。這裡我們將創建一個唯一性約束,這也是一個索引。它的工作是確保共享公共標籤的所有節點具有唯一的特定屬性。 所以如果你看這個查詢,它說創建約束,我們將稱之為[約束名]。 當然,以下是您提供的會議記錄的逐字翻譯,時間標註已被移除: 如果還不存在,我們會創建一個名為unique chunk的約束。這個約束將適用於具有chunk標籤的節點,我們將要求所有節點的chunk ID都是唯一的。讓我們來運行這個。好的,並顯示索引。太好了,我們看到了這兩個內置於Neo4j中用於進行關係和節點查找的token lookup索引。這裡是我們新創建的unique chunk索引。 現在我們準備好遍歷所有的塊。對於每一個塊,我們將運行那個合併查詢,並傳入參數,即那個塊的chunk參數。讓我們向下滾動以查看這個的結尾。好的,太好了。它創建了23個節點。完美。 為了檢查我們實際上是否有23個節點,我們將運行另一個查詢。在這裡,我們將為數據庫中的所有節點進行一個簡單的匹配。我們將只返回這些的數量作為節點數。我們預期這將是23個節點。讓我們看看。太棒了。 現在我們將創建另一個索引,這次是向量索引。這一個將用於那些我們將創建文本嵌入向量的塊。索引將被稱為form 10k chunks,並將為標籤為chunk的節點在一個叫做text embedding的屬性中儲存嵌入向量。這些嵌入向量將符合OpenAI默認嵌入模型的推薦配置。 我們可以通過查看所有索引來檢查該索引是否已創建。太好了。我們可以看到我們有這個form 10k chunks可用。它是在線的,這意味著它已經準備好了。這是一個向量索引,正如我們所要求的,它在文本嵌入向量的塊上。 你現在可以使用一個單獨的查詢來匹配所有塊,然後用塊呼叫OpenAI獲取嵌入向量,最後在每個節點上設置嵌入向量。這可能需要一分鐘來運行,這取決於網絡流量。這就是現在圖形的樣子。我們知道我們有帶有文本嵌入向量的塊作為列表,而我們還沒有任何關係。 現在我們有了一個帶有帶有文本嵌入向量的塊的知識圖譜,你可以創建一個幫助函數來使用Neo4j進行向量搜索。這正像我們在前一課中所做的,我們呼叫Neo4j進行編碼,這 將呼叫回到OpenAI,並使用該嵌入向量,我們將實際將值存儲在節點內的叫做text embedding的屬性中。 你可能還記得,我們將表格轉換成知識圖譜的公司叫做NetApp。我們選擇了只有一個表格。它恰好是這家公司NetApp的。你可以嘗試我們的新向量搜索幫助函數來詢問NetApp,看看我們得到什麼。 Neo4j向量搜索幫助函數返回一個結果列表。這裡我們正在查看結果零,我們可以看到你得到了一個分數,表示這個文本有多相似,然後是我們要求的文本本身。所以我們問的是,告訴我關於NetApp的情況,你可以看到這份文件是關於一家名為NetApp Incorporated的公司。 請注意,我們只進行了向量搜索。如果我們想要創建一個能夠提供實際問題答案的聊天機器人,我們可以使用Lang chain構建一個rag系統。讓我們來看看你將如何做到這一點。 使用Neo4j和Lang chain最簡單的方法是使用Neo4j向量接口。這使得Neo4j看起來像一個向量存儲。在幕後,它將使用賽弗語言進行向量相似性搜索。配置指定了一些重要的事情。這些都是我們在這堂課頂部設置的全局變數。 我們將使用這個簡單的呼叫作為檢索器將向量存儲轉換為檢索器。Lang chain框架提供了許多不同方式的聊天應用程序。這裡我們將使用檢索QA with sources chain,這是他們為進行問答類型交互而構建的特定鏈。如果你想了解更多Lang chain提供的東西,我完全鼓勵你去他們的網站看看。這很棒。 對於這條鏈,我們將說我們使用的是chat open AI,這意味著我們將使用open AI實際進行LLM部分。我們在這裡使用的鏈類型是stuff,這意味著它正在使用提示填充。所以它將把東西組合在一起,傳遞給LLM一個提示。它將使用我們上面定義的檢索器,該檢索器使用Neo4j向量存儲。 我還有一個漂亮的幫助函數叫做pretty chain,它只接受一個問題。然後它調用那個問題的鏈,只把答案字段本身提取出來,並以適合屏幕的漂亮方式打印出來。 好的,完成了所有這些工作,我們終於可以做一些有趣的事情並提出一些問題。既然我們知道這裡有NetApp,讓我們繼續問,NetApp的主要業務 是什麼?我們將使用pretty chain傳遞問題並立即顯示回應。好的,我們可以看到NetApp的主要業務是企業存儲和數據管理,雲存儲和雲運營。你可以看到我們對問題有了一個實際的回答,而不僅僅是一些可能有答案的原始文本。這正是你希望LLM能做的事情。 讓我們再試一個問題,看看我們得到什麼。讓我們看看它是否能告訴我們NetApp的總部所在地。位於加州聖何塞,這是正確的。現在我們將LLM加入循環,我們可以提出各種有趣的問題,甚至可以給LLM一些指示。所以,我們不是問NetApp的主要業務是什麼,我們可以說用一句話告訴我關於NetApp的情況。讓我們看看我們得到什麼。好吧,我猜這在技術上是一個單句。這有點囉嗦,但很好地遵循了我們的指示。 所以我想在這裡給你展示一些有趣的東西。我們將問一個聽起來與NetApp相似的不同公司。有一家蘋果電腦公司,對吧?所以讓我們問同樣的問題,但不是告訴我關於NetApp的情況,而是告訴我關於蘋果的情況。讓我們看看我們對於回應得到什麼。嗯。關於蘋果的描述似乎與NetApp的描述非常相似。這是典型的幻覺。 讓我們嘗試通過更多的提示工程來修正這一點。如果你不確定。關於答案。說你不知道。好的,這個答案好多了,從LLM那裡得到了更誠實的回答。完美的提示工程取得了勝利。 在這堂課中,你一直在使用Neo4j作為向量存儲,但並不是真正地作為知識圖譜。讓我們繼續下一堂課,那裡你將添加關係到節點,為聊天應用程序增加更多的圖形功能。 ## Lesson 6 當然,以下是您提供的會議記錄的逐字翻譯,時間標註已被移除: 現在你已經建立了你的圖形的節點,其中每一個都包含了來自SEC表格的一塊文本,下一步是在圖形中添加關係,以保留這些文件的原始結構。讓我們開始吧。這裡在筆記本中,首先像往常一樣,導入一些Python包,然後設置筆記本中將使用的一些全局變數。 對於我們想要發送給Neo4j的所有查詢,我們將再次使用Lang chain集成,稱為Neo4j graph。你已經有了chunk節點。你將要創建一個新的節點來代表10K表格本身。這個10K表格節點將有一個表格標籤和以下屬性。你將擁有一個表格ID,這是表格的唯一標識符。你將擁有一個源屬性,這將是回到SEC的原始10K文件的鏈接。還有一個CIK號碼,這是來自SEC的中央索引鍵,以及QSIB6代碼。 現在在這個筆記本中,我們將使用一些新的賽弗功能。首先,讓我們來看看這些。每個塊都有我們需要創建表格節點的信息。你可以匹配任何一個塊,並用那個塊,只返回其中一個節點。然後我們將使用一個特殊的符號,從任何一個塊節點的屬性中提取特定的鍵。你可以看到我們在這裡擁有所有我們需要的信息。 當你創建表格節點時,它將是帶參數的查詢。如果你記得,帶參數的查詢可以帶一個字典作為參數之一。在這裡,我們剛好有我們需要用來創建表格節點的完美字典。讓我們將其保存到一個Python變數中。完美。我們現在將使用該字典創建一個表格節點。 你可以在這裡看到,當我們呼叫查詢時,我們將傳入一些參數,這個表格信息參數,我們將傳入字典。在查詢內部,以表格信息參數的名義可用的這個字典,我們將合併一個新的表格,使用該表格信息中的信息,當我們創建它時,我們將根據傳入的參數設置名稱、來源、SIC,以及QSIP ID。和往常一樣,我們將做一些理智檢查以確保我們做得正確。我們將繼續對所有表格進行匹配,並返回這些表格的計數。我們預期只有一個。 表格數為一,正如我們所希望的。完美。現在你可以開始將一切聯 繫起來。我們的目標是添加關係,以提高每個塊周圍的上下文。你將把塊彼此連接起來,並連接到剛創建的表格節點。結果將反映文檔的原始結構。 你可以首先為每個部分創建節點的鏈接列表。首先,讓我們只找出所有屬於一起的塊。我們將匹配所有來自同一個表格ID的塊,我們將以參數形式傳入表格ID。在這裡,你只有一個表格,但我正在向你展示一個查詢,即使你有來自多個表格的塊,它也將有效。表格ID作為查詢參數被傳入。然後在where子句中使用,以檢查塊是否具有相同的表格ID。 查看結果,你可以看到這些都來自同一個表格ID。他們有不同的塊ID和不同的序列,所以看起來不錯。特別注意,我們正在返回塊序列ID,似乎它正在遞增0、1、2、3和4。看起來不錯。我們將使用它來確保我們按正確的順序擁有所有塊。 當然,以下是您提供的會議記錄的逐字翻譯,時間標註已被移除: 讓我們更改這個查詢,以確保我們實際上已經按順序擁有它們。這是同樣的查詢,但是現在我們在其中添加了兩個額外的子句。在這裡的返回子句中,我們說我們想按照chunk序列ID對我們返回的所有塊進行排序,並讓其遞增,然後將其限制為只有10個,再次,只是為了檢查我們的工作。 查看結果,它們仍然都來自同一個表格。這很好。如果我們查看序列ID以確保它是有序的,我們看到0,然後又是0。這不好。為什麼會這樣?仔細觀察,我們可以看到我們有來自表格不同部分的塊。這裡,這是來自第7A節的,這是來自第7節的。他們都有序列ID為0的塊。這不是我們想要的。我們只想要來自同一部分的塊序列。 現在,讓我們更細緻地完善這個。我們在這裡查詢中添加了一個新的部分。我們已經有了表格ID與參數匹配的檢查。我們還添加了一個名為F10k item的新查詢參數,這是我們希望塊來自的部分的名稱。這被傳入,然後通過and函數添加到where子句中。 再次檢查結果。所有東西都來自同一個表格。這很好。我們也來自同一個表格項目,項目1、項目1、項目1,我們有我們想要的序列0、1、2、3,遞增。完美。 現在我們知道我們可以從同一部分、同一表格獲得塊,接下來我們要做的是將這些收集到一個列表中。這裡有一條新的線,這裡在末尾,只是一種在一行末尾加上註釋的方式。所以在這條新線中,我們有一個將要把所有這些塊收集到一個列表中的返回。所以這個的結果將與我們之前所擁有的相同,而不是每個塊的個別行,所有的塊將在一個列表中,所以我們應該只有一行。 酷。所以你可以看到我們有一行,這行中有一個包含塊的列表,這些塊正是我們想要的,來自同一個表格,並按其序列ID順序排列。 現在你擁有了一個塊的列表,我們可以採取下一步,並在圖形中實際創建關係。所以這是我們之前擁有的相同查詢,實際上只是按順序獲得了一個塊列表,我 們將其保存到一個名為section chunk list的新變數中。並且不僅僅是返回那個列表,我們將呼叫一個名為apoc.nodes.link的過程。 現在這個過程將接受一個節點列表,然後是你想要的關係類型。所以這裡我們將說next,它將創建一個鏈接列表。所以對於每對節點之間,它將創建一個名為next的新關係。sector參數避免重複說,即使我們多次運行這個,它也不會在每對之間創建多個next。 我們可以檢查圖形結構來看到有一個新的關係類型。我們看到我們有具有屬性的節點和以下關係:我們有由next關係連接到其他塊的塊。完美。 現在你可以為單一部分創建一個鏈接列表,我們可以通過只有一個Python迴圈來做所有部分,這個迴圈會穿過不同的部分名稱並調用查詢,傳遞進去這些不同的部分名稱。因為我們有避免重複為真,即使我們再次嘗試做項目1,我們也不會創建一個新的鏈接列表,因為那會避免重複。 接下來,你可以將塊連接到它們所屬的表格。匹配一個塊和一個表格,其中它們具有相同的表格ID,然後在它們之間合併一個新的part of關係。 你可以看到我們創建了23個新的關係。作為提醒,我們在這裡使用一個小的塊集合來保持筆記本運行順暢。在完整的10K表格樣本中,有幾百個塊。你可以在圖形中添加一個更多的關係,將表格連接到每個部分的第一個塊。 這與之前的查詢類似,但還檢查塊是序列ID零。將表格連接到第一個塊的section關係也將獲得一個F10K項目屬性。它將從塊中取得該值。你可以在這裡的合併中看到這一點發生。這是對人類的一種仁慈。 當然,以下是您提供的會議記錄的逐字翻譯,時間標註已被移除: 我們可以向圖表中添加一個新的關係。您可以在這裡的合併中看到這一點發生。這對於觀看知識圖的人來說是一種仁慈,使他們能夠輕鬆地從一個表格導航到特定部分的開頭。 我們有四個部分,所以我們當然創建了四個部分關係。我們現在可以嘗試一些示例Cypher查詢來探索圖形。例如,您可以使用從表格到由section關係連接的塊的模式匹配來獲得一個部分的第一個塊。您將使用where子句來確保表格具有我們想要的表格ID,並且關係是我們想要的部分。我們將以查詢參數的形式傳遞這些。所以這是來自該部分第一個塊的塊ID,然後是這一點看起來很熟悉的文本。 有了關於第一個塊的信息,您可以通過遵循下一個關係來獲得部分中的下一個塊。您可以看到這一部分繼續談論我們最喜歡的公司NetApp。 為了檢查我們所做的工作,我們將查看並確保我們確實有兩個連續的塊。所以您可以在這裡看到,所有的表格東西都是一樣的,然後這是塊000,這個是塊0001。完美。 要查找塊窗口,您可以使用模式匹配使用兩個節點之間的next關係。您只需要指定其中一個塊的塊ID,因為有了關係,您知道您將得到所有三個塊。在這裡,我們將提供中間節點的塊ID,這是窗口的中心。 您可以看到我們有塊C1、C2和C3,它們的塊ID分別為000、01和02。這很棒。您開始看到擁有圖表的優勢。一旦您在圖表中有一個起點,您就可以輕鬆獲得連接的信息。在rag應用中,您可能會使用語義搜索發現一個節點。現在您可以通過在圖表中使用模式匹配來為該節點添加額外的上下文。 使用圖表,信息存儲在節點和關係中。圖表的結構中還有信息。您剛剛匹配了一個包含三個節點和兩個關係的模式。您找到的東西稱為路徑。路徑是圖表的強大特性,在算法中非常著名,比如找到兩個節點之間的最短路徑。 您可以在模式的開頭將整個匹配的路徑捕獲為一個變數,如此處所示。在這裡,我們將 其稱為window路徑。路徑通過路徑中的關係數量來測量。對於一個三個節點的路徑,我們預期長度將是二。讓我們來看看。 完美。您將在接下來的幾個查詢中使用路徑。這將是很多樂趣。請注意,chunk window模式圍繞列表中的第二個塊,來自下一個塊信息。如果我們尋找圍繞第一個塊的窗口會發生什麼?讓我們來看看。 將next chunk信息更改為first chunk信息,然後運行。所以模式沒有匹配到任何東西。這是因為first chunk信息沒有模式所需的前一個塊。 我們可以更改模式以尋找所謂的變長路徑。使用變長路徑,您可以指定要匹配的關係範圍。您可以在這裡看到標記,在我們指定關係類型時。它是冒號關係類型,然後是一個星號,然後是範圍。第一個數字是關係的最小數量。第二個數字是匹配的關係的最大數量。 使用變長,在這個模式的開始和結尾,我們可以匹配鏈接列表的邊界條件。無論您是在查看鏈接列表的第一項還是鏈接列表的最後一項。請注意,當我們運行查詢時,實際上匹配了兩個模式。第一個的長度為零,第二個的長度為一,意味著它有兩個節點和一個關係。如果我們要圍繞一個節點尋找塊窗口,我們希望找到最長的可能路徑。讓我們來看看怎麼做。 這就像我們剛剛做的查詢,但現在我們將我們稱之為window的路徑,即匹配的路徑,並尋找最長的chunk window,通過將所有這些路徑按照它們的長度進行排序,降序並將其限制為僅一個。這應該是匹配模式的最長路徑。 正如我們所希望的,最長的路徑是一,所以看起來正確。這是實際上暫停視頻並嘗試這個查詢的變化的一個很好的時機。例如,您可能想嘗試尋找窗口兩側的兩個塊。嘗試不同的變化,看看您會得到什麼。 您現在可以創建一個問答鏈。如果您看一下這裡的Cypher查詢,這是對VectorSearchCypher的擴展。您在開始時看到的是傳入了兩個變數,節點和分數。這些來自VectorSimilaritySearch本身。我們把它拿來,然後只是拿一個字面上的字符串說這 將被稱為額外文本。我們將返回該額外文本加上節點或塊的文本所前置的文本,我們將其稱為text,返回分數,然後是有關結果的一些元數據。 現在,這是一個Cypher查詢的基本模式,擴展了Neo4j Vector類的內置VectorSearch。輸出名為text的變量、名為score的東西和名為metadata的東西是強制性的,但那可以是任何您想要的,所以您可以在開始時運行任何類型的Cypher。這是我們可以運行的最小量的Cypher,將說明發生了什麼。讓我們繼續並使用這個查詢構建一個Lang chain工作流程。 我在這裡要強調的新部分是,我們正在傳入一個名為retrieval query的參數,對於該參數,我們將傳入我們剛才在上面定義的Cypher查詢。這是將VectorSearch擴展為我們想要做的任何額外查詢。所以我們將調用該鏈,我們知道我們必須向其傳遞一個問題。讓我們來看看它對Andreas了解多少。 好吧,顯然我知道很多事情。我不僅知道Cypher,我還知道自然災害和災難,但我們知道這不是事實,對吧?所以這裡有一點額外的東西是不真實的。我們可能會稍微改變一下問題,也許問Andreas單一主題了解多少? 好吧,現在它只認為我知道Cypher。所以您可以查看我們知道的關於Cypher的事情清單。好吧,現在它只認為我知道Cypher。所以您可以搞弄這個整個單元,更改retrieval query本身,您傳入的信息,更改您提問的問題。我認為這是一個很好的探索方式,真正感受一下如何僅通過添加額外的文本就擴展上下文,無論您在塊內找到什麼。 好吧,您現在知道如何通過使用Cypher來自定義VectorSearch的結果。您可以使用這個功能來通過chunk window查詢擴展塊周圍的上下文。讓我們嘗試一下並比較結果,無論是有還是沒有額外窗口,僅僅是vector找到的塊。 首先,創建一個使用Neo4j Vector附帶的默認Cypher查詢的鏈,稱其為window list chain。現在,創建另一個使用chunk window查詢的鏈。您的目標是通過JSON塊擴展上下文,這些塊可能與提供完整答案相關。為此,使用chunk window查詢,然後從窗口中的每個塊中提取文本。最後,所有這些文本將被連接在一起,為LLM提供完整的 上下文。 有了這個查詢,我們現在可以創建另一個VectorStore。請注意,當我們創建這個VectorStore時,我們將其傳遞為retrieval query,對吧?所以最初的VectorSearch將通過我們的窗口進行擴展,所有這些結合在一起,然後那就是提供給LLM的上下文,從中創建出retriever,然後將其傳遞給Lang chain Q&A chain本身。 好吧,讓我們嘗試一下並比較擁有chunk窗口和沒有chunk窗口。所以這是我們將問的問題,我們將說,用一句話告訴我關於那個應用程序的業務。當我們運行這兩個鏈時,我們將做一些額外的格式化,以提供格式化良好的答案。記得這個沒有窗口的鏈將僅僅匹配VectorSearch的單一塊,並用它提供其答案。 這似乎是一個相當不錯的總結。好吧,不錯。讓我們嘗試chunk window。好吧,您可以看到這兩個答案非常相似。我能發現的唯一差異是,擁有擴展的上下文,它實際上強調了NetApp的Keystone,這是他們的首要產品。所以如果你要寫一個關於NetApp的亮點總結,你不僅要說公司做什麼,還要提到他們的一些產品也是一個很好的觸摸。 您可以在這裡暫停,嘗試chunk window查詢的變化。另外,嘗試提出不同的問題,看看您會得到什麼。當您準備好時,加入我在下一課,我們將通過來自包含NetApp投資者信息的另一個SEC論壇的數據來擴展上下文。 ## Lesson 7 當然可以,以下是這堂課的逐字翻譯,並已將時間標籤去除,做成文章陳述形式: 好的,現在您已經擁有了完整的圖形數據庫,您可以開始最有趣的部分——與SEC文件進行交流。讓我們深入探究並提出一些問題。 讓我們先退後一步,思考一下您創建的內容。您創建了一個知識圖譜。您是怎麼做到的呢?您從最初創建了一個我稱之為「最小可行圖形」的東西開始,就是一個包含了知識圖譜中數據的非常小的圖形。這很好。然後我們一起經歷了一個過程,從圖譜中或其他數據源中提取一些數據,以某種方式增強這些數據,然後圍繞這些數據擴展圖形,逐步擴大圖形的規模。 "提取"意味著從原始數據源中找到有趣的信息並將其分離為單獨的節點。"增強"意味著以某種方式提升數據,無論是通過向量嵌入還是其他方式。"擴展"實際上就是將數據連接到您已有的圖形中。 回想一下課程的開始,當您導入表格10K數據時,您有原始數據,我們只進行了文本分塊,創建了每一塊的節點。這正是我所說的過程。您從原始數據源中提取了一些信息,通過嵌入增強了數據,然後將其擴展到圖形中。 在之後的課程中,您繼續使用相同的模式,無論是從文本節點開始創建塊,還是從這些塊中創建一些表格,或者從新數據源——表格13 CSV——中創建公司和管理者。在每一步,您都發現了一些有趣的信息,從這些信息中創建了數據,通過一些像索引這樣的東西增強了這些數據,然後將它們連接到圖形中。 您從塊到下一塊,從塊到表格的一部分,從公司到提交了該表格的公司,最終到擁有該公司股票的管理者。您可以根據想要回答的問題類型,盡可能多地做這些事情。例如,您可以交叉連接公司本身。這些提交的文件中提到了它們所涉及的其他公司,無論是供應商還是合作夥伴,您可以找到這些公司,然後將這些公司相互連接。 您還可以提取出文件中有趣的人物、地點和主題,這將成為圖形的另一部分,供您進行查詢並增強您所提問題的背景。或者,您還可以添加外部數 據源,正如我們在Form 13 CSV中所做的那樣。 最後,您可以採取另一步,這對於提升用戶體驗非常棒。將用戶添加到圖形中,這樣用戶就可以提供關於他們所看到的答案的反饋,無論是好是壞。您可以追踪他們的互動情況,並從這些互動中學習,以提高圖形的整體質量和用戶體驗的質量。他們的參與越多,他們的體驗就越好。 在第七課中,您要處理的數據已經得到了一些擴展。公司和管理者都有地址字符串。所以管理者節點和公司節點中的一個字段就是地址。我們只是將地址進行地理編碼,然後將其分離為單獨的節點。這使您能夠提出有趣的新問題,因為一旦您有了單獨的地址節點,您就可以執行像添加地理空間索引這樣的操作,從而可以執行基於距離的查詢,比如“我附近有什麼”或“這家公司靠近另一家公司嗎?”。 您將要在本課中使用的知識圖形的模式如下所示。我們之前看到的是管理者擁有公司股份,公司提交了表格,這些表格被分成塊。現在,管理者和公司都連接到地址。利用這些地址,您可以提出像“哪些公司靠近彼此”這樣的有趣問題,只需在圖形中跟隨指針即可。 首先,我們將導入一些庫,並定義一些全局變量,這些變量將在查詢中使用。接下來,我們將創建一個Neo4j圖形實例。現在,您已經準備好使用Cypher進行一些探索了。 讓我們從簡單的事情開始。首先找到任意一位隨機的管理者。所以我們只是匹配一個模式,從一位管理者到位於某個地址,然後返回管理者和地址,並將其限制為一個。您可以看到,我們找到了Katie Capital Management,位於紐約市。這很完美。所以我們有了管理者在地址,以及那個地址是什麼。 注意這裡我們除了城市和州之外,還在地址節點中添加了另外一部分,稱為位置,我們將經緯度存儲為數據值。這就是您可以進行像地理空間搜索這樣的操作以及靠近搜索的原因。 那是一位隨機的管理者。如果您想找到某個特定的人,在數據集中的某個地方有一個叫做皇家銀行的人。我們可以進行全文搜索來找到這位管理者。太棒了,這裡是管理者的名字,實際上是加拿大皇家銀行。注意我們還得到 了一個分數。這是進行全文搜索的分數,這是不同於向量搜索所得到的值範圍,但原理是相同的。 您當然也可以將這兩個查詢結合起來,尋找皇家銀行,然後找出他們位於哪裡。首先,通過全文查找銀行,然後從管理者到地址查找他們位於何處,並返回這些值。毫不奇怪,加拿大皇家銀行位於加拿大。所以我們可以進一步探索,找出我們知道的哪些信息。例如,您可以問哪個州擁有最多的投資公司。 您首先從一個模式開始匹配,從管理者到地址,然後返回結果,包括州的名稱,然後通過聚合對地址州進行計數,將其命名為管理者數量,然後返回前10名。不出所料,紐約、加州等地都有很多管理公司,其他州也有一些。 您也可以用相同的方式提出有關公共公司的問題。同樣的思路,您有從公司到地址的模式匹配,列出地址的州,然後對地址州進行聚合,得到公司數量。看起來,無論是管理投資公司還是公司,至少在這個樣本數據集中,大多數都位於加利福尼亞州。這是重點所在。讓我們再深入一點研究加州。 您可能會問,加州哪些城市擁有最多的投資公司?我們可以繼續使用相同的方法。找到模式匹配,從管理者到地址,然後我們將地址限制為加州。同樣地,我們要知道是哪個城市,計數城市,看看這個城市出現了多少次,然後返回前10名。這裡有點北加州和南加州的對立。北部和南部都很重,其他地方則是混合的。 我們問了關於管理者的問題,現在問同樣的問題,但關於公司。現在這是一個非常不同的列表。現在我們知道,至少在這個樣本數據集中,大部分公司位於聖克拉拉、聖何塞、桑尼維爾和庫比蒂諾。我不記得這些名字出現在管理列表中。 讓我們再看一下。看來公共公司和管理者位於完全不同的城市。這很有趣。注意舊金山有很多投資公司。我們現在可以深入研究舊金山本身,當然可以問,舊金山最大的管理公司是哪些? 相同的模式,從管理者到地址。所以我們要求城市必須是舊金山。除了管理者的名字,我們還想知道他們投資了哪些公司,取這些關係的價值屬性之和,稱之為總投資價值,然後返回前10名。這將是舊金山最大的管理公司。這些名字中的一些您可能會認出,有些對我來說是非常獨特的, 但這些數字非常令人印象深刻。 這就是舊金山最大的投資公司。如果我們回顧這裡的公司,對於這個樣本數據集,我們在聖克拉拉有最多的公司。讓我們看看這些公司是誰。這很簡單。我們將從公司匹配到地址,要求地址城市是聖克拉拉,然後給我這些公司的名字。Palo Alto Networks、希捷科技和Atlassian Corporation。 到目前為止,您一直在使用明確的關係來探索圖形。您也可以根據它們的位置坐標找到事物。記得我們添加了一個地理空間索引。這很有趣。這與進行向量搜索很像,但是在二維空間內,並且使用我們的笛卡爾距離而不是餘弦相似度。 您可以問哪些公司靠近聖克拉拉,這與聖克拉拉的公司類似。但我們不希望它們在聖克拉拉,而是在附近。您如何解決這個問題?首先匹配一個地址。我們將其稱為SC,並且我們希望SC城市是聖克拉拉。好的,這讓我們找到了聖克拉拉。然後我們希望公司位於某個公司地址。 這很有趣,我們要思考的是這一行。我們的where語句將說點.距離,這是我們內置在賽弗中的距離函數,從兩個不同的位置計算點.距離。SC的位置,這是聖克拉拉的位置,以及地址的位置,這是這家公司的位置。我們希望這小於10000。這是10000米。好的,所有的距離都是以米為單位測量的。無論滿足這一條件的是哪家公司,然後我們將返回公司名稱和公司地址。您可以看到,Palo Alto Networks當然靠近聖克拉拉。這很好。您還看到了Sunnyvale,聖克拉拉再次出現,然後在這裡為蘋果出現Cupertino。但這很棒。所以現在我們用兩種不同的方式做事情。您要么知道您想要的東西,例如在聖克拉拉的人,或者在聖克拉拉附近的事物,這是一個距離函數。 回想一下,我們更新了模式,我們有了這些新標籤為地址的節點。這些是只有城市、州和國家的地址節點。公司和管理公司都通過一個名為位於的關係與這些公司連接。當然,我們可以問同樣的問題,但關於管理公司。讓我們把這些結果留在這裡。只需嘗試相同的查詢。我們將匹配地址,然後對於位於聖克拉拉的地址,匹配一位管理者位於某個地址,然後做 點.距離計算,您可以看到這兩行。同樣的限制在這個看起來像是10000米之內。看起來只有一個管理公司在附近,他們在加州坎貝爾。您可以玩弄一下,看看如果您走得更遠,您還能得到什麼。是的,正如您所預期的那樣,當您走得越遠時,您會找到更多的管理公司。所以我們繼續做同樣的事情。看看還能得到什麼? 正如您之前所做的那樣,您可以將這些單獨的查詢結合起來,做更有趣的事情。例如,您可以問的不僅僅是在某個地點附近的投資公司。讓我們找到一些靠近您所知道的某家公司的投資公司。您可能記得這裡有Palo Alto Networks。我甚至拼錯了。沒關係。 我們將運行一個查詢,即使是拼錯了,也會找到這家公司,然後找出哪些管理公司在這家公司附近,從而進行距離計算。所以我們從全文查詢開始。我們將在全文公司名稱中尋找拼錯的Palo Alto Networks。然後我們得到了這個查詢的節點,以及這個節點的分數。這裡,我們只是將節點重命名為com,因為我們知道它代表一家公司,然後進行模式匹配,從公司到位於某個公司地址。 同樣地,找到一位管理者位於某個管理者地址。這裡注意這兩種模式沒有任何聯繫。您有一個where子句說找到點.距離計算,從公司地址位置和管理者地址位置之間計算點.距離。再次,我們將這個限制在10K之內,然後返回管理者並將距離轉換為公里。所以即使有拼寫錯誤,我們也找到了Palo Alto Networks。我們找到了名為Mine and Arrow Wealth Creation and Management LLC的管理公司。您已經閱讀了這堂課中的許多賽弗。這一次可能需要消化一下。 在繼續之前,暫停視頻並對筆記本中給出的提示、作為示例提供給提示的賽弗查詢進行一些小修改,並提出不同的問題。看看LLM能否生成適合這些問題的賽弗。當它不能時,只需向提示中顯示更多示例,查找筆記本中相關的示例,將其添加到原始模板中,更新所有鏈條,然後再次運行問題,看看您得到了什麼。完成後,請加入我觀看最後一段視頻進行總結。 ## final 祝賀您完成課程。我希望您喜歡構建一個由知識圖譜驅動的 RAG 系統,並探索 SEC 財務文件的細節。像這樣的公共記錄當您能直接與它們交談時,分析起來肯定更有趣。您在本課程中完成的 SEC 實例代表了公司正在使用知識圖譜和生成式 AI 構建的應用類型。我希望這門課程激勵您構建自己的知識圖譜。 如果您想繼續學習,Neo4j 網站上有很多資源。在那裡,您可以註冊一個免費的雲端托管 Neo4j 帳戶,並了解其他工具,以幫助您打造自己的知識圖譜。感謝您堅持到課程結束,我迫不及待地想看到您構建的東西。