給Javaer看的大模型開發(fā)指南
一、概述
二、什么是大模型
三、大模型的特點(diǎn)
1. 無(wú)狀態(tài)
2. 結(jié)構(gòu)化輸出
3. 函數(shù)調(diào)用
四、大模型接口
1. 模型封裝
2. 接口輸入
3. 接口輸出
五、RAG架構(gòu)
六、MCP協(xié)議
七、Spring-AI
1. 模型抽象
2. 聊天會(huì)話
3. RAG拓展
4. 代碼示例
八、智能體示例
1. 接口骨架
2. 構(gòu)造外部函數(shù)定義
3. 系統(tǒng)提示詞
4. 發(fā)起調(diào)用
九、總結(jié)
一、概述
伴隨著大模型的性能提升、成本下降,在Web在線對(duì)話場(chǎng)景以外,大模型也越來(lái)越多的被集成到傳統(tǒng)業(yè)務(wù)場(chǎng)景。
在大模型API交互模式、業(yè)務(wù)集成模式經(jīng)百家爭(zhēng)鳴現(xiàn)已趨于穩(wěn)定的背景下,Spring作為Java生態(tài)里的OSS巨頭也下場(chǎng)為L(zhǎng)LM提供生態(tài)支持,于近期釋出 spring-ai 正式版。
需要說(shuō)明的是,Spring-AI 所提供的能力并不神秘,業(yè)務(wù)上也并非必須用Spring-AI不可。但是,就像過(guò)去Spring對(duì)新的數(shù)據(jù)庫(kù)、新的中間件提供生態(tài)支持一樣,Spring-AI提供了一套和Spring全家桶兼容并且語(yǔ)義一致、良好設(shè)計(jì)、易拓展的大模型交互的Java API,可以極大的降低LLM集成和開發(fā)的成本。
從大模型的工程化、實(shí)用化角度來(lái)說(shuō),當(dāng)你厘清Spring-AI這一套API設(shè)施的邏輯后,事情最后還是會(huì)回歸到業(yè)務(wù)開發(fā)人最熟悉的CRUD領(lǐng)域。就像使用Mybatis操作MySQL一樣,我們會(huì)用 spring-ai 來(lái)操作大模型。
那我們開始今天的討論吧!
二、什么是大模型
大模型的舞臺(tái)上,從來(lái)不缺新面孔。自ChatGPT開啟AI新紀(jì)元后,各類大模型層出不窮。
但是我們不去考慮大模型的訓(xùn)練原理、推理/運(yùn)算架構(gòu)、參數(shù)調(diào)優(yōu)等較為復(fù)雜的數(shù)學(xué)范疇的東西,就像我們很少關(guān)心MySQL是怎么用代碼來(lái)實(shí)現(xiàn)效果的一樣。
此處類比我們熟悉的知識(shí),對(duì)大模型有一個(gè)盲人摸象式的基礎(chǔ)且能夠自洽的認(rèn)識(shí)即可。
- 從某種意義上來(lái)說(shuō),模型訓(xùn)練就是通過(guò)分析海量文本(如維基百科、圖書、網(wǎng)頁(yè)等)尋找到人類語(yǔ)言的規(guī)律,再將這個(gè)規(guī)律固化成一個(gè)包含數(shù)十億【參數(shù)】的超級(jí)【數(shù)學(xué)公式】。就像簡(jiǎn)單公式 y = 5x + 8 中的 5 和 8 ,這兩個(gè)【參數(shù)】決定了將輸入X如何轉(zhuǎn)化為輸出Y。
- 訓(xùn)練好的【數(shù)學(xué)公式】就像代碼,需要部署在算力平臺(tái)上,借助【顯卡】的并行運(yùn)算能力來(lái)實(shí)現(xiàn)高效運(yùn)算。
- 用戶的輸入作為這個(gè)【數(shù)學(xué)公式】的入?yún)?,?jīng)公式運(yùn)算后,得到相關(guān)的【輸出】。
圖片
假設(shè)大模型是上述的數(shù)學(xué)公式,不同的大模型「ChatGPT/DeepSeek」是不同的架構(gòu)、不同的公式,那么模型訓(xùn)練就是通過(guò)對(duì)海量文本的分析、學(xué)習(xí),找到合適的參數(shù)值。
三、大模型的特點(diǎn)
接下來(lái)我們關(guān)注在工程應(yīng)用場(chǎng)景下,需要開發(fā)人關(guān)注的大模型特點(diǎn)。
就像MySQL,我們集成時(shí)也需要關(guān)注不同的存儲(chǔ)引擎(InnoDB/MyISAM)的特點(diǎn)。
無(wú)狀態(tài)
圖片
大模型是沒有記憶、沒有狀態(tài)的,它是一個(gè)純函數(shù)。
它不知道之前跟你說(shuō)過(guò)什么。所以每次進(jìn)行大模型輸入的時(shí)候,我們需要根據(jù)業(yè)務(wù)場(chǎng)景把之前的【輸入】,【反饋】一并給它,避免大模型失憶導(dǎo)致的對(duì)話不流暢。
圖片
結(jié)構(gòu)化輸出
大模型是具備結(jié)構(gòu)化輸出能力的,雖然有些模型支持的不夠好,但是沒關(guān)系,只是支持的程度不同,重要的是它們都支持!
所謂的結(jié)構(gòu)化輸出是指,大模型除了可以返回口語(yǔ)化、沒有模式的自然語(yǔ)言文本外,還可以按你需求給你返回其他的文本格式,比如:JSON。
圖片
你看,這像不像在調(diào)一個(gè)REST接口?甚至是一個(gè)萬(wàn)能接口,畢竟大模型什么都會(huì),不會(huì)的也可以現(xiàn)編。
圖片
函數(shù)調(diào)用
其實(shí)看到這里我們就可以實(shí)現(xiàn)一個(gè)大模型驅(qū)動(dòng)的RPC調(diào)用引擎了!
圖片
大模型幫你推理、規(guī)劃得到了需要執(zhí)行的函數(shù)和對(duì)應(yīng)的函數(shù)參數(shù),至于這個(gè)【函數(shù)名】對(duì)應(yīng)的到底是一個(gè)進(jìn)程內(nèi)的方法、HTTP接口、Dubbo接口還是MCP接口都沒有那么重要,這只是智能體實(shí)現(xiàn)的一個(gè)技術(shù)細(xì)節(jié)而已。
我們可以用自然語(yǔ)言表述需求,同時(shí)告訴大模型有哪些輔助【工具/函數(shù)】可以供它備用。它會(huì)推理、編排這些工具來(lái)達(dá)成需求。
圖片
- 把用戶輸入和可用函數(shù)輸入給大模型,大模型推理發(fā)現(xiàn)需要調(diào)用外部函數(shù),于是返回函數(shù)名+函數(shù)調(diào)用參數(shù)。
- 智能體捕獲輸出,對(duì)指定函數(shù)發(fā)起調(diào)用,再將用戶輸入和函數(shù)結(jié)果一起輸入到大模型,大模型基于這些上下文推理輸出結(jié)果。
考慮到大模型發(fā)起函數(shù)調(diào)用的普遍需求,大模型供應(yīng)商一般都在API層面提供了【function call】能力,用于將文本輸出和函數(shù)調(diào)用輸出區(qū)分開,明白了原理,我們知道這只是API抽象層次的問題。
四、大模型接口
考慮到大模型對(duì)硬件資源的特別需求(如顯卡),所以大模型一般是獨(dú)立部署,以SaaS模式提供能力。就像MySQL對(duì)資源有特別的需求(如大內(nèi)存),所以一般也是進(jìn)行獨(dú)立部署。
圖片
訓(xùn)練好的大模型就是一套二進(jìn)制數(shù)據(jù)集,SaaS化需要做外圍的服務(wù)化、產(chǎn)品化封裝,同一套模型可以在不同的算力平臺(tái)部署,提供截然不同的服務(wù)化API。
模型封裝
示例偽代碼如下:
圖片
我們可以簡(jiǎn)單看下當(dāng)下比較熱門的幾大供應(yīng)商提供的API文檔:
- OpenAI-會(huì)話補(bǔ)全https://openai.apifox.cn/api-67883981
- DeepSeek-會(huì)話補(bǔ)全https://api-docs.deepseek.com/zh-cn/api/create-chat-completion
- 硅基流動(dòng)-會(huì)話補(bǔ)全https://docs.siliconflow.cn/cn/api-reference/chat-completions/chat-completions
- Ollama-會(huì)話補(bǔ)全https://www.runoob.com/ollama/ollama-api.html
硅基流動(dòng)和Ollama都屬于大模型算力/治理平臺(tái)。他們不研發(fā)大模型,只是大模型的搬運(yùn)工??梢园汛竽P屠斫獬晌⒎?wù)集群,把硅基流動(dòng)和Ollama理解成微服務(wù)構(gòu)建/發(fā)布平臺(tái)即可。
大概瀏覽一下,會(huì)發(fā)現(xiàn)核心API都差不多,畢竟有OpenAI珠玉在前,許多系統(tǒng)都已對(duì)接了OpenAI的API。后發(fā)的大模型為了兼容,降低接入難度,基本上也都和OpenAI的API大差不差。
就像是MySQL,盡管數(shù)據(jù)庫(kù)產(chǎn)品類型百花齊放,但都兼容SQL語(yǔ)法。
我們?cè)诖酥挥懻摗緯?huì)話補(bǔ)全】這一點(diǎn),會(huì)發(fā)現(xiàn)會(huì)話補(bǔ)全接口的輸入/輸出大概都是以下情況:
接口輸入
{
"stream": false, // 是否是流式輸出(要不要SSE)
"model": "deepseek-chat", //選用的哪個(gè)模型
"messages": [ // 歷史對(duì)話消息,因?yàn)榇竽P蜔o(wú)狀態(tài),所以按場(chǎng)景提供一定數(shù)量的歷史消息
{
"content": "You are a helpful assistant",
"role": "system"
},
{
"content": "Hi", //消息內(nèi)容
"role": "user" //消息類型
}
],
"tools": null, //外部函數(shù)列表,【函數(shù)調(diào)用】能力在 API 層面的支持
"frequency_penalty": 0, //無(wú)關(guān)緊要的模型行為控制參數(shù)
"presence_penalty": 0, //無(wú)關(guān)緊要的模型行為控制參數(shù)
"temperature": 1, //無(wú)關(guān)緊要的模型行為控制參數(shù)
"top_p": 1, //無(wú)關(guān)緊要的模型行為控制參數(shù)
"logprobs": false, //無(wú)關(guān)緊要的模型行為控制參數(shù)
"top_logprobs": null //無(wú)關(guān)緊要的模型行為控制參數(shù)
}
這里以目標(biāo)達(dá)成作為要點(diǎn),內(nèi)容中部分不理解的參數(shù)可以忽略。
接口輸出
{
"id": "<string>", //無(wú)關(guān)緊要
"choices": [
{
"message": {
"role": "assistant",
"content": "<string>", // 大模型生成的內(nèi)容
"reasoning_content": "<string>",
"tool_calls": [ //需要發(fā)起的【函數(shù)調(diào)用】
{
"id": "<string>",
"type": "function",
"function": {
"name": "<string>",
"arguments": "<string>"
}
}
]
},
"finish_reason": "stop" //有點(diǎn)重要,但是我們先不管
}
],
"usage": { //token使用量 計(jì)數(shù)、計(jì)費(fèi)
"prompt_tokens": 123,
"completion_tokens": 123,
"total_tokens": 123
},
"created": 123, //無(wú)關(guān)緊要
"model": "<string>", //無(wú)關(guān)緊要
"object": "chat.completion" //無(wú)關(guān)緊要
}
看到這里時(shí),你是不是已經(jīng)開始躍躍欲試了?是不是感覺打造一個(gè)垂直領(lǐng)域的智能體沒有想象中那么困難了~
五、RAG架構(gòu)
除非是圍繞特定業(yè)務(wù)場(chǎng)景結(jié)合私域數(shù)據(jù)訓(xùn)練的專用大模型,否則涉及到一些企業(yè)內(nèi)部的私域信息時(shí),通用大模型也只能不懂裝懂的現(xiàn)編。
例如:當(dāng)你詢問大模型【DJob如何接入與使用】,除非訓(xùn)練大模型時(shí)輸入了相關(guān)資料,不然大模型只能現(xiàn)編了。
考慮到專用大模型的成本,工程上解決這個(gè)問題的方法一般是通過(guò)外掛知識(shí)庫(kù)來(lái)實(shí)現(xiàn):
- 結(jié)合具體業(yè)務(wù)場(chǎng)景,將相關(guān)的文檔與資料提前錄入到【知識(shí)庫(kù)】中。
- 用戶提交一個(gè)【輸入】后,先使用 用戶【輸入】作為搜索條件,去【知識(shí)庫(kù)】中搜索得到相關(guān)的【資料】。
- 將用戶【輸入】和【資料】一起提供給大模型。
此【知識(shí)庫(kù)】組件的具體選型屬于實(shí)現(xiàn)細(xì)節(jié),簡(jiǎn)單的可以用MySQL、Elasticsearch,如果想提升【知識(shí)庫(kù)搜索結(jié)果】的匹配度,也可以使用近期討論度很高的【向量數(shù)據(jù)庫(kù)】。
添加了RAG后,流程如下:
圖片
詳情可參考下文:https://www.zhihu.com/tardis/zm/art/675509396?source_id=1003
六、MCP協(xié)議
可以看到,將大模型作為一個(gè)【函數(shù)調(diào)用】的規(guī)劃引擎,借助它的推理與生成能力,可以實(shí)現(xiàn)復(fù)雜的業(yè)務(wù)流程。如果說(shuō)大模型是【腦】,那提供給大模型規(guī)劃、使用的【函數(shù)】就是它的【手】和【腳】。有腦有手的大模型,可以迸發(fā)出巨大的業(yè)務(wù)潛力。
那如何打通大模型和傳統(tǒng)軟件系統(tǒng)(如存量微服務(wù))呢?
我們關(guān)注的問題,開源社區(qū)也在積極的關(guān)注,這就是MCP協(xié)議誕生的背景和目的。
圖片
MCP協(xié)議介紹
https://mcp-docs.cn/introduction
在這里我們不展開MCP協(xié)議的細(xì)節(jié),僅作個(gè)人對(duì)MCP協(xié)議的思考,重點(diǎn)在于打破MCP協(xié)議的神秘感、破除MCP迷信。
- MCP協(xié)議本身并非高精尖的內(nèi)容,簡(jiǎn)單來(lái)說(shuō),就是常用人群約定系統(tǒng)間調(diào)用的流程、格式。若不考慮通用,誰(shuí)都可以設(shè)計(jì)符合自己需求的、領(lǐng)域特定的交互協(xié)議。
- MCP協(xié)議的優(yōu)勢(shì)在于,它出現(xiàn)的非常及時(shí),且基本滿足了常規(guī)交互需求,因此快速在社區(qū)達(dá)成了共識(shí)。
- 不管是MAP、MBP還是MCP,都沒有那么重要,但是形成共識(shí)非常重要。協(xié)議達(dá)成了共識(shí),開源社區(qū)才可以合力圍繞協(xié)議進(jìn)行生態(tài)建設(shè)。
七、 Spring-AI
到了這一步,我們開始探討Java代碼,首先我們需要熟悉下 spring-ai 的整套代碼架構(gòu),一步一步來(lái),以整體到到細(xì)節(jié)的節(jié)奏進(jìn)行討論。
模型抽象
核心的API實(shí)體是 Model ,是一個(gè)帶泛型的純函數(shù),提供了對(duì)大模型能力的頂層抽象:
圖片
org.springframework.ai.model.Model
大模型的能力本質(zhì)就是:輸入一個(gè)( request ),返回一個(gè)輸出。
至于輸入/輸出的具體類型,由細(xì)分的子類限定:
不同模態(tài)的大模型支持不同類型的輸入/輸出,在此我們只討論 ChatModel 。
org.springframework.ai.chat.model.ChatModel
spring-ai 提供了不同平臺(tái)、不同模型的API集成,開發(fā)者只需要提供接口地址、調(diào)用憑證即可開箱使用~
聊天會(huì)話
考慮到大模型對(duì)話是熱點(diǎn)場(chǎng)景, spring-ai 針對(duì)性的提供了會(huì)話接口抽象。
org.springframework.ai.chat.client.ChatClient
RAG拓展
類似Spring-AOP, spring-ai 基于請(qǐng)求橫切提供了開箱即用的RAG能力抽象。
圖片
圖片
org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor
代碼示例
基于供應(yīng)商構(gòu)建ChatModel
圖片
構(gòu)建ChatClient發(fā)起會(huì)話
八、智能體示例
到這里,我們已經(jīng)自上而下的理解了大模型的工程化,現(xiàn)在我們來(lái)開發(fā)一個(gè)【DJob智能助手】吧!
接口骨架
圖片
通過(guò) POST 接口,響應(yīng) Content-Type 為 text/event-stream 。
構(gòu)造外部函數(shù)定義
假設(shè)有以下幾個(gè)函數(shù)可以給大模型提供能力:
圖片
將上述3個(gè)本地方法封裝成 ChatClient API 認(rèn)識(shí)的【ToolCallback】:
圖片
構(gòu)建可用的 函數(shù)/工具 信息,這里用本地方法來(lái)mock。實(shí)際使用時(shí)可以利用MCP/HTTP/gRPC/Dubbod等實(shí)現(xiàn)跨系統(tǒng)調(diào)用。
系統(tǒng)提示詞
由于不能讓大模型自由發(fā)揮,因此需要在用戶輸入的內(nèi)容外,給大模型一些定向信息補(bǔ)充或場(chǎng)景限定,幫助大模型更好地解決問題!
圖片
發(fā)起調(diào)用
圖片
- 考慮到大模型無(wú)狀態(tài),所以每次會(huì)話時(shí)歷史消息也需要一并輸入。
- 歷史消息可以由前端收集、提交,也可以由后端每次會(huì)話存儲(chǔ)、收集。
九、總結(jié)
綜上所述,太陽(yáng)底下沒有新鮮事,工程領(lǐng)域所有的新生事物都可以暫時(shí)把它當(dāng)做MySQL,沒有人比Java工程師更懂MySQL了(開玩笑)。