共用方式為


Azure Cosmos DB 中的 AI 代理程式

AI 代理程式旨在針對使用者執行特定工作、回答問題,以及自動化程序所設計。 這些代理程式在複雜度上有很大的差異。 範圍從簡單的聊天機器人到共同作業,以及採用數位或機器人系統形式的進階 AI 助理,這些助理可以自發執行複雜的工作流程。

本文提供 AI 代理程式的概念概觀和詳細的實作範例。

什麼是 AI 代理程式?

不同於獨立大型語言模型 (LLM) 或規則型軟體/硬體系統,AI 代理程式擁有下列常見功能:

  • 規劃:AI 代理程式可以規劃動作及加以排序,以達成特定目標。 LLM 的整合已徹底改變了其規劃能力。
  • 工具使用方式:進階 AI 代理程式可以使用各種工具 (例如程式碼執行、搜尋和計算功能),以有效地執行工作。 AI 代理程式通常會透過函式呼叫來使用工具。
  • 感知:AI 代理程式可以從其環境中感知和處理資訊,使其更具互動性和內容感知。 此資訊包括視覺、聽覺和其他感應資料。
  • 記憶:AI 代理程式能夠記住過去的互動 (工具使用方式和感知) 和行為 (工具使用方式和規劃)。 其會儲存這些體驗,甚至執行自我反思,以通知未來的動作。 此記憶體元件可讓您在一段時間內持續改善代理程式效能。

注意

AI 代理程式內容中「記憶」一詞的用法與電腦記憶體 (例如揮發性、非揮發性和持續性記憶體) 的概念不同。

Copilots

Copilot 是一種 AI 代理程式。 其與使用者一起工作,而不是獨立運作。 不同於完全自動化的代理程式,Copilot 會提供各種建議,以協助使用者完成工作。

例如,當使用者正在撰寫電子郵件時,Copilot 可能會建議片語、句子或段落。 使用者也可要求 Copilot 在其他電子郵件或檔案中找到相關資訊以支持建議 (請參閱擷取擴增生成)。 使用者可以接受、拒絕或編輯建議的段落。

自發代理程式

自發代理程式可以更獨立地運作。 當您設定自發代理程式以協助電子郵件撰寫時,可以讓這些代理程式執行下列工作:

  • 諮詢與主題相關的現有電子郵件、聊天、檔案和其他內部及公開資訊。
  • 對收集到的資訊執行定性或定量分析,並得出與電子郵件相關的結論。
  • 根據結論撰寫完整的電子郵件,並納入佐證證據。
  • 將相關檔案附加至電子郵件。
  • 檢閱電子郵件,以確保所有已合併的資訊實際上都正確無誤,且判斷提示有效。
  • 針對 [收件者]、[副本] 和 [密件副本] 選取適當的收件者,並查閱其電子郵件地址。
  • 排程適當的時間傳送電子郵件。
  • 在預期回應但卻未收到回應的情況下執行後續操作。

您可以設定代理程式,以在有或沒有人員核准的情況下執行上述每個工作。

多重代理程式系統

達成高效能自發代理程式的熱門策略是使用多重代理程式系統。 在多重代理程式中,多個自發代理程式會 (無論是以數位或機器人形式) 互動或共同合作,以達成個別或集體目標。 系統中的代理程式可以獨立運作,並擁有自己的知識或資訊。 每個代理程式也可以根據其目標來感知其環境、做出決策及執行動作。

多重代理程式系統的主要特性:

  • 自發:每個代理程式都會獨立運作。 自行做出決定,而不需要直接人為介入或由其他代理程式控制。
  • 互動:代理程式會彼此通訊並共同作業,以共用資訊、交涉及協調其動作。 這種互動可透過各種通訊協定和通訊通道來進行。
  • 目標導向:多重代理程式中的代理程式旨在達成特定目標,其可與個別目標或代理程式之間共用的目標一致。
  • 分散式:多重代理程式系統以分散式方式運作,且沒有單一控制點。 此散發可增強系統的健全性、延展性和資源效率。

多重代理程式系統提供下列優點,優於 Copilot 或單一 LLM 推斷執行個體:

  • 動態推理:相較於思維鏈或思維樹提示,多重代理程式系統允許動態瀏覽各種推理路徑。
  • 複雜的功能:多重代理程式系統可執行徹底的決策流程,並在多個代理程式之間散發工作,藉此處理複雜或大規模的問題。
  • 增強的記憶體:具有記憶體的多重代理程式系統可以克服 LLM 的內容視窗,以進一步了解和保留資訊。

AI 代理程式的實作

推理和規劃

複雜的推理和規劃是進階自發代理程式的標誌。 熱門的自發代理程式架構會納入下列一或多個推理和規劃方法 (具有 arXiv 封存頁面的連結):

  • 自我詢問

    讓模型在回答初始問題之前,明確詢問自己 (並回答) 後續問題,以改善思維鏈。

  • 推理和動作 (ReAct)

    使用 LLM 以交錯方式產生推理追蹤和工作特定動作。 推理追蹤可協助模型引發、追蹤和更新行動計劃,以及處理例外狀況。 動作可讓模型與外部來源連線,例如知識庫或環境,以收集其他資訊。

  • 規劃及解決

    設計一個計畫,將整個任務分成較小型的子工作,然後根據計劃執行子工作。 此方法可降低計算錯誤、遺漏步驟錯誤和語意誤解錯誤,而這些錯誤通常存在於零樣本思維鏈提示中。

  • 反映/自我批評

    使用會口頭反射工作意見反應訊號的反映代理程式。 這些代理程式會在偶發記憶體緩衝區中維持自己的反射文字,以在後續試驗中引發更好的決策。

架構

各種架構和工具可協助開發及部署 AI 代理程式。

對於不需要複雜規劃和記憶體的工具使用方式和感知,一些熱門的 LLM 協調器架構為 LangChain、LlamaIndex、Prompt Flow 和 Semantic Kernel。

針對進階和自發規劃及執行工作流程,AutoGen 推動了多重代理程式浪潮,於 2022 年底開始。 OpenAI 的助理 API 可讓其使用者在 GPT 生態系統中以原生方式建立代理程式。 LangChain 代理程式LlamaIndex 代理程式也同時出現。

提示

若要了解如何使用其中一個熱門架構及整合代理程式記憶體系統來建置簡單多重代理程式系統,請參閱本文稍後的實作範例

AI 代理程式記憶體系統

從 2022 年到 2024 年試驗 AI 增強型應用程式的普遍做法,是針對各種資料工作流程或類型使用獨立資料庫管理系統。 例如,您可以針對快取使用記憶體內部資料庫、針對操作資料使用關聯式資料庫 (包括追蹤/活動記錄和 LLM 交談歷程記錄),以及針對內嵌管理使用純向量資料庫

不過,使用複雜 Web 的獨立資料庫做法可能會損害 AI 代理程式的效能。 將這些不同的資料庫整合到 AI 代理程式的一致、互通且具復原性的記憶體系統,對於 AI 代理程式而言是一項挑戰。

此外,許多常用的資料庫服務對於 AI 代理程式系統所需的速度和可擴縮性而言並不理想。 在多重代理程式系統中,這些資料庫的個別弱點會加劇。

記憶體內部資料庫

記憶體內部資料庫的速度非常出色,但可能會難以應付 AI 代理程式所需的大規模資料持續性。

關聯式資料庫

關聯式資料庫不適合代理程式所處理資料具有不同情態和流暢結構描述的情況。 關聯式資料庫需要手動工作而甚至停機,才能管理佈建、資料分割和分區化。

純向量資料庫

純向量資料庫對於交易作業、即時更新和分散式工作負載而言,往往效率較為不彰。 目前常用的純向量資料庫通常會提供:

  • 不保證讀取和寫入。
  • 有限的擷取輸送量。
  • 低可用性 (低於 99.9%,或 9 小時以上的年化中斷)。
  • 一個一致性層級 (最終)。
  • 耗用大量資源的記憶體內部向量索引。
  • 有限的多租用戶選項。
  • 有限的安全性。

健全 AI 代理程式記憶體系統的特性

如同有效率的資料庫管理系統對於軟體應用程式的效能至關重要,為 LLM 提供的代理程式提供相關且有用的資訊來引導其推斷也非常重要。 健全的記憶體系統可讓您組織及儲存代理程式可在推斷時擷取的各種資訊。

目前,LLM 提供的應用程式通常會使用擷取擴增生成,其會使用基本語意搜尋或向量搜尋來擷取段落或文件。 向量搜尋對於尋找一般資訊很有用。 但向量搜尋可能無法擷取與特定工作或網域相關的特定內容、結構或關聯性。

例如,如果工作是撰寫程式碼,向量搜尋就可能無法擷取語法樹狀結構、檔案系統配置、程式碼摘要或 API 簽章,這些簽章對於產生一致且正確的程式碼很重要。 同樣地,如果工作是使用表格式資料,向量搜尋就可能無法擷取結構描述、外部索引鍵、預存程序或用於查詢或分析資料的報表。

將獨立記憶體內部、關聯式和向量資料庫拼湊在一起 (如先前所述),不是具有多種資料類型之案例的最佳解決方案。 這種方法可能適用於原型代理程式系統。 不過,這會增加複雜度和效能瓶頸,以阻礙進階自發代理程式的效能。

強固的記憶體系統應具有下列特性。

多模式

AI 代理程式記憶體系統應該提供集合,以儲存中繼資料、關聯性、實體、摘要,或可用於各種工作和網域的其他資訊類型。 這些集合可以根據資料的結構和格式,例如文件、資料表或程式碼。 或者可以根據資料的內容和意義,例如概念、關聯或程序步驟。

記憶體系統不只是 AI 代理程式的關鍵。 對於開發、維護和使用這些代理程式的人員也很重要。

例如,人員可能需要近乎即時地監督代理程式的規劃和執行工作流程。 在監督時,人員可能會插話指導,或進行代理程式對話或獨白的內嵌編輯。 人員可能也需要稽核代理程式的推理和動作,以驗證最終輸出的有效性。

人員代理/程式互動可能是使用自然語言或程式設計語言,而代理程式則透過內嵌來「思考」、「學習」和「記憶」。 此差異對記憶體系統的一致性在資料形式之間構成另一個需求。

運作

記憶體系統應該提供記憶體庫,以儲存與使用者和環境互動相關的資訊。 這類資訊可能包括聊天記錄、使用者喜好設定、感測器資料、所制定決策、所學習事實,或以高頻率和高容量更新的其他操作資料。

這些記憶體庫可協助代理程式記住短期和長期資訊、避免重複或相互矛盾,並保持工作一致性。 即使代理程式連續執行許多不相關的工作,這些需求仍必須成立。 在進階案例中,代理程式也可以測試許多分支計劃在不同的點分歧或融合。

可共用但也可分離

在宏觀層級,記憶體系統應啟用多個 AI 代理程式,透過提供可供所有代理程式存取的共用記憶體,來共同處理問題或處理不同層面的問題。 共用記憶體有助於交換資訊,以及協調代理程式之間的動作。

同時,記憶體系統必須允許代理程式保留自己的角色和特性,例如其獨特的提示和記憶集合。

建置健全的 AI 代理程式記憶體系統

上述特性需要 AI 代理程式記憶體系統具有高度可調整性和快捷性。 將大量不同記憶體內部、關聯式和向量資料庫拼湊在一起 (如先前所述) 可能適用於早期啟用 AI 的應用程式。 不過,這種方法會增加複雜度和效能瓶頸,而這可能會阻礙進階自發代理程式的效能。

為了取代所有獨立資料庫,Azure Cosmos DB 可做為 AI 代理程式記憶體系統的整合解決方案。 其強固性可讓 OpenAI 的 ChatGPT 服務以高效可靠且低維護方式進行動態調整。 其由 atom-record-sequence 引擎所提供,這是世界上第一個提供無伺服器模式的全球分散式 NoSQL關聯式向量資料庫服務。 以 Azure Cosmos DB 為基礎的 AI 代理程式提供速度、規模和簡易性。

速度

Azure Cosmos DB 提供個位數毫秒的延遲。 這項功能適合需要快速資料存取和管理的程序。 這些程式包括快取(傳統和語意快取、交易和作業工作負載。

對於需要執行複雜推理、做出即時決策並提供立即回應的 AI 代理程式而言,低延遲至關重要。 此外,服務使用 DiskANN 演算法,可提供精確且快速的向量搜尋,且記憶體耗用量最少。

調整

Azure Cosmos DB 是針對全域散發和水平可擴縮性所設計。 支援多重區域 I/O 和多組織用戶管理。

此服務可協助確保記憶體系統可以順暢地擴充,並跟上快速成長的代理程式和相關聯的資料。 服務等級協定 (SLA) 中的可用性保證會轉譯為每年少於 5 分鐘的停機時間。 相較之下,純向量資料庫服務具有 9 小時以上的停機時間。 此可用性為任務關鍵性工作負載提供了堅實的基礎。 同時,Azure Cosmos DB 中的各種服務模型,例如保留容量或無伺服器,可協助降低財務成本。

簡單

Azure Cosmos DB 可將多個資料庫功能整合到單一的整合平台,藉此簡化資料管理和架構。

其整合式向量資料庫功能可與自然或程式設計語言中的對應資料一起儲存、編製索引及查詢內嵌。 這項功能可提升資料一致性、規模和效能。

其彈性可支援中繼資料、關聯性、實體、摘要、聊天記錄、使用者喜好設定、感測器資料、決策、所了解的事實,或其他涉及代理程式工作流程之操作資料的不同情態和流暢的結構描述。 資料庫會自動編製所有資料的索引,而不需要結構描述或索引管理,協助 AI 代理程式能夠快速且有效率地執行複雜的查詢。

Azure Cosmos DB 是完全受控服務,可消除例如調整、修補和備份等資料庫管理工作的額外負荷。 沒有了這個額外負荷,開發人員可以專注於建置及最佳化 AI 代理程式,而不必擔心底層資料基礎結構。

進階功能

Azure Cosmos DB 包含進階功能 (例如變更摘要),可讓您即時追蹤和回應資料中的變更。 這項功能適用於需要立即回應新資訊的 AI 代理程式。

此外,多重主機寫入的內建支援可提供高可用性和復原能力,協助確保即使在區域失敗之後,仍能持續操作 AI 代理程式。

五個可用的一致性層級 (從強式到最終) 也可以根據案例需求來迎合各種分散式工作負載。

提示

您可以從兩個 Azure Cosmos DB API 中進行選擇,以建置 AI 代理程式記憶體系統:

  • Azure Cosmos DB for NoSQL 提供 99.999% 的可用性保證,並提供三種向量搜尋演算法:IVF、HNSW 和 DiskANN
  • V 核心型 Azure Cosmos DB for MongoDB 提供 99.995% 的可用性保證,並提供兩種向量搜尋演算法:IVF 和 HNSW (DiskANN 即將推出)

如需這些 API 可用性保證的詳細資訊,請參閱服務 SLA

實作範例

本節探討實作自發代理程式,以處理旅遊應用程式中對於遊輪的旅客查詢和預訂。

聊天機器人是一個長期的概念,但 AI 代理程式已超越基本的人員對話,以根據自然語言來執行工作。 這些工作在傳統上是需要編碼邏輯才能完成的事情。 此實作範例中的 AI 旅遊代理程式會使用 LangChain 代理程式架構來進行代理程式規劃、工具使用和感知。

AI 旅遊代理程式的統一記憶體系統會使用 Azure Cosmos DB 的向量資料庫和文件存放區功能來解決旅客查詢並協助預訂行程。 針對此目的使用 Azure Cosmos DB 有助於確保速度、規模和簡易性,如先前所述。

範例代理程式會在 Python FastAPI 後端內運作。 支援透過 React JavaScript 使用者介面進行使用者互動。

必要條件

  • Azure 訂用帳戶。 如果您沒有 Azure Cosmos DB,您可以免費試用 Azure Cosmos DB 30 天,而不需建立 Azure 帳戶。 免費試用不需要信用卡,且試用期間不需任何承諾。
  • OpenAI API 或 Azure OpenAI 服務的帳戶。
  • Azure Cosmos DB for MongoDB 中的 V 核心 叢集。 您可以依照下列本快速入門建立一個帳戶。
  • 整合式開發環境,例如 Visual Studio Code。
  • 安裝在開發環境中的 Python 3.11.4。

下載專案

所有程式碼和範例資料集皆已在此 GitHub 存放庫上提供。 存放庫包含下列資料夾:

  • loader:此資料夾包含 Python 程式碼,可用來載入 Azure Cosmos DB 中的範例文件和向量內嵌。
  • api:此資料夾包含用來裝載 AI 旅遊代理程式的 Python FastAPI 專案。
  • web:此資料夾包含 React Web 介面的程式碼。

將旅遊文件載入 Azure Cosmos DB

GitHub 存放庫在 loader 目錄中包含 Python 專案。 其適用於將範例旅遊文件載入 Azure Cosmos DB。

設定環境

執行下列命令,在 loader 目錄中設定 Python 虛擬環境:

python -m venv venv

啟動您的環境,並在 loader 目錄中安裝相依性:

venv\Scripts\activate
python -m pip install -r requirements.txt

loader 目錄中建立名為 .env 的檔案,以儲存下列環境變數:

OPENAI_API_KEY="<your OpenAI key>"
MONGO_CONNECTION_STRING="mongodb+srv:<your connection string from Azure Cosmos DB>"

載入文件和向量

Python 檔案 main.py 會做為將資料載入 Azure Cosmos DB 的中央進入點。 此程式碼會處理來自 GitHub 存放庫的範例旅遊資料,包括船舶和目的地的相關資訊。 程式碼也會產生每個船舶和目的地的旅遊行程套裝組合,讓旅客能夠使用 AI 代理程式預訂。 CosmosDBLoader 工具負責在 Azure Cosmos DB 執行個體中建立集合、向量內嵌和索引。

以下是 main.py 的內容:

from cosmosdbloader import CosmosDBLoader
from itinerarybuilder import ItineraryBuilder
import json

cosmosdb_loader = CosmosDBLoader(DB_Name='travel')

#read in ship data
with open('documents/ships.json') as file:
        ship_json = json.load(file)

#read in destination data
with open('documents/destinations.json') as file:
        destinations_json = json.load(file)

builder = ItineraryBuilder(ship_json['ships'],destinations_json['destinations'])

# Create five itinerary packages
itinerary = builder.build(5)

# Save itinerary packages to Cosmos DB
cosmosdb_loader.load_data(itinerary,'itinerary')

# Save destinations to Cosmos DB
cosmosdb_loader.load_data(destinations_json['destinations'],'destinations')

# Save ships to Cosmos DB, create vector store
collection = cosmosdb_loader.load_vectors(ship_json['ships'],'ships')

# Add text search index to ship name
collection.create_index([('name', 'text')])

藉由從 loader 目錄執行下列命令,即可載入文件、載入向量及建立索引:

python main.py

以下是 main.py 的輸出:

--build itinerary--
--load itinerary--
--load destinations--
--load vectors ships--

使用 Python FastAPI 建置 AI 旅遊代理程式

AI 旅遊代理程式是透過 Python FastAPI 裝載在後端 API 中,有助於與前端使用者介面整合。 API 專案會透過針對資料圖層為 LLM 提示建基來處理代理程式要求,特別是 Azure Cosmos DB 中的向量和文件。

代理程式會使用各種工具,特別是 API 服務層所提供的 Python 函式。 本文著重於 API 程式碼內 AI 代理程式所需的程式碼。

GitHub 存放庫中的 API 專案結構如下:

  • 資料模型化元件使用 Pydantic 模型。
  • Web 層元件負責路由要求和管理通訊。
  • 服務層元件負責主要商務邏輯和與資料圖層、LangChain 代理程式和代理程式工具互動。
  • 資料圖層元件負責與 Azure Cosmos DB for MongoDB 文件儲存體和向量搜尋互動。

設定 API 的環境

我們使用 Python 3.11.4 版進行 API 的開發及測試。

api 目錄中設定 Python 虛擬環境:

python -m venv venv

api 目錄中使用需求檔案來啟用您的環境並安裝相依性:

venv\Scripts\activate
python -m pip install -r requirements.txt

api 目錄中建立名為 .env 的檔案,以儲存您的環境變數:

OPENAI_API_KEY="<your Open AI key>"
MONGO_CONNECTION_STRING="mongodb+srv:<your connection string from Azure Cosmos DB>"

既然您已設定環境並設定變數,請從 api 目錄執行下列命令來起始伺服器:

python app.py

FastAPI 伺服器預設會在 localhost 回送 127.0.0.1 連接埠 8000 上啟動。 您可以使用下列 localhost 位址來存取 Swagger 文件:http://127.0.0.1:8000/docs

針對 AI 代理程式記憶體使用工作階段

旅遊代理程式必須能夠參考先前在進行中交談內提供的資訊。 這項功能通常稱為 LLM 內容中的記憶體

為了達成此目標,使用聊天訊息歷程記錄,此記錄會儲存在 Azure Cosmos DB 執行個體中。 每個聊天工作階段都會使用工作階段識別碼來儲存其歷程記錄,以確保只有來自目前交談工作階段的訊息可供存取。 此必要性是 API 中存在 Get Session 方法背後的原因。 這個預留位置方法可用來管理 Web 工作階段,以說明聊天訊息歷程記錄的使用方式。

針對 /session/ 選取 [試試看]

在 Python FastAPI 中使用取得工作階段方法的螢幕擷取畫面,其中包含試試看的按鈕。

{
  "session_id": "0505a645526f4d68a3603ef01efaab19"
}

針對 AI 代理程式,您只需要模擬工作階段。 stubbed-out 方法只會傳回用於追蹤訊息歷程記錄的所產生工作階段識別碼。 在實際實作中,此工作階段會儲存在 Azure Cosmos DB 中,並可能儲存在 React localStorage 中。

以下是 web/session.py 的內容:

@router.get("/")
def get_session():
    return {'session_id':str(uuid.uuid4().hex)}

開始與 AI 旅遊代理程式的對話

使用您從上一個步驟取得的工作階段識別碼,開始與 AI 代理程式進行新的對話,以便驗證其功能。 提交下列語句來進行測試:"I want to take a relaxing vacation." (我想好好放鬆度個假。)

針對 /agent/agent_chat 選取 [試試看]

在 Python FastAPI 中使用代理程式聊天方法的螢幕擷取畫面,其中包含試試看的按鈕。

使用此範例參數:

{
  "input": "I want to take a relaxing vacation.",
  "session_id": "0505a645526f4d68a3603ef01efaab19"
}

最初的執行結果為 Tranquil Breeze Cruise (平靜的微風遊輪) 和 Fantasy Seas Adventure Cruise (幻想海洋冒險遊輪) 的建議,因為代理程式預期這是透過向量搜尋所得到最放鬆的航遊。 這些文件對在 API 資料圖層 data.mongodb.travel.similarity_search() 中所呼叫的 similarity_search_with_score 有最高分數。

相似性搜尋分數會顯示為 API 的輸出,以供偵錯之用。 以下是呼叫 data.mongodb.travel.similarity_search() 之後的輸出:

0.8394561085977978
0.8086545112328692
2

提示

如果未傳回向量搜尋的文件,請視需要在 data.mongodb.travel.similarity_search() 中修改 similarity_search_with_score 限制或分數篩選值 ([doc for doc, score in docs if score >=.78])。

第一次呼叫 agent_chat 會在 Azure Cosmos DB 中建立名為 history 的新集合,以依工作階段儲存交談。 此呼叫可讓代理程式視需要存取儲存的聊天訊息歷程記錄。 後續使用相同參數執行的 agent_chat 會產生不同的結果,因為其會從記憶體中繪製。

逐步執行 AI 代理程式

將 AI 代理程式整合到 API 時,Web 搜尋元件會負責起始所有要求。 Web 搜尋元件接著是搜尋服務,最後則是資料元件。

在此特定案例中,您會使用 MongoDB 資料搜尋,其會連線到 Azure Cosmos DB。 這些層可協助交換模型元件,並搭配位於服務層中的 AI 代理程式和 AI 代理程式工具程式碼。 此方法可讓您順暢地交換資料來源。 也會使用其他更複雜的功能或工具,擴充 AI 代理程式的功能。

AI 旅遊代理程式的 FastAPI 層圖表。

服務層

服務層是形成核心商務邏輯的基石。 在此特定案例中,服務層會扮演 LangChain 代理程式程式碼存放庫的重要角色。 可協助將使用者提示與適用於 AI 代理程式的 Azure Cosmos DB 資料、交談記憶體和代理程式函式緊密整合。

服務層會採用單一模式模組來處理 init.py 檔案中的代理程式相關初始化。 以下是 service/init.py 的內容:

from dotenv import load_dotenv
from os import environ
from langchain.globals import set_llm_cache
from langchain_openai import ChatOpenAI
from langchain_mongodb.chat_message_histories import MongoDBChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain.agents import AgentExecutor, create_openai_tools_agent
from service import TravelAgentTools as agent_tools

load_dotenv(override=False)

chat : ChatOpenAI | None=None
agent_with_chat_history : RunnableWithMessageHistory | None=None

def LLM_init():
    global chat,agent_with_chat_history
    chat = ChatOpenAI(model_name="gpt-3.5-turbo-16k",temperature=0)
    tools = [agent_tools.vacation_lookup, agent_tools.itinerary_lookup, agent_tools.book_cruise ]

    prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "You are a helpful and friendly travel assistant for a cruise company. Answer travel questions to the best of your ability providing only relevant information. In order to book a cruise you will need to capture the person's name.",
        ),
        MessagesPlaceholder(variable_name="chat_history"),
        ("user", "Answer should be embedded in html tags. {input}"),
         MessagesPlaceholder(variable_name="agent_scratchpad"),
    ]
    )

    #Answer should be embedded in HTML tags. Only answer questions related to cruise travel, If you can not answer respond with \"I am here to assist with your travel questions.\". 


    agent = create_openai_tools_agent(chat, tools, prompt)
    agent_executor  = AgentExecutor(agent=agent, tools=tools, verbose=True)

    agent_with_chat_history = RunnableWithMessageHistory(
        agent_executor,
        lambda session_id: MongoDBChatMessageHistory( database_name="travel",
                                                 collection_name="history",
                                                   connection_string=environ.get("MONGO_CONNECTION_STRING"),
                                                   session_id=session_id),
        input_messages_key="input",
        history_messages_key="chat_history",
)

LLM_init()

init.py 檔案會使用 load_dotenv(override=False) 方法,從 .env 檔案起始載入環境變數。 然後,針對代理程式具現化名為 agent_with_chat_history 的全域變數。 這個代理程式適用於 TravelAgent.py

會在模組初始化期間叫用 LLM_init() 方法,以透過 API Web 層設定 AI 代理程式進行交談。 OpenAI chat 物件會透過 GPT-3.5 模型具現化,並且併入特定參數,例如模型名稱和溫度。 chat 物件、工具清單和提示範本會進行合併以產生 AgentExecutor,其會以 AI 旅遊代理程式的形式運作。

具有歷程記錄 agent_with_chat_history 的代理程式是透過 RunnableWithMessageHistory 與聊天記錄 (MongoDBChatMessageHistory) 建立的。 此動作可讓其透過 Azure Cosmos DB 維護完整的交談記錄。

提示

LLM 的提示最初始於簡單的陳述 "You are a helpful and friendly travel assistant for a cruise company." (您是遊輪公司實用且友好的旅遊助理。)不過,測試顯示您可以獲得更一致的結果,方法是包括指示 "Answer travel questions to the best of your ability, providing only relevant information. To book a cruise, capturing the person's name is essential." (盡所能回答旅遊問題,只提供相關資訊。若要預訂遊輪,擷取旅客的姓名至關重要。)結果會以 HTML 格式顯示,以增強 Web 介面的視覺吸引力。

代理程式工具

工具是代理程式可用來與世界互動的介面,通常是透過函式呼叫。

建立代理程式時,必須提供一組其可以使用的工具。 @tool 裝飾項目提供定義自訂工具最直接的方法。

根據預設,裝飾項目會使用函式名稱作為工具名稱,不過這可透過提供字串作為第一個引數來取代。 裝飾項目會使用函式的 docstring 作為工具的描述,因此需要佈建 docstring。

以下是 service/TravelAgentTools.py 的內容:

from langchain_core.tools import tool
from langchain.docstore.document import Document
from data.mongodb import travel
from model.travel import Ship


@tool
def vacation_lookup(input:str) -> list[Document]:
    """find information on vacations and trips"""
    ships: list[Ship] = travel.similarity_search(input)
    content = ""

    for ship in ships:
        content += f" Cruise ship {ship.name}  description: {ship.description} with amenities {'/n-'.join(ship.amenities)} "

    return content

@tool
def itinerary_lookup(ship_name:str) -> str:
    """find ship itinerary, cruise packages and destinations by ship name"""
    it = travel.itnerary_search(ship_name)
    results = ""

    for i in it:
        results += f" Cruise Package {i.Name} room prices: {'/n-'.join(i.Rooms)} schedule: {'/n-'.join(i.Schedule)}"

    return results


@tool
def book_cruise(package_name:str, passenger_name:str, room: str )-> str:
    """book cruise using package name and passenger name and room """
    print(f"Package: {package_name} passenger: {passenger_name} room: {room}")

    # LLM defaults empty name to John Doe 
    if passenger_name == "John Doe":
        return "In order to book a cruise I need to know your name."
    else:
        if room == '':
            return "which room would you like to book"            
        return "Cruise has been booked, ref number is 343242"

TravelAgentTools.py 檔案會定義三個工具:

  • vacation_lookup 會對 Azure Cosmos DB 進行向量搜尋。 使用 similarity_search 來擷取相關的旅遊相關材料。
  • itinerary_lookup 會擷取指定遊輪的遊輪套裝行程詳細資料和排程。
  • book_cruise 會為乘客預訂遊輪套裝行程。

可能需要具體指示 ("In order to book a cruise I need to know your name." (為了預訂遊輪,我需要知道您的名字。)) 才能確保擷取乘客的姓名和房間號碼,以預訂遊輪套裝行程,即使您在 LLM 提示中包含這些指示。

AI 代理程式

底層代理程式的基本概念是利用語言模型來選取要執行的動作順序。

以下是 service/TravelAgent.py 的內容:

from .init import agent_with_chat_history
from model.prompt import PromptResponse
import time
from dotenv import load_dotenv

load_dotenv(override=False)


def agent_chat(input:str, session_id:str)->str:

    start_time = time.time()

    results=agent_with_chat_history.invoke(
    {"input": input},
    config={"configurable": {"session_id": session_id}},
    )

    return  PromptResponse(text=results["output"],ResponseSeconds=(time.time() - start_time))

TravelAgent.py 檔案很簡單,因為 agent_with_chat_history 與其相依性 (工具、提示和 LLM) 會在 init.py 檔案中進行初始化及設定。 此檔案會使用從使用者那裡收到的輸入以及交談記憶體的工作階段識別碼來呼叫代理程式。 之後,PromptResponse (模型/提示) 會使用代理程式的輸出和回應時間傳回。

AI 代理程式與 React 使用者介面整合

透過 API 成功載入 AI 代理程式的資料和輔助功能,您現在可以為您的旅遊網站建立 Web 使用者介面 (使用 React) 來完成解決方案。 使用 React 的功能有助於說明 AI 代理程式與旅遊網站的無縫整合。 這項整合可透過交談式旅遊助理以進行查詢和預訂,來增強使用者體驗。

設定 React 的環境

先安裝 Node.js 和相依性之後,再測試 React 介面。

web 目錄執行下列命令,以執行專案相依性的全新安裝。 安裝可能需要一些時間。

npm ci

接下來,在 web 目錄中建立名為 .env 的檔案,以利儲存環境變數。 在新建立的 .env 檔案中包括下列詳細資料:

REACT_APP_API_HOST=http://127.0.0.1:8000

現在,從 web 目錄執行下列命令,以起始 React Web 使用者介面:

npm start

執行上述命令會開啟 React Web 應用程式。

逐步執行 React Web 介面

GitHub 存放庫的 Web 專案是一個直覺的應用程式,可協助使用者與 AI 代理程式進行互動。 與代理程式交談所需的主要元件為 TravelAgent.jsChatLayout.jsMain.js 檔案可做為中央模組或使用者登陸頁面。

React JavaScript Web 介面的螢幕擷取畫面。

主要

主要元件可作為應用程式的中央管理員。 作為路由的指定進入點。 在轉譯函式中,其會產生 JSX 程式碼來劃定主頁面版面配置。 此版面配置包含應用程式的預留位置元素 (例如標誌和連結)、包含旅遊代理程式元件的區段,以及包含應用程式性質範例免責聲明的頁尾。

以下是 main.js 的內容:

import React, {  Component } from 'react'
import { Stack, Link, Paper } from '@mui/material'
import TravelAgent from './TripPlanning/TravelAgent'

import './Main.css'

class Main extends Component {
  constructor() {
    super()

  }

  render() {
    return (
      <div className="Main">
        <div className="Main-Header">
          <Stack direction="row" spacing={5}>
            <img src="/mainlogo.png" alt="Logo" height={'120px'} />
            <Link
              href="#"
              sx={{ color: 'white', fontWeight: 'bold', fontSize: 18 }}
              underline="hover"
            >
              Ships
            </Link>
            <Link
              href="#"
              sx={{ color: 'white', fontWeight: 'bold', fontSize: 18 }}
              underline="hover"
            >
              Destinations
            </Link>
          </Stack>
        </div>
        <div className="Main-Body">
          <div className="Main-Content">
            <Paper elevation={3} sx={{p:1}} >
            <Stack
              direction="row"
              justifyContent="space-evenly"
              alignItems="center"
              spacing={2}
            >
              
                <Link href="#">
                  <img
                    src={require('./images/destinations.png')} width={'400px'} />
                </Link>
                <TravelAgent ></TravelAgent>
                <Link href="#">
                  <img
                    src={require('./images/ships.png')} width={'400px'} />
                </Link>
              
              </Stack>
              </Paper>
          </div>
        </div>
        <div className="Main-Footer">
          <b>Disclaimer: Sample Application</b>
          <br />
          Please note that this sample application is provided for demonstration
          purposes only and should not be used in production environments
          without proper validation and testing.
        </div>
      </div>
    )
  }
}

export default Main

旅遊代理程式

旅遊代理程式元件具有直覺的用途:擷取使用者輸入並顯示回應。 其扮演著管理與後端 AI 代理程式整合的關鍵角色,主要方式是擷取工作階段,並將使用者提示轉送至 FastAPI 服務。 產生的回應會儲存在陣列中以供顯示,其由聊天版面配置元件所促成。

以下是 TripPlanning/TravelAgent.js 的內容:

import React, { useState, useEffect } from 'react'
import { Button, Box, Link, Stack, TextField } from '@mui/material'
import SendIcon from '@mui/icons-material/Send'
import { Dialog, DialogContent } from '@mui/material'
import ChatLayout from './ChatLayout'
import './TravelAgent.css'

export default function TravelAgent() {
  const [open, setOpen] = React.useState(false)
  const [session, setSession] = useState('')
  const [chatPrompt, setChatPrompt] = useState(
    'I want to take a relaxing vacation.',
  )
  const [message, setMessage] = useState([
    {
      message: 'Hello, how can I assist you today?',
      direction: 'left',
      bg: '#E7FAEC',
    },
  ])

  const handlePrompt = (prompt) => {
    setChatPrompt('')
    setMessage((message) => [
      ...message,
      { message: prompt, direction: 'right', bg: '#E7F4FA' },
    ])
    console.log(session)
    fetch(process.env.REACT_APP_API_HOST + '/agent/agent_chat', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ input: prompt, session_id: session }),
    })
      .then((response) => response.json())
      .then((res) => {
        setMessage((message) => [
          ...message,
          { message: res.text, direction: 'left', bg: '#E7FAEC' },
        ])
      })
  }

  const handleSession = () => {
    fetch(process.env.REACT_APP_API_HOST + '/session/')
      .then((response) => response.json())
      .then((res) => {
        setSession(res.session_id)
      })
  }

  const handleClickOpen = () => {
    setOpen(true)
  }

  const handleClose = (value) => {
    setOpen(false)
  }

  useEffect(() => {
    if (session === '') handleSession()
  }, [])

  return (
    <Box>
      <Dialog onClose={handleClose} open={open} maxWidth="md" fullWidth="true">
        <DialogContent>
          <Stack>
            <Box sx={{ height: '500px' }}>
              <div className="AgentArea">
                <ChatLayout messages={message} />
              </div>
            </Box>
            <Stack direction="row" spacing={0}>
              <TextField
                sx={{ width: '80%' }}
                variant="outlined"
                label="Message"
                helperText="Chat with AI Travel Agent"
                defaultValue="I want to take a relaxing vacation."
                value={chatPrompt}
                onChange={(event) => setChatPrompt(event.target.value)}
              ></TextField>
              <Button
                variant="contained"
                endIcon={<SendIcon />}
                sx={{ mb: 3, ml: 3, mt: 1 }}
                onClick={(event) => handlePrompt(chatPrompt)}
              >
                Submit
              </Button>
            </Stack>
          </Stack>
        </DialogContent>
      </Dialog>
      <Link href="#" onClick={() => handleClickOpen()}>
        <img src={require('.././images/planvoyage.png')} width={'400px'} />
      </Link>
    </Box>
  )
}

選取 [Effortlessly plan your voyage] \(毫不費力地規劃您的航程\) 來開啟旅遊助理。

聊天版面配置

聊天版面配置元件會監督聊天的排列方式。 其會有系統地處理聊天訊息,並實作 message JSON 物件中所指定的格式。

以下是 TripPlanning/ChatLayout.js的內容

import React from 'react'
import {  Box, Stack } from '@mui/material'
import parse from 'html-react-parser'
import './ChatLayout.css'

export default function ChatLayout(messages) {
  return (
    <Stack direction="column" spacing="1">
      {messages.messages.map((obj, i = 0) => (
        <div className="bubbleContainer" key={i}>
          <Box
            key={i++}
            className="bubble"
            sx={{ float: obj.direction, fontSize: '10pt', background: obj.bg }}
          >
            <div>{parse(obj.message)}</div>
          </Box>
        </div>
      ))}
    </Stack>
  )
}

使用者提示位於右側,色彩為藍色。 AI 旅遊代理程式的回應位於左側,色彩為綠色。 如下圖所示,HTML 格式化的回應會包含在交談中。

與 AI 代理程式聊天的螢幕快照。

當您的 AI 代理程式準備好進入實際執行環境時,您可以使用語意快取,將查詢效能提升 80%,並減少 LLM 推斷和 API 呼叫成本。 若要實作語意快取,請參閱 Stochastic Coder 部落格上的此文章

AI 代理程式的語意快取圖表。