Spring AI進(jìn)階實(shí)戰(zhàn):RAG、工具調(diào)用、流式對(duì)話、多模型切換

做Java開發(fā)一年多,我最大的感受就是:AI開發(fā)的風(fēng)口真的來(lái)了。

從公司內(nèi)部的智能客服,到對(duì)外業(yè)務(wù)的AI助手,越來(lái)越多項(xiàng)目需要集成大模型能力。而在Java生態(tài)里,Spring AI無(wú)疑是最順手的框架——它不像Python那樣零散,能完美貼合我們熟悉的Spring開發(fā)習(xí)慣,從基礎(chǔ)到企業(yè)級(jí)場(chǎng)景都能覆蓋。

不過很多朋友跟我反饋:入門Demo看了不少,真要做項(xiàng)目的時(shí)候,還是會(huì)卡在幾個(gè)關(guān)鍵點(diǎn):

? RAG知識(shí)庫(kù)怎么搭才能檢索準(zhǔn)確?

? Function工具調(diào)用怎么寫才能自動(dòng)觸發(fā)?

? 像ChatGPT那樣的流式對(duì)話怎么實(shí)現(xiàn)?

? 不同場(chǎng)景要切換不同大模型,怎么做到動(dòng)態(tài)切換不重啟?

這篇文章我把四個(gè)最常用的進(jìn)階場(chǎng)景整理成了可運(yùn)行的實(shí)戰(zhàn)代碼,一步步講清楚,新手也能直接抄作業(yè)。

一、Spring AI RAG 知識(shí)庫(kù)實(shí)戰(zhàn):從能用變好用

RAG(檢索增強(qiáng)生成)絕對(duì)是企業(yè)落地AI最常見的場(chǎng)景——把公司的產(chǎn)品手冊(cè)、內(nèi)部文檔、歷史問答導(dǎo)入向量庫(kù),讓大模型基于私有知識(shí)回答,既不會(huì)胡說(shuō)八道,又能解決數(shù)據(jù)隱私問題。Spring AI對(duì)RAG的封裝非常友好,我們直接上代碼:

1. 環(huán)境準(zhǔn)備與依賴導(dǎo)入

我這里用最常用的PostgreSQL+PgVector做向量存儲(chǔ),導(dǎo)入依賴:

xml

<dependencies>

? ? <!-- Spring AI PgVector 向量庫(kù)支持 -->

? ? <dependency>

? ? ? ? <groupId>org.springframework.ai</groupId>

? ? ? ? <artifactId>spring-ai-pgvector-store</artifactId>

? ? </dependency>

? ? <!-- PostgreSQL驅(qū)動(dòng) -->

? ? <dependency>

? ? ? ? <groupId>org.postgresql</groupId>

? ? ? ? <artifactId>postgresql</artifactId>

? ? </dependency>

</dependencies>

2. 基礎(chǔ)配置

在application.yml中添加數(shù)據(jù)庫(kù)和大模型配置:

yaml

spring:

? datasource:

? ? url: jdbc:postgresql://localhost:5432/rag_knowledge

? ? username: postgres

? ? password: your_db_password

? ai:

? ? # 用OpenAI做嵌入生成,也可以換成文心、通義等其他模型

? ? openai:

? ? ? api-key: ${OPENAI_API_KEY}

? ? ? embedding:

? ? ? ? options:

? ? ? ? ? model: text-embedding-3-small

? ? # PgVector向量庫(kù)配置

? ? vectorstore:

? ? ? pgvector:

? ? ? ? index-name: product_knowledge_base

? ? ? ? dimension: 1536 # 和嵌入模型維度對(duì)應(yīng)

3. 核心代碼實(shí)現(xiàn)

實(shí)現(xiàn)文檔導(dǎo)入和RAG問答兩個(gè)核心接口:

java

@RestController

@RequestMapping("/rag")

public class RAGKnowledgeController {

? ? private final VectorStore vectorStore;

? ? private final ChatClient chatClient;

? ? // 自動(dòng)注入VectorStore和ChatClient

? ? public RAGKnowledgeController(VectorStore vectorStore, ChatClient.Builder chatClientBuilder) {

? ? ? ? this.vectorStore = vectorStore;

? ? ? ? this.chatClient = chatClientBuilder.build();

? ? }

? ? /**

? ? * 上傳文檔,拆分后導(dǎo)入向量庫(kù)

? ? */

? ? @PostMapping("/upload")

? ? public String uploadDocument(@RequestBody String content) {

? ? ? ? // 讀取文本,Spring AI自動(dòng)幫我們做拆分

? ? ? ? TextReader textReader = new StringTextReader(content);

? ? ? ? List<Document> splitDocuments = textReader.read();

? ? ? ? // 生成嵌入向量,寫入向量庫(kù)

? ? ? ? vectorStore.add(splitDocuments);

? ? ? ? return "導(dǎo)入成功,共拆分%s個(gè)文檔塊".formatted(splitDocuments.size());

? ? }

? ? /**

? ? * 基于RAG的知識(shí)庫(kù)問答

? ? */

? ? @GetMapping("/query")

? ? public String queryKnowledge(@RequestParam String question) {

? ? ? ? // 1. 語(yǔ)義檢索,撈出最相關(guān)的5個(gè)文檔塊

? ? ? ? List<Document> searchResults = vectorStore.similaritySearch(question, 5);

? ? ? ? // 2. 拼接上下文,構(gòu)造Prompt

? ? ? ? String context = searchResults.stream()

? ? ? ? ? ? ? ? .map(Document::getContent)

? ? ? ? ? ? ? ? .reduce("", (acc, doc) -> acc + "\n\n" + doc);

? ? ? ? String prompt = """

? ? ? ? ? ? ? ? 你是公司的產(chǎn)品客服助手,請(qǐng)嚴(yán)格基于下面給的上下文回答用戶的問題。

? ? ? ? ? ? ? ? 如果上下文中沒有找到答案,請(qǐng)直接回答"抱歉,這個(gè)問題我暫時(shí)無(wú)法解答",不要自己編造內(nèi)容。

? ? ? ? ? ? ? ? ------------------------------

? ? ? ? ? ? ? ? 上下文內(nèi)容:%s

? ? ? ? ? ? ? ? ------------------------------

? ? ? ? ? ? ? ? 用戶問題:%s

? ? ? ? ? ? ? ? """.formatted(context, question);

? ? ? ? // 3. 調(diào)用大模型生成回答

? ? ? ? return chatClient.call(prompt);

? ? }

}

4. RAG優(yōu)化小技巧

很多人搭完基礎(chǔ)RAG會(huì)覺得“效果不好”,其實(shí)只要做兩個(gè)小優(yōu)化就能提升很多:

? ? 自定義分段規(guī)則:不要用默認(rèn)的固定長(zhǎng)度拆分,長(zhǎng)文檔按照標(biāo)題、章節(jié)分段,把同一主題的內(nèi)容放在一個(gè)塊里,檢索精度能提升20%;

? ? 結(jié)果重排序:先用向量檢索撈出top20,再用輕量排序模型(比如BGE-reranker)對(duì)結(jié)果重排序,把最相關(guān)的內(nèi)容放到前面,效果提升非常明顯。

二、Function工具調(diào)用:讓AI能調(diào)用你的業(yè)務(wù)接口

大模型本身只能生成文字,要讓它幫你查數(shù)據(jù)庫(kù)、調(diào)接口、操作業(yè)務(wù)系統(tǒng),就需要用到Function Calling(工具調(diào)用)。Spring AI 1.x版本把這個(gè)能力簡(jiǎn)化到了極致,只需要加個(gè)注解就能用:

1. 定義工具函數(shù)

給你的業(yè)務(wù)方法加上@Tool注解,Spring AI會(huì)自動(dòng)掃描注冊(cè):

java

@Component

public class BusinessTools {

? ? @Tool(description = "查詢指定城市的當(dāng)前天氣氣溫")

? ? public String getCityWeather(

? ? ? ? ? ? @ToolParam(description = "要查詢的城市名稱,比如北京、上海") String city

? ? ) {

? ? ? ? // 這里調(diào)用第三方天氣API,返回真實(shí)結(jié)果

? ? ? ? return "當(dāng)前%s的氣溫是26攝氏度,天氣晴朗,紫外線中等".formatted(city);

? ? }

? ? @Tool(description = "查詢公司員工的工號(hào)和部門信息")

? ? public String getEmployeeInfo(

? ? ? ? ? ? @ToolParam(description = "員工姓名") String name

? ? ) {

? ? ? ? // 這里調(diào)用內(nèi)部員工數(shù)據(jù)庫(kù)查詢

? ? ? ? if ("張三".equals(name)) {

? ? ? ? ? ? return "張三的工號(hào)是00123,所屬部門是產(chǎn)品研發(fā)部";

? ? ? ? }

? ? ? ? return "未找到該員工的信息";

? ? }

}

2. 啟用工具調(diào)用

只需要在構(gòu)建ChatClient的時(shí)候默認(rèn)注入所有工具就可以了:

java

@RestController

@RequestMapping("/function")

public class FunctionCallController {

? ? private final ChatClient chatClient;

? ? // Spring會(huì)自動(dòng)把所有帶@Tool的工具注入進(jìn)來(lái)

? ? public FunctionCallController(ChatClient.Builder builder, List<ToolCallback> toolCallbacks) {

? ? ? ? this.chatClient = builder

? ? ? ? ? ? ? ? .defaultTools(toolCallbacks)

? ? ? ? ? ? ? ? .build();

? ? }

? ? @GetMapping("/chat")

? ? public String chat(@RequestParam String question) {

? ? ? ? // 大模型會(huì)自動(dòng)判斷是否需要調(diào)用工具,拿到結(jié)果后再生成自然語(yǔ)言回答

? ? ? ? return chatClient.call(question);

? ? }

}

就這么簡(jiǎn)單!當(dāng)用戶問“北京今天多少度?張三在哪個(gè)部門?”,大模型會(huì)自動(dòng)調(diào)用對(duì)應(yīng)的工具,拿到結(jié)果后再整理成自然語(yǔ)言回答,整個(gè)過程都是自動(dòng)處理的,完全不需要你手動(dòng)判斷。

三、流式對(duì)話:實(shí)現(xiàn)逐字輸出的聊天體驗(yàn)

傳統(tǒng)的問答是一次性返回完整回答,大模型生成長(zhǎng)回答的時(shí)候用戶要等好幾秒,體驗(yàn)特別差。流式對(duì)話就是像ChatGPT那樣,回答逐字輸出,用戶不用等,Spring AI只需要幾行代碼就能實(shí)現(xiàn):

java

@RestController

@RequestMapping("/stream")

public class StreamChatController {

? ? private final ChatClient chatClient;

? ? public StreamChatController(ChatClient.Builder builder) {

? ? ? ? this.chatClient = builder.build();

? ? }

? ? /**

? ? * 流式對(duì)話接口,返回SSE流

? ? */

? ? @GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

? ? public Flux<String> streamChat(@RequestParam String question) {

? ? ? ? // Spring AI直接返回Flux流,自動(dòng)處理分塊輸出

? ? ? ? return chatClient.stream(question);

? ? }

}

前端用EventSource對(duì)接就可以了,非常簡(jiǎn)單:

javascript

// 前端對(duì)接代碼示例

const responseContainer = document.getElementById('response');

const question = "請(qǐng)介紹一下Spring AI的核心優(yōu)勢(shì)";

const eventSource = new EventSource(`/api/stream/chat?question=${encodeURIComponent(question)}`);

// 收到消息就追加到頁(yè)面上

eventSource.onmessage = (event) => {

? ? responseContainer.innerHTML += event.data;

};

// 流結(jié)束關(guān)閉連接

eventSource.onclose = () => eventSource.close();

不管是做聊天機(jī)器人還是智能客服,流式對(duì)話基本都是標(biāo)配了,沒想到Spring AI實(shí)現(xiàn)起來(lái)這么簡(jiǎn)單吧?

四、多模型動(dòng)態(tài)切換:一套接口對(duì)接多個(gè)大模型

很多項(xiàng)目都會(huì)有切換模型的需求:比如簡(jiǎn)單問答用便宜的小模型降成本,復(fù)雜推理用能力強(qiáng)的大模型,國(guó)內(nèi)業(yè)務(wù)要用國(guó)產(chǎn)大模型,怎么做到不重啟服務(wù)動(dòng)態(tài)切換呢?

其實(shí)非常簡(jiǎn)單,Spring AI提供了ModelRegistry統(tǒng)一管理所有模型,我們只需要根據(jù)前端傳的模型ID動(dòng)態(tài)獲取就可以了:

java

@RestController

@RequestMapping("/model")

public class DynamicModelController {

? ? private final ModelRegistry modelRegistry;

? ? private final ChatClient.Builder chatClientBuilder;

? ? public DynamicModelController(ModelRegistry modelRegistry, ChatClient.Builder chatClientBuilder) {

? ? ? ? this.modelRegistry = modelRegistry;

? ? ? ? this.chatClientBuilder = chatClientBuilder;

? ? }

? ? @GetMapping("/chat")

? ? public String chat(

? ? ? ? ? ? @RequestParam String question,

? ? ? ? ? ? @RequestParam(defaultValue = "openai-gpt-3.5-turbo") String modelId

? ? ) {

? ? ? ? // 1. 根據(jù)模型ID從注冊(cè)中心獲取對(duì)應(yīng)的ChatModel

? ? ? ? ChatModel chatModel = modelRegistry.getChatModel(modelId)

? ? ? ? ? ? ? ? .orElseThrow(() -> new IllegalArgumentException("暫不支持該模型:" + modelId));

? ? ? ? // 2. 動(dòng)態(tài)構(gòu)建ChatClient,調(diào)用大模型

? ? ? ? ChatClient chatClient = chatClientBuilder

? ? ? ? ? ? ? ? .chatModel(chatModel)

? ? ? ? ? ? ? ? .build();

? ? ? ? return chatClient.call(question);

? ? }

}

配置文件里只需要把多個(gè)模型的配置都加上,Spring AI會(huì)自動(dòng)注冊(cè)到ModelRegistry:

yaml

spring:

? ai:

? ? # OpenAI配置

? ? openai:

? ? ? api-key: ${OPENAI_API_KEY}

? ? ? chat:

? ? ? ? options:

? ? ? ? ? model: gpt-3.5-turbo

? ? # 百度文心一言配置

? ? ernie:

? ? ? api-key: ${ERNIE_API_KEY}

? ? ? chat:

? ? ? ? options:

? ? ? ? ? model: ernie-3.5-8k

? ? # 阿里通義千問配置

? ? qwen:

? ? ? api-key: ${QWEN_API_KEY}

? ? ? chat:

? ? ? ? options:

? ? ? ? ? model: qwen-max

這樣就實(shí)現(xiàn)了一個(gè)接口對(duì)接多個(gè)大模型,需要哪個(gè)用哪個(gè),完全不用重啟服務(wù),靈活適配不同業(yè)務(wù)場(chǎng)景。

寫在最后:持續(xù)更新進(jìn)階教程

上面這四個(gè)場(chǎng)景——RAG知識(shí)庫(kù)搭建、Function工具調(diào)用、流式對(duì)話、多模型動(dòng)態(tài)切換,是我做Spring AI項(xiàng)目用得最多的進(jìn)階功能,所有代碼都是經(jīng)過實(shí)際項(xiàng)目驗(yàn)證的,大家可以直接拿去用。

接下來(lái)我會(huì)持續(xù)更新更多Spring AI進(jìn)階實(shí)戰(zhàn)內(nèi)容:

?? 企業(yè)級(jí)RAG怎么做文檔權(quán)限控制?

?? 多Agent協(xié)作系統(tǒng)怎么搭?

?? 怎么用Spring AI對(duì)接本地部署的大模型?

?? AI應(yīng)用怎么優(yōu)化延遲、降低成本?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容