微信截圖_1741089002375.png)
RESTful Web API 設(shè)計(jì)中要避免的 6 個(gè)常見錯(cuò)誤
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
<version>1.0.0-M7</version>
</dependency>
配置LLM接口
Spring-AI支持的LLM類型有很多,基本涵蓋了各個(gè)平臺(tái)的LLM接口規(guī)范,具體可以參考官方文檔中介紹。
我們平常用OpenAI的規(guī)范多一些,并且Idealab中也提供了開放接口,這里我們采用OpenAI接口作為chatModel,當(dāng)然大家也可自行封裝自己的API為OpenAI規(guī)范,如Whale上部署的模型也都是支持OpenAI協(xié)議的。
#配置chatModel的域名,這里我們使用的idealab
spring.ai.openai.base-url=https://idealab.alibaba-inc.com
#配置chat的ak
spring.ai.openai.api-key=脫敏
#配置chant的接口路徑
spring.ai.openai.chat.completions-path=api/openai/v1/chat/completions
#配置模型
spring.ai.openai.chat.options.model=gpt-4o-0513-global
#其他參數(shù)配置
spring.ai.openai.chat.options.temperature=0.1
模型配置完以后開始注冊我們的chatModel:
@Configuration
publicclassChatClientConfig {
@Autowired
private ToolCallbackProvider tools;
@Autowired
OpenAiChatModel chatModel;
@Bean
public CommandLineRunner predefinedQuestions(
ConfigurableApplicationContext context) {
return args -> {
// 構(gòu)建ChatClient,此時(shí)不注入任何工具
var chatClient = ChatClient.builder(chatModel)
.build();
String userInput = "幫我將這個(gè)網(wǎng)頁內(nèi)容進(jìn)行抓取 https://www.shuaijiao.cn/news/view/68320.html";
System.out.println("\n>>> QUESTION: " + userInput);
System.out.println("\n>>> ASSISTANT: " + chatClient.prompt().user(userInput).call().content());
context.close();
};
}
此時(shí)我們先不配置chatModel的工具,只是作為一個(gè)最基本的LLM利用Spring-AI配置化框架進(jìn)行調(diào)用看看效果。
此時(shí)的回答就是沒有任何工具的一個(gè)最基礎(chǔ)的LLM能力,接下來我們開始為這個(gè)chatModel上添加MCP工具,利用框架去直接讓大模型調(diào)用MCP服務(wù)。
配置MCP服務(wù)
上述配置chatClient的過程中我們發(fā)現(xiàn),使用框架提供的能力去調(diào)用LLM的API比我們自己去寫客戶端對接API來的方便。
而調(diào)用MCP的過程,使用框架的提效會(huì)更明顯,直接給chatClient注入工具即可,不用我們?nèi)ナ謩?dòng)寫functionCall的組裝,以及獲取到結(jié)果后再去調(diào)用對應(yīng)服務(wù)。
1.服務(wù)端為SSE方式提供
a.設(shè)置配置文件
spring.ai.mcp.client.name=ai-demo
spring.ai.mcp.client.type=SYNC
spring.ai.mcp.client.toolcallback.enabled=true
spring.ai.mcp.client.request-timeout=30000
spring.ai.mcp.client.enabled=true
# 配置mcp的服務(wù)端sse地址,這里選用了一個(gè)開源的抓取網(wǎng)站內(nèi)容的工具
spring.ai.mcp.client.sse.connections.server1.url=https://mcp-09724909-442f-4b85.api-inference.modelscope.cn
b.給chatModel注入工具
運(yùn)行結(jié)果:
此時(shí)可以發(fā)現(xiàn),我們的chatModel不再是一個(gè)原生的LLM接口,已經(jīng)可以根據(jù)用戶意圖來自主調(diào)用我們的MCP工具。
當(dāng)然我們也可以看出,利用Spring-AI做MCP調(diào)用是非常簡單,只需配置好LLM的調(diào)用接口和MCP工具地址即可。
作為Demo此時(shí)沒有任何問題,但如果作為工程實(shí)現(xiàn),比如我們要去做一個(gè)助理平臺(tái),這時(shí)候其實(shí)每個(gè)助理所綁定的MCP工具是動(dòng)態(tài)的,而非像上述這樣在應(yīng)用啟動(dòng)時(shí)初始化好的Bean,這種場景也是可以實(shí)現(xiàn)的,Spring-AI也支持在調(diào)用過程中動(dòng)態(tài)封裝工具,這些工具可以是MCP,也可以是程序中的Bean,或者是某些HTTP、RCP接口等。
2.服務(wù)端為stdio方式提供
a.配置Properties
Stdio和SSE配置的區(qū)別是將調(diào)用方式由顯示的服務(wù)地址換成npm、java、python等腳本命令直接執(zhí)行的遠(yuǎn)程包或本地包。
這里采用本地配置文件的方式去配置Stdio,即spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config.json。
spring.ai.mcp.client.name=ai-demo
#spring.ai.mcp.client.type=SYNC
spring.ai.mcp.client.toolcallback.enabled=true
spring.ai.mcp.client.request-timeout=30000
spring.ai.mcp.client.enabled=true
spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config.json
b.配置config.json
這里注入了兩個(gè)比較經(jīng)典的MCP服務(wù),百度地圖和新聞熱點(diǎn)服務(wù):
{
"mcpServers": {
"baidu-map": {
"command": "npx",
"args": [
"-y",
"@baidumap/mcp-server-baidu-map"
],
"env": {
"BAIDU_MAP_API_KEY": "Qr0GV6v4krVPlIJkupyPpi63d1zXh0Ko"
}
},
"mcp-server-hotnews": {
"command": "npx",
"args": [
"-y",
"@wopal/mcp-server-hotnews"
]
}
}
}
c.給chatModel注入工具
@Configuration
publicclassChatClientConfig {
@Autowired
private ToolCallbackProvider tools;
@Autowired
OpenAiChatModel chatModel;
@Bean
public CommandLineRunner predefinedQuestions(
ConfigurableApplicationContext context) {
return args -> {
// 構(gòu)建ChatClient,注入mcp工具
var chatClient = ChatClient.builder(chatModel).defaultTools(tools.getToolCallbacks())
.build();
// 使用ChatClient與LLM交互
String userInput = "幫我查找今天的知乎熱帖";
System.out.println("\n>>> QUESTION: " + userInput);
System.out.println("\n>>> ASSISTANT: " + chatClient.prompt().user(userInput).call().content());
context.close();
};
}
}
d.讓LLM自主選擇工具調(diào)用
用戶輸入為熱點(diǎn)查詢的提問時(shí):
用戶輸入為地點(diǎn)檢索時(shí):
通過上述例子可以發(fā)現(xiàn),我們在初始化時(shí)配置了兩個(gè)MCP服務(wù),LLM可以根據(jù)用戶不同提問來自行選擇不同的工具去調(diào)用,方式也比較簡單。
上述介紹了Spring-AI框架對于MCP調(diào)用的支持和使用方式,但在集團(tuán)內(nèi)部,Spring-AI-Alibaba項(xiàng)目在此基礎(chǔ)上也做了很多封裝,更適合集團(tuán)技術(shù)棧和內(nèi)部中間件的無縫銜接,以及基于Spring-AI項(xiàng)目擴(kuò)展了很多example項(xiàng)目可以參閱學(xué)習(xí)。如:Stramable HTTP 方式的MCP調(diào)用、OpenManus的實(shí)現(xiàn)等等,可以幫助開發(fā)者最更上層的封裝調(diào)用。
基于Spring-AI-Alibaba做MCP客戶端的實(shí)現(xiàn)與Spring-AI框架基本相似,一些差一點(diǎn)主要有:
1.引入依賴時(shí),在Spring-AI-MCP客戶端包的基礎(chǔ)上,需要新增com.alibaba.cloud.ai的依賴,如下所示:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
<version>1.0.0-M7</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0-M6.1</version>
</dependency>
2.配置chatModel時(shí),Spring-AI-Alibaba支持百煉平臺(tái)上的模型及API,即
spring:
ai:
dashscope:
# 配置通義千問API密鑰
api-key: ${DASH_SCOPE_API_KEY}
3.支持Stramable HTTP 模式的MCP調(diào)用,配置方式與Spring-AI的SSE客戶端配置方式一致,但Spring-AI-Alibaba底層是自主實(shí)現(xiàn)了Stramable HTTP的get和post請求并集成在了Spring-AI框架中,具體可參考。
通過上述介紹,我們發(fā)現(xiàn)既然框架層面已經(jīng)為我們封裝好了很規(guī)范的MCP調(diào)用方式,通過簡單配置即可實(shí)現(xiàn)MCP客戶端,本章節(jié)將介紹原生的MCP SDK調(diào)用方式,對于平臺(tái)類研發(fā)可能更有幫助。
1. 引入依賴
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.9.0</version>
</dependency>
2. 獲取該MCP服務(wù)中的所資源(方法、入?yún)ⅰ⒚枋龅龋?/strong>
private McpSyncClient mcpClient;
privatestaticfinal String sseServerUrl = "https://mcp-09724909-442f-4b85.api-inference.modelscope.cn";
@PostConstruct
publicvoidinit(){
try {
McpClientTransport transport = new HttpClientSseClientTransport(sseServerUrl);
mcpClient = McpClient.sync(transport)
.requestTimeout(Duration.ofSeconds(20L))
.capabilities(ClientCapabilities.builder()
.roots(true)
.sampling()
.build())
.build();
mcpClient.initialize();
} catch (Exception e) {
thrownew RuntimeException("初始化MCP客戶端對象失敗", e);
}
}
@PreDestroy
publicvoiddestroy(){
if (mcpClient != null) {
mcpClient.closeGracefully();
}
}
@Bean
public String getToolList(){
ListToolsResult toolsResult = mcpClient.listTools();
for (Tool tool:toolsResult.tools()) {
System.out.println(tool.name());
System.out.println(tool.description());
System.out.println(tool.inputSchema());
}
return null;
}
即初始化構(gòu)建了MCP客戶端和實(shí)例關(guān)閉動(dòng)作,通過listTools即可得到該MCP-Server中的所有資源,如下示例:
其中inputSchema即用來作為functionCall中tools中的對象,獲取到該描述后,我們就可以在調(diào)用functionCall的時(shí)候這樣填寫tools中每個(gè)方法中的properties。
3. McpSyncClient來發(fā)起調(diào)用
@Bean
public String getToolList(){
ListToolsResult toolsResult = mcpClient.listTools();
for (Tool tool:toolsResult.tools()) {
System.out.println(tool.name());
System.out.println(tool.description());
System.out.println(tool.inputSchema());
Map<String, Object> schemaMap = new HashMap<>();
schemaMap.put("type", "object");
Map<String, Object> properties = new HashMap<>();
if (tool.inputSchema().properties() != null) {
tool.inputSchema().properties().forEach((key, value) -> {
properties.put(key, value);
});
}
schemaMap.put("properties", properties);
if (tool.inputSchema().required() != null && !tool.inputSchema().required().isEmpty()) {
schemaMap.put("required", tool.inputSchema().required());
}
Map<String,Object> parameters = new HashMap<>();
parameters.put("url","https://www.shuaijiao.cn/news/view/68320.html");
CallToolResult toolResult = mcpClient.callTool(new CallToolRequest("fetch", parameters));
System.out.println(extractTextContent(toolResult));
}
return null;
}
private String extractTextContent(CallToolResult toolResult){
StringBuilder resultText = new StringBuilder();
toolResult.content().forEach(content -> {
if (content instanceof TextContent) {
resultText.append(((TextContent) content).text());
}
});
return resultText.toString();
}
上述示例中,通過mock了一個(gè)parameters作為functionCall返回的格式,傳給CallToolRequest中,并且顯示的指定調(diào)用的方法是fetch方法,運(yùn)行結(jié)果如下:
通過上述過程發(fā)現(xiàn),其實(shí)調(diào)用MCP的SDK去做封裝和開發(fā)也是比較簡單的,只需調(diào)用資源獲取接口,拿到工具列表傳給functionCall,并且將LLM分析出的結(jié)果拿到后來調(diào)用MCP的call方法即可。
1. 1.0.0-M6版本SSE方式報(bào)錯(cuò)Caused by: java.lang.IllegalStateException: Multiple tools with the same name
解決方式:在啟動(dòng)類上排除SseHttpClientTransportAutoConfiguration 即可
。(換成1.0.0-M7版本后沒有出現(xiàn))。
2. SSE方式不帶/SSE的時(shí)候報(bào)錯(cuò) 服務(wù)找不到,本質(zhì)原因是地址url被改寫
解決方式:Spring-AI的話升級1.0.0-M6到1.0.0-M7版本,并且單獨(dú)引入io.modelcontextprotocol.sdk 0.9的版本。
3. 啟動(dòng)時(shí)報(bào)錯(cuò)MCP服務(wù)連接超時(shí)
試了很久發(fā)現(xiàn),本身一些MCP服務(wù)是做了IP安全證書等等,localhost本身ping不通,可嘗試換一些公開可訪問的url嘗試,如本文示例中的fetch網(wǎng)頁內(nèi)容的url。
本文針對框架和原生SDK的調(diào)用都做了總結(jié),那如何選型,或者說如何取舍兩種不同技術(shù)路線來支撐平臺(tái)型研發(fā)呢?
如果我們自己開發(fā)一套這樣的流程,其中包括意圖識別、工具描述獲取、functionCall入?yún)⑵唇印LM調(diào)用結(jié)果獲取、工具調(diào)用、上下文記憶配置……豈不是需要投入很多的人力成本?甚至說是Spring-AI-Alibaba開源的openManus,如果我們自己開發(fā)工作量可想而知,從使用者的視角出發(fā),采用框架固然可以很快的構(gòu)建出自己的個(gè)人超級Agent,但從平臺(tái)開發(fā)視角出發(fā),我們?nèi)匀恍枰{(diào)用底層sdk去更好的服務(wù)上層,比如現(xiàn)在很多的AI助理平臺(tái)中,一個(gè)Agent不僅僅是支持調(diào)用工具,還支持其他Agent的嵌套調(diào)用、如RAG知識檢索、工作流調(diào)用等等,如果采用框架來開發(fā)。
一方面需要很大的成本將自己平臺(tái)的調(diào)用姿勢對接成框架底層的調(diào)用,如將平臺(tái)某個(gè)Agent關(guān)聯(lián)的其他Agent及工具都要抽象成適合框架的工具,才能發(fā)起調(diào)用;另一方面,框架調(diào)用會(huì)屏蔽很多處理細(xì)節(jié),比如一次規(guī)劃中到底使用了哪些工具、當(dāng)前執(zhí)行到哪個(gè)工具,此工具的輸出是什么等等,都是需要展示給用戶的,框架層面難以將很多個(gè)性化的細(xì)節(jié)一一暴露給用戶。
因此面向AI平臺(tái)研發(fā)工程,個(gè)人覺得還是需要用原生的方式去打磨平臺(tái)能力,一些如openMauns等復(fù)雜流程,如果在框架中后續(xù)暴露出接口,可以結(jié)合框架做調(diào)用,而不是完全依賴框架。
本文針對MCP客戶端的開發(fā),詳細(xì)介紹了如何通過Spring-AI框架和原生SDK調(diào)用MCP服務(wù),并對使用過程中遇到的一些問題進(jìn)行了記錄。整體來說,框架調(diào)用簡單快捷,適合快速構(gòu)建應(yīng)用;而原生SDK則提供更靈活的控制,適合平臺(tái)級開發(fā)。
本文所實(shí)踐的示例僅基于一次調(diào)用過程,但MCP真正發(fā)揮其“鏈接模型和數(shù)據(jù)”意義可能需要體現(xiàn)在規(guī)劃反思類場景中,最近也在做此類場景的研發(fā)模式探索,大致上有幾個(gè)方向:
1. 提示詞中做規(guī)劃打標(biāo),每次結(jié)束后重新思考需要調(diào)用什么工具
2. 規(guī)劃本身作為一個(gè)工具去每次調(diào)用
3. Spring-AI-Alibaba發(fā)布的openManus開源代碼的設(shè)計(jì)。后續(xù)將在新的文章中做分享。
文章轉(zhuǎn)載自:MCP客戶端調(diào)用看這一篇就夠了(Java版)
RESTful Web API 設(shè)計(jì)中要避免的 6 個(gè)常見錯(cuò)誤
深入解析API Gateway:微服務(wù)架構(gòu)中的關(guān)鍵組件及其重要功能
REST API設(shè)計(jì)開源工具:值得推薦的10+款
實(shí)測:阿里云百煉上線「全周期 MCP 服務(wù)」,AI 工具一站式托管
使用.Net構(gòu)建一個(gè)RESTful Web API
如何獲取 Seeed 開放平臺(tái) API Key 密鑰(分步指南)
使用LoRA(低秩適應(yīng))微調(diào)大型語言模型的實(shí)用技巧
醫(yī)療機(jī)構(gòu)如何防范API漏洞威脅
使用API自動(dòng)化實(shí)驗(yàn)室流程 [附示例指南]