### [AI / ML領域相關學習筆記入口頁面](https://hackmd.io/@YungHuiHsu/BySsb5dfp)
### [Deeplearning.ai GenAI/LLM系列課程筆記](https://learn.deeplearning.ai/)
#### [Large Language Models with Semantic Search。大型語言模型與語義搜索 ](https://hackmd.io/@YungHuiHsu/rku-vjhZT)
#### [Finetuning Large Language Models。微調大型語言模型](https://hackmd.io/@YungHuiHsu/HJ6AT8XG6)
#### [LangChain for LLM Application Development](https://hackmd.io/1r4pzdfFRwOIRrhtF9iFKQ)
---
[LangChain for LLM Application Development](https://www.youtube.com/watch?v=jFo_gDOOusk) 系列課程筆記
- [Models, Prompts and Output Parsers](https://hackmd.io/1r4pzdfFRwOIRrhtF9iFKQ)
- [Memory](https://hackmd.io/@YungHuiHsu/Hy120mR23)
- [Chains](https://hackmd.io/@YungHuiHsu/SJJvZ-ya2)
- [Question-and-Answer](https://hackmd.io/@YungHuiHsu/BJ10qunzp)
- [Evaluation](https://hackmd.io/@YungHuiHsu/Hkg0SgazT)
- [Agents](https://hackmd.io/@YungHuiHsu/rkBMDgRM6)

source : [LangChain.dart](https://pub.dev/packages/langchain)
---
# [LangChain - Question and Answer over Documents](https://learn.deeplearning.ai/langchain/lesson/5/question-and-answer)
## 課程摘要
這部分的課程同樣滿缺乏組織性的,以下內容根據官方文件重新架構進行補充
- **文件檢索方法**,利用大型語言模型(LLM)回答從PDF、網頁或內部文件中提取的文本問題,擴展LLM的應用範圍
- 包括展示簡單的索引(Index)與使用檢索器(RetrievalQA)查詢。後者檢索增強生成Retrieval-augmented generation (RAG)的一種抽象化實作類別
- **資料與模型結合**:結合LLM與未訓練數據,使用LangChain組件,如嵌入模型和向量存儲庫,增加模型的靈活性和適應性。
- **文件處理技術**:透過CSV加載器和"DocArrayInMemorySearch"向量存儲庫進行文檔加載和索引,並透過嵌入技術(Embeddings)轉換文本為數值化表示
- 最後補充文件檢索後的的4種處理方法,包括stuff、Map_reduce、Refine、Map_rerank等,進行文件的問答處理,允許對大量文件進行深入分析
## 大型語言模型在文件處理上的應用(LLM’s on Documents)
說明大型語言模型如何通過嵌入向量(Embedding)理解不同文本的內容,即使在處理文本量上有所限制
- LLM 處理文本的限制
- 目前的LLM一次只能處理數千個單詞(token)

source : [2023.08。Comparison of Various Large Language Models (LLMs) & Their Capabilities](https://www.linkedin.com/pulse/what-llm-token-limits-comparative-analysis-top-large-language-mohan/)
- 
- 利用嵌入向量(Embedding vector)能捕捉文本的內容與含義
- 具有相似內容的文本會擁有相似的向量表示
- Embedding的實際應用示例:

* 關於寵物的文本:
* "我的狗Rover喜歡追逐松鼠。"(My dog Rover likes to chase squirrels.)
* 這句話的嵌入向量會聚焦於狗的行為和對松鼠的興趣。
* 另一個關於寵物的文本:
* "Fluffy,我的貓,拒絕從罐頭吃食物。"(Fluffy, my cat, refuses to eat from a can.)
* 這裡的向量將捕捉貓的飲食偏好和特定行為。
* 非寵物相關文本:
* "雪佛蘭Bolt在6.7秒內加速至60英里/小時。"(The Chevy Bolt accelerates to 60 mph in 6.7 seconds.)
- 此句的向量會關注於汽車的性能和速度,與前兩句形成明顯對比。
## 利用向量資料庫(Vector Database)進行索引(Index)查找文件

* 創建 (Create)

* 將文件切分成小塊 (Chunks):
* 大型文件被分割成更小的文本塊,以適應 LLM 的處理限制
* 創建嵌入向量 (Embed)
* 對每個文本塊進行數值化表示,創建嵌入向量(Embeddings)
* 將這些數值向量(Embeddings)存儲在向量資料庫中
* 索引 (Index)

* 處理查詢 (Query):
* 當用戶進行查詢時,首先將該查詢文本進行嵌入
* 比較所有條目:
* 將查詢的嵌入向量與資料庫中的所有向量進行比較
* 選取最相似的項目:
* 選取最相似的n個向量(即原始的文本片段)
本節顯示了如何使用向量嵌入技術在大量文本數據中迅速查找和檢索相關信息。這在自然語言處理和機器學習中是一個常見的方法,尤其是當數據庫中有大量文本數據,並希望快速找到與特定查詢最相關的文本時
## 使用檢索問答鏈RetrievalQA。
範例中所用的`RetrievalQA`其實是放在"檢索增強生成"[`Retrieval-augmented generation (RAG)`](https://python.langchain.com/docs/use_cases/question_answering/)的類別下
補充一下官網給的Use case範例圖
- LLM資料檢索增強生成Retrieval-augmented generation(RAG)的流程
<div style="text-align: center;">
<figure>
<img src="https://python.langchain.com/assets/images/qa_intro-9b468dbffe1cbe7f0bd822b28648db9e.png" width="600">
<figcaption>
<span style="color: #3F7FBF; font-weight: bold;">
<a href="https://python.langchain.com/docs/use_cases/question_answering/" target="_blank">RAG Use case</a>
</figcaption>
</figure>
</div>
- 數據來源 (Data Sources)
* 非結構化數據 (Unstructured Data): 這可能包括PDF文件、博客、Notion頁面等。這些數據是無結構的,即沒有固定的格式或模式
* 程式碼 (Code): 這是指具有特定語法和結構的代碼,例如Python代碼
* 結構化數據 (Structured Data): 通常存儲在數據庫中的數據,如SQL,它具有預定義的結構,如表格、行和列
* 應用程序 (Application)
* 數據源被送入一個應用程序,這個應用程序具有LLM集成,用於處理和理解這些數據
- 用例 (Use-Cases):
LLM應用可以用於多種用途,其中一個主要的用途是問答(QA)或聊天(Chat)
- 流程:處理非結構化數據並將其轉換為問答(QA)格式
<div style="text-align: center;">
<figure>
<img src="https://python.langchain.com/assets/images/qa_flow-9fbd91de9282eb806bda1c6db501ecec.jpeg" width="600">
<figcaption>
<span style="color: #3F7FBF; font-weight: bold;">
<a href="https://python.langchain.com/docs/use_cases/question_answering/" target="_blank">Pipeline for converting raw unstructured data into a QA chain </a>
</figcaption>
</figure>
</div>
1. **Document Loading** (文檔加載):
- 資料來源可以是URLs、PDFs、數據庫或其他類型的文檔
2. **Splitting** (分割):
- 一旦數據被加載,它通常太大或不易於一次性處理。因此,它被分解成較小的、可管理的塊(chunks))或"分割"
3. **Storage** (存儲):
- 分割隨後以結構化的方式存儲,通常在一個向量存儲([Vector stores](https://python.langchain.com/docs/modules/data_connection/vectorstores/))中。使用向量存儲的原因是它允許高效地存儲和檢索數據,特別是在處理大數據集時
4. **Retrieval** (檢索):
- 當用戶提出一個問題時,系統需要確定哪些分割與該問題最相關。這是通過比較問題的嵌入與存儲的分割的嵌入來完成的。然後檢索最相似的分割,這些分割可能包含問題的答案
5. **Output/Generation** (輸出/生成):
- 使用大型語言模型(LLM),產生回答。這一步涉及到取得已檢索的數據和用戶的問題,將它們整理成一個提示,並將這個提示提供給LLM。然後,LLM基於提示中提供的訊息生成一個連貫而簡潔的答案。
ps: 這個流程圖缺乏"增強"的部分
#### Langchain中的文件處理方法 [modules/chains/document/](https://python.langchain.com/docs/modules/chains/document/)
- `BaseCombineDocumentsChain` 類別
Langchain中將文件處理的`Chain`實作為一個通用介面( common interface)
- ABC (Abstract Base Class): 一個抽象基礎類別,不能直接實例化,需由子類實現其抽象方法。
```python=
class BaseCombineDocumentsChain(Chain, ABC):
"""Base interface for chains combining documents."""
@abstractmethod
def combine_docs(self, docs: List[Document], **kwargs: Any) -> Tuple[str, dict]:
"""Combine documents into a single string."""
```
### 文件上的問答技術摘要(Markdown表格)

| 方法 | 描述 | 優點 | 缺點 |
|------------|----------------------------------------------------------------------------------------|----------------------------------------|----------------------------------------------------|
| Stuff | 將所有數據放入一個提示中,發送給語言模型並獲得單一回應。 | 簡單實用,適用於小文件。 | 不適用於大型或多個文件,因為會超出語言模型的上下文長度限制。 |
| Map_reduce | 將所有文件與問題一起傳遞給語言模型,獲得回應後再用另一個語言模型呼叫來匯總各個回應。 | 功能強大,可平行處理問題。 | 需要更多語言模型呼叫,並將所有文檔視為獨立。 |
| Refine | 在多個文檔間進行迭代處理,每一步建立在前一個文檔的答案上。 | 適合組合信息,隨時間構建答案。 | 處理速度較慢,因為呼叫之間不獨立,且答案通常較長。 |
| Map_rerank | 對每個文檔進行單獨呼叫,要求語言模型返回一個評分,然後選擇最高分。 | 呼叫獨立,可以批量處理,相對快速。 | 依賴語言模型正確評分,需要多次語言模型呼叫,成本較高。 |
- **最常用方法:** Stuff 方法
- **次常用方法:** Map_reduce 方法
- **廣泛應用於:** 問答、摘要、信息整合等場景
#### Stuff

Stuff 方法是處理文件的最直接方式。它將一系列文件填充(stuff)到一個提示中,然後將這個提示傳遞給大型語言模型(LLM)
* 優點(Pros)
* 單次語言模型呼叫: 使用 Stuff 方法只需對 LLM 進行一次呼叫
* 數據一次性訪問: 操作簡單直接,適合快速整合多個文件,LLM 可以一次訪問所有文件資料
* 缺點(Cons)
* 上下文長度限制: LLM 有一個上下文長度限制,對於大型文檔或多個文檔,這種方法可能不適用,因為會超過提示token限制
- 不適合需要深度分析或特定格式整合的場景
- 適用情境:
當需要快速獲得多個文件的整體視角時適用
大量文件的問答見下三種方法簡介
#### Map_reduce

Map_reduce 方法首先對每個文件單獨應用一個 LLM 鏈(Map 步驟),然後將所有新文件傳遞給另一個組合文件鏈以獲得單一輸出(Reduce 步驟)。這個方法還可以選擇性地先壓縮映射的文件,以確保它們適合組合文件鏈
* 優點:
- 能夠對每個文件進行個別(**平行**)處理,然後再整合結果,適合複雜的文件處理任務
* 缺點:
- 過程較為複雜,可能需要更多的處理時間
* 適用情境:
- 適用於需要對一系列文件進行深入分析和綜合處理的場景
#### Refine

Refine 方法通過循環處理輸入文件並迭代更新其答案來構建響應。對於每個文件,它將所有非文件輸入、當前文件和最新的中間答案傳遞給 LLM 鏈以獲得新答案
* 優點:
- 能夠逐步改進答案,適合需要逐步精煉和更新信息的情境
* 缺點:
- 可能需要較長的處理時間(多次呼叫LLM),且對於迭代過程的管理要求較高
- 當文件**經常相互交叉引用或一項任務需要來自許多文件的詳細資訊**時,Refine 鏈可能會表現不佳
* 適用情境:
* 當需要逐步改進和精煉從一系列文件中提取的信息時適用
- 有點類似連鎖思考提示(Chain-of-thought prompting),看起來適合推理任務?
#### Map_rerank
見[Large Language Models with Semantic Search-ReRank。重新排序](https://hackmd.io/@YungHuiHsu/HyT7uSJzT)這邊也有介紹到

Map_rerank 方法在每個文件上運行一個初始提示,不僅嘗試完成任務,還會給出其答案確定性的評分。返回得分最高的回應
* 優點
- 通過評分機制選擇最佳答案,適合需要從多個可能答案中選擇最佳選項的場景
* 缺點
- 依賴於評分機制的準確性,可能不適合所有類型的文件或查詢
- 補充:這個評分模型是需要額外訓練的、給分這個指標本身就帶有強烈主觀判斷不易標準化
* 適用情境
- 當需要從一系列文件中選擇最有信心的答案時適用
---
## Lab - 如何使用LLM和文件資料庫來建立能回答特定問題的系統
接下來看看程式碼實作部分,主要demo了 Index 與RetrievalQA(使用stuff方法)兩種檢索文件資料的方式
| | 使用檢索問答鏈處理<br>RetrievalQA | 使用索引直接處理查詢<br>Index |
| -------------- |:------------------------------------------------------------------------------------- |:------------------------------------------------------------------------------ |
| **過程描述** | 1. 查詢經過檢索器找到相關文件<br>2. 文件被整合成提示送給 LLM<br>3. LLM 生成回答 | 1. 查詢直接送到索引<br>2. 索引找到相關文件<br>3. 文件被送給 LLM 處理 |
| **適用情況** | 適合複雜查詢,需要綜合多個文件信息 | 適合快速、直接從文件集檢索信息,當查詢答案直接且明確 |
| **特點和優勢** | 結合多個文件的信息提供全面回答<br>適合於涉及大量文件的複雜情境 | 直接從特定文件集中檢索相關文件<br>適用於查詢答案可直接從特定文件獲取的情況 |
- 本節程式碼流程圖
```mermaid
flowchart TD
A[Load Documents] -->|CSVLoader| B[Embed Documents]
B -->|OpenAIEmbeddings| C[Create Document Search System]
C -->|DocArrayInMemorySearch Similarity Search| D[Establish Retrieval and QA System]
D -->|Query using Combined Document Content| E[Execute Query using RetrievalQA]
E -->|chain_type=stuff,\nretriever=retriever| F[Display RetrievalQA Response]
D -->|Query using Index| G[Create Index with\n VectorstoreIndexCreator]
G --> H[Display Index Query Response]
classDef default fill:#f0ffff ,stroke:#333,stroke-width:2px;
class A,B,C,D,E,F,G,H default;
```
### 1. 單純使用索引`index`方法進行查詢
案例:如何將有興趣的商品向量化對目錄進行檢索
- 環境設定
- `import os`: 導入 `os` 模塊,通常用於與操作系統交互。
- `from dotenv import load_dotenv, find_dotenv`: 從 `dotenv` 包導入 `load_dotenv` 和 `find_dotenv` 函數。這個包用於從 `.env` 文件讀取環境變數。
- `_ = load_dotenv(find_dotenv())`: 使用 `find_dotenv()` 函數找到 `.env` 文件的路徑,然後使用 `load_dotenv()` 函數加載該文件中的環境變數。`_` 表示忽略返回值。
```python
import os
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv()) # read local .env file
from langchain.chains import RetrievalQA
from langchain.chat_models import ChatOpenAI
from langchain.document_loaders import CSVLoader
from langchain.vectorstores import DocArrayInMemorySearch
from IPython.display import display, Markdown
```
- 指定LLM
- 大型語言模型(LLM)的過時問題
如果當前日期超過目標日期,則將 `llm_model` 變數設置為 `"gpt-3.5-turbo"`。否則,設置為 `"gpt-3.5-turbo-0301"`。這表明代碼作者預計在特定日期後會有一個新版本的語言模型可用
```python=
import datetime
current_date = datetime.datetime.now().date()
target_date = datetime.date(2024, 6, 12)
if current_date > target_date:
llm_model = "gpt-3.5-turbo"
else:
llm_model = "gpt-3.5-turbo-0301"
```
- 建立檢索問答系統加載並索引數據
創建了一個基於 `DocArrayInMemorySearch` 的索引,用於存儲和檢索從 CSV 文件加載的文件數據。這樣的索引使得文件的檢索變得快速和高效,特別適合於需要從大量數據中快速找到相關信息的應用。
```python
file = 'OutdoorClothingCatalog_1000.csv'
loader = CSVLoader(file_path=file)
index = VectorstoreIndexCreator(
vectorstore_cls=DocArrayInMemorySearch
).from_loaders([loader])
```
- `VectorstoreIndexCreator`
- 用途:用於創建文件向量存儲(vector store)的工具。向量存儲是一種結構,用於保存文件的向量表示,從而支持快速的搜索和檢索操作。
- 功能: 它允許用戶定義如何將文件加載到索引中,並指定使用哪種類型的向量存儲(在這個例子中是 `DocArrayInMemorySearch`)
- `DocArrayInMemorySearch` 是一種用於**在記憶體(內存)中**建立並管理文件集合的工具,通常用於快速檢索和處理文件 ,主要功能包括:
- 嵌入向量(embedding)存儲
- 這些向量(vectors)是文件內容的數值化表示。這種表示通常是通過機器學習模型(如大型語言模型)生成
* 快速檢索
* 允許快速地從大量文件中檢索出與特定查詢最相關的文件。這是通過比較查詢的嵌入向量與存儲在系統中的文件嵌入向量來實現的
* **記憶體(內存)中處理**
* 所有操作都在記憶體中進行,這意味著對文件的訪問和處理速度非常快,但也受限於可用的記憶體大小
* 相似度搜索
* 通過計算嵌入向量間的相似度,這個工具可以找出與給定查詢或文件最相似的其他文件
- `DocArrayInMemorySearch` 使用場景
* 訊息檢索系統: 用於構建快速的文件檢索系統,如在聊天機器人、問答系統或推薦系統中檢索相關訊息
* 自然語言處理: 在處理大量文本數據時,用於快速篩選和處理相關文件
* 數據分析: 對於需要從大量數據中快速提取信息的場景,如分析客戶反饋或市場研究
- 發送查詢並獲得回應
- 定義一個查詢,要求列出所有帶有防曬功能的襯衫,並以 Markdown 表格形式總結每一件。
- 使用先前建立的索引對這個查詢進行檢索,獲得回應
```python
query = "Please list all your shirts with sun protection \
in a table in markdown and summarize each one."
response = index.query(query)
```
- 得到以markdown表格整理好的商品清單與摘要

### 使用檢索器處理(RetrievalQA)進行查詢
- 加載文件
- `CSVLoader`
```python
from langchain.document_loaders import CSVLoader
loader = CSVLoader(file_path=file)
docs = loader.load()
docs[0]
```
- 建立Embeddings
- 使用 `OpenAIEmbeddings` 生成文件的語義Embeddings。這些Embeddings用於捕捉文件的意義,並在後續的搜索中使用
```python=
from langchain.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
embed = embeddings.embed_query("Hi my name is Harrison")
```
#### 創建文件搜索系統
- 使用 `DocArrayInMemorySearch` 和前面創建的嵌入建立一個文件搜索系統。這允許對文件進行相似性搜索。
```python=
db = DocArrayInMemorySearch.from_documents(docs, embeddings)
```
- 建立問答系統
- 利用文件搜索系統進行相似性搜索,找出與特定查詢(如「請建議一件有防曬功能的襯衫」)最相關的文件。
```python=
query = "Please suggest a shirt with sunblocking"
docs = db.similarity_search(query)
```
- 使用 `ChatOpenAI` 語言模型生成自然語言回答
```python=!
llm = ChatOpenAI(temperature = 0.0, model=llm_model)
qdocs = "".join([docs[i].page_content for i in range(len(docs))])
response = llm.call_as_llm(
f"{qdocs} Question: Please list all your shirts with sun protection in a table in markdown and summarize each one.")
```
#### 問答鏈的應用
- 以下程式碼碼展示如何整合文件檢索和語言模型的能力,建立一個能夠針對特定查詢提供精確回答的系統
```python=
query = "Please list all your shirts with sun protection in a table \
in markdown and summarize each one."
retriever = db.as_retriever()
qa_stuff = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
verbose=True)
response = qa_stuff.run(query)
```
1. 建立檢索器
- 將之前建立的文件搜索系統(db),即 `DocArrayInMemorySearch` 的實例,轉換成一個檢索器(`retriever = db.as_retriever()`)
- 這個檢索器能夠接受查詢,並返回與該查詢最相關的文件
- 這是構建檢索問答系統的關鍵部分,因為它確定了哪些文件將被送到語言模型進行回答生成
2. 建立檢索問答鏈
- 使用`RetrievalQA.from_chain_type`方法建立了檢索問答鏈。這個方法需要幾個參數:
* `llm`:
* 這是大型語言模型(在此案例中為 ChatOpenAI)的實例。它用於生成對查詢的回答
* **`chain_type`**:指定問答鏈的類型。在這裡使用的**stuff**類型意味著將所有相關文件「塞入」(stuff)到一個單一的提示中,然後一次性送到語言模型進行處理
* `retriever`:這就是前面創建的檢索器,它用於根據用戶的查詢來獲取相關文件
3. 執行問答鏈
- 用剛剛建立的問答鏈 `qa_stuff` 執行查詢。這個 `run` 方法接受一個查詢(query),然後執行以下流程:
* 使用檢索器找到與查詢最相關的文件
* 將這些文件和查詢結合,形成一個完整的提示
* 將該提示發送給語言模型,生成自然語言的回答
* 返回這個回答作為 response
- 結果比較
- 使用RGA得到的結果
- 這邊的範例用的是了檢索問答鏈RetrievalQA的"stuff"方法

- 使用簡單索引(Index)查詢得到的結果
```python=
index = VectorstoreIndexCreator(
vectorstore_cls=DocArrayInMemorySearch,
embedding=embeddings).from_loaders([loader])
response = index.query(query, llm=llm)
display(Markdown(response))
```

- 結果說明
總結來說,兩個方法在描述相同的產品時,核心信息相同,但細節和陳述方式有所不同。
- 材質描述:
- RetrievalQA方法提供每件上衣的具體材料比例。
- Index方法提及材質時增加了“內襯”的描述,並指出“Men's TropicVibe Shirt, Short-Sleeve”的內襯是 100% 聚酯纖維編織網。
- 額外特性描述:
* RetrievalQA方法提到“Sun Shield Shirt”的耐磨性和快乾特性。
* Index方法提到“Sun Shield Shirt”適合穿在最喜愛的泳裝外面。
* 文本結構和細節:
- RetrievalQA方法更側重於細節描述,如材質的比例和額外的功能特性
- Index方法在材質描述中增加了內襯資訊,並稍微調整了文本的結構,使之更為流暢
個人認為在這個案例中並未凸顯RetrievalQA的優勢,可能材料與使用的RGA方法都相對單純