SpringAI之MCP 服务端

SpringAI之MCP 服务端
MCP 概述和客户端示例代码可查看

https://blog.ZEEKLOG.net/weixin_45948519/article/details/157142319?spm=1011.2415.3001.5331

SpringAI官方文档地址

https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html

目录

1、stdio 服务端(使用高德API查询天气信息)

1.1 创建stdio工具

1.2 测试

2、SSE 服务端(使用百度翻译API翻译语言)

2.1 创建工具服务端

2.2 测试

3、Steam http 服务端 (邮件发送MCP)

3.1 创建工具服务端

3.2 测试

4、Steam http 使用 SearXNG 实现联网MCP

4.1 环境准备

4.2 创建MCP工具服务端

4.3 测试


1、stdio 服务端(使用高德API查询天气信息)

这里示例一个调用高德API获取天气信息的MCP

高德文档地址 https://lbs.amap.com/api/webservice/guide/api-advanced/weatherinfo

1.1 创建stdio工具

官方文档地址  https://docs.spring.io/spring-ai/reference/api/mcp/mcp-stdio-sse-server-boot-starter-docs.html

  • 创建一个springboot项目 引入pom依赖
<properties> <java.version>17</java.version> <spring-ai.version>1.1.2</spring-ai.version> </properties> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server</artifactId> </dependency> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring-ai.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
  • 创建一个service 工具类
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; import java.util.List; @Service public class WeatherService { private final String KEY; private final RestClient restClient; public WeatherService() { this.restClient = RestClient.builder().build(); this.KEY = System.getenv("AMAP_KEY"); } public static void main(String[] args) { WeatherService weatherService = new WeatherService(); String code = weatherService.getAdCode("深圳"); System.out.println(code); String weather = weatherService.getWeather(code); System.out.println(weather); } @Tool(description = "根据城市名称查询对应的城市编码") public String getAdCode(@ToolParam(description = "查询的城市,比如: 北京") String keywords) { if (keywords == null || keywords.isEmpty()) { return "请输入城市名称"; } String url = String.format("https://restapi.amap.com/v3/config/district?keywords=%s&subdistrict=0&key=%s", keywords, KEY); CodeResponse response = this.restClient.get().uri(url).retrieve().body(CodeResponse.class); if (response == null || response.districts() == null || response.districts().isEmpty()) { return String.format("查询不到%s对应的城市编码", keywords); } return response.districts().get(0).adcode(); } @Tool(description = "根据城市编码查询对应城市的天气信息") public String getWeather(@ToolParam(description = "查询的城市编码,比如: 110101") String adCode) { String url = String.format("https://restapi.amap.com/v3/weather/weatherInfo?city=%s&key=%s", adCode, KEY); WeatherResponse response = this.restClient.get().uri(url).retrieve().body(WeatherResponse.class); if (response == null || response.lives() == null || response.lives().isEmpty()) { return String.format("查询不到%s对应城市的天气信息", adCode); } WeatherResponse.Live live = response.lives().get(0); return String.format("当前城市: %s, 天气: %s, 温度: %s 摄氏度, 风向: %s, 风力: %s, 湿度: %s, 更新时间: %s", live.city, live.weather, live.temperature, live.winddirection, live.windpower, live.humidity, live.reporttime); } @JsonIgnoreProperties(ignoreUnknown = true) public record CodeResponse(@JsonProperty("districts") List<Districts> districts) { @JsonIgnoreProperties(ignoreUnknown = true) public record Districts(@JsonProperty("adcode") String adcode, @JsonProperty("name") String name) { } } @JsonIgnoreProperties(ignoreUnknown = true) public record WeatherResponse(@JsonProperty("lives") List<Live> lives) { @JsonIgnoreProperties(ignoreUnknown = true) public record Live(@JsonProperty("province") String province, @JsonProperty("city") String city, @JsonProperty("weather") String weather, @JsonProperty("temperature") String temperature, @JsonProperty("winddirection") String winddirection, @JsonProperty("windpower") String windpower, @JsonProperty("humidity") String humidity, @JsonProperty("reporttime") String reporttime ) { } } }
  • 注册工具
import com.weimin.springaimcpstdioserver.server.WeatherService; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.method.MethodToolCallbackProvider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class SpringAiMcpStdioServerApplication { public static void main(String[] args) { SpringApplication.run(SpringAiMcpStdioServerApplication.class, args); } @Bean public ToolCallbackProvider weatherTools(WeatherService weatherService) { return MethodToolCallbackProvider.builder().toolObjects(weatherService).build(); } }
  • 然后使用maven 打包 mvn package

1.2 测试

  • 使用java代码进行测试
import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.McpSyncClient; import io.modelcontextprotocol.client.transport.ServerParameters; import io.modelcontextprotocol.client.transport.StdioClientTransport; import io.modelcontextprotocol.json.McpJsonMapper; import io.modelcontextprotocol.spec.McpSchema; import java.util.Map; public class ClientStdio { public static void main(String[] args) { ServerParameters stdioParams = ServerParameters.builder("java") .args( "-Dfile.encoding=UTF-8", "-Dsun.jnu.encoding=UTF-8", "-jar", // 这是相对于项目的路径 "spring-ai-mcp-stdio-server/target/spring-ai-mcp-stdio-server-0.0.1-SNAPSHOT.jar" ) .env(Map.of("AMAP_KEY", "换成你的API_KEY")) .build(); StdioClientTransport transport = new StdioClientTransport(stdioParams, McpJsonMapper.createDefault()); McpSyncClient client = McpClient.sync(transport).build(); client.initialize(); McpSchema.ListToolsResult toolsList = client.listTools(); System.out.println("所有的工具 = " + toolsList); McpSchema.CallToolResult callToolResult = client.callTool( McpSchema.CallToolRequest.builder() .name("getAdCode") .arguments(Map.of("keywords", "深圳")) .build()); String code = ((McpSchema.TextContent) callToolResult.content().get(0)).text(); System.out.println("得到的城市code = " + code); McpSchema.CallToolResult weatherResult = client.callTool( McpSchema.CallToolRequest.builder() .name("getWeather") .arguments(Map.of("adCode", code)) .build()); String weather = ((McpSchema.TextContent) weatherResult.content().get(0)).text(); System.out.println("天气信息 = " + weather); client.closeGracefully(); } }
  • 使用 Cherry Studio 进行测试
  • 选择从json导入工具
{ "mcpServers": { "weather-server": { "command": "java", "args": [ "-Dfile.encoding=UTF-8", "-Dsun.jnu.encoding=UTF-8", "-jar", "D:\\学习\\spring-ai-mcp-stdio-server\\target\\spring-ai-mcp-stdio-server-0.0.1-SNAPSHOT.jar" ], "env": { "AMAP_KEY": "换成你的API_KEY" } } } }
  • 启用工具,并对话进行测试

2、SSE 服务端(使用百度翻译API翻译语言)

SSE服务端的示例创建一个翻译的服务端,用来翻译语言,这里调用的是百度翻译平台的API

百度翻译平台地址  https://api.fanyi.baidu.com/product/113

2.1 创建工具服务端

  • 创建spring web项目后引入依赖
<properties> <java.version>17</java.version> <spring-ai.version>1.1.2</spring-ai.version> </properties> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> <version>${spring-ai.version}</version> </dependency>
  • 配置文件配置
server.port=8086 spring.ai.mcp.server.protocol=sse spring.ai.mcp.server.sse-endpoint=/sse
  • 创建工具类
 import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Slf4j @Service public class TranslationService { private static final String appId = "123455"; private static final String securityKey = "44444444"; private final RestClient restClient; public TranslationService() { this.restClient = RestClient.builder().build(); } public static void main(String[] args) throws Exception { TranslationService translationService = new TranslationService(); String result = translationService.translate("hello world", Language.EN, Language.ZH); System.out.println(result); } @Tool(description = "将句子或词从一种语言翻译为另一种语言") public String translate( @ToolParam(description = "需要翻译的句子或词") String text, @ToolParam(description = "句子当前语言标识,如果不清楚属于什么语言请填入 auto") Language languageFrom, @ToolParam(description = "将要翻译的语言标识") Language languageTo) throws Exception { log.info("开始翻译 {} -> {} {}", languageFrom.getDescription(), languageTo.getDescription(), text); Map<String, String> params = buildTranslateParams(text, languageFrom.getCode(), languageTo.getCode()); StringBuilder urlBuilder = new StringBuilder("https://fanyi-api.baidu.com/api/trans/vip/translate"); urlBuilder.append("?"); params.forEach((k, v) -> { log.info("参数: {} = {}", k, v); urlBuilder.append(k).append("=").append(v).append("&"); }); urlBuilder.deleteCharAt(urlBuilder.length() - 1); String url = urlBuilder.toString(); log.info("请求地址: {}", url); TranslationResult result = this.restClient.get().uri(url).retrieve().body(TranslationResult.class); log.info("响应结果: {}", result); if (result != null && result.trans_result != null && !result.trans_result.isEmpty()) { return result.trans_result.stream().map(TranslationMsg::dst).collect(Collectors.joining(" ")); } return "转换失败"; } private Map<String, String> buildTranslateParams(String query, String from, String to) throws Exception { Map<String, String> params = new HashMap<String, String>(); params.put("q", query); params.put("from", from); params.put("to", to); params.put("appid", appId); // 随机数 String salt = String.valueOf(System.currentTimeMillis()); params.put("salt", salt); // 签名 String src = appId + query + salt + securityKey; // 加密前的原文 params.put("sign", md5(src)); return params; } public static String md5(String str) throws NoSuchAlgorithmException { MessageDigest messageDigest = MessageDigest.getInstance("MD5"); messageDigest.update(str.getBytes(StandardCharsets.UTF_8)); byte[] resultByteArray = messageDigest.digest(); return byteArrayToHex(resultByteArray); } private static final char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; private static String byteArrayToHex(byte[] byteArray) { char[] resultCharArray = new char[byteArray.length * 2]; int index = 0; for (byte b : byteArray) { resultCharArray[index++] = hexDigits[b >>> 4 & 0xf]; resultCharArray[index++] = hexDigits[b & 0xf]; } return new String(resultCharArray); } public record TranslationResult(String from, String to, List<TranslationMsg> trans_result) { } public record TranslationMsg(String src, String dst) { } @Getter public enum Language { AUTO("auto", "自动检测"), ZH("zh", "中文"), EN("en", "英语"), YUE("yue", "粤语"), WYW("wyw", "文言文"), JP("jp", "日语"), KOR("kor", "韩语"), FRA("fra", "法语"), SPA("spa", "西班牙语"), TH("th", "泰语"), ARA("ara", "阿拉伯语"), RU("ru", "俄语"), PT("pt", "葡萄牙语"), DE("de", "德语"), IT("it", "意大利语"), EL("el", "希腊语"), NL("nl", "荷兰语"), PL("pl", "波兰语"), BUL("bul", "保加利亚语"), EST("est", "爱沙尼亚语"), DAN("dan", "丹麦语"), FIN("fin", "芬兰语"), CS("cs", "捷克语"), ROM("rom", "罗马尼亚语"), SLO("slo", "斯洛文尼亚语"), SWE("swe", "瑞典语"), HU("hu", "匈牙利语"), CHT("cht", "繁体中文"), VIE("vie", "越南语"); private final String code; private final String description; Language(String code, String description) { this.code = code; this.description = description; } public static Language fromCode(String code) { for (Language lang : values()) { if (lang.code.equals(code)) { return lang; } } throw new IllegalArgumentException("Unknown language code: " + code); } @Override public String toString() { return code; } } }
  • 注册工具
import com.weimin.springaimcpsseserver.service.TranslationService; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.method.MethodToolCallbackProvider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class SpringAiMcpSseServerApplication { public static void main(String[] args) { SpringApplication.run(SpringAiMcpSseServerApplication.class, args); } @Bean public ToolCallbackProvider weatherToolCallbackProvider(TranslationService translationService) { return MethodToolCallbackProvider.builder().toolObjects(translationService).build(); } }

2.2 测试

  • 启动springboot项目 使用 Cherry Studio 进行添加MCP
  • 使用 Cherry Studio 进行测试

3、Steam http 服务端 (邮件发送MCP)

SpringAI文档地址  https://docs.spring.io/spring-ai/reference/api/mcp/mcp-streamable-http-server-boot-starter-docs.html

这里示例的是一个简单的邮件发送MCP示例,使用的是163邮箱,要提前开启SMTP

3.1 创建工具服务端

  • 导入依赖
<properties> <java.version>17</java.version> <spring-ai.version>1.1.2</spring-ai.version> </properties> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> <version>${spring-ai.version}</version> </dependency> <!-- 邮件依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <!-- md转html --> <dependency> <groupId>com.vladsch.flexmark</groupId> <artifactId>flexmark-all</artifactId> <version>0.64.8</version> </dependency>
  • 配置文件配置
# server.port=8081 # spring.ai.mcp.server.protocol=streamable #使用webflux需要配置这个 #spring.main.web-application-type=reactive spring.ai.mcp.server.streamable-http.mcp-endpoint=/mcp # # 邮件服务器地址 spring.mail.host=smtp.163.com # 端口 spring.mail.port=465 # 邮箱账号 [email protected] # 授权码 spring.mail.password=YPuKQXPuGACgiK2M # 编码 spring.mail.default-encoding=UTF-8 # SSL配置 spring.mail.properties.mail.smtp.ssl.enable=true spring.mail.properties.mail.smtp.auth=true
  • 定义一个邮箱工具
import com.vladsch.flexmark.html.HtmlRenderer; import com.vladsch.flexmark.parser.Parser; import com.vladsch.flexmark.util.data.MutableDataSet; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; @Slf4j @Service @RequiredArgsConstructor public class EmailService { private final JavaMailSender mailSender; @Tool(description = "给指定邮箱发送邮件信息") public String sendSimpleEmail( @ToolParam(description = "收件人邮箱") String[] to, @ToolParam(description = "发送邮件的标题/主题") String subject, @ToolParam(description = "发送邮件的消息/正文内容") String content, @ToolParam(description = "邮件类型 有 MARKDOWN、HTML、TEXT 三种类型") EmailType emailType) throws MessagingException { log.info("开始发送邮件 {} {} {} {}", to, subject, content, emailType); if (mailSender instanceof JavaMailSenderImpl senderImpl) { String username = senderImpl.getUsername(); if (username != null) { // 创建MIME消息对象(支持复杂内容) MimeMessage mimeMessage = mailSender.createMimeMessage(); // 创建MimeMessageHelper(true表示支持多部分消息,UTF-8解决中文乱码) MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8"); helper.setFrom(username); helper.setTo(to); helper.setSubject(subject); if (emailType == EmailType.MARKDOWN) { helper.setText(convertToHtml(content), true); } else if (emailType == EmailType.HTML) { helper.setText(content, true); } else { helper.setText(content); } mailSender.send(mimeMessage); return "邮件发送成功"; } } return "参数错误,发送失败"; } // @Tool(description = "将Markdown格式的文本转换为HTML格式") public static String convertToHtml( @ToolParam(description = "Markdown格式的文本") String markdownStr) { log.info("开始将Markdown格式的文本转换为HTML格式 {}", markdownStr); MutableDataSet dataSet = new MutableDataSet(); return HtmlRenderer.builder(dataSet).build() .render(Parser.builder(dataSet).build() .parse(markdownStr)); } public enum EmailType { MARKDOWN, HTML, TEXT } }
  • 注册工具
import com.weimin.springaimcpstreamserver.service.EmailService; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.method.MethodToolCallbackProvider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class SpringAiMcpStreamServerApplication { public static void main(String[] args) { SpringApplication.run(SpringAiMcpStreamServerApplication.class, args); } @Bean public ToolCallbackProvider emailTools(EmailService emailService) { return MethodToolCallbackProvider.builder().toolObjects(emailService).build(); } }

3.2 测试

  • 使用 Cherry Studio 进行工具设置
  • 测试

4、Steam http 使用 SearXNG 实现联网MCP

4.1 环境准备

SearXNG 官方文档 https://docs.searxng.org/

Docker安装文档地址 https://docs.searxng.org/admin/installation-docker.html#installation-container
DockerHub 地址 https://hub.docker.com/r/searxng/searxng
  • 使用docker 安装SearXNG
docker pull searxng/searxng:latest mkdir -p ./searxng/config/ ./searxng/data/ cd ./searxng/ docker run --name searxng -d \ -p 8888:8080 \ -v "./config/:/etc/searxng/" \ -v "./data/:/var/cache/searxng/" \ docker.io/searxng/searxng:latest
  • 使用浏览器验证访问 http://127.0.0.1:8888
  • 修改配置文件 config/settings.yml 开启输出json格式
  • 根据API构建MCP api文件地址 https://docs.searxng.org/dev/search_api.html
  • 发送链接测试API
curl 'http://127.0.0.1:8888/search?q=人工智能&format=json&engines=bing&categories=general&language=zh-CN'
  • 参数示例
  • q 搜索词,比如这里的搜索词是人工智能
  • format 结果的输出格式,需要在配置文件开启,这里使用的是json
  • engines 指定使用搜索引擎,逗号拼接,需要在配置文件开启。可在文档查看枚举https://docs.searxng.org/user/configured_engines.html#configured-engines
  • categories 指定类别,这里使用的是综合搜索,可指定为新闻news等
  • language 指定输出的语言,这里是中文简体。枚举可在链接查看 https://github.com/searxng/searxng/blob/master/searx/sxng_locales.py

4.2 创建MCP工具服务端

  • 配置配置文件相关信息
# server.port=8081 # spring.ai.mcp.server.protocol=streamable #使用webflux需要配置这个 #spring.main.web-application-type=reactive spring.ai.mcp.server.streamable-http.mcp-endpoint=/mcp
  • 构建工具服务类
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; import java.util.List; import java.util.stream.Collectors; @Slf4j @Service public class SearXNGService { private final RestClient restClient; public SearXNGService() { this.restClient = RestClient.builder().baseUrl("http://127.0.0.1:8888").build(); } @Tool(description = "根据关键字联网搜索") public String search(@ToolParam(description = "搜索关键字") String keyword) { String url = "/search?q=" + keyword + "&format=json&engines=bing&categories=general&language=zh-CN"; log.info("开始查询:{}", url); Response response = restClient.get().uri(url).retrieve().body(Response.class); if (response == null || response.results() == null || response.results().isEmpty()) { return "没有找到结果"; } String result = response.results().stream().map((item) -> String.format("标题: %s\n内容: %s\n链接: %s\n搜索引擎: %s\n\n", item.title(), item.url(), item.content(), item.engine())).collect(Collectors.joining("\n")); log.info("查询结果:{}", result); return result; } @JsonIgnoreProperties(ignoreUnknown = true) public record Response(@JsonProperty("query") String query, @JsonProperty("number_of_results") Integer number_of_results, @JsonProperty("results") List<Results> results) { @JsonIgnoreProperties(ignoreUnknown = true) public record Results(@JsonProperty("url") String url, @JsonProperty("title") String title, @JsonProperty("content") String content, @JsonProperty("engine") String engine ) { } } }
  • 注册工具
import com.weimin.springaimcpstreamserver.service.SearXNGService; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.method.MethodToolCallbackProvider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class SpringAiMcpStreamServerApplication { public static void main(String[] args) { SpringApplication.run(SpringAiMcpStreamServerApplication.class, args); } @Bean public ToolCallbackProvider emailTools(SearXNGService searXNGService) { return MethodToolCallbackProvider.builder().toolObjects(searXNGService).build(); } }

4.3 测试

  • 使用 Cherry Studio 测试

Read more

Gitea 服务器搭建:如何在公司服务器搭建 Gitea 环境实现代码仓库私有化托管

Gitea 服务器搭建:如何在公司服务器搭建 Gitea 环境实现代码仓库私有化托管

Gitea 服务器搭建:如何在公司服务器搭建 Gitea 环境实现代码仓库私有化托管 在当今软件开发高速迭代的时代,企业对「私有化代码仓库托管」的需求愈发迫切。通过在公司服务器部署 Gitea,不仅可以实现“Git 私有化托管”与“代码安全隔离”,还能够结合 CI/CD 流水线快速交付,彻底摆脱公有云成本与权限风控的双重烦恼。本教程将手把手教您在 Ubuntu 20.04 环境下,完成从服务器准备、依赖安装到域名配置、SSL 加密、自动化运维的一整套 “Gitea 服务器搭建” 方案,确保您能够快速上手 “Gitea 安装教程” 并实现“企业级 Git 自托管”最佳实践。 本文详细介绍在公司服务器上从零开始部署 Gitea 服务的全流程。全程使用 Ubuntu 20.04 LTS

By Ne0inhk
ClaudeCode武装三件套:Ghostty + Yazi + Lazygit 打造高效开发环境

ClaudeCode武装三件套:Ghostty + Yazi + Lazygit 打造高效开发环境

引言:多终端切换之痛 在终端里深度使用 Claude Code 一段时间后,你很快会遇到一个现实问题: 场景:前后端需求同时开发,一个终端跑 Claude Code,另一个查看日志,还需要随时管理文件、提交代码……多个终端窗口切来切去,既麻烦又不直观,完全看不到各终端的实时状态。 以前我的解法是 tmux。但 tmux 毕竟是上个世纪的工具:命令多、记不住,界面也不美观,感觉像在用古董。 直到我在 X 上看到 Claude Code 之父 Boris 的推文,他在用 Ghostty。我去试了试,然后又发现了 Yazi 和 Lazygit,这套组合彻底改变了我的终端工作流。 今天我们就来聊这个终端三件套: * 🖥️ Ghostty:现代化终端模拟器,原生支持多标签、分屏 * 📂 Yazi:用

By Ne0inhk
从DeepSeek-R1爆火看开源大模型推理优化:我在脉脉找到的实战方案

从DeepSeek-R1爆火看开源大模型推理优化:我在脉脉找到的实战方案

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 文章目录: * 【前言】 * 一、场景痛点直击:两个行业的共性困境与差异化难题 * 1. 电商智能客服场景(日均请求10万+) * 2. 金融智能咨询场景(日均请求3万+) * 二、实战突破:分场景落地优化方案(附完整代码+流程图) * 1. 核心优化架构总览(流程图) * 2. 分场景核心代码实现(新增4个关键代码片段) * (1)量化分级实现(适配金融场景精度需求) * (2)多租户隔离与共享实例实现(适配电商、金融双场景) * (3)边缘节点轻量化部署代码(适配电商峰值卸载) * (4)动态批处理与负载调度优化(核心优化代码) * 3. 优化效果对比表(分场景) * 三、脉向AI核心价值:技术人破圈的“

By Ne0inhk