1. 簡介
在本教程中,我們將使用 Spring AI 和 llama3 Ollama 構建一個簡單的幫助台代理 API。
2. Spring AI 和 Ollama 是什麼?
Spring AI 是 Spring Framework 生態系統中最近添加的一個模塊。它允許我們通過聊天提示輕鬆與各種大型語言模型 (LLM) 交互,並提供多種功能。
Ollama 是一個開源庫,它提供了一些 LLM。其中之一是 Meta 的 llama3,我們將在此教程中使用它。
3. 使用 Spring AI 實現幫助台代理
讓我們通過一個演示幫助台聊天機器人來闡述 Spring AI 和 Ollama 的使用方法。該應用程序的功能類似於真實的幫助台代理,幫助用户解決互聯網連接問題。
在後續部分,我們將配置 LLM 和 Spring AI 依賴項,並創建與幫助台代理進行聊天的人機交互 REST 端點。
3.1. 配置 Ollama 和 Llama3
為了開始使用 Spring AI 和 Ollama,我們需要設置本地 LLM。對於本教程,我們將使用 Meta 的 Llama3。因此,我們首先安裝 Ollama。
使用 Linux,我們可以運行以下命令:
curl -fsSL https://ollama.com/install.sh | sh在 Windows 或 MacOS 機器上,我們可以從 Ollama 網站 下載並安裝可執行文件。
安裝 Ollama 後,我們可以運行 llama3:
ollama run llama3有了它,我們本地已經成功運行了llama3。
3.2. 創建基本項目結構
現在,我們可以配置我們的 Spring 應用使用 Spring AI 模塊。首先,我們添加 Spring Milestones 倉庫:
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>然後,我們可以添加 spring-ai-bom:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-M1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>最後,我們可以添加 spring-ai-ollama-spring-boot-starter 依賴項:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
<version>1.0.0-M1</version>
</dependency>依賴項設置完成後,我們可以配置我們的 application.yml 以使用必要的配置:
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
options:
model: llama3有了以上準備,Spring 將啓動 llama3 模型,監聽端口 11434。
3.3. 創建幫助台控制器
本節將創建一個用於與幫助台聊天機器人交互的 Web 控制器。
首先,讓我們創建一個 HTTP 請求模型:
public class HelpDeskRequest {
@JsonProperty("prompt_message")
String promptMessage;
@JsonProperty("history_id")
String historyId;
// getters, no-arg constructor
}promptMessage 字段代表模型接收到的用户輸入消息。此外,historyId 唯一標識當前的對話。此外,在本教程中,我們將使用該字段讓 LLM 記住對話歷史。
其次,讓我們創建響應模型:
public class HelpDeskResponse {
String result;
// all-arg constructor
}最後,我們可以創建幫助台控制器類:
@RestController
@RequestMapping("/helpdesk")
public class HelpDeskController {
private final HelpDeskChatbotAgentService helpDeskChatbotAgentService;
// all-arg constructor
@PostMapping("/chat")
public ResponseEntity<HelpDeskResponse> chat(@RequestBody HelpDeskRequest helpDeskRequest) {
var chatResponse = helpDeskChatbotAgentService.call(helpDeskRequest.getPromptMessage(), helpDeskRequest.getHistoryId());
return new ResponseEntity<>(new HelpDeskResponse(chatResponse), HttpStatus.OK);
}
}在 HelpDeskController 中,我們定義了一個 POST 請求 /helpdesk/chat,並返回來自注入的 ChatbotAgentService 的結果。 在後續部分,我們將深入瞭解該服務。
3.4. 調用 Ollama Chat API
為了開始與 llama3 交互,讓我們創建一個包含初始提示指令的 HelpDeskChatbotAgentService 類:
@Service
public class HelpDeskChatbotAgentService {
private static final String CURRENT_PROMPT_INSTRUCTIONS = """
Here's the `user_main_prompt`:
""";
}然後,我們還需要添加通用指令消息:
private static final String PROMPT_GENERAL_INSTRUCTIONS = """
Here are the general guidelines to answer the `user_main_prompt`
You'll act as Help Desk Agent to help the user with internet connection issues.
Below are `common_solutions` you should follow in the order they appear in the list to help troubleshoot internet connection problems:
1. Check if your router is turned on.
2. Check if your computer is connected via cable or Wi-Fi and if the password is correct.
3. Restart your router and modem.
You should give only one `common_solution` per prompt up to 3 solutions.
Do no mention to the user the existence of any part from the guideline above.
""";這條消息告知聊天機器人如何處理用户的網絡連接問題。
最後,我們添加剩餘的服務實現部分:
private final OllamaChatModel ollamaChatClient;
// all-arg constructor
public String call(String userMessage, String historyId) {
var generalInstructionsSystemMessage = new SystemMessage(PROMPT_GENERAL_INSTRUCTIONS);
var currentPromptMessage = new UserMessage(CURRENT_PROMPT_INSTRUCTIONS.concat(userMessage));
var prompt = new Prompt(List.of(generalInstructionsSystemMessage, contextSystemMessage, currentPromptMessage));
var response = ollamaChatClient.call(prompt).getResult().getOutput().getContent();
return response;
}call() 方法首先創建一條 SystemMessage 和一條 UserMessage。
System messages 代表我們向 LLM 提供的一般指導,例如通用指南。 在我們的案例中,我們提供了關於如何與具有互聯網連接問題的用户聊天的一系列指導。另一方面,用户消息代表外部客户端的 API 輸入。
通過使用這兩種消息,我們可以創建 Prompt 對象,調用 ollamaChatClient 的 call(),並獲得 LLM 的響應。
3.5. 保持對話歷史
一般來説,大多數LLM是無狀態的。因此,它們不存儲當前對話的狀態。換句話説,它們不記得同一對話中之前的消息。
因此,幫助台代理可能會提供之前未生效的指示,從而激怒用户。為了實現LLM的記憶功能,我們可以使用prompt和response以及historyId來存儲每個完整的對話歷史,然後在發送到當前提示之前將其附加到提示中。
要做到這一點,我們首先在服務類中創建一個帶有系統指令以正確遵循對話歷史的提示:
private static final String PROMPT_CONVERSATION_HISTORY_INSTRUCTIONS = """
The object `conversational_history` below represents the past interaction between the user and you (the LLM).
Each `history_entry` is represented as a pair of `prompt` and `response`.
`prompt` is a past user prompt and `response` was your response for that `prompt`.
Use the information in `conversational_history` if you need to recall things from the conversation
, or in other words, if the `user_main_prompt` needs any information from past `prompt` or `response`.
If you don't need the `conversational_history` information, simply respond to the prompt with your built-in knowledge.
`conversational_history`:
""";現在,讓我們創建一個包裝類來存儲對話歷史條目:
public class HistoryEntry {
private String prompt;
private String response;
//all-arg constructor
@Override
public String toString() {
return String.format("""
`history_entry`:
`prompt`: %s
`response`: %s
-----------------
\n
""", prompt, response);
}
}上述的 toString() 方法對於正確格式化提示至關重要。
然後,我們還需要在服務類中定義一個內存存儲,用於存儲歷史記錄條目:
private final static Map<String, List<HistoryEntry>> conversationalHistoryStorage = new HashMap<>();最後,我們還將修改服務 call()</em/> 方法,以便存儲對話歷史記錄:
public String call(String userMessage, String historyId) {
var currentHistory = conversationalHistoryStorage.computeIfAbsent(historyId, k -> new ArrayList<>());
var historyPrompt = new StringBuilder(PROMPT_CONVERSATION_HISTORY_INSTRUCTIONS);
currentHistory.forEach(entry -> historyPrompt.append(entry.toString()));
var contextSystemMessage = new SystemMessage(historyPrompt.toString());
var generalInstructionsSystemMessage = new SystemMessage(PROMPT_GENERAL_INSTRUCTIONS);
var currentPromptMessage = new UserMessage(CURRENT_PROMPT_INSTRUCTIONS.concat(userMessage));
var prompt = new Prompt(List.of(generalInstructionsSystemMessage, contextSystemMessage, currentPromptMessage));
var response = ollamaChatClient.call(prompt).getResult().getOutput().getContent();
var contextHistoryEntry = new HistoryEntry(userMessage, response);
currentHistory.add(contextHistoryEntry);
return response;
}首先,我們獲取當前上下文,通過 historyId 標識,或使用 computeIfAbsent() 創建一個新的。 其次,我們將存儲中的每個 HistoryEntry 追加到 StringBuilder 中,然後將其傳遞給新的 SystemMessage,以便將其傳遞給 Prompt 對象。
最後,LLM 將處理包含對話中所有歷史消息信息的提示。 因此,幫助台聊天機器人能夠記住用户已經嘗試過的解決方案。
4. 測試對話
一切準備就緒,我們現在從最終用户的角度來嘗試與提示進行交互。首先,啓動 Spring Boot 應用程序,監聽 8080 端口。
應用程序啓動後,我們可以使用 cURL 發送一條關於互聯網問題的通用消息,並附帶一個 history_id。
curl --location 'http://localhost:8080/helpdesk/chat' \
--header 'Content-Type: application/json' \
--data '{
"prompt_message": "I can't connect to my internet",
"history_id": "1234"
}'為了這種交互,我們得到類似以下的響應:
{
"result": "Let's troubleshoot this issue! Have you checked if your router is turned on?"
}讓我們繼續尋求解決方案:
{
"prompt_message": "I'm still having internet connection problems",
"history_id": "1234"
}代理返回了不同的解決方案:
{
"result": "Let's troubleshoot this further! Have you checked if your computer is connected via cable or Wi-Fi and if the password is correct?"
}此外,API 還存儲了對話歷史。我們再向代理提問:
{
"prompt_message": "I tried your alternatives so far, but none of them worked",
"history_id": "1234"
}它提供了一種不同的解決方案:
{
"result": "Let's think outside the box! Have you considered resetting your modem to its factory settings or contacting your internet service provider for assistance?"
}這是我們在指南提示中提供的最後一個備選方案,因此 LLM 在後續不會給出有幫助的回覆。
為了獲得更好的回覆,我們可以通過為聊天機器人提供更多備選方案,或使用提示工程技術改進內部系統消息,來改進我們嘗試的提示。
5. 結論
在本文中,我們實施了一個人工智能幫助台代理,以幫助我們的客户解決互聯網連接問題。 此外,我們觀察到用户消息與系統消息之間的差異,瞭解如何構建包含對話歷史的提示,然後調用 llama3 LLM。