【AI】利用生成式AI 建構應用 - 07 - 實作使用Ollama、LangChain、Qdrant 的 RAG

實作使用Ollama、LangChain、Qdrant 的 RAG

前面幾篇已經對RAG 之前需要的步驟做過詳解了,本篇直接做RAG 的範例。

RAG 的實作有以下步驟:

  1. 準備建立向量資料庫時所使用的相同 embedding model
  2. 準備vector store 用來取得與問題相關的向量資料
  3. 使用LangChain 初始化與Ollama 的連線
  4. 使用LangChain 架構做RAG 的Prompt 與 Agent
  5. 透過LangChain 串起整個RAG 流程
from datetime import datetime
from langchain.agents import create_agent
from langchain_core.embeddings import Embeddings
from langchain_ollama import ChatOllama
from langchain_qdrant import QdrantVectorStore
from sentence_transformers import SentenceTransformer
from langchain.agents.middleware import dynamic_prompt, ModelRequest

# 1. 準備embedding model
embedding_model = SentenceTransformer('intfloat/multilingual-e5-large')
class E5Embedding(Embeddings):
    def embed_query(self, text: str):
        return embedding_model.encode(f"query: {text}", normalize_embeddings=True).tolist()

    def embed_documents(self, texts):
        return [
            embedding_model.encode(f"passage: {t}", normalize_embeddings=True).tolist()
            for t in texts
        ]

# 2. 準備vector_store
vector_store = QdrantVectorStore.from_existing_collection(
    embedding=E5Embedding(),
    collection_name="domain",
    url=qdrant_url,
)

# 3. initial LLM with langChain
llm = ChatOllama(
    model="llama3.1-tw",
    base_url=ollama_url,
    temperature= 0.8,
    num_predict = 512
)

# 4. create agent
@dynamic_prompt
def prompt_with_context(request: ModelRequest) -> str:
    """Inject context into state messages."""
    last_query = request.state["messages"][-1].text
    retrieved_docs = vector_store.similarity_search(last_query, k=3)
    docs_content = "\n\n".join(doc.page_content for doc in retrieved_docs)

    print("\n================================ Retrieved Documents =================================")
    print(docs_content)
    now = datetime.now().strftime("%H:%M:%S")
    print(f"[{now}]: retrieved finish\n")

    return  (
        f"""你是一個專業的電商客服 AI 助理,使用繁體中文回答。
        重要規則:
        1. 仔細閱讀,理解,吸收參考資料
        2. 利用參考資料,盡力回答使用者的問題.並嘗試提供解決方案
        3. 用你自己的話總結和回答,不會直接拿參考資料來回答
        
        利用以下的上下文回答問題:
        {docs_content}
        """
    )
agent = create_agent(llm, tools=[], middleware=[prompt_with_context])

# 5. call LLM
question = "賣家收到我的退貨了 甚麼時候才會退款給我?"
for step in agent.stream(
    {"messages": [{"role": "user", "content": question}]},
    stream_mode="values",
):
    step["messages"][-1].pretty_print()
    now = datetime.now().strftime("%H:%M:%S")
    print(f"[{now}]: message end\n")

vector_store.client.close()

從以下的輸出可以看到拿出檢索資料(Retrieved) 速度其實是蠻快的,呼叫與回傳幾乎是同一秒就完成。
筆者刻意將取出的檢索資料(Retrieved Documents) 顯示出來,用以判斷LLM 的是否有確實根據檢索資料回覆:

> ================================ Human Message =================================
> 賣家收到我的退貨了 甚麼時候才會退款給我?
> [14:12:35]: message end
> 
> ================================ Retrieved Documents =================================
> 退款將統一使用日幣,將於賣家收到退貨商品後,三千個工作天內完成退款
> 買家收到商品後,發現有問題7天內可以無條件退貨退款
> 若退貨包裹遺失,請聯絡高速婆婆處理
> [14:12:35]: retrieved finish
> 
> ================================== Ai Message ==================================
> 根據文中提到的資訊,「退款將統一使用日幣,將於賣家收到退貨商品後,三千個工作天內完成退款」,我們可以推斷出以下幾點:
> 1. 退貨的流程是:買家先通知賣家退貨,賣家收到貨物後再進行退款。
> 2. 退款的時間點是「賣家收到退貨商品後」,也就是在貨物寄回賣家之後。
> 3. 退款完成的時間為「三千個工作天內」,也就是在接獲退貨通知後,三千天(或更短的時間)內。 
> 4. 退款金額與退貨商品金額相同,因為「統一使用日幣」。
> 因此,從買家通知賣家退貨開始,到賣家收到貨物並完成退款,大約需要3千天的時間。建議你可以再次確認一下退貨時的約定意見,或許還有其他細節要特別留意。
> [14:12:51]: message end
> 
> Process finished with exit code 0

到這邊恭喜我們🎉🎉🎉
終於完成了一個簡易的RAG 流程 ~~

references:
Build a RAG agent with LangChain - Docs by LangChain