咱們先還原一下,直接調(diào)用大模型API通常是怎么寫的。無非就是構(gòu)造請(qǐng)求、發(fā)HTTP、解析結(jié)果,大概長這樣:
java
// 直接裸寫調(diào)用OpenAI API
public String callOpenAI(String userPrompt) {
? ? String apiUrl = "https://api.openai.com/v1/chat/completions";
? ? String apiKey = "sk-xxxxxxxxxxxxxxxx";
? ? // 1. 手動(dòng)構(gòu)造請(qǐng)求JSON
? ? JSONObject reqBody = new JSONObject();
? ? reqBody.put("model", "gpt-3.5-turbo");
? ? JSONArray messages = new JSONArray();
? ? messages.add(new JSONObject()
? ? ? ? .put("role", "user")
? ? ? ? .put("content", userPrompt));
? ? reqBody.put("messages", messages);
? ? // 2. 設(shè)置請(qǐng)求頭、發(fā)請(qǐng)求
? ? HttpHeaders headers = new HttpHeaders();
? ? headers.setContentType(MediaType.APPLICATION_JSON);
? ? headers.setBearerAuth(apiKey);
? ? HttpEntity<String> entity = new HttpEntity<>(reqBody.toJSONString(), headers);
? ? ResponseEntity<String> response = restTemplate.postForEntity(apiUrl, entity, String.class);
? ? // 3. 手動(dòng)解析返回結(jié)果
? ? JSONObject resJson = JSON.parseObject(response.getBody());
? ? return resJson.getJSONArray("choices")
? ? ? ? .getJSONObject(0)
? ? ? ? .getJSONObject("message")
? ? ? ? .getString("content");
}
看起來代碼不多,挺簡單對(duì)不對(duì)?但真放到企業(yè)項(xiàng)目里做個(gè)生產(chǎn)可用的AI應(yīng)用,問題就全冒出來了:
1. 切換大模型=重寫一半代碼
今天項(xiàng)目要用OpenAI,明天老板說要換成國產(chǎn)的文心一言,后天又要接通義千問做備選。每個(gè)大模型的請(qǐng)求格式不一樣,返回結(jié)構(gòu)不一樣,錯(cuò)誤碼也不一樣,你得把原來的請(qǐng)求構(gòu)造、結(jié)果解析全改一遍,純純出力不討好。
2. 重試、限流、日志、監(jiān)控全要自己搭
大模型API不是百分百穩(wěn)定的,超時(shí)、限流、500錯(cuò)誤都是常有的事。這些異常你得處理吧?失敗了要重試吧?請(qǐng)求耗時(shí)要打日志吧?線上出問題要監(jiān)控報(bào)警吧?這些通用邏輯每個(gè)接口都要寫一遍,純純重復(fù)造輪子。
3. 復(fù)雜提示詞根本沒法維護(hù)
正經(jīng)業(yè)務(wù)里的提示詞往往很長,還要?jiǎng)討B(tài)替換參數(shù),全堆在Java字符串里,代碼亂成一鍋粥,改個(gè)提示詞還要重新發(fā)布項(xiàng)目,體驗(yàn)極差。
4. RAG開發(fā)全靠自己,從零搭到懷疑人生
現(xiàn)在做AI應(yīng)用基本都離不開RAG(檢索增強(qiáng)生成),從文檔讀取、文本切片、向量生成、存儲(chǔ)到向量數(shù)據(jù)庫,檢索的時(shí)候再把相關(guān)片段拼到提示詞里,這套流程全自己寫沒有幾百行代碼根本下不來,太折磨人了。
Spring AI:把這些破事全給你搞定了
Spring AI本質(zhì)上做的事情,就是把AI開發(fā)中這些通用、重復(fù)的工作全都封裝好,讓你用熟悉的Spring風(fēng)格開發(fā)AI應(yīng)用,不用從零造輪子,專注寫業(yè)務(wù)就行。
給大家看看用Spring AI調(diào)用大模型是什么體驗(yàn):
java
// Spring AI調(diào)用大模型,就這么點(diǎn)代碼
@RestController
public class ChatController {
? ? private final ChatClient chatClient;
? ? // 自動(dòng)注入配置好的ChatClient
? ? public ChatController(ChatClient.Builder builder) {
? ? ? ? this.chatClient = builder.build();
? ? }
? ? @GetMapping("/chat")
? ? public String chat(String prompt) {
? ? ? ? return chatClient.call(prompt);
? ? }
}
代碼寫完了,剩下的配置全丟在application.yml里:
yaml
spring:
? ai:
? ? openai:
? ? ? api-key: ${OPENAI_API_KEY}
? ? ? chat:
? ? ? ? options:
? ? ? ? ? model: gpt-3.5-turbo
就搞定了?沒錯(cuò)!如果哪天你想換成百度文心一言,只需要改依賴和配置,業(yè)務(wù)代碼一行都不用動(dòng):
yaml
spring:
? ai:
? ? qianfan:
? ? ? api-key: ${QIANFAN_API_KEY}
? ? ? secret-key: ${QIANFAN_SECRET_KEY}
? ? ? chat:
? ? ? ? options:
? ? ? ? ? model: ERNIE-Speed-8K
就問你香不香?除了統(tǒng)一API屏蔽差異,Spring AI還給我們準(zhǔn)備了一大堆實(shí)用功能:
1. 提示詞模板支持外部化,好維護(hù)太多
Spring AI內(nèi)置了提示詞模板,支持把長提示詞放到單獨(dú)的文件里,動(dòng)態(tài)替換參數(shù),改提示詞不用動(dòng)代碼:
java
// 從resources加載提示詞模板,動(dòng)態(tài)替換參數(shù)
PromptTemplate promptTemplate = new PromptTemplate(
? ? ResourceLoaderUtils.getResource("classpath:prompts/customer-service.tpl")
);
Prompt prompt = promptTemplate.create(Map.of(
? ? "serviceName", "我的電商網(wǎng)站",
? ? "userQuestion", "我的訂單什么時(shí)候發(fā)貨?"
));
ChatResponse response = chatClient.call(prompt);
2. RAG全流程封裝,一行代碼接入向量存儲(chǔ)
Spring AI把RAG需要的文檔讀取、切片、向量生成、存儲(chǔ)檢索全做好了,想接向量數(shù)據(jù)庫只需要改配置:
java
// 基于RAG實(shí)現(xiàn)問答,就這么簡單
public String ragChat(String userQuestion) {
? ? // 從向量數(shù)據(jù)庫找相關(guān)文檔
? ? List<Document> relatedDocs = vectorStore.similaritySearch(userQuestion);
? ? // 拼到提示詞里調(diào)用大模型
? ? String prompt = STR."根據(jù)以下內(nèi)容回答問題:\{relatedDocs}\n問題:\{userQuestion}";
? ? return chatClient.call(prompt);
}
Redis、PGVector、Milvus、Chroma這些常用的向量數(shù)據(jù)庫全都支持,切換存儲(chǔ)只需要改依賴,不用改業(yè)務(wù)代碼。
3. 函數(shù)調(diào)用開箱即用,不用自己解析
現(xiàn)在大模型都支持函數(shù)調(diào)用,讓大模型能調(diào)用你的本地方法獲取數(shù)據(jù)。Spring AI直接把這個(gè)流程封裝好了,只需要定義方法加注解就行:
java
// 定義查詢天氣的函數(shù)
@Bean
public Function<GetWeatherRequest, GetWeatherResponse> getCurrentWeather() {
? ? return request -> weatherService.getWeather(request.getCity());
}
// 直接調(diào)用大模型,函數(shù)調(diào)用自動(dòng)處理
ChatResponse response = chatClient.call(new Prompt("北京今天天氣怎么樣?"));
不用自己解析大模型返回的函數(shù)調(diào)用請(qǐng)求,也不用自己把結(jié)果塞回給大模型,全流程自動(dòng)完成。
4. 無縫集成Spring生態(tài),復(fù)用你熟悉的能力
如果你本來就是Spring Boot項(xiàng)目,集成Spring AI簡直不要太舒服:自動(dòng)配置、依賴注入、切面日志、異常處理全都和原生Spring打通,你可以直接用Spring Security做權(quán)限,用Spring Retry做重試,用Micrometer做監(jiān)控,完全不用額外適配。
什么時(shí)候用Spring AI,什么時(shí)候直接調(diào)用?
Spring AI不是銀彈,也不是所有場景都必須用,我給大家總結(jié)了判斷標(biāo)準(zhǔn):
? 推薦用Spring AI的場景
? ? 企業(yè)級(jí)Java/Spring項(xiàng)目,需要長期維護(hù)
? ? 需要對(duì)接多個(gè)大模型,方便后續(xù)切換擴(kuò)展
? ? 需要做RAG、函數(shù)調(diào)用這類復(fù)雜AI應(yīng)用
? ? 團(tuán)隊(duì)熟悉Spring生態(tài),想快速落地AI功能
? 可以直接調(diào)用的場景
? ? 簡單的Demo、測試項(xiàng)目,寫完就扔
? ? 只對(duì)接一個(gè)大模型,未來也不會(huì)換
? ? 對(duì)包體積要求極高的輕量項(xiàng)目
最后嘮兩句
Spring AI 不是要替代直接調(diào)用大模型,它存在的意義就是降低Java生態(tài)開發(fā)AI應(yīng)用的門檻:把不同大模型的差異給你屏蔽掉,把通用的異常處理、重試、日志、RAG這些能力給你封裝好,讓Spring開發(fā)者不用從零開始造輪子,把精力放在業(yè)務(wù)邏輯上就行。
你寫個(gè)幾十行的helloworldDemo,直接發(fā)HTTP當(dāng)然沒問題;但要是做一個(gè)生產(chǎn)環(huán)境可用、需要長期維護(hù)的AI應(yīng)用,Spring AI真的能幫你省至少80%的重復(fù)工作,這就是它比直接調(diào)用大模型好用的原因。
如果你最近正在做Java+AI相關(guān)的項(xiàng)目,不妨試試Spring AI,相信你會(huì)回來給我點(diǎn)zan的~