1. 簡介
本程式碼研究室著重於託管於 Google Cloud Vertex AI 的 Gemini 大型語言模型 (LLM),Vertex AI 平台涵蓋 Google Cloud 上的所有機器學習產品、服務和模型。
您將透過 Java 使用 LangChain4j 架構與 Gemini API 互動。以下將列舉具體範例,說明如何利用大型語言模型回答問題、構思想法、擷取實體與結構化內容,以及進行檢索增強生成和函式呼叫。
什麼是生成式 AI?
生成式 AI 是指運用人工智慧來創造新內容,例如文字、圖片、音樂、音訊和影片等。
生成式 AI 採用大型語言模型 (LLM),能進行多工處理,執行摘要、問與答、分類等立即可用的工作。只需進行少量訓練,即可使用極少範例資料,針對目標用途調整基礎模型。
生成式 AI 的運作方式為何?
生成式 AI 運用機器學習 (ML) 模型,學習人類建立內容資料集內的模式和關係,然後再運用所學的模式生成新內容。
如要訓練生成式 AI 模型,最常見的方式是使用監督式學習。模型會提供一組人工內容及對應的標籤。並學習生成類似於真人建立的內容。
什麼是常見的生成式 AI 應用程式?
生成式 AI 有助於:
- 透過強化的即時通訊和搜尋體驗,改善客戶互動情形。
- 透過對話式介面和摘要功能,探索大量的非結構化資料。
- 協助完成重複性工作,例如回覆提案請求、將不同語言的行銷內容本地化,以及確認客戶合約是否遵循規定等。
Google Cloud 提供哪些生成式 AI 產品與服務?
有了 Vertex AI,您幾乎不需要具備機器學習專業知識,就能與基礎模型互動,並將模型嵌入應用程式中。您可以在 Model Garden 中存取基礎模型、透過 Vertex AI Studio 的簡易使用者介面調整模型,或是使用數據資料學筆記本中的模型。
Vertex AI Search and Conversation 可讓開發人員以最快的速度,建構採用生成式 AI 的搜尋引擎和聊天機器人。
Google Cloud 專用 Gemini 採用 Gemini 技術,是採用 AI 技術的協作工具,可在 Google Cloud 和 IDE 中助您更快完成更多工作。Gemini Code Assist 提供程式碼補全、程式碼生成、程式碼說明,並可讓您與 Gemini Code Assist 對話,取得技術問題。
Gemini 是什麼?
Gemini 是由 Google DeepMind 開發的一系列生成式 AI 模型,專為多模態用途而設計。多模態意味著它可以處理並產生不同類型的內容,例如文字、程式碼、圖片和音訊。
Gemini 提供多個變化版本和大小:
- Gemini Ultra:規模最大、性能最優異的版本,適合處理複雜工作。
- Gemini Flash:速度最快、成本效益最高,適合處理大量工作。
- Gemini Pro:中型,已針對各種工作進行最佳化調整。
- Gemini Nano:最有效率,專為裝置端工作而設計。
主要功能:
- 多模態:比起傳統的純文字語言模型,Gemini 能解讀及處理多種資訊格式,簡直是一大步。
- 效能:Gemini Ultra 在許多基準中的表現,優於目前最先進的技術,同時也是第一個在艱難的 MMLU (大規模多工語言理解) 基準上,超越人類專家的模型。
- 使用彈性:Gemini 有多種大小,可因應各種用途 (包括大規模研究與在行動裝置上部署)。
如何透過 Java 與 Vertex AI 的 Gemini 互動?
方法有以下兩種:
- 官方 Vertex AI Java API for Gemini 程式庫。
- LangChain4j 架構。
在本程式碼研究室中,您將使用 LangChain4j 架構。
什麼是 LangChain4j 架構?
LangChain4j 架構是一個開放原始碼程式庫,可透過自動化調度管理各種元件 (例如 LLM 本身) 與向量資料庫 (用於語意搜尋)、文件載入器和分割器 (用於分析文件及從中學習)、輸出剖析器等,將 LLM 整合至 Java 應用程式。
該專案靈感來自 LangChain Python 專案,但目標是為 Java 開發人員提供服務。
課程內容
- 如何設定 Java 專案以使用 Gemini 和 LangChain4j
- 如何透過程式輔助方式將第一個提示傳送至 Gemini
- 如何逐句顯示 Gemini 的回覆
- 如何在使用者和 Gemini 建立對話
- 如何透過傳送文字和圖片,在多模態情境中使用 Gemini
- 如何從非結構化內容擷取實用的結構化資訊
- 如何操控提示範本
- 如何進行文字分類 (例如情緒分析)
- 如何與自己的文件進行即時通訊 (檢索增強生成)
- 如何使用函式呼叫來擴充聊天機器人
- 如何在本機搭配 Ollama 和 TestContainers 使用 Gemma
軟硬體需求
- Java 程式設計語言知識
- Google Cloud 專案
- 瀏覽器,例如 Chrome 或 Firefox
2. 設定和需求
自修環境設定
- 登入 Google Cloud 控制台,建立新專案或重複使用現有專案。如果您還沒有 Gmail 或 Google Workspace 帳戶,請先建立帳戶。
- 「專案名稱」是這項專案參與者的顯示名稱。這是 Google API 未使用的字元字串。您可以隨時更新付款方式。
- 所有 Google Cloud 專案的專案 ID 均不得重複,而且設定後即無法變更。Cloud 控制台會自動產生一個不重複的字串。但通常是在乎它何在在大部分的程式碼研究室中,您必須參照專案 ID (通常為
PROJECT_ID
)。如果您對產生的 ID 不滿意,可以隨機產生一個 ID。或者,您也可以自行嘗試,看看是否支援。在這個步驟後,這個名稱即無法變更,而且在專案期間內仍會保持有效。 - 資訊中的第三個值是專案編號,部分 API 會使用這個編號。如要進一步瞭解這三個值,請參閱說明文件。
- 接下來,您需要在 Cloud 控制台中啟用計費功能,才能使用 Cloud 資源/API。執行本程式碼研究室不會產生任何費用 (如果有的話)。如要關閉資源,以免產生本教學課程結束後產生的費用,您可以刪除自己建立的資源或刪除專案。新使用者符合 $300 美元免費試用計畫的資格。
啟動 Cloud Shell
雖然 Google Cloud 可以從筆記型電腦遠端操作,但在本程式碼研究室中,您將使用 Cloud Shell,這是一種在 Cloud 中執行的指令列環境。
啟用 Cloud Shell
- 在 Cloud 控制台中,按一下「啟用 Cloud Shell」圖示 。
如果您是第一次啟動 Cloud Shell,系統會顯示中繼畫面,說明這項服務的內容。如果系統顯示中繼畫面,請按一下「繼續」。
佈建並連線至 Cloud Shell 只需幾分鐘的時間。
這個虛擬機器已載入所有必要的開發工具。提供永久的 5 GB 主目錄,而且在 Google Cloud 中運作,大幅提高網路效能和驗證能力。在本程式碼研究室中,您的大部分作業都可透過瀏覽器完成。
連線至 Cloud Shell 後,您應會發現自己通過驗證,且專案已設為您的專案 ID。
- 在 Cloud Shell 中執行下列指令,確認您已通過驗證:
gcloud auth list
指令輸出
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- 在 Cloud Shell 中執行下列指令,確認 gcloud 指令知道您的專案:
gcloud config list project
指令輸出
[core] project = <PROJECT_ID>
如果尚未設定,請使用下列指令進行設定:
gcloud config set project <PROJECT_ID>
指令輸出
Updated property [core/project].
3. 準備開發環境
在這個程式碼研究室中,您將使用 Cloud Shell 終端機和 Cloud Shell 編輯器開發 Java 程式。
啟用 Vertex AI API
請前往 Google Cloud 控制台,確認 Google Cloud 控制台頂端會顯示你的專案名稱。如果沒有,請按一下「Select a project」開啟「Project Selector」,然後選取所需的專案。
您可以透過 Google Cloud 控制台的「Vertex AI」專區或 Cloud Shell 終端機啟用 Vertex AI API。
如要透過 Google Cloud 控制台啟用這項功能,請先前往 Google Cloud 控制台選單的「Vertex AI」專區:
在 Vertex AI 資訊主頁中,按一下「Enable All Recommended APIs」。
這會啟用多個 API,但本程式碼研究室最重要的 API 是 aiplatform.googleapis.com
。
您也可以使用下列指令,從 Cloud Shell 終端機啟用這個 API:
gcloud services enable aiplatform.googleapis.com
複製 GitHub 存放區
在 Cloud Shell 終端機中,複製本程式碼研究室的存放區:
git clone https://github.com/glaforge/gemini-workshop-for-java-developers.git
如要確認專案是否已可執行,您可以嘗試執行「Hello World」計畫。
確認您位於頂層資料夾:
cd gemini-workshop-for-java-developers/
建立 Gradle 包裝函式:
gradle wrapper
使用 gradlew
執行:
./gradlew run
您應該會看到以下的輸出內容:
.. > Task :app:run Hello World!
開啟並設定 Cloud 編輯器
使用 Cloud Shell 中的 Cloud Code 編輯器開啟程式碼:
在 Cloud Code 編輯器中,選取 File
-> 開啟程式碼研究室的來源資料夾Open Folder
並指向程式碼研究室的來源資料夾 (例如/home/username/gemini-workshop-for-java-developers/
)。
安裝 Java 適用的 Gradle
如要讓雲端程式碼編輯器能與 Gradle 正確搭配運作,請安裝 Gradle for Java 擴充功能。
首先,請前往「Java 專案」區段,然後按下加號:
選取 Gradle for Java
:
選取 Install Pre-Release
版本:
安裝完成後,您應該會看到 Disable
和 Uninstall
按鈕:
最後,請清理工作區以套用新設定:
接著請重新載入並刪除研討會。繼續並選擇 Reload and delete
:
現在,如果您開啟其中一個檔案 (例如 App.java),應該會看到編輯器在語法標示中正確運作:
你現在可以針對 Gemini 執行一些樣本了!
設定環境變數
依序選取「Terminal
」->,在 Cloud Code 編輯器中開啟新的終端機New Terminal
。設定執行程式碼範例所需的兩個環境變數:
- PROJECT_ID:您的 Google Cloud 專案 ID
- LOCATION - Gemini 模型的部署區域
按照下列方式匯出變數:
export PROJECT_ID=$(gcloud config get-value project) export LOCATION=us-central1
4. 第一次呼叫 Gemini 模型
專案已正確設定,現在可以呼叫 Gemini API。
請看看 app/src/main/java/gemini/workshop
目錄中的 QA.java
:
package gemini.workshop;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.model.chat.ChatLanguageModel;
public class QA {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-001")
.build();
System.out.println(model.generate("Why is the sky blue?"));
}
}
在第一個範例中,您必須匯入 VertexAiGeminiChatModel
類別,用來實作 ChatModel
介面。
在 main
方法中,您可以使用 VertexAiGeminiChatModel
的建構工具設定即時通訊語言模型,並指定:
- 專案
- 位置
- 模型名稱 (
gemini-1.5-flash-001
)。
語言模型已準備就緒,您可以呼叫 generate()
方法,然後傳送提示、問題或指示,並傳送至 LLM。在這裡,你詢問一個關於天空藍的簡單問題。
你可以變更這個提示內容,嘗試不同的問題或工作。
在原始碼根資料夾執行範例:
./gradlew run -q -DjavaMainClass=gemini.workshop.QA
畫面會顯示類似以下的輸出內容:
The sky appears blue because of a phenomenon called Rayleigh scattering. When sunlight enters the atmosphere, it is made up of a mixture of different wavelengths of light, each with a different color. The different wavelengths of light interact with the molecules and particles in the atmosphere in different ways. The shorter wavelengths of light, such as those corresponding to blue and violet light, are more likely to be scattered in all directions by these particles than the longer wavelengths of light, such as those corresponding to red and orange light. This is because the shorter wavelengths of light have a smaller wavelength and are able to bend around the particles more easily. As a result of Rayleigh scattering, the blue light from the sun is scattered in all directions, and it is this scattered blue light that we see when we look up at the sky. The blue light from the sun is not actually scattered in a single direction, so the color of the sky can vary depending on the position of the sun in the sky and the amount of dust and water droplets in the atmosphere.
恭喜,你已成功撥打電話給 Gemini!
串流回應
你有發現系統在幾秒鐘後給予回覆嗎?您也可以透過串流回應變體,逐步取得回應。串流回應則需要逐一傳回回應。
在本程式碼研究室中,我們會繼續採用非串流回應,但以下來看看串流回應如何實際執行。
在 app/src/main/java/gemini/workshop
目錄的 StreamQA.java
中,您可以看到串流回應的實際運作情形:
package gemini.workshop;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiStreamingChatModel;
import dev.langchain4j.model.StreamingResponseHandler;
public class StreamQA {
public static void main(String[] args) {
StreamingChatLanguageModel model = VertexAiGeminiStreamingChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-001")
.build();
model.generate("Why is the sky blue?", new StreamingResponseHandler<>() {
@Override
public void onNext(String text) {
System.out.println(text);
}
@Override
public void onError(Throwable error) {
error.printStackTrace();
}
});
}
}
這次,我們要匯入實作 StreamingChatLanguageModel
介面的串流類別變化版本 VertexAiGeminiStreamingChatModel
。也需要 StreamingResponseHandler
。
此時,generate()
方法的簽名會略有不同。傳回類型是 void 而不是傳回字串的。除了提示之外,您也必須傳遞串流回應處理常式。在此,您可以建立匿名內部類別,並使用 onNext(String text)
和 onError(Throwable error)
這兩種方法實作介面。每當有新的回應時,系統就會呼叫前者,而只有在發生錯誤時才會呼叫後者。
執行作業:
./gradlew run -q -DjavaMainClass=gemini.workshop.StreamQA
您會在先前類別中獲得的答案與先前類別類似,但您會注意到答案在 shell 中逐步出現,而不是等待顯示完整的答案。
額外設定
在設定方面,我們只會定義專案、位置和模型名稱,但您也可以指定其他模型參數:
temperature(Float temp)
:定義您希望回應的廣告素材 (0 表示廣告素材不足且通常與事實不符,1 則代表生成更多創意)topP(Float topP)
:選取總機率加總等於該浮點數的可能字詞 (介於 0 至 1 之間)topK(Integer topK)
:從可能字詞補全的字詞數目上限中隨機選取一個字詞 (從 1 到 40 之間)maxOutputTokens(Integer max)
:指定模型提供的答案長度上限 (一般而言,4 個符記代表大約 3 個字詞)maxRetries(Integer retries)
:如果您超出要求每個時間配額,或是平台發生技術問題,您可以讓模型重試呼叫 3 次
您目前向 Gemini 問了一個問題,但也可以進行多輪對話。這就是我們會在下一節介紹的內容。
5. 與 Gemini 對話
在上一個步驟中,您提出了一個問題。接著,使用者與大型語言模型進行真實對話。每個問題和答案都可接續先前的問題發展,形成真正的討論。
請看看 app/src/main/java/gemini/workshop
資料夾中的 Conversation.java
:
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.service.AiServices;
import java.util.List;
public class Conversation {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-001")
.build();
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
interface ConversationService {
String chat(String message);
}
ConversationService conversation =
AiServices.builder(ConversationService.class)
.chatLanguageModel(model)
.chatMemory(chatMemory)
.build();
List.of(
"Hello!",
"What is the country where the Eiffel tower is situated?",
"How many inhabitants are there in that country?"
).forEach( message -> {
System.out.println("\nUser: " + message);
System.out.println("Gemini: " + conversation.chat(message));
});
}
}
本課程有幾個有趣的新匯入項目:
MessageWindowChatMemory
- 這個類別可協助處理對話的多輪 (多輪) 部分,並將先前問答集的回答保存在本機記憶體AiServices
:這個類別將即時通訊模型和即時通訊記憶體結合
在主要方法中,您會設定模型、聊天記憶體和 AI 服務。模型會照常設定,包含專案、位置和模型名稱資訊。
針對即時通訊記憶體,我們使用 MessageWindowChatMemory
的建構工具建立記憶體,保留最近 20 則訊息交換。它是一個滑動視窗,其內容會儲存在 Java 類別用戶端本機中。
然後建立 AI service
,將即時通訊模型與即時通訊記憶體繫結在一起。
請注意,AI 服務如何使用我們定義的自訂 ConversationService
介面;LangChain4j 會實作這個介面,該介面會接收 String
查詢並傳回 String
回應。
現在就與 Gemini 對話吧!首先,系統會傳送一個簡單的問候語,然後傳送第一個關於艾菲爾鐵塔的問題,告知其可在哪個國家/地區設立。請注意,最後一句與第一個問題的答案有關,因為您想知道哪個國家/地區居住在艾菲爾鐵塔的地方,但沒有明確提及上一個答案中指出的國家/地區有多少人。這表示每個提示都會傳送過往的問題和答案。
執行範例:
./gradlew run -q -DjavaMainClass=gemini.workshop.Conversation
您應該會看到類似以下三個答案:
User: Hello! Gemini: Hi there! How can I assist you today? User: What is the country where the Eiffel tower is situated? Gemini: France User: How many inhabitants are there in that country? Gemini: As of 2023, the population of France is estimated to be around 67.8 million.
你可以詢問單輪問題,或與 Gemini 進行多輪對話,但目前輸入內容只有文字。圖片呢?我們要在下一個步驟中探索圖片。
6. Gemini 的多模態功能
Gemini 是多模態模型,它不僅接受文字輸入,也接受圖片或甚至影片等輸入內容。本節將說明混合文字和圖片的用途。
你覺得 Gemini 認得這隻貓嗎?
維基百科上雪中一隻貓的圖片https://upload.wikimedia.org/wikipedia/commons/b/b6/Felis_catus-cat_on_snow.jpg
請查看 app/src/main/java/gemini/workshop
目錄中的 Multimodal.java
:
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
public class Multimodal {
static final String CAT_IMAGE_URL =
"https://upload.wikimedia.org/wikipedia/" +
"commons/b/b6/Felis_catus-cat_on_snow.jpg";
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-001")
.build();
UserMessage userMessage = UserMessage.from(
ImageContent.from(CAT_IMAGE_URL),
TextContent.from("Describe the picture")
);
Response<AiMessage> response = model.generate(userMessage);
System.out.println(response.content().text());
}
}
匯入時,會注意到我們區分不同類型的郵件和內容。UserMessage
可包含 TextContent
和 ImageContent
物件。目前我們採用的多模態功能:混合文字和圖片,模型會傳回包含 AiMessage
的 Response
。
然後,您可以透過 content()
從回應中擷取 AiMessage
,以及 text()
提供的訊息文字。
執行範例:
./gradlew run -q -DjavaMainClass=gemini.workshop.Multimodal
圖片名稱可以清楚指出圖片中的內容,但 Gemini 的輸出內容會與下方相似:
A cat with brown fur is walking in the snow. The cat has a white patch of fur on its chest and white paws. The cat is looking at the camera.
結合圖片和文字提示可帶來有趣的應用實例。您可以建立的應用程式如下:
- 辨識圖片中的文字。
- 檢查圖片是否安全。
- 製作圖片說明文字。
- 搜尋內含純文字說明的圖片資料庫。
除了從圖片中擷取資訊之外,您也可以從非結構化文字中擷取資訊。也就是下一節要說明的內容。
7. 從非結構化文字中擷取結構化資訊
在許多情況下,報表文件、電子郵件或其他長篇文字中的重要資訊可能各有不同的非結構化方式。在理想情況下,您想要能夠以結構化物件的形式,擷取非結構化文字中的重要詳細資料。讓我們來看看實際做法。
假設您想要擷取某個人的簡介或描述,因而擷取其姓名和年齡。您可以使用精心微調的提示,指示 LLM 從非結構化文字中擷取 JSON,這通常稱為「提示工程」。
看看 app/src/main/java/gemini/workshop
中的 ExtractData.java
:
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.UserMessage;
public class ExtractData {
static record Person(String name, int age) {}
interface PersonExtractor {
@UserMessage("""
Extract the name and age of the person described below.
Return a JSON document with a "name" and an "age" property, \
following this structure: {"name": "John Doe", "age": 34}
Return only JSON, without any markdown markup surrounding it.
Here is the document describing the person:
---
{{it}}
---
JSON:
""")
Person extractPerson(String text);
}
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-001")
.temperature(0f)
.topK(1)
.build();
PersonExtractor extractor = AiServices.create(PersonExtractor.class, model);
Person person = extractor.extractPerson("""
Anna is a 23 year old artist based in Brooklyn, New York. She was born and
raised in the suburbs of Chicago, where she developed a love for art at a
young age. She attended the School of the Art Institute of Chicago, where
she studied painting and drawing. After graduating, she moved to New York
City to pursue her art career. Anna's work is inspired by her personal
experiences and observations of the world around her. She often uses bright
colors and bold lines to create vibrant and energetic paintings. Her work
has been exhibited in galleries and museums in New York City and Chicago.
"""
);
System.out.println(person.name()); // Anna
System.out.println(person.age()); // 23
}
}
以下說明這個檔案的各個步驟:
Person
記錄的定義為代表個人詳細資料 ( 名稱和年齡)。- 定義
PersonExtractor
介面的方法會採用提供非結構化文字字串的方法,該方法會傳回Person
執行個體。 extractPerson()
會以@UserMessage
註解加上註解,以建立提示與提示。這就是模型用來擷取資訊,並以 JSON 文件格式傳回詳細資料的提示,系統會剖析該文件,並將其解封為Person
執行個體。
現在,我們來看看 main()
方法的內容:
- 即時通訊模型已執行個體化。請注意,我們使用非常低的
temperature
(值為 0),而topK
僅使用一個,以確保產生非常確定的答案。這也能幫助模型更妥善地按照說明操作。我們尤其不希望 Gemini 使用額外的 Markdown 標記包裝 JSON 回應。 - 藉由 LangChain4j 的
AiServices
類別建立PersonExtractor
物件, - 接著,只要呼叫
Person person = extractor.extractPerson(...)
即可從非結構化文字中擷取人物詳細資料,然後取得包含名稱和年齡的Person
例項。
執行範例:
./gradlew run -q -DjavaMainClass=gemini.workshop.ExtractData
您應該會看到以下的輸出內容:
Anna 23
是的,我是 Anna,今年 23 歲!
透過這種 AiServices
方法,您可以處理強類型物件。您未直接與 LLM 互動,而是使用具體類別 (例如用來代表已擷取個人資訊的 Person
記錄),並擁有含有 extractPerson()
方法的 PersonExtractor
物件,該物件會傳回 Person
例項。LLM 的概念已經過簡化,而身為 Java 開發人員,您只需處理一般的類別和物件。
8. 使用提示範本建立提示
使用一組常見的指示或問題與 LLM 互動時,提示的部分不會改變,其他部分則包含資料。舉例來說,如果你想製作食譜,可以使用類似「你是優秀的廚師,請先建立含有下列食材的食譜:...」的提示,然後在這段文字結尾附加食材。這就是提示範本的用途,與程式設計語言中的內插字串類似。提示範本含有預留位置,方便您在特定大型語言模型呼叫中,替換為正確的資料。
更具體來說,我們來研究 app/src/main/java/gemini/workshop
目錄中的「TemplatePrompt.java
」:
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.model.output.Response;
import java.util.HashMap;
import java.util.Map;
public class TemplatePrompt {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-001")
.maxOutputTokens(500)
.temperature(0.8f)
.topK(40)
.topP(0.95f)
.maxRetries(3)
.build();
PromptTemplate promptTemplate = PromptTemplate.from("""
You're a friendly chef with a lot of cooking experience.
Create a recipe for a {{dish}} with the following ingredients: \
{{ingredients}}, and give it a name.
"""
);
Map<String, Object> variables = new HashMap<>();
variables.put("dish", "dessert");
variables.put("ingredients", "strawberries, chocolate, and whipped cream");
Prompt prompt = promptTemplate.apply(variables);
Response<AiMessage> response = model.generate(prompt.toUserMessage());
System.out.println(response.content().text());
}
}
按照慣例,您會設定「VertexAiGeminiChatModel
」模型,模型的創意性高、隨機性高、「TopP」和「TopK」值都很高。接著,您可以傳遞提示字串,並使用雙大括號預留位置變數 {{dish}}
和 {{ingredients}}
,藉此透過 from()
靜態方法建立 PromptTemplate
。
如要建立最終提示,請呼叫 apply()
,該組鍵/值組合會使用代表預留位置名稱的鍵/值組合,以及要替換為預留位置的字串值。
最後,您必須使用 prompt.toUserMessage()
指令建立使用者訊息,藉此呼叫 Gemini 模型的 generate()
方法。
執行範例:
./gradlew run -q -DjavaMainClass=gemini.workshop.TemplatePrompt
畫面會顯示類似以下的輸出內容:
**Strawberry Shortcake** Ingredients: * 1 pint strawberries, hulled and sliced * 1/2 cup sugar * 1/4 cup cornstarch * 1/4 cup water * 1 tablespoon lemon juice * 1/2 cup heavy cream, whipped * 1/4 cup confectioners' sugar * 1/4 teaspoon vanilla extract * 6 graham cracker squares, crushed Instructions: 1. In a medium saucepan, combine the strawberries, sugar, cornstarch, water, and lemon juice. Bring to a boil over medium heat, stirring constantly. Reduce heat and simmer for 5 minutes, or until the sauce has thickened. 2. Remove from heat and let cool slightly. 3. In a large bowl, combine the whipped cream, confectioners' sugar, and vanilla extract. Beat until soft peaks form. 4. To assemble the shortcakes, place a graham cracker square on each of 6 dessert plates. Top with a scoop of whipped cream, then a spoonful of strawberry sauce. Repeat layers, ending with a graham cracker square. 5. Serve immediately. **Tips:** * For a more elegant presentation, you can use fresh strawberries instead of sliced strawberries. * If you don't have time to make your own whipped cream, you can use store-bought whipped cream.
您可以隨時在地圖上變更 dish
和 ingredients
的值,並調整溫度 topK
和 tokP
,然後重新執行程式碼。藉此觀察變更這些參數對 LLM 的影響。
提示範本是為 LLM 呼叫提供可重複使用及參數化指示的好方法。您可以傳送資料,並根據使用者提供的不同值自訂提示。
9. 使用少量樣本提示進行文字分類
LLM 很擅長將文字分為不同類別。您可以提供一些文字和相關類別的範例,協助 LLM 處理該工作。這種做法通常稱為「少量樣本提示」。
請查看 app/src/main/java/gemini/workshop
目錄中的 TextClassification.java
,執行特定類型的文字分類:情緒分析。
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.input.Prompt;
package gemini.workshop;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.model.output.Response;
import java.util.Map;
public class TextClassification {
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-001")
.maxOutputTokens(10)
.maxRetries(3)
.build();
PromptTemplate promptTemplate = PromptTemplate.from("""
Analyze the sentiment of the text below. Respond only with one word to describe the sentiment.
INPUT: This is fantastic news!
OUTPUT: POSITIVE
INPUT: Pi is roughly equal to 3.14
OUTPUT: NEUTRAL
INPUT: I really disliked the pizza. Who would use pineapples as a pizza topping?
OUTPUT: NEGATIVE
INPUT: {{text}}
OUTPUT:
""");
Prompt prompt = promptTemplate.apply(
Map.of("text", "I love strawberries!"));
Response<AiMessage> response = model.generate(prompt.toUserMessage());
System.out.println(response.content().text());
}
}
使用 main()
方法時,您會照常建立 Gemini 對話模型,但輸出符記數量上限微小,因為文字為 POSITIVE
、NEGATIVE
或 NEUTRAL
。
接著,向模型指示一些輸入和輸出內容範例,藉此建立可重複使用的提示範本,方法是使用少量樣本提示技術。這也有助於模型遵循實際輸出內容。Gemini 不會撰寫完整的句子,而是指示只回覆一個字詞。
您可以使用 apply()
方法套用變數,以實際參數 ("I love strawberries"
) 取代 {{text}}
預留位置,並使用 toUserMessage()
將該範本轉換成使用者訊息。
執行範例:
./gradlew run -q -DjavaMainClass=gemini.workshop.TextClassification
您應該會看到一個字詞:
POSITIVE
看起來愛的草莓是好事!
10. 檢索增強生成
藉由大量文字訓練 LLM。不過,學生的知識只涵蓋訓練期間看到的資訊。如果模型訓練截止日之後發布新資訊,這些詳細資料將無法提供給模型。因此,模型將無法回答從未見過的資訊。
正因如此,LLM 可能需要知道的額外資訊 (如檢索增強生成 (RAG)) 才能提供必要的額外資訊,以滿足使用者要求,進而提供較新資訊或訓練期間無法存取的私人資訊。
我們回到對話。屆時,您將可以提出與文件相關的問題。您會建構一個聊天機器人,從含有不同文件 (「區塊」) 的資料庫擷取相關資訊,模型就會根據這些資訊取得答案,而不是只依賴訓練期間的知識。
RAG 分為兩個階段:
- 擷取階段 - 系統會在記憶體中載入文件,然後分割成較小的區塊,向量嵌入 (即區塊的高多維度向量表示法) 並儲存在能夠執行語意搜尋的向量資料庫中。當需要將新文件加入文件語料庫時,這個擷取階段通常會執行一次。
- 查詢階段 - 使用者現在可以提出與文件相關的問題。問題也會轉換為向量,與資料庫中其他所有的向量進行比較。最相似的向量通常在語意上彼此相關,並由向量資料庫傳回。接著,LLM 會取得對話的脈絡,也就是與資料庫傳回的向量相對應的文字區塊,並要求 LLM 查看這些區塊以建立答案。
準備文件
在這項新的示範中,您會提出下列問題:研究論文。內容說明 Google 創立的 Transformer 類神經網路架構,這也是目前所有現代大型語言模型如何實現。
這份文件已下載到存放區中的 attention-is-all-you-need.pdf。
實作聊天機器人
讓我們來探討如何建構三階段方法:首先是文件擷取作業,再接著使用者詢問文件相關問題的查詢時間。
在本例中,這兩個階段都是在同一類別中實作。一般來說,您會有一個處理擷取作業的應用程式,而另一個應用程式則是為使用者提供聊天機器人介面。
此外,在本例中,我們會使用記憶體內向量資料庫。在實際的實際工作環境中,擷取和查詢階段會分為兩個不同的應用程式,而向量則保存在獨立的資料庫中。
文件擷取
文件擷取階段的第一個步驟是找出已下載的 PDF 檔案,然後準備 PdfParser
以便讀取:
URL url = new URI("https://github.com/glaforge/gemini-workshop-for-java-developers/raw/main/attention-is-all-you-need.pdf").toURL();
ApachePdfBoxDocumentParser pdfParser = new ApachePdfBoxDocumentParser();
Document document = pdfParser.parse(url.openStream());
您可以建立嵌入模型的執行個體,而非建立平常的聊天語言模型。這個模型專門用來建立文字片段 (字詞、句子或段落) 的向量表示法。會傳回浮點數的向量,而非傳回文字回應。
VertexAiEmbeddingModel embeddingModel = VertexAiEmbeddingModel.builder()
.endpoint(System.getenv("LOCATION") + "-aiplatform.googleapis.com:443")
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.publisher("google")
.modelName("textembedding-gecko@003")
.maxRetries(3)
.build();
接下來,你需要在幾堂課程中協作,達成以下目標:
- 載入並分割 PDF 文件。
- 為所有這些區塊建立向量嵌入。
InMemoryEmbeddingStore<TextSegment> embeddingStore =
new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor storeIngestor = EmbeddingStoreIngestor.builder()
.documentSplitter(DocumentSplitters.recursive(500, 100))
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
storeIngestor.ingest(document);
系統會建立 InMemoryEmbeddingStore
的執行個體,即記憶體內向量資料庫,以儲存向量嵌入。
因為 DocumentSplitters
類別會將文件分為多個部分。我們要將 PDF 檔案的文字分割成 500 個字元的片段,並且與 100 個字元重疊 (若為避免截斷字或句子,以位元和片段進行避免)。
儲存庫擷取器會連結文件分割器、用於計算向量的嵌入模型,以及記憶體內向量資料庫。接著,ingest()
方法會負責擷取。
第一階段結束後,文件已轉換為文字區塊,內含相關向量嵌入項目,並儲存在向量資料庫中。
提出問題
現在就開放提問!建立聊天模型以開始對話:
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-001")
.maxOutputTokens(1000)
.build();
您還需要擷取器類別,才能將向量資料庫 (位於 embeddingStore
變數中) 連結至嵌入模型。其工作為計算使用者查詢的向量嵌入,藉此查詢向量資料庫,進而在資料庫中找出類似的向量:
EmbeddingStoreContentRetriever retriever =
new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);
除了主要方法之外,請建立代表 LLM 專家助理的介面,而此介面是 AiServices
類別讓您實作的介面,方便您與模型互動:
interface LlmExpert {
String ask(String question);
}
您現在可以設定新的 AI 服務:
LlmExpert expert = AiServices.builder(LlmExpert.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.contentRetriever(retriever)
.build();
這項服務將下列項目繫結在一起:
- 您先前設定的即時通訊語言模型。
- 追蹤對話的即時通訊記憶體。
- retriever 會將向量嵌入查詢與資料庫中的向量進行比對。
- 提示範本會明確指出聊天模型應根據所提供的資訊做出回覆,也就是相關文件摘錄,其向量嵌入與使用者問題的向量相似。
.retrievalAugmentor(DefaultRetrievalAugmentor.builder()
.contentInjector(DefaultContentInjector.builder()
.promptTemplate(PromptTemplate.from("""
You are an expert in large language models,\s
you excel at explaining simply and clearly questions about LLMs.
Here is the question: {{userMessage}}
Answer using the following information:
{{contents}}
"""))
.build())
.contentRetriever(retriever)
.build())
現在可以開始提問了!
List.of(
"What neural network architecture can be used for language models?",
"What are the different components of a transformer neural network?",
"What is attention in large language models?",
"What is the name of the process that transforms text into vectors?"
).forEach(query ->
System.out.printf("%n=== %s === %n%n %s %n%n", query, expert.ask(query)));
);
完整原始碼位於 app/src/main/java/gemini/workshop
目錄中的 RAG.java
:
執行範例:
./gradlew -q run -DjavaMainClass=gemini.workshop.RAG
輸出結果中會顯示您問題的答案:
=== What neural network architecture can be used for language models? === Transformer architecture === What are the different components of a transformer neural network? === The different components of a transformer neural network are: 1. Encoder: The encoder takes the input sequence and converts it into a sequence of hidden states. Each hidden state represents the context of the corresponding input token. 2. Decoder: The decoder takes the hidden states from the encoder and uses them to generate the output sequence. Each output token is generated by attending to the hidden states and then using a feed-forward network to predict the token's probability distribution. 3. Attention mechanism: The attention mechanism allows the decoder to attend to the hidden states from the encoder when generating each output token. This allows the decoder to take into account the context of the input sequence when generating the output sequence. 4. Positional encoding: Positional encoding is a technique used to inject positional information into the input sequence. This is important because the transformer neural network does not have any inherent sense of the order of the tokens in the input sequence. 5. Feed-forward network: The feed-forward network is a type of neural network that is used to predict the probability distribution of each output token. The feed-forward network takes the hidden state from the decoder as input and outputs a vector of probabilities. === What is attention in large language models? === Attention in large language models is a mechanism that allows the model to focus on specific parts of the input sequence when generating the output sequence. This is important because it allows the model to take into account the context of the input sequence when generating each output token. Attention is implemented using a function that takes two sequences as input: a query sequence and a key-value sequence. The query sequence is typically the hidden state from the previous decoder layer, and the key-value sequence is typically the sequence of hidden states from the encoder. The attention function computes a weighted sum of the values in the key-value sequence, where the weights are determined by the similarity between the query and the keys. The output of the attention function is a vector of context vectors, which are then used as input to the feed-forward network in the decoder. The feed-forward network then predicts the probability distribution of the next output token. Attention is a powerful mechanism that allows large language models to generate text that is both coherent and informative. It is one of the key factors that has contributed to the recent success of large language models in a wide range of natural language processing tasks. === What is the name of the process that transforms text into vectors? === The process of transforming text into vectors is called **word embedding**. Word embedding is a technique used in natural language processing (NLP) to represent words as vectors of real numbers. Each word is assigned a unique vector, which captures its meaning and semantic relationships with other words. Word embeddings are used in a variety of NLP tasks, such as machine translation, text classification, and question answering. There are a number of different word embedding techniques, but one of the most common is the **skip-gram** model. The skip-gram model is a neural network that is trained to predict the surrounding words of a given word. By learning to predict the surrounding words, the skip-gram model learns to capture the meaning and semantic relationships of words. Once a word embedding model has been trained, it can be used to transform text into vectors. To do this, each word in the text is converted to its corresponding vector. The vectors for all of the words in the text are then concatenated to form a single vector, which represents the entire text. Text vectors can be used in a variety of NLP tasks. For example, text vectors can be used to train machine translation models, text classification models, and question answering models. Text vectors can also be used to perform tasks such as text summarization and text clustering.
11. 函式呼叫
在某些情況下,您會希望 LLM 能存取外部系統,例如可以擷取資訊或具有特定動作的遠端網路 API,或是執行某種運算的服務。例如:
遠端網路 API:
- 追蹤及更新客戶訂單。
- 在問題追蹤工具中尋找或建立支援單。
- 擷取即時資料,例如股票報價或 IoT 感應器測量結果。
- 傳送電子郵件。
運算工具:
- 計算更進階數學問題的計算機。
- 如果 LLM 需要推理邏輯,適合執行程式碼的程式碼解讀作業。
- 將自然語言要求轉換為 SQL 查詢,讓 LLM 查詢資料庫。
函式呼叫可讓模型要求代表其進行一或多個函式呼叫,以便以更即時的資料正確回覆使用者的提示。
根據使用者的特定提示,以及可能與該情境相關的現有函式,大型語言模型可以透過函式呼叫要求進行回覆。接著,與 LLM 整合的應用程式可以呼叫函式,然後回覆大型語言模型並生成回覆,LLM 就會以文字回答來解讀結果。
函式呼叫的四個步驟
我們來看看函式呼叫的例子:取得天氣預報資訊。
如果你向 Gemini 或其他 LLM 詢問巴黎的天氣狀況,模型就會指出該大型語言模型沒有天氣預報資訊。如要讓 LLM 即時存取天氣資料,需定義幾項可用的函式。
請看下圖:
1️️ 首先,使用者詢問巴黎的天氣資訊。聊天機器人應用程式知道有幾個功能可以運用在大型語言模型完成查詢。聊天機器人會同時傳送初始提示和可呼叫的函式清單。此處名為 getWeather()
的函式會採用位置的字串參數。
LLM 不知道天氣預報資訊,因此只會傳回函式執行要求,而非透過簡訊回覆。聊天機器人必須使用 "Paris"
做為位置參數呼叫 getWeather()
函式。
2️️ 聊天機器人會代表 LLM 叫用函式回應,並擷取函式回應。在這個範例中,我們假設回應是 {"forecast": "sunny"}
。
3️️ 聊天機器人應用程式會將 JSON 回應傳回 LLM。
4️️ LLM 查看 JSON 的回覆內容並解讀該資訊後,再以天氣晴朗的文字回覆。
透過程式碼編寫每個步驟
首先,請照常設定 Gemini 模型:
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-001")
.maxOutputTokens(100)
.build();
您可以指定工具規格,說明可呼叫的函式:
ToolSpecification weatherToolSpec = ToolSpecification.builder()
.name("getWeatherForecast")
.description("Get the weather forecast for a location")
.addParameter("location", JsonSchemaProperty.STRING,
JsonSchemaProperty.description("the location to get the weather forecast for"))
.build();
函式名稱經過定義,以及參數的名稱和類型,不過請注意,函式和參數兩者都有說明。說明極為重要,可協助 LLM 真正瞭解函式的功能,進而判斷是否要在對話時呼叫這個函式。
讓我們開始步驟 1,傳送有關巴黎天氣的初始問題:
List<ChatMessage> allMessages = new ArrayList<>();
// 1) Ask the question about the weather
UserMessage weatherQuestion = UserMessage.from("What is the weather in Paris?");
allMessages.add(weatherQuestion);
在步驟 2 中,我們會傳遞我們要使用的工具,然後模型回覆的執行要求太過過多:
// 2) The model replies with a function call request
Response<AiMessage> messageResponse = model.generate(allMessages, weatherToolSpec);
ToolExecutionRequest toolExecutionRequest = messageResponse.content().toolExecutionRequests().getFirst();
System.out.println("Tool execution request: " + toolExecutionRequest);
allMessages.add(messageResponse.content());
步驟 3:現在我們知道 LLM 希望呼叫哪些功能,在程式碼中,我們不會實際呼叫外部 API,而是直接傳回假設的天氣預報:
// 3) We send back the result of the function call
ToolExecutionResultMessage toolExecResMsg = ToolExecutionResultMessage.from(toolExecutionRequest,
"{\"location\":\"Paris\",\"forecast\":\"sunny\", \"temperature\": 20}");
allMessages.add(toolExecResMsg);
在步驟 4 中,LLM 會學習函式執行結果,然後合成文字回覆:
// 4) The model answers with a sentence describing the weather
Response<AiMessage> weatherResponse = model.generate(allMessages);
System.out.println("Answer: " + weatherResponse.content().text());
輸出內容如下:
Tool execution request: ToolExecutionRequest { id = null, name = "getWeatherForecast", arguments = "{"location":"Paris"}" }
Answer: The weather in Paris is sunny with a temperature of 20 degrees Celsius.
您可以在工具執行要求上方的輸出內容中查看解答,以及答案。
完整原始碼位於 app/src/main/java/gemini/workshop
目錄中的 FunctionCalling.java
:
執行範例:
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCalling
畫面會顯示類似以下的輸出:
Tool execution request: ToolExecutionRequest { id = null, name = "getWeatherForecast", arguments = "{"location":"Paris"}" }
Answer: The weather in Paris is sunny with a temperature of 20 degrees Celsius.
12. LangChain4j 處理函式呼叫
在上一個步驟中,您已看到一般文字問題/答案和函式要求/回應互動的交錯方式,並且在兩者之間直接提供要求的函式回應,而不會呼叫實際函式。
不過,LangChain4j 也提供更高層級的抽象化機制,可以透明地為您處理函式呼叫,並照常處理對話。
單一函式呼叫
一起看看 FunctionCallingAssistant.java
吧!
首先,您需要建立記錄函式回應資料結構的記錄:
record WeatherForecast(String location, String forecast, int temperature) {}
回覆中包含地點、天氣預報和溫度等資訊。
然後建立類別,其中包含您想提供給模型的實際函式:
static class WeatherForecastService {
@Tool("Get the weather forecast for a location")
WeatherForecast getForecast(@P("Location to get the forecast for") String location) {
if (location.equals("Paris")) {
return new WeatherForecast("Paris", "Sunny", 20);
} else if (location.equals("London")) {
return new WeatherForecast("London", "Rainy", 15);
} else {
return new WeatherForecast("Unknown", "Unknown", 0);
}
}
}
請注意,這個類別包含單一函式,但附有 @Tool
註解註解,且該註解與模型可要求呼叫的函式說明相對應。
函式的參數 (這裡一個) 也會加上註解,但加上這個簡短 @P
註解也會提供參數說明。您可以視需要新增多個函式,數量不限,如此一來,模型就能在更複雜的情境下使用。
在這個類別中,您會傳回一些罐頭回應,但如果想要呼叫實際的外部天氣預報服務,這是您可以呼叫該服務的方法主體。
如同我們在上一個方法中建立 ToolSpecification
所見,請務必記錄函式的功能,並描述參數對應的項目。這有助於模型瞭解這個函式的使用方式和時機。
接下來,LangChain4j 能讓您提供與模型互動之合約對應的介面。這裡是簡單的介面,能接收代表使用者訊息的字串,並傳回與模型回應相對應的字串:
interface WeatherAssistant {
String chat(String userMessage);
}
此外,您也可以使用涉及 LangChain4j 的 UserMessage
(用於使用者訊息) 或 AiMessage
(用於模型回應) 的更複雜的簽章,甚至使用 TokenStream
來處理更進階的情況,因為這些較複雜的物件也含有額外資訊,例如使用的權杖數量等等。但為簡單起見,我們只需在輸入內容中取得字串,並在輸出內容中擷取字串。
現在,我們來使用 main()
方法將所有片段結合在一起:
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-001")
.maxOutputTokens(100)
.build();
WeatherForecastService weatherForecastService = new WeatherForecastService();
WeatherAssistant assistant = AiServices.builder(WeatherAssistant.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.tools(weatherForecastService)
.build();
System.out.println(assistant.chat("What is the weather in Paris?"));
}
照常設定 Gemini 對話模型。接著,將含有「function」的天氣預報服務執行個體化模型會要求我們呼叫
現在,您再次使用 AiServices
類別來繫結即時通訊模型、即時通訊記憶體和工具 (例如天氣預報服務和其函式)。AiServices
會傳回一個物件,用於實作您定義的 WeatherAssistant
介面。只要呼叫該助理的 chat()
方法即可。叫用此方法時,您只會看到文字回應,但開發人員不會看到函式呼叫要求和函式呼叫回應,而系統會自動以公開透明的方式處理這些要求。如果 Gemini 認為應呼叫函式,便會以函式呼叫要求回覆,而 LangChain4j 會代您呼叫本機函式。
執行範例:
./gradlew run -q -DjavaMainClass=gemini.workshop.FunctionCallingAssistant
畫面會顯示類似以下的輸出:
OK. The weather in Paris is sunny with a temperature of 20 degrees.
這是單一函式的示例。
多次函式呼叫
您也可以擁有多個函式,讓 LangChain4j 代表您處理多項函式呼叫。查看 MultiFunctionCallingAssistant.java
中的多個函式範例。
其中包含轉換貨幣的函式:
@Tool("Convert amounts between two currencies")
double convertCurrency(
@P("Currency to convert from") String fromCurrency,
@P("Currency to convert to") String toCurrency,
@P("Amount to convert") double amount) {
double result = amount;
if (fromCurrency.equals("USD") && toCurrency.equals("EUR")) {
result = amount * 0.93;
} else if (fromCurrency.equals("USD") && toCurrency.equals("GBP")) {
result = amount * 0.79;
}
System.out.println(
"convertCurrency(fromCurrency = " + fromCurrency +
", toCurrency = " + toCurrency +
", amount = " + amount + ") == " + result);
return result;
}
另一個用於取得股票值的函式:
@Tool("Get the current value of a stock in US dollars")
double getStockPrice(@P("Stock symbol") String symbol) {
double result = 170.0 + 10 * new Random().nextDouble();
System.out.println("getStockPrice(symbol = " + symbol + ") == " + result);
return result;
}
另一個將百分比套用至指定金額的函式:
@Tool("Apply a percentage to a given amount")
double applyPercentage(@P("Initial amount") double amount, @P("Percentage between 0-100 to apply") double percentage) {
double result = amount * (percentage / 100);
System.out.println("applyPercentage(amount = " + amount + ", percentage = " + percentage + ") == " + result);
return result;
}
然後結合上述所有函式和 MultiTools 類別,並提出「從美元換算為歐元的 AAPL 股價是多少?」這類問題。
public static void main(String[] args) {
ChatLanguageModel model = VertexAiGeminiChatModel.builder()
.project(System.getenv("PROJECT_ID"))
.location(System.getenv("LOCATION"))
.modelName("gemini-1.5-flash-001")
.maxOutputTokens(100)
.build();
MultiTools multiTools = new MultiTools();
MultiToolsAssistant assistant = AiServices.builder(MultiToolsAssistant.class)
.chatLanguageModel(model)
.chatMemory(withMaxMessages(10))
.tools(multiTools)
.build();
System.out.println(assistant.chat(
"What is 10% of the AAPL stock price converted from USD to EUR?"));
}
執行方式如下:
./gradlew run -q -DjavaMainClass=gemini.workshop.MultiFunctionCallingAssistant
您應該會看到多個函式稱為:
getStockPrice(symbol = AAPL) == 172.8022224055534 convertCurrency(fromCurrency = USD, toCurrency = EUR, amount = 172.8022224055534) == 160.70606683716468 applyPercentage(amount = 160.70606683716468, percentage = 10.0) == 16.07060668371647 10% of the AAPL stock price converted from USD to EUR is 16.07060668371647 EUR.
獎勵探員
函式呼叫是 Gemini 等大型語言模型的絕佳擴充功能機制。這使我們能建構更複雜的系統,通常稱為「代理程式」或「AI 助理」這些服務專員可透過外部 API 與外部世界互動,並連結至對外部環境可能有副作用的服務 (例如傳送電子郵件、建立支援單等)。
建立這類功能強大的代理程式時,您應負責任地進行這類操作。建議您先考慮人機迴圈,再自動執行動作。設計採用 LLM 技術的虛擬服務專員與外界互動時,務必注意安全。
13. 使用 Ollama 和 TestContainers 執行 Gemma
到目前為止,我們已使用 Gemini,但還有小型姊妹模型 Gemma。
Gemma 是一組最先進的開放式模型,與建立 Gemini 模型所使用的研究和技術相同。Gemma 提供兩種版本,Gemma1 和 Gemma2 各有不同尺寸。Gemma1 提供 2B 和 7B 兩種大小。Gemma2 提供 9B 和 27B 兩種大小。牠們的重量是免費的,而且大小較小,即便是筆電或 Cloud Shell,您也可以自行執行。
Gemma 如何執行?
執行 Gemma 的方式有很多種,例如在雲端中,按一下按鈕就能透過 Vertex AI 執行,或是用 GKE 搭配部分 GPU 執行,但也能在本機執行。
如要在本機執行 Gemma,有一個不錯的選擇,就是使用 Ollama,這項工具可讓您在本機電腦上執行小型模型,例如 Llama 2、Mistral 等。與 Docker 類似,但適用於 LLM。
請按照作業系統操作說明安裝 Ollama。
如果您使用 Linux 環境,必須在安裝 Ollama 後先行啟用。
ollama serve > /dev/null 2>&1 &
在本機安裝後,您可以執行指令來提取模型:
ollama pull gemma:2b
等待模型提取完成。這項作業可能需要一點時間。
執行模型:
ollama run gemma:2b
現在,您可以與模型互動:
>>> Hello! Hello! It's nice to hear from you. What can I do for you today?
如要退出提示,請按下 Ctrl+D 鍵
在 Ollama 中執行 Gemma 在 TestContainers 上
您可以在 TestContainers 的容器中使用 Ollama,不必在本機安裝及執行 Ollama。
TestContainers 不僅可用於測試,也可用來執行容器。您甚至還有一套 OllamaContainer
可以善加利用!
請參考以下的全文:
導入
一起看看 GemmaWithOllamaContainer.java
吧!
首先,您需要建立提取 Gemma 模型的衍生 Ollama 容器,這個映像檔已存在先前的執行作業,或是即將建立。如果映像檔已存在,您只需告知 TestContainers ,您要以採用 Gemma 技術的變化版本取代預設的 Ollama 映像檔:
private static final String TC_OLLAMA_GEMMA_2_B = "tc-ollama-gemma-2b";
// Creating an Ollama container with Gemma 2B if it doesn't exist.
private static OllamaContainer createGemmaOllamaContainer() throws IOException, InterruptedException {
// Check if the custom Gemma Ollama image exists already
List<Image> listImagesCmd = DockerClientFactory.lazyClient()
.listImagesCmd()
.withImageNameFilter(TC_OLLAMA_GEMMA_2_B)
.exec();
if (listImagesCmd.isEmpty()) {
System.out.println("Creating a new Ollama container with Gemma 2B image...");
OllamaContainer ollama = new OllamaContainer("ollama/ollama:0.1.26");
ollama.start();
ollama.execInContainer("ollama", "pull", "gemma:2b");
ollama.commitToImage(TC_OLLAMA_GEMMA_2_B);
return ollama;
} else {
System.out.println("Using existing Ollama container with Gemma 2B image...");
// Substitute the default Ollama image with our Gemma variant
return new OllamaContainer(
DockerImageName.parse(TC_OLLAMA_GEMMA_2_B)
.asCompatibleSubstituteFor("ollama/ollama"));
}
}
接著,建立並啟動 Ollama 測試容器,然後指向含有所需模型的容器位址和通訊埠,建立 Ollama 聊天模型。最後,您像往常一樣叫用 model.generate(yourPrompt)
:
public static void main(String[] args) throws IOException, InterruptedException {
OllamaContainer ollama = createGemmaOllamaContainer();
ollama.start();
ChatLanguageModel model = OllamaChatModel.builder()
.baseUrl(String.format("http://%s:%d", ollama.getHost(), ollama.getFirstMappedPort()))
.modelName("gemma:2b")
.build();
String response = model.generate("Why is the sky blue?");
System.out.println(response);
}
執行方式如下:
./gradlew run -q -DjavaMainClass=gemini.workshop.GemmaWithOllamaContainer
第一次執行作業需要一點時間建立及執行容器,但完成後,您應該會看到 Gemma 的回應:
INFO: Container ollama/ollama:0.1.26 started in PT2.827064047S
The sky appears blue due to Rayleigh scattering. Rayleigh scattering is a phenomenon that occurs when sunlight interacts with molecules in the Earth's atmosphere.
* **Scattering particles:** The main scattering particles in the atmosphere are molecules of nitrogen (N2) and oxygen (O2).
* **Wavelength of light:** Blue light has a shorter wavelength than other colors of light, such as red and yellow.
* **Scattering process:** When blue light interacts with these molecules, it is scattered in all directions.
* **Human eyes:** Our eyes are more sensitive to blue light than other colors, so we perceive the sky as blue.
This scattering process results in a blue appearance for the sky, even though the sun is actually emitting light of all colors.
In addition to Rayleigh scattering, other atmospheric factors can also influence the color of the sky, such as dust particles, aerosols, and clouds.
您已在 Cloud Shell 中執行 Gemma!
14. 恭喜
恭喜,您已成功使用 LangChain4j 和 Gemini API,以 Java 建構第一個生成式 AI 即時通訊應用程式!一路走來,您發現多模態大型語言模型的功能強大且能夠處理各種工作,例如提問/回答,甚至在您自己的文件、資料擷取、與外部 API 互動等事項上。
後續步驟
讓您透過強大的 LLM 整合功能,強化應用程式!