使用 Apache Camel、LangChain4j 和 WhatsApp 建立對話式 AI
1. 概述
在本教程中,我們將了解如何將 Apache Camel 和 LangChain4j 整合到 Spring Boot 應用程式中,以透過 WhatsApp 處理 AI 驅動的對話,並使用本地安裝的 Ollama 進行 AI 處理。 Apache Camel 處理不同系統之間的資料路由和轉換,而 LangChain4j 提供與大型語言模型互動並提取有意義資訊的工具。
我們在教學How to Install Ollama Generative AI on Linux
中討論了 Ollama 的主要優點、安裝和硬體需求。無論如何,它是跨平台的,並且適用於 Windows 和 macOS。
我們將使用 Postman 來測試 Ollama API、WhatsApp API 和 Spring Boot 控制器。
2. Spring Boot 的初始設置
首先,我們確保本機連接埠8080
未被使用,因為 Spring Boot 需要它。
由於我們將使用@RequestParam
註解將請求參數綁定到 Spring Boot 控制器,因此我們需要新增-parameters
編譯器參數:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
如果我們錯過了它,有關參數名稱的資訊將無法透過反射獲得,因此我們的 REST 呼叫將拋出java.lang.IllegalArgumentException
。
此外,傳入和傳出訊息的DEBUG
等級日誌記錄可以幫助我們,所以讓我們在application.properties
中啟用它:
# Logging configuration
logging.level.root=INFO
logging.level.com.baeldung.chatbot=DEBUG
如果出現問題,我們也可以使用 Linux 和 macOS 的tcpdump
或 Windows 的windump
來分析 Ollama 和 Spring Boot 之間的本機網路流量。另一方面,嗅探 Spring Boot 和 WhatApp Cloud 之間的流量要困難得多,因為它是透過 HTTPS 協定進行的。
3.Ollama的LangChain4j
典型的 Ollama 安裝正在偵聽連接埠11434
。在本例中,我們將使用qwen2:1.5b
模型來運行它,因為它對於聊天來說足夠快,但我們可以自由選擇任何其他模型。
LangChain4j 為我們提供了幾個參數不同的ChatLanguageModel.generate(..)
方法。所有這些方法都呼叫 Ollama 的 REST API /api/chat
,我們可以透過檢查網路流量來驗證。因此,讓我們使用 Ollama 文件中的JSON 範例之一來確保它正常工作:
我們的查詢得到了有效的 JSON 回應,因此我們準備好轉到 LangChain4j。
為了防止問題,我們要確保尊重參數的大小寫。例如, “role”: “user”
將產生正確的回應,而“role”: “USER”
則不會。
3.1.配置LangChain4j
在pom.xml
中,我們需要 LangChain4j 的兩個相依性。我們可以從 Maven 儲存庫中查看最新版本:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-core</artifactId>
<version>0.33.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
<version>0.33.0</version>
</dependency>
然後讓我們將這些參數加入到application.properties
:
# Ollama API configuration
ollama.api_url=http://localhost:11434/
ollama.model=qwen2:1.5b
ollama.timeout=30
ollama.max_response_length=1000
參數ollama.timeout
和ollama.max_response_length
是可選的。我們將它們作為一項安全措施納入其中,因為已知某些模型存在導致回應過程中出現循環的錯誤。
3.2.實施ChatbotService
使用@Value
註釋,讓我們在運行時從application.properties
注入這些值,確保配置與應用程式邏輯解耦:
@Value("${ollama.api_url}")
private String apiUrl;
@Value("${ollama.model}")
private String modelName;
@Value("${ollama.timeout}")
private int timeout;
@Value("${ollama.max_response_length}")
private int maxResponseLength;
下面是服務 bean 完全建置後需要執行的初始化邏輯。 OllamaChatModel
物件保存與對話式 AI 模型互動所需的配置:
private OllamaChatModel ollamaChatModel;
@PostConstruct
public void init() {
this.ollamaChatModel = OllamaChatModel.builder()
.baseUrl(apiUrl)
.modelName(modelName)
.timeout(Duration.ofSeconds(timeout))
.numPredict(maxResponseLength)
.build();
}
此方法會取得問題,將其發送到聊天模型,接收回應,並處理在此過程中可能發生的任何錯誤:
public String getResponse(String question) {
logger.debug("Sending to Ollama: {}", question);
String answer = ollamaChatModel.generate(question);
logger.debug("Receiving from Ollama: {}", answer);
if (answer != null && !answer.isEmpty()) {
return answer;
} else {
logger.error("Invalid Ollama response for:\n\n" + question);
throw new ResponseStatusException(
HttpStatus.SC_INTERNAL_SERVER_ERROR,
"Ollama didn't generate a valid response",
null);
}
}
我們已經準備好控制器了。
3.3.建立ChatbotController
此控制器在開發過程中有助於測試ChatbotService
是否正常運作:
@Autowired
private ChatbotService chatbotService;
@GetMapping("/api/chatbot/send")
public String getChatbotResponse(@RequestParam String question) {
return chatbotService.getResponse(question);
}
讓我們試試看:
它按預期工作。
4. 用於 WhatsApp 的 Apache Camel
在繼續之前,讓我們在Meta for Developers上建立一個帳戶。出於我們的測試目的,使用 WhatsApp API 是免費的。
4.1. ngrok
反向代理
要將本地 Spring Boot 應用程式與 WhatsApp Business 服務集成,我們需要一個跨平台反向代理,例如連接到免費靜態網域的ngrok
。它創建了一條從採用 HTTPS 協定的公共 URL 到採用 HTTP 協定的本機伺服器的安全性隧道,允許 WhatsApp 與我們的應用程式進行通訊。在此命令中,我們將xxx.ngrok-free.app
替換為ngrok
分配給我們的靜態網域:
ngrok http --domain=xxx.ngrok-free.app 8080
這會將https://xxx.ngrok-free.app
轉送到http://localhost:8080
。
4.2.設定 Apache Camel
第一個依賴項camel-spring-boot-starter
將Apache Camel整合到Spring Boot應用程式中,並為Camel路由提供必要的配置。第二個依賴項, camel-http-starter
,支援建立基於 HTTP(S) 的路由,使應用程式能夠處理 HTTP 和 HTTPS 請求。第三個依賴項, camel-jackson
,有助於使用 Jackson 庫進行 JSON 處理,允許 Camel 路由轉換和編組 JSON 資料:
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
<version>4.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-http-starter</artifactId>
<version>4.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jackson</artifactId>
<version>4.7.0</version>
</dependency>
我們可以從 Maven 儲存庫中查看 Apache Camel 的 最新版本。
最後,我們將此配置新增至application.properties
:
# WhatsApp API configuration
whatsapp.verify_token=BaeldungDemo-Verify-Token
whatsapp.api_url=https://graph.facebook.com/v20.0/PHONE_NUMBER_ID/messages
whatsapp.access_token=ACCESS_TOKEN
要取得PHONE_NUMBER_ID
和ACCESS_TOKEN
的實際值來取代屬性值並非易事。我們將詳細了解如何做到這一點。
4.3.用於驗證 Webhook 令牌的控制器
作為第一步,我們還需要一個 Spring Boot 控制器來驗證WhatsApp webhook令牌。目的是在開始從 WhatsApp 服務接收實際資料之前驗證我們的 Webhook 端點:
@Value("${whatsapp.verify_token}")
private String verifyToken;
@GetMapping("/webhook")
public String verifyWebhook(@RequestParam("hub.mode") String mode,
@RequestParam("hub.verify_token") String token,
@RequestParam("hub.challenge") String challenge) {
if ("subscribe".equals(mode) && verifyToken.equals(token)) {
return challenge;
} else {
return "Verification failed";
}
}
那麼,讓我們回顧一下到目前為止我們所做的事情:
-
ngrok
透過 HTTPS 在公用 IP 上公開我們的本機 Spring Boot 伺服器 - 新增了 Apache Camel 依賴項
- 我們有一個控制器來驗證 WhatsApp webhook 令牌
- 但是,我們還沒有
PHONE_NUMBER_ID
和ACCESS_TOKEN
的實際值
現在是時候設定我們的 WhatsApp Business 帳戶來取得這些值並訂閱 webhook 服務了。
4.4. WhatsApp 企業帳戶
官方的入門指南很難遵循,也不符合我們的需求。這就是為什麼接下來的影片將有助於獲取我們的 Spring Boot 應用程式的相關步驟。
建立名為「Baeldung Chatbot」的業務組合後,讓我們建立我們的業務應用程式:
https://www.baeldung.com/wp-content/uploads/2024/08/1-Create-Whatsapp-Business-APP.mp4
然後,讓我們取得 WhatsApp 企業電話號碼的 ID,將其複製到application.properties
中的whatsapp.api_url
中,並向我們的個人手機發送測試訊息。讓我們將此Quickstart API Setup
頁面加入書籤,因為我們在程式碼開發過程中可能需要它:
此時,我們的手機上應該已經收到了這樣一則訊息:
現在我們需要application.properties
中的whatsapp.access_token
值。讓我們使用對我們的應用程式具有管理員完全存取權限的帳戶,轉到「系統使用者」產生一個沒有過期時間的令牌:
https://www.baeldung.com/wp-content/uploads/2024/08/3-Generate-Token-Without-Expiration.mp4
我們準備好設定我們先前使用@GetMapping(“/webhook”)
控制器所建立的 webhook 端點。在繼續之前讓我們啟動我們的 Spring Boot 應用程式。
作為 webhook 的回呼 URL,我們需要插入以/webhook
為後綴的ngrok
靜態域,而我們的驗證令牌是BaeldungDemo-Verify-Token
:
https://www.baeldung.com/wp-content/uploads/2024/08/4-Configure-Webhook.mp4
請務必按照我們顯示的順序執行這些步驟,以避免錯誤。
4.5.配置WhatsAppService
發送訊息
作為參考,在我們進入init()
和sendWhatsAppMessage(…)
方法之前,讓我們使用 Postman 向手機發送簡訊。這樣我們就可以看到所需的 JSON 和標頭並將它們與程式碼進行比較。
Authorization
標頭值由Bearer
後面跟著空格和我們的whatsapp.access_token
組成,而Content-Type
標頭則由 Postman 自動處理:
JSON 結構非常簡單。我們必須小心, HTTP 200
回應代碼並不表示訊息確實已傳送。只有當我們透過從手機向 WhatsApp 業務號碼發送訊息來開始對話時,我們才會收到該訊息。換句話說,我們創建的聊天機器人永遠無法發起對話,它只能回答使用者的問題:
也就是說,讓我們注入whatsapp.api_url
和whatsapp.access_token
:
@Value("${whatsapp.api_url}")
private String apiUrl;
@Value("${whatsapp.access_token}")
private String apiToken;
init()
方法負責設定透過 WhatsApp API 傳送訊息所需的設定。它定義並新增了一條到CamelContext
的新路由,該路由負責處理 Spring Boot 應用程式和 WhatsApp 服務之間的通訊。
在此路由配置中,我們指定身份驗證和內容類型所需的標頭,複製使用 Postman 測試 API 時使用的標頭:
@Autowired
private CamelContext camelContext;
@PostConstruct
public void init() throws Exception {
camelContext.addRoutes(new RouteBuilder() {
@Override
public void configure() {
JacksonDataFormat jacksonDataFormat = new JacksonDataFormat();
jacksonDataFormat.setPrettyPrint(true);
from("direct:sendWhatsAppMessage")
.setHeader("Authorization", constant("Bearer " + apiToken))
.setHeader("Content-Type", constant("application/json"))
.marshal(jacksonDataFormat)
.process(exchange -> {
logger.debug("Sending JSON: {}", exchange.getIn().getBody(String.class));
}).to(apiUrl).process(exchange -> {
logger.debug("Response: {}", exchange.getIn().getBody(String.class));
});
}
});
}
這樣, direct:sendWhatsAppMessage
端點允許在應用程式內以程式設計方式觸發路由,確保訊息由 Jackson 正確編組並使用必要的標頭發送。
sendWhatsAppMessage(…)
使用 Camel ProducerTemplate
將 JSON 有效負載傳送至direct:sendWhatsAppMessage
路由。 HashMap
的結構遵循我們先前在 Postman 中使用的 JSON 結構。此方法確保與 WhatsApp API 無縫集成,提供從 Spring Boot 應用程式發送訊息的結構化方式:
@Autowired
private ProducerTemplate producerTemplate;
public void sendWhatsAppMessage(String toNumber, String message) {
Map<String, Object> body = new HashMap<>();
body.put("messaging_product", "whatsapp");
body.put("to", toNumber);
body.put("type", "text");
Map<String, String> text = new HashMap<>();
text.put("body", message);
body.put("text", text);
producerTemplate.sendBody("direct:sendWhatsAppMessage", body);
}
發送訊息的程式碼已經準備好了。
4.6.配置WhatsAppService
接收訊息
為了處理來自 WhatsApp 用戶的傳入訊息, processIncomingMessage(…)
方法處理從我們的 webhook 端點接收到的有效負載,提取相關訊息,例如寄件者的電話號碼和訊息內容,然後使用我們的聊天機器人服務生成適當的響應。最後,它使用sendWhatsAppMessage(…)
方法將 Ollama 的回應傳送回使用者:
@Autowired
private ObjectMapper objectMapper;
@Autowired
private ChatbotService chatbotService;
public void processIncomingMessage(String payload) {
try {
JsonNode jsonNode = objectMapper.readTree(payload);
JsonNode messages = jsonNode.at("/entry/0/changes/0/value/messages");
if (messages.isArray() && messages.size() > 0) {
String receivedText = messages.get(0).at("/text/body").asText();
String fromNumber = messages.get(0).at("/from").asText();
logger.debug(fromNumber + " sent the message: " + receivedText);
this.sendWhatsAppMessage(fromNumber, chatbotService.getResponse(receivedText));
}
} catch (Exception e) {
logger.error("Error processing incoming payload: {} ", payload, e);
}
}
下一步是編寫控制器來測試我們的WhatsAppService
方法。
4.7.建立WhatsAppController
sendWhatsAppMessage(…)
控制器在開發過程中非常有用,可以測試傳送訊息的過程:
@Autowired
private WhatsAppService whatsAppService;
@PostMapping("/api/whatsapp/send")
public String sendWhatsAppMessage(@RequestParam String to, @RequestParam String message) {
whatsAppService.sendWhatsAppMessage(to, message);
return "Message sent";
}
讓我們試試看:
它按預期工作。一切準備就緒,可以編寫receiveMessage(…)
控制器,該控制器將接收用戶發送的訊息:
@PostMapping("/webhook")
public void receiveMessage(@RequestBody String payload) {
whatsAppService.processIncomingMessage(payload);
}
這是最終測試:
Ollama 使用 LaTeX 語法回答了我們的數學問題。我們使用的qwen2:1.5b
LLM 支援 29 種語言,以下是完整清單。
5. 結論
在本文中,我們示範如何將 Apache Camel 和 LangChain4j 整合到 Spring Boot 應用程式中,以透過 WhatsApp 管理 AI 驅動的對話,並使用本地安裝的 Ollama 進行 AI 處理。我們首先設定 Ollama 並配置 Spring Boot 應用程式來處理請求參數。
然後,我們整合 LangChain4j 與 Ollama 模型交互,使用ChatbotService
處理 AI 回應並確保無縫通訊。
對於 WhatsApp 集成,我們設定了 WhatsApp Business 帳戶並使用ngrok
作為反向代理,以促進本地伺服器和 WhatsApp 之間的通訊。我們配置了 Apache Camel 並建立了WhatsAppService
來處理傳入訊息、使用ChatbotService
產生回應並進行適當回應。
我們使用專用控制器測試了ChatbotService
和WhatsAppService
,以確保完整功能。
與往常一樣,完整的源代碼可以在 GitHub 上取得。