深入理解并掌握 Spring AI 與 Open AI 的使用方法
Open AI和Spring AI簡介
當(dāng)OpenAI發(fā)布ChatGPT時(shí),它引起了全球的關(guān)注。那是語言模型第一次能夠生成類似人類的響應(yīng)。自那時(shí)以來,OpenAI又發(fā)布了其他幾款模型,包括可以根據(jù)文本提示生成圖像的DALL-E。
Spring AI是一個(gè)Java庫,提供了一個(gè)簡單易用的接口,可以與LLM模型進(jìn)行交互。Spring AI提供了更高級(jí)的抽象,可以與Open AI, Azure Open AI, Hugging Face, Google Vertex, Ollama, Amazon Bedrock等各種LLM進(jìn)行交互。
在本文中,我們將探討如何使用Spring AI與Open AI進(jìn)行交互。
首先,我們需要在OpenAI中創(chuàng)建一個(gè)賬戶并獲取API密鑰。
前往OpenAI平臺(tái)并創(chuàng)建一個(gè)賬戶。在儀表板中,點(diǎn)擊左側(cè)導(dǎo)航菜單中的API Keys,然后創(chuàng)建一個(gè)新的API密鑰。如果您正在創(chuàng)建一個(gè)新賬戶,您將獲得一些免費(fèi)的額度來使用OpenAI的APIs。 否則,您需要購買額度才能使用OpenAI的APIs。
一旦您擁有API密鑰,將環(huán)境變量OPENAI_API_KEY設(shè)置為API密鑰。
export OPENAI_API_KEY=<your-api-key>
創(chuàng)建Spring AI項(xiàng)目讓我們使用Spring Initializr創(chuàng)建一個(gè)新的Spring Boot項(xiàng)目。
前往Spring Initializr https://start.spring.io/選擇Web,并且選擇OpenAI starters使用ChatClient與Open AI進(jìn)行交互Spring AI提供了ChatClient抽象,能夠與不同類型的LLM進(jìn)行交互,而無需與實(shí)際的LLM模型耦合。
例如,我們可以使用ChatClient與OpenAI進(jìn)行如下交互:
@RestController
class ChatController {
private final ChatClient chatClient;
ChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/ai/chat")
Map<String, String> chat(@RequestParam String question) {
String response = chatClient.call(question);
return Map.of("question", question, "answer", response);
}
}
在上面的代碼中,沒有任何東西與OpenAI耦合。
我們可以通過在 application.properties 文件中提供 API 密鑰和其他參數(shù)來配置 ChatClient 以使用OpenAI。
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.model=gpt-3.5-turbo
spring.ai.openai.chat.temperature=0.7
現(xiàn)在,我們可以運(yùn)行應(yīng)用并測(cè)試聊天API。首先,啟動(dòng)你的Spring Boot應(yīng)用程序。然后,你可以使用 Postman 或者任何其他的 API 測(cè)試工具來發(fā)送 POST 請(qǐng)求到你的服務(wù)。記住,你應(yīng)該在你的請(qǐng)求正文中包含一個(gè)消息體,這將使得 ChatClient 能夠與 OpenAI 進(jìn)行交互。你將在響應(yīng)中看到自由形式的答復(fù)。此答復(fù)是 OpenAI 模型根據(jù)你的消息生成的。
curl --location 'http://localhost:8080/ai/chat?question=Tell%20me%20about%20SpringBoot'
//OUTPUT:
{
"question":"請(qǐng)介紹下SpringBoot框架",
"answer":"Spring Boot是一個(gè)開源的基于Java的框架,用于構(gòu)建和部署獨(dú)立的、生產(chǎn)就緒的應(yīng)用程序。它是更大的Spring生態(tài)系統(tǒng)的一部分,提供了更簡單、更快捷的方式來設(shè)置和配置Spring應(yīng)用程序。
Spring Boot消除了手動(dòng)配置的需要,通過為大多數(shù)Spring項(xiàng)目提供默認(rèn)設(shè)置,讓開發(fā)人員能夠快速開始他們的應(yīng)用程序開發(fā)。它還提供了一系列的特性,如內(nèi)嵌服務(wù)器、度量、健康檢查和安全性,這些都是預(yù)配置的,可以開箱即用。"
}
使用提示詞模板我們可以使用提示詞模板為ChatClient提供一組預(yù)定義的提示詞。
@RestController
class ChatController {
private final JokeService jokeService;
ChatController(JokeService jokeService) {
this.jokeService = jokeService;
}
@GetMapping("/ai/chat-with-prompt")
Map<String,String> chatWithPrompt(@RequestParam String subject) {
String answer = jokeService.getJoke(subject);
return Map.of("answer", answer);
}
}
@Service
class JokeService {
private final ChatClient chatClient;
JokeService(ChatClient chatClient) {
this.chatClient = chatClient;
}
String getJoke(String subject) {
PromptTemplate promptTemplate = new PromptTemplate("告訴我一個(gè)關(guān)于 {subject} 的笑話"");
Prompt prompt = promptTemplate.create(Map.of("subject", subject));
ChatResponse response = chatClient.call(prompt);
return response.getResult().getOutput().getContent();
}
}
通過使用提示詞模板,我們可以隱藏創(chuàng)建提示詞的復(fù)雜性,并為用戶提供一個(gè)簡單的接口。
在上述示例中,我們創(chuàng)建了代表用戶消息的提示詞。我們可以使用 SystemMessage 來表示 LLM 在對(duì)話中的角色。
@Service
class JokeService {
private final ChatClient chatClient;
JokeService(ChatClient chatClient) {
this.chatClient = chatClient;
}
String getJoke(String subject) {
SystemMessage systemMessage = new SystemMessage("你是一個(gè)有用又風(fēng)趣的聊天機(jī)器人");
UserMessage userMessage = new UserMessage("告訴我一個(gè)關(guān)于 " + subject +" 的笑話");
Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
ChatResponse response = chatClient.call(prompt);
return response.getResult().getOutput().getContent();
}
}
在上述示例中,我們創(chuàng)建了一個(gè)系統(tǒng)消息和用戶消息,以代表用戶和 LLM 之間的對(duì)話。通過使用系統(tǒng)消息,我們可以定義角色并向 LLM 提供額外的上下文。
使用輸出解析器
在前面的例子中,我們將 LLM 的回應(yīng)作為字符串獲取。我們可以使用輸出解析器來解析回應(yīng)并以所需格式提取所需信息。
目前,Spring AI 提供了以下類型的輸出解析器:
BeanOutputParser - 用于解析回應(yīng)并轉(zhuǎn)換成Java Bean。MapOutputParser - 用于解析回應(yīng)并轉(zhuǎn)換成Map。ListOutputParser - 用于解析回應(yīng)并轉(zhuǎn)換成List。
我們創(chuàng)建了一個(gè)新的 MovieController 控制器,用來獲取某位導(dǎo)演導(dǎo)演的電影列表。
@RestController
class MovieController {
private final ChatClient chatClient;
MovieController(ChatClient chatClient) {
this.chatClient = chatClient;
}
private static final String PROMPT_TEMPLATE = """
What are the best movies directed by {director}?
{format}
""";
//...
}
現(xiàn)在,讓我們來看一下如何使用 BeanOutputParser 來解析響應(yīng)并將其轉(zhuǎn)換為 Java Bean。
record DirectorResponse(String director, List<String> movies) {}
@RestController
class MovieController {
//...
@GetMapping("/ai/chat/movies")
DirectorResponse chat(@RequestParam String director) {
var outputParser = new BeanOutputParser<>(DirectorResponse.class);
var userPromptTemplate = new PromptTemplate(PROMPT_TEMPLATE);
Map<String, Object> model = Map.of("director", director, "format", outputParser.getFormat());
var prompt = userPromptTemplate.create(model);
var response = chatClient.call(prompt);
return outputParser.parse(response.getResult().getOutput().getContent());
}
}
在上述示例中,我們創(chuàng)建了一個(gè)名為 DirectorResponse 的 Java Bean,用于表示 LLM 的響應(yīng)。BeanOutputParser 將解析響應(yīng)并將其轉(zhuǎn)為 DirectorResponse 對(duì)象。
同樣,我們可以使用 MapOutputParser 和 ListOutputParser 來解析響應(yīng)并分別將其轉(zhuǎn)換為 Map 和 List。
@RestController
class MovieController {
//...
@GetMapping("/ai/chat/movies-as-map")
Map<String, Object> chatWithMapOutput(@RequestParam String director) {
var outputParser = new MapOutputParser();
var userPromptTemplate = new PromptTemplate(PROMPT_TEMPLATE);
Map<String, Object> model = Map.of("director", director, "format", outputParser.getFormat());
var prompt = userPromptTemplate.create(model);
var response = chatClient.call(prompt);
return outputParser.parse(response.getResult().getOutput().getContent());
}
@GetMapping("/ai/chat/movies-as-list")
List<String> chatWithListOutput(@RequestParam String director) {
var outputParser = new ListOutputParser(new DefaultConversionService());
var userPromptTemplate = new PromptTemplate(PROMPT_TEMPLATE);
Map<String, Object> model = Map.of("director", director, "format", outputParser.getFormat());
var prompt = userPromptTemplate.create(model);
var response = chatClient.call(prompt);
return outputParser.parse(response.getResult().getOutput().getContent());
}
}
我們可以按照以下方式測(cè)試API:
curl --location 'http://localhost:8080/ai/chat/movies?director=Quentin%20Tarantino'
//OUTPUT:
{"director":"Quentin Tarantino","movies":["Pulp Fiction","Inglourious Basterds","Django Unchained","Kill Bill: Volume 1","Kill Bill: Volume 2"]}
curl --location 'http://localhost:8080/ai/chat/movies-as-map?director=Quentin%20Tarantino'
//OUTPUT:
{"best_movies":[{"title":"Pulp Fiction","year":1994},{"title":"Inglourious Basterds","year":2009},{"title":"Kill Bill: Volume 1","year":2003},{"title":"Kill Bill: Volume 2","year":2004},{"title":"Django Unchained","year":2012}]}
curl --location 'http://localhost:8080/ai/chat/movies-as-list?director=Quentin%20Tarantino'
//OUTPUT:
["Pulp Fiction","Kill Bill: Volume 1","Inglourious Basterds","Django Unchained","Once Upon a Time in Hollywood"]
你需要根據(jù) LLM 的響應(yīng)以及你希望轉(zhuǎn)換的格式,使用相應(yīng)的 OutputParser。
結(jié)論
在這篇文章中,我們了解了如何使用 Spring AI 與 OpenAI 進(jìn)行交互。我們創(chuàng)建了Java Bean并使用了BeanOutputParser,MapOutputParser,和ListOutputParser來解析不同的響應(yīng)類型。通過本文,我們可以了解到如何根據(jù) LLM 的響應(yīng)和預(yù)期的格式選擇適合的 OutputParser 。