現在我們已經熟悉了LangChain的元件,是時候開始使用我們的LLM了。如果你想使用開放原始碼LLM,利用Hugging Face Hub整合是極其靈活的選擇。實際上,只需一個存取令牌,你就可以利用Hugging Face儲存函式庫中所有可用的開放原始碼LLM。

在非生產場景中,我會使用免費的Inference API。然而,如果你計劃構建生產級應用,你可以輕鬆擴充套件到Inference Endpoint,這為你提供了專用與完全代管的基礎設施來託管和使用你的LLM。

建立Hugging Face使用者存取令牌

要存取免費的Inference API,你需要一個使用者存取令牌作為執行服務的憑證。以下是啟用使用者存取令牌的步驟:

  1. 建立Hugging Face帳戶:你可以在https://huggingface.co/join免費建立一個Hugging Face帳戶。

  2. 取得使用者存取令牌:一旦你有了帳戶,前往個人資料的右上角,進入「設定 | 存取令牌」。在該選項卡中,你將能夠複製你的秘密令牌並用它來存取Hugging Face模型。

  3. 設定許可權:存取令牌讓使用者、應用程式和筆記本能夠根據分配的角色執行特定操作。有兩種可用的角色:

    • 讀取:允許令牌提供對你有許可權讀取的儲存函式庫的讀取存取。這包括你或你的組織擁有的公共和私有儲存函式庫。這個角色適合下載私有模型或推理等任務。
    • 寫入:除了讀取存取外,具有此角色的令牌還授予對你有寫入許可權的儲存函式庫的寫入存取。這個令牌適用於訓練模型或更新模型卡等活動。

    在我們的一系列使用案例中,我們將保持令牌的寫入許可權。

  4. 管理使用者存取令牌:在你的個人資料中,你可以建立和管理多個存取令牌,這樣你也可以區分許可權。要建立新令牌,你可以點選「新令牌」按鈕。

  5. 令牌管理:在任何時候,你都可以在「管理」按鈕下刪除或重新整理你的令牌。

重要的是不要洩露你的令牌,定期重新生成它是一個好的做法。在我的開發實踐中,我通常每隔幾個月就會重新生成所有API令牌,特別是在團隊成員變動或發現任何安全隱患時。

在.env檔案中儲存你的金鑰

有了在前一節中生成的使用者存取令牌,我們有了第一個需要管理的金鑰是需要保護免受未授權存取的資料,如密碼、令牌、金鑰和憑證。金鑰用於對API端點的請求進行身份驗證和授權,以及加密和解密敏感資料。

在這個實作部分中,我們將把所有金鑰儲存在一個.env檔案中。

在Python專案中將金鑰儲存在.env檔案中是增強安全性和可維護性的常見做法。要做到這一點,在你的專案目錄中建立一個名為.env的檔案,並將你的敏感訊息列為鍵值對:在我們的場景中,將有HUGGINGFACEHUB_API_TOKEN="你的使用者存取令牌"。這個檔案應該增加到你專案的.gitignore中,以防止意外暴露。

要在你的Python程式碼中存取這些金鑰,使用python-dotenv函式庫載入.env檔案的值作為環境變數。你可以在終端透過pip install python-dotenv輕鬆安裝它。

這種方法將敏感資料與你的程式碼函式庫分開,並有助於確保機密訊息在開發和佈署過程中保持機密。

以下是如何取得存取令牌並將其設定為環境變數的範例:

import os
from dotenv import load_dotenv

load_dotenv()
os.environ["HUGGINGFACEHUB_API_TOKEN"]

這段程式碼展示瞭如何在Python中安全地存取API令牌。首先匯入必要的函式庫,然後使用load_dotenv()函式載入.env檔案中的環境變數。這樣做可以確保敏感訊息不會直接寫入程式碼中,而是從外部設定檔案讀取。最後一行程式碼展示瞭如何存取已載入的環境變數,在實際應用中,這個值通常會被賦給一個變數或直接用於API呼叫。

值得注意的是,預設情況下,load_dotenv()將在當前工作目錄中查詢.env檔案。但你也可以指定金鑰檔案的路徑:

from dotenv import load_dotenv
from pathlib import Path

dotenv_path = Path('path/to/.env')
load_dotenv(dotenv_path=dotenv_path)

這段程式碼展示瞭如何指定.env檔案的自定義路徑。使用Path類別建立一個路徑物件,然後將其傳遞給load_dotenv()函式的dotenv_path引數。這在專案結構複雜或需要使用多個環境設定檔案的情況下特別有用。

在處理敏感憑證時,我總是建議遵循以下實踐:

  1. 永遠不要將真實憑證提交到版本控

在應用程式中嵌入大模型語言(LLM)

大模型語言(LLM)已經成為現代AI應用的核心,而將這些強大的模型整合到實際應用中,需要有效的框架支援。LangChain作為一個強大的AI協調工具,提供了便捷的方法來整合各種LLM,讓開發者能夠專注於應用邏輯而非底層實作。

使用Hugging Face Hub的開放原始碼LLM

Hugging Face Hub的優勢在於其豐富的模型目錄,開發者可以根據不同需求選擇適合的模型。平台按類別(電腦視覺、自然語言處理、音訊等)和能力(摘要、分類別、問答等)組織模型,使搜尋過程變得簡單直觀。

對於文字生成任務,我們可以嘗試使用Falcon LLM-7B這樣的開放原始碼模型:

from langchain import HuggingFaceHub

repo_id = "tiiuae/falcon-7b-instruct"
llm = HuggingFaceHub(
    repo_id=repo_id, 
    model_kwargs={"temperature": 0.5, "max_length": 1000}
)

print(llm("what was the first disney movie?"))

這段程式碼展示瞭如何使用LangChain整合Hugging Face Hub上的模型。我們首先指定模型的儲存函式庫ID(tiiuae/falcon-7b-instruct),然後設定模型引數如溫度(控制創意程度)和最大回應長度。當我們查詢「第一部迪士尼電影是什麼?」時,模型會回答「第一部迪士尼電影是《白雪公主與七個小矮人》」。

只需幾行程式碼,我們就完成了Hugging Face Hub上LLM的整合。使用類別似的方法,你可以測試和使用Hub上的所有可用LLM。

LangChain的一大優勢是模型的可替換性 - 你可以簡單地初始化不同的LLM,而不需要更改整個應用程式的程式碼。這種靈活性讓開發者能夠輕鬆實驗不同的模型,找出最適合特定應用場景的選擇。

開發對話式應用

對話式應用是一種能夠使用自然語言與使用者互動的軟體。隨著LLM技術的進步,現代對話應用不僅能提供更自然的語言互動,還能根據使用者偏好進行推理、利用引數化和非引數化知識,並透過不同型別的記憶機制追蹤對話脈絡。

對話應用的架構

一個典型的對話機器人架構包含以下關鍵元件:

  • LLM作為核心引擎
  • 記憶元件用於保持對話上下文
  • 知識函式庫連線以提供外部資訊
  • 工具整合以執行特定任務
  • 使用者介面層處理輸入和輸出

讓我們從基礎開始,一步構建一個名為GlobeBotter的旅遊規劃聊天機器人。

建立基礎聊天機器人

首先,我們需要初始化LLM並設定聊天機器人的訊息模式。在這個例子中,我們將使用三種型別的訊息:

  • 系統訊息:指導機器人作為旅遊助手的指令
  • AI訊息:由LLM生成的回應
  • 人類訊息:使用者的查詢

以下是一個簡單的設定:

from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage
)
from langchain.chains import LLMChain, ConversationChain 
from langchain.chat_models import ChatOpenAI

chat = ChatOpenAI()
messages = [
    SystemMessage(content="You are a helpful assistant that help the user to plan an optimized itinerary."),
    HumanMessage(content="I'm going to Rome for 2 days, what can I visit?")
]

output = chat(messages)
print(output.content)

這段程式碼建立了一個基本的聊天機器人框架。我們首先從LangChain匯入必要的元件,包括訊息型別和聊天模型。然後初始化ChatOpenAI模型作為我們的對話引擎。

訊息列表包含兩個元素:

  1. 系統訊息:定義機器人的角色和行為方式
  2. 人類訊息:模擬使用者的查詢

當我們將訊息傳遞給聊天模型並輸出結果時,模型會生成一個最佳化的兩天羅馬遊覽行程,包括景點如羅馬競技場、羅馬廣場、萬神殿、特拉斯特維雷區、特雷維噴泉、梵蒂岡城等,並按天分配。

這個基礎版本已經能夠提供有用的旅遊建議,但它缺乏記憶能力 - 機器人無法記住之前的對話。接下來,我們將增加記憶元件來改進這一點。

增加記憶元件

對話應用的一個關鍵功能是能夠記住之前的互動,這樣使用者就不必在每次查詢中重複上下文訊息。LangChain提供了多種記憶元件,讓我們能夠為聊天機器人增加不同型別的記憶能力。

記憶型別包括:

  • ConversationBufferMemory:儲存所有對話歷史
  • ConversationBufferWindowMemory:只儲存最近N次互動
  • ConversationSummaryMemory:儲存對話摘要而非完整歷史
  • ConversationEntityMemory:專注於追蹤對話中提到的實體

對於我們的旅遊助手,我們可以使用ConversationBufferMemory來儲存整個對話歷史,這樣機器人就能根據之前的查詢和回應提供連貫的建議。

在下一部分中,我們將實作這個記憶元件,並進一步增強我們的GlobeBotter,使其能夠處理更複雜的旅遊規劃問題。此外,我們還將探討如何增加非引數化知識和工具,讓聊天機器人能夠存取外部資訊並執行特定任務。

增強聊天機器人的功能

對話應用的真正價值在於它們能夠隨著對話的進行保持上下文理解,並利用外部知識和工具來提供更全面、更有用的回應。接下來,我們將探討如何透過增加記憶、知識函式庫和工具來提升我們的旅遊助手GlobeBotter的能力。

實作記憶元件

記憶元件使聊天機器人能夠記住之前的對話,從而提供更連貫的體驗。讓我們使用ConversationChain和ConversationBufferMemory來實作這一功能:

from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

# 初始化記憶元件
memory = ConversationBufferMemory()

# 建立帶有記憶的對話鏈
conversation = ConversationChain(
    llm=chat,
    memory=memory,
    verbose=True
)

# 第一次查詢
response = conversation.predict(input="I'm going to Rome for 2 days, what can I visit?")
print(response)

# 第二次查詢,機器人應該能記住之前的對話
response = conversation.predict(input="What about food recommendations?")
print(response)

這段程式碼展示瞭如何為聊天機器人增加記憶能力。我們首先初始化一個ConversationBufferMemory例項,然後將其與LLM一起傳遞給ConversationChain。

當使用者提出第一個關於羅馬景點的問題時,機器人會提供旅遊建議。當使用者隨後詢問食物推薦時,由於記憶元件的存在,機器人能夠理解這個問題是在之前羅馬旅遊討論的上下文中提出的,因此能夠直接推薦羅馬當地美食,而不需要使用者重新提及他們正在計劃羅馬之旅。

這種上下文感知能力大提升了使用者經驗,使對話更自然、更高效。

增加非引數化知識

雖然LLM擁有豐富的引數化知識,但它們的知識截止日期有限,與可能缺乏特定領域或最新的訊息。透過增加非引數化知識(如檔案、網頁或資料函式庫),我們可以大擴充套件聊天機器人的能力。

LangChain提供了多種向量儲存和檔案載入器,使我們能夠輕鬆地將外部知識整合到聊天機器人中:

from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings

# 載入檔案
loader = TextLoader('travel_guides/rome_guide.txt')
documents = loader.load()

# 分割檔案
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)

# 建立向量儲存
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(texts, embeddings)

# 設定根據知識函式庫的問答鏈
from langchain.chains import RetrievalQA

qa = RetrievalQA.from_chain_type(
    llm=chat,
    chain_type="stuff",
    retriever=vectorstore.as_retriever()
)

# 查詢特定訊息
response = qa.run("What are the best gelato places in Rome?")
print(response)

這段程式碼展示瞭如何將外部知識整合到聊天機器人中。我們首先載入一個關於羅馬的旅遊文字檔案,然後將其分割成較小的塊。接著,我們使用OpenAI的嵌入模型建立這些文字塊的向量表示,並儲存在FAISS向量資料函式庫中。

然後,我們建立一個RetrievalQA鏈,它能夠從向量儲存中檢索相關訊息並生成回應。當使用者詢問羅馬最佳冰淇淋店時,系統會從旅遊中檢索相關訊息,並提供根據這些訊息的回應。

這種方法使聊天機器人能夠提供更準確、更具體的訊息,特別是關於可能不在LLM訓練資料中或需要最新訊息的主題。

增加工具和代理功能

為了進一步增強聊天機器人的能力,我們可以增加工具和代理功能,使其能夠執行特定任務並做出決策。LangChain提供了豐富的工具整合,如網路搜尋、計算器、API呼叫等。

讓我們為我們的旅遊助手增加Google搜尋功能,使其能夠提供最新的旅遊訊息:

from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent
from langchain.utilities import GoogleSearchAPIWrapper

# 設定Google搜尋
search = GoogleSearchAPIWrapper()

# 定義工具
tools = [
    Tool(
        name="Google Search",
        func=search.run,
        description="Useful for finding up-to-date information about travel destinations, attractions, and recommendations"
    )
]

# 建立代理
from langchain.agents import initialize_agent

agent = initialize_agent(
    tools, 
    chat, 
    agent="chat-conversational-react-description", 
    verbose=True,
    memory=memory
)

# 使用代理回答問題
response = agent.run("What are the current COVID restrictions for travelers to Rome?")
print(response)

這段程式碼展示瞭如何向聊天機器人增加工具和代理功能。我們首先設定Google搜尋API包裝器,然後將其定義為一個工具,描述其用途是查詢有關旅遊目的地的最新訊息。

接著,我們使用LangChain的initialize_agent函式建立一個代理,它能夠使用定義的工具並保持對話記憶。當使用者詢問羅馬的COVID限制時,代理會決定使用Google搜尋工具查詢最新訊息,然後提供根據搜尋結果的回應。

這種代理功能使聊天機器人能夠超越其預訓練知識的限制,存取實時訊息和執行複雜任務,從而

開發人工智慧旅遊助手:使用LangChain建立具有記憶和知識的對話系統

在現代AI應用開發中,建立能夠保持對話上下文並具備專業知識的聊天機器人是一項關鍵技能。本文將帶你瞭解如何使用LangChain框架開發一個人工智慧旅遊助手,從基礎對話模型開始,逐步增加記憶功能和外部知識函式庫能力。

從基本對話開始

讓我們以一個簡單的羅馬一日遊行程生成為例,看基本的模型表現:

# 假設我們已經初始化了一個基本的LLM模型
response = chat.predict("請為我提供一個羅馬一日遊行程")

上面的程式碼會生成類別似以下的回應:

  1. 早上參觀古羅馬的象徵——羅馬競技場和古羅馬廣場
  2. 中午在台伯河沿岸散步,順道遊覽聖天使堡附近的風景區
  3. 下午前往西班牙階梯,這是一個能俯瞰城市美景的熱門聚集地
  4. 結束一天的行程,探索納沃納廣場,欣賞巴洛克風格建築和活力四射的氛圍

雖然模型能夠根據簡單的提示生成合理的行程,但如果我們想要進一步最佳化行程,提供更多關於個人偏好的訊息,就需要讓機器人能夠記住我們之前的對話內容。這就需要為我們的聊天機器人增加記憶功能。

為聊天機器人增加記憶功能

在對話型應用中,記憶是維持連貫對話的關鍵。LangChain提供了多種記憶元件,對於相對簡短的對話,ConversationBufferMemory是個不錯的選擇。

初始化記憶元件和對話鏈

from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

# 初始化記憶元件
memory = ConversationBufferMemory()

# 建立對話鏈,將LLM和記憶元件結合
conversation = ConversationChain(
    llm=chat,
    verbose=True,
    memory=memory
)

與記憶型聊天機器人互動

讓我們看這個具有記憶功能的機器人如何處理連續的對話:

# 第一次互動
conversation.run("你好!")

# 第二次互動
conversation.run("羅馬最具標誌性的地方是什麼?")

# 第三次互動,測試記憶能力
conversation.run("那裡還舉辦過什麼其他活動?")

在這個例子中,我們建立了一個帶有記憶功能的對話鏈。verbose=True引數讓我們能夠看到機器人如何追蹤之前的訊息。當我們問"那裡還舉辦過什麼其他活動?“時,機器人能夠理解這個問題是關於之前提到的羅馬競技場,並提供相關回答,展示了它能夠保持對話上下文的能力。

我們可以透過memory.load_memory_variables()方法檢索完整的對話歷史:

memory.load_memory_variables()  # 回傳完整對話歷史

為了使互動更加流暢,我們可以編寫一個簡單的迴圈來持續接收使用者輸入:

while True:
    query = input('你: ')
    if query == 'q':
        break
    output = conversation({"input": query})
    print('使用者: ', query)
    print('AI系統: ', output['response'])

這樣我們就可以進行連續對話,如:

使用者: 你好
AI系統: 你好!有什麼我可以幫助你的嗎?
使用者: 我計劃在威尼斯待一天,應該參觀哪些地方?
AI系統: 聽起來是個很棒的計劃!在威尼斯,有幾個一日遊必訪的景點。以下是建議行程:
1. 聖馬可廣場(Piazza San Marco):...

這個記憶功能讓我們的聊天機器人能夠保持對話的連貫性,記住使用者之前提到的訊息,並在後續互動中參考這些訊息。

整合外部知識函式庫

想象一下,如果我們希望旅遊助手能夠取得最新的旅遊資訊或特定的旅遊訊息?這時我們需要為機器人增加非引數化知識——即外部知識函式庫。

LangChain提供了多種方法來實作這一點。在這個例子中,我們將使用向量儲存支援的檢索器和ConversationalRetrievalChain,這種鏈可以在提供的知識函式庫上利用檢索器,並結合對話歷史。

準備知識函式庫

首先,我們需要載入和處理外部檔案:

from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import FAISS
from langchain.document_loaders import PyPDFLoader
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory

# 初始化文字分割器
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1500,
    chunk_overlap=200
)

# 載入PDF檔案
raw_documents = PyPDFLoader('italy_travel.pdf').load()

# 分割檔案
documents = text_splitter.split_documents(raw_documents)

# 建立向量資料函式庫
db = FAISS.from_documents(documents, OpenAIEmbeddings())

# 初始化記憶元件
memory = ConversationBufferMemory(
    memory_key='chat_history',
    return_messages=True
)

# 初始化語言模型
llm = ChatOpenAI()

這段程式碼設定了我們需要的所有元件:

  1. 檔案載入器(PyPDFLoader):用於載入PDF格式的義大利旅行
  2. 文字分割器(RecursiveCharacterTextSplitter):將檔案分割成較小的塊,便於處理
  3. 向量資料函式庫(FAISS):儲存檔案的向量表示,用於相似性搜尋
  4. 記憶元件(ConversationBufferMemory):儲存對話歷史
  5. 語言模型(ChatOpenAI):處理對話的核心AI模型

這裡的引數設定很關鍵:

  • chunk_size=1500:每個文字塊的大小,需要平衡詳細度和處理效率
  • chunk_overlap=200:相鄰塊之間的重疊部分,確保上下文連貫性
  • memory_key='chat_history':指定記憶元件中儲存對話歷史的鍵名
  • return_messages=True:確保記憶以訊息形式回傳,而不是字元串

建立和使用對話檢索鏈

現在,我們可以建立一個結合了語言模型、知識函式庫和記憶功能的對話檢索鏈:

# 建立對話檢索鏈
qa_chain = ConversationalRetrievalChain.from_llm(
    llm, 
    retriever=db.as_retriever(), 
    memory=memory, 
    verbose=True
)

# 使用鏈進行查詢
qa_chain.run({'question': '給我一些關於萬神殿的評論'})

這個鏈會首先從義大利旅行PDF中檢索關於萬神殿的相關訊息,然後結合這些訊息和之前的對話歷史生成回答。

進階技巧:調整檢索引數

為了進一步提高檢索品質,我們可以調整檢索器的引數:

# 使用自定義搜尋引數設定檢索器
retriever = db.as_retriever(
    search_type="mmr",  # 最大邊際相關性搜尋
    search_kwargs={"k": 4, "fetch_k": 8}
)

# 使用調整後的檢索器建立鏈
qa_chain = ConversationalRetrievalChain.from_llm(
    llm, 
    retriever=retriever, 
    memory=memory
)

這裡我們使用了"mmr”(Maximum Marginal Relevance)搜尋型別,它能夠在相關性和多樣性之間取得平衡。引數說明:

  • k=4:回傳的檔案數量
  • fetch_k=8:初始檢索的檔案數量,然後從中選擇最終的k個檔案

這種設定有助於減少回答中的冗餘訊息,同時保持高相關性。

實際應用:建立完整的旅遊助手

將所有功能組合在一起,我們可以建立一個完整的旅遊助手:

def travel_assistant():
    # 初始化模型和記憶
    llm = ChatOpenAI(temperature=0.7)
    memory = ConversationBufferMemory(memory_key='chat_history', return_messages=True)
    
    # 載入知識函式庫
    documents = PyPDFLoader('travel_guides.pdf').load()
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1500, chunk_overlap=200)
    splits = text_splitter.split_documents(documents)
    
    # 建立向量資料函式庫
    embeddings = OpenAIEmbeddings()
    vectordb = FAISS.from_documents(splits, embeddings)
    
    # 建立檢索器
    retriever = vectordb.as_retriever(
        search_type="mmr",
        search_kwargs={"k": 4, "fetch_k": 8}
    )
    
    # 建立對話檢索鏈
    qa_chain = ConversationalRetrievalChain.from_llm(
        llm, 
        retriever=retriever, 
        memory=memory,
        return_source_documents=True  # 回傳源檔案,便於解釋
    )
    
    # 對話迴圈
    print("旅遊助手已啟動!輸入'結束'結束對話。")
    while True:
        query = input("你: ")
        if query.lower() in ['結束', 'exit', 'quit', 'q']:
            break
            
        result = qa_chain({"question": query})
        print(f"旅遊助手: {result['answer']}")
        
        # 可選:顯示訊息來源
        if 'source_documents' in result:
            print("\n參考訊息來源:")
            for i, doc in enumerate(result['source_documents'][:2]):
                print(f"來源 {i+1}: {doc.metadata.get('source', '未知')}")
    
    print("感謝使用旅遊助手!")

這個完整的旅遊助手函式整合了我們前面討論的所有元素:

  1. 語言模型與記憶功能
  2. 知識函式庫載入與處理
  3. 向量檢索與最大邊際相關性搜尋
  4. 互動式對話迴圈

特別值得注意的是return_source_documents=True引數,它允許我們檢視機器人回答的訊息來源,這對於提高透明度和可解釋性非常有價值。

LangChain對話應用的擴充套件思路

在掌握了基本的對話鏈和知識函式庫整合後,還有許多方向可以擴充套件我們的應用:

  1. 多記憶型別組合:根據對話長度和複雜性選擇不同型別的記憶元件,如ConversationSummaryMemory用於長對話,ConversationKGMemory用於構建知識圖譜。

  2. 多知識源整合:將多個PDF檔案、網頁或API回傳的資料整合到同一個知識函式庫中,豐富模型的背景知識。

  3. 實時資料取得:透過工具呼叫整合實時天氣、酒店價格或景點開放時間等動態訊息。

  4. 個人化定製:根據使用者過往往偏好調整回答風格和內容,提供更加個人化的旅遊建議。

  5. 多模態支援:整合影像處理能力,讓使用者可以上載照片詢問相關景點訊息。

效能與成本最佳化

在開發對話應用時,效能和成本同樣重要:

  1. 模型選擇:對於不同的任

人工智慧對話系統:從知識檢索到主動決策

在人工智慧應用開發中,構建能夠理解並回應使用者問題的對話系統是一個核心需求。但普通的對話模型往往受限於訓練資料,無法處理特定領域的專業知識。而透過結合外部知識函式庫與大模型語言,我們可以開發更加人工智慧與專業的對話系統。

在這篇文章中,我將帶領大家使用LangChain框架構建一個旅遊顧問機器人,它不僅能夠從提供的檔案中檢索相關知識,還能根據問題性質決定是否需要使用網路搜尋取得實時資訊。

從簡單檢索到人工智慧代理決策

在過去幾年開發對話系統的過程中,我發現單純依靠語言模型的引數知識往往不足以提供專業與最新的領域資訊。這就是為什麼將LLM與外部知識結合變得如此重要。

最初的對話檢索系統通常採用固定流程:接收問題→檢索檔案→生成答案。但這種方式缺乏靈活性,無法根據問題性質動態調整策略。而透過代理(Agent)機制,我們可以讓模型自行決定何時使用哪種工具,大幅提升系統人工智慧程度。

構建具備檔案檢索能力的對話系統

首先,讓我們看如何為對話系統增加檔案檢索能力。在這個例子中,我們使用的是關於義大利旅遊的知識函式庫。

從檢索鏈到對話檢索鏈

LangChain提供了ConversationalRetrievalChain,這是一個專門為對話式檢索設計的元件。它的特別之處在於能夠將使用者的當前問題與之前的對話歷史結合,形成更完整的查詢。

這個元件預設使用CONDENSE_QUESTION_PROMPT範本來合併使用者的最新查詢與對話歷史,從而生成一個完整的查詢傳送給檢索器。如果需要自定義這個過程,可以透過condense_question_prompt引數來實作。

然而,這種設定下的機器人僅會檢視提供的檔案,而無法利用語言模型本身的引數知識。那麼,如何讓機器人既能查閱檔案,又能在適當時候使用自身知識呢?

從固定流程到人工智慧代理

為了讓我們的旅遊顧問機器人更加人工智慧,我們需要使它成為一個"代理"(Agent),能夠根據使用者問題自行判斷使用哪些工具。

代理系統的關鍵元件

在LangChain中實作這一功能,需要使用兩個主要元件:

  1. create_retriever_tool:建立一個檢索工具,需要指定檢索的資料函式庫、工具名稱和描述
  2. create_conversational_retrieval_agent:初始化一個對話代理,設定為與檢索工具和聊天模型協同工作

讓我們看如何實作:

from langchain.agents.agent_toolkits import create_retriever_tool

tool = create_retriever_tool(
    db.as_retriever(),
    "italy_travel",
    "Searches and returns documents regarding Italy."
)

tools = [tool]

memory = ConversationBufferMemory(
    memory_key='chat_history',
    return_messages=True
)

from langchain.agents.agent_toolkits import create_conversational_retrieval_agent
from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(temperature=0)
agent_executor = create_conversational_retrieval_agent(
    llm, 
    tools, 
    memory_key='chat_history', 
    verbose=True
)

這段程式碼建立了一個人工智慧代理系統。首先,我們使用create_retriever_tool將義大利旅遊知識函式庫包裝成一個工具,並為其命名為"italy_travel",同時提供描述讓模型瞭解何時該使用這個工具。

然後我們建立了一個對話記憶元件,用於儲存聊天歷史。最後,使用create_conversational_retrieval_agent函式將語言模型、工具和記憶元件結合,建立一個能夠處理對話並根據需要使用檢索工具的人工智慧代理。

verbose=True引數讓我們能看到代理的思考過程,非常適合開發和除錯。

人工智慧代理的決策能力

現在讓我們測試這個代理,看它如何根據問題型別選擇是否使用檢索工具:

# 測試與檔案相關的問題
agent_executor({"input": "Tell me something about Pantheon"})

# 測試與檔案無關的問題
output = agent_executor({"input": "what can I visit in India in 3 days?"})

當問到關於萬神殿(Pantheon)的問題時,代理會立即呼叫義大利旅遊知識函式庫工具進行查詢。而當問到關於印度旅遊的問題時,由於這不在義大利旅遊檔案的範圍內,代理會依靠語言模型本身的知識來回答。

這種動態的工具選擇能力是代理系統的核心優勢,它讓我們的機器人更加人工智慧化,能夠根據問題性質自行決定最佳的回答策略。

增強旅遊顧問機器人:增加網路搜尋功能

作為一個旅遊顧問機器人,能夠取得最新的訊息非常重要。天氣變化、景點開放時間、當地活動等都可能隨時更新。因此,我們需要為機器人增加網路搜尋能力。

整合Google搜尋API

LangChain提供了與Google SerpApi整合的工具,讓我們能夠輕鬆地將網路搜尋功能增加到代理系統中:

from langchain import SerpAPIWrapper
import os
from dotenv import load_dotenv

load_dotenv()
os.environ["SERPAPI_API_KEY"]

search = SerpAPIWrapper()
tools = [
    Tool.from_function(
        func=search.run,
        name="Search",
        description="useful for when you need to answer questions about current events"
    ),
    create_retriever_tool(
        db.as_retriever(),
        "italy_travel",
        "Searches and returns documents regarding Italy."
    )
]

agent_executor = create_conversational_retrieval_agent(
    llm, 
    tools, 
    memory_key='chat_history', 
    verbose=True
)

在這段程式碼中,我們整合了SerpAPI,這是一個用於存取Google搜尋結果的實時API。它簡化了網路搜尋過程,處理了代理、CAPTCHA解決和結果解析等複雜問題。

我們首先初始化SerpAPIWrapper,然後將其包裝為一個名為"Search"的工具,並提供描述幫助模型理解何時使用它。接著將這個搜尋工具與之前的義大利旅遊檢索工具一起增加到工具列表中,最後重新建立代理系統。

這樣設定後,代理將能夠根據問題自行決定:使用義大利檔案、進行網路搜尋,或者直接使用自身知識來回答。

多工具人工智慧代理的表現測試

讓我們透過三個不同型別的問題來測試增強後的代理系統:

  1. 不需要外部工具的一般知識問題

    "What can I visit in India in 3 days?"
    

    在這種情況下,模型使用自身引數知識直接回答,不呼叫任何外部工具。

  2. 需要實時訊息的問題

    "What is the weather currently in Delhi?"
    

    代理識別出這是一個關於當前實時訊息的問題,自動呼叫搜尋工具取得最新天氣資料。

  3. 與義大利旅遊相關的問題

    "I'm traveling to Italy. Can you give me some suggestions for the main attractions to visit?"
    

    代理識別出這是關於義大利旅遊的問題,因此呼叫義大利旅遊檔案檢索工具來提供專業的旅遊建議。

這個測試展示了代理系統的核心優勢:能夠根據問題性質動態選擇最合適的工具,而不是固定地使用某一種方法。這種靈活性讓我們的旅遊顧問機器人能夠提供更全面、更準確的回答。