使用 Ollama 和 Spring AI 創建類似 ChatGPT 的聊天機器人
一、簡介
在本教程中,我們將使用 Spring AI 和llama3
Ollama 建立一個簡單的幫助台代理程式 API。
2. Spring AI和Ollama是什麼?
Spring AI 是 Spring 框架生態系中最新加入的模組。除了各種功能之外,它還允許我們使用聊天提示輕鬆地與各種大型語言模型 (LLM) 互動。
Ollama是為一些法學碩士提供服務的開源程式庫。其中之一是 Meta 的llama3
,我們將在本教程中使用它。
3. 使用 Spring AI 實作幫助台代理
讓我們透過示範幫助台聊天機器人來說明 Spring AI 和 Ollama 的使用。該應用程式的工作方式與真正的服務台代理類似,可以幫助用戶解決網路連線問題。
在以下部分中,我們將配置 LLM 和 Spring AI 依賴項並建立與服務台代理程式聊天的 REST 端點。
3.1.配置 Ollama 和L
lama3
要開始使用 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 里程碑儲存庫:
<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](https://mvnrepository.com/artifact/org.springframework.ai/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 將在連接埠11434
啟動llama3
模型。
3.3.建立幫助台控制器
在本節中,我們將建立 Web 控制器來與幫助台聊天機器人互動。
首先,我們來建立HTTP請求模型:
public class HelpDeskRequest {
@JsonProperty("prompt_message")
String promptMessage;
@JsonProperty("history_id")
String historyId;
// getters, no-arg constructor
}
promptMessage
欄位表示模型的使用者輸入訊息。此外, historyId
唯一標識目前對話。此外,在本教程中,我們將使用該欄位讓法學碩士記住對話歷史記錄。
其次,讓我們建立響應模型:
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);
}
}
在HelpDeskControlle
中r,
我們定義一個 POST /helpdesk/chat
並傳回從注入的ChatbotAgentService
中獲得的內容。在以下部分中,我們將深入探討該服務。
3.4.呼叫 Ollama 聊天 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
。
系統訊息代表我們內部向法學碩士發出的指令,例如一般準則。在我們的案例中,我們提供瞭如何與遇到網路連線問題的使用者聊天的說明。另一方面,用戶訊息代表 API 外部客戶端的輸入。
透過這兩個訊息,我們可以建立一個Prompt
對象,呼叫ollamaChatClient
的call()
,並從 LLM 取得回應。
3.5.保留對話歷史記錄
一般來說,大多數法學碩士都是無國籍的。因此,它們不儲存對話的當前狀態。換句話說,他們不記得同一對話中先前的消息。
因此,幫助台代理可能會提供以前不起作用的指令並激怒使用者。為了實現LLM內存,我們可以使用historyId
儲存每個prompt
和response
,並在發送之前將完整的對話歷史記錄附加到當前提示中。
為此,我們首先在服務類別中建立一個提示,其中包含系統指令,以正確追蹤對話歷史記錄:
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<>();
最後,讓我們修改 service call()
方法來儲存對話歷史記錄:
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. 測試對話
一切就緒後,讓我們嘗試從最終用戶的角度與提示進行互動。我們首先在連接埠8080
上啟動 Spring Boot 應用程式來執行此操作。
當應用程式運行時,我們可以發送一個 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 還儲存對話歷史記錄。我們再問一下agent:
{
"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?"
}
這是我們在指南提示中提供的最後一個替代方案,因此法學碩士之後不會給出有用的答案。
為了獲得更好的回應,我們可以透過為聊天機器人提供更多替代方案或使用提示工程技術來改進內部系統訊息來改進我們嘗試的提示。
5. 結論
在本文中,我們實現了人工智慧服務台代理來幫助我們的客戶解決網路連線問題。此外,我們還看到了使用者訊息和系統訊息之間的區別,如何使用對話歷史記錄建立提示,然後呼叫llama3
LLM。
與往常一樣,程式碼可以在 GitHub 上取得。