当前位置: 首页 > article >正文

【NexLM 开源系列】如何封装多个大模型 API 调用

🌟 在这系列文章中,我们将一起探索如何搭建一个支持大模型集成项目 NexLM 的开发过程,从 架构设计代码实战,逐步搭建一个支持 多种大模型(GPT-4、DeepSeek 等)一站式大模型集成与管理平台,并集成 认证中心、微服务、流式对话 等核心功能。

🔍 从架构设计到代码实现,一起探讨如何应对不同技术挑战,最终打造出高效、可扩展的大模型平台,目前项目基础架构已经搭建完成。

系列目录规划:

  1. NexLM:从零开始打造你的专属大模型集成平台
  2. Spring Boot + OpenAI/DeepSeek:如何封装多个大模型 API 调用
  3. 微服务 + 认证中心:如何保障大模型 API 的安全调用
  4. 支持流式对话 SSE & WebSocket:让 AI 互动更丝滑
  5. 缓存与性能优化:提高 LLM API 响应速度
  6. NexLM 开源部署指南(Docker)

第二篇:Spring Boot + OpenAI/DeepSeek:如何封装多个大模型 API 调用

🎯 如何让你的项目支持 OpenAI、DeepSeek、本地大模型等多种 LLM?
🎯 如何封装 API,做到扩展性强、调用方便?
🎯 这篇文章带你一步步搭建通用 LLM 调用服务!

为什么要封装 LLM API?

在大模型开发中,我们往往需要 支持多个模型,例如:

  • GPT-4(OpenAI) :行业最强模型之一,但 API 价格较贵
  • DeepSeek:性价比高,部分场景效果接近 GPT-4
  • 本地大模型(如 ChatGLM) :适合私有化部署,数据安全

如果在代码里直接写多个 API 请求,会导致 代码冗余、扩展性差。我们需要一个 统一封装的 LLM API 调用层,让项目可以随时切换模型,甚至同时支持多个模型。

效果展示

在这里插入图片描述
DeepSeek API 调用交互稍微有点点耗时…目前还没有支持流式输出效果(下一期优化),代码仓库地址:https://github.com/pitt1997/NexLM

代码实现

实现简单的 AI 接口调用还是比较简单,定义接口和具体接口的调用逻辑即可,下面是代码演示。

在这里插入图片描述

1)Controller 层

ChatController 定义一个页面的路由地址

@RestController
public class ChatController {/*** chat页面*/@GetMapping("/auth/chat")public ModelAndView chat(ModelAndView modelAndView) {modelAndView.setViewName("ftl/chat");return modelAndView;}
}

ChatApiController 定义一个 API 交互接口。

@RestController
@RequestMapping("/api/ai")
public class ChatApiController {@Autowiredprivate ChatService chatService;@Autowiredprivate JwtTokenProvider jwtTokenProvider;@PostMapping("/chat")public ResponseEntity<String> chat(@RequestBody ChatRequest request, @RequestHeader("Authorization") String token) {// TODO 测试放行(后续加上JWT票据认证)if (token.startsWith("Bearer ")) {return ResponseEntity.ok(chatService.callAIModel(request.getMessage(), request.getModelType()));}// 认证中心解析 JWT 验证权限SessionUser sessionUser = jwtTokenProvider.validateUserToken(token);if (sessionUser == null) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized");}return ResponseEntity.ok(chatService.callAIModel(request.getMessage(), request.getModelType()));}
}
2)VO 层

请求对象,主要定义输入的消息和模型的类型。

@Data
public class ChatRequest {private String message;   // 用户输入的消息private String modelType; // 选择的大模型,例如 "chatgpt" 或 "local"
}
3)Service 层

这里封装不同模型的调用实现。

@Service
public class ChatService {@Autowiredprivate OpenAIClient openAIClient;@Autowiredprivate DeepSeekClient deepSeekClient;@Autowiredprivate LocalLLMClient localLLMClient;public String callAIModel(String prompt, String modelType) {if ("chatgpt".equalsIgnoreCase(modelType)) {return openAIClient.chat(prompt);} else if ("deepseek".equalsIgnoreCase(modelType)) {return deepSeekClient.chat(prompt);} else if ("local".equalsIgnoreCase(modelType)) {return localLLMClient.chat(prompt);}return "Invalid Model Type";}
}
4)调用大模型接口 API

实现具体大模型的 API 调用逻辑。以 DeepSeek 为例,注意 DeepSeek 需要提前在官网注册密钥。(注:按照官网要求的请求参数格式进行请求即可,注意需要正确解析返回结果中的内容)。

@Component
public class DeepSeekClient {private static final String API_URL = "https://api.deepseek.com/chat/completions"; // DeepSeek API 地址private static final String API_KEY = "你的 DeepSeek API Key"; // 替换为你的 API Keyprivate static final String MODEL = "deepseek-chat"; // deepseek-v3 / deepseek-r1public String chat(String prompt) {RestTemplate restTemplate = new RestTemplate();HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);headers.add("Authorization", "Bearer " + API_KEY);Map<String, Object> body = new HashMap<>();body.put("model", MODEL); // deepseek-v3 / deepseek-r1body.put("temperature", 0.7); // 可调整温度body.put("max_tokens", 2048); // 控制回复长度List<Map<String, String>> messages = Arrays.asList(new HashMap<String, String>() {{put("role", "user");put("content", prompt);}});body.put("messages", messages);HttpEntity<Map<String, Object>> request = new HttpEntity<>(body, headers);ResponseEntity<String> response = restTemplate.exchange(API_URL, HttpMethod.POST, request, String.class);return extractContent(response.getBody());}// 解析一下大模型返回结果的json参数。private String extractContent(String responseBody) {try {ObjectMapper objectMapper = new ObjectMapper();JsonNode root = objectMapper.readTree(responseBody);return root.path("choices").get(0).path("message").path("content").asText();} catch (Exception e) {e.printStackTrace();return "Error parsing response";}}
}

OpenAI 调用逻辑基本一致。

@Component
public class OpenAIClient {private static final String API_URL = "https://api.openai.com/v1/chat/completions";private static final String API_KEY = "你的 OpenAI API Key"; // 请替换为你的 API Keypublic String chat(String prompt) {RestTemplate restTemplate = new RestTemplate();HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);headers.add("Authorization", "Bearer " + API_KEY);Map<String, Object> body = new HashMap<>();body.put("model", "gpt-4");List<Map<String, String>> messages = Arrays.asList(new HashMap<String, String>() {{put("role", "user");put("content", prompt);}});body.put("messages", messages);HttpEntity<Map<String, Object>> request = new HttpEntity<>(body, headers);ResponseEntity<String> response = restTemplate.exchange(API_URL, HttpMethod.POST, request, String.class);return response.getBody();}
}
5)调用本地模型

也可以调用本地部署的大模型(需要提前部署本地大模型,可以看我之前的文章部署方法)。

@Component
public class LocalLLMClient {private static final String LOCAL_MODEL_URL = "http://localhost:5000/api/chat";public String chat(String prompt) {RestTemplate restTemplate = new RestTemplate();Map<String, String> requestBody = new HashMap<>();requestBody.put("prompt", prompt);ResponseEntity<String> response = restTemplate.postForEntity(LOCAL_MODEL_URL, requestBody, String.class);return response.getBody();}
}

3. 前端页面(HTML + JavaScript)

一个简单的 HTML 页面,输入问题后,调用后端 API 获取大模型的回答(暂时使用 HTML 做一个演示)。

<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>AI 聊天</title><style>body {font-family: Arial, sans-serif;background: #f4f4f4;margin: 0;padding: 0;display: flex;justify-content: center;align-items: center;height: 100vh;}.chat-container {width: 500px;background: #fff;padding: 20px;border-radius: 10px;box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);}h1 {text-align: center;color: #333;}.input-group {margin: 15px 0;}input, select, button {width: 100%;padding: 10px;margin-top: 5px;border: 1px solid #ccc;border-radius: 5px;}button {background: #007bff;color: white;cursor: pointer;}button:hover {background: #0056b3;}.response {margin-top: 20px;background: #eef;padding: 10px;border-radius: 5px;min-height: 50px;}</style>
</head>
<body>
<div class="chat-container"><h1>大模型 AI 聊天</h1><div class="input-group"><label>输入你的问题:</label><input type="text" id="message" placeholder="请输入问题"></div><div class="input-group"><label>选择模型:</label><select id="modelType"><option value="chatgpt">ChatGPT</option><option value="deepseek">DeepSeek</option><option value="local">本地模型</option></select></div><button onclick="sendMessage()">发送</button><div class="response" id="response">AI 回复将在这里显示...</div>
</div><script>async function sendMessage() {const message = document.getElementById('message').value;const modelType = document.getElementById('modelType').value;// 你的 JWT 令牌const token = getCookie('JSESSIONID'); // 这里应从登录系统获取console.log('JWT Token:', token); // 打印 token 值const response = await fetch('/web/api/ai/chat', {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': `Bearer B6FC7391D7856A16391F9860DA5DA3B8}`},body: JSON.stringify({ message, modelType })});const text = await response.text();document.getElementById('response').innerText = text;}function getCookie(name) {const cookies = document.cookie.split(';');for (let cookie of cookies) {const [cookieName, cookieValue] = cookie.trim().split('=');if (cookieName === name) {return decodeURIComponent(cookieValue);}}return null;}
</script>
</body>
</html>

4. 认证中心(JWT 解析示例)

接口调用时应当校验当前用户的票据(登录时会存储用户会话票据信息)。

        // 认证中心解析 JWT 验证权限SessionUser sessionUser = jwtTokenProvider.validateUserToken(token);if (sessionUser == null) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized");}

5. 效果测试

🔥 现在,你的 AI 聊天前后端已完成!

  1. 直接访问登录 http://localhost:8080/web/auth/login 页面
  2. 输出用户名/密码:admin/123456
  3. 跳转大模型页面,选择对应大模型,开始对话

6. 总结

  • 架构:微服务 + 认证中心 + API 网关 + 本地/远程大模型
  • Java 代码:完整的 Controller、Service、API 调用示例
  • 前端:简单 HTML + JS 渲染
  • 认证:JWT 校验

这样,你的系统可以支持用户认证,并调用本地或第三方大模型进行 AI 交互。

结语

本篇文章,我们介绍了 如何封装多个 LLM(大模型) API 调用

但是,目前的 对话是一次性返回的,后续我们将完善 微服务 + 认证中心:如何保障大模型 API 的安全调用, 并且支持流式对话(SSE)增加 WebSocket 实时消息实现 实时输出 AI 回复!敬请期待!

📌 下一章预告:SSE + WebSocket 实现流式对话!


📢 你对这个项目感兴趣吗?欢迎 Star & 关注! 📌 GitHub 项目地址

相关文章:

【NexLM 开源系列】如何封装多个大模型 API 调用

&#x1f31f; 在这系列文章中&#xff0c;我们将一起探索如何搭建一个支持大模型集成项目 NexLM 的开发过程&#xff0c;从 架构设计 到 代码实战&#xff0c;逐步搭建一个支持 多种大模型&#xff08;GPT-4、DeepSeek 等&#xff09; 的 一站式大模型集成与管理平台&#xff…...

Git和GitHub基础教学

文章目录 1. 前言2. 历史3. 下载安装Git3.1 下载Git3.2 安装Git3.3 验证安装是否成功 4. 配置Git5. Git基础使用5.1 通过Git Bash使用5.1.1 创建一个新的仓库。5.1.1.1 克隆别人的仓库5.1.1.2 自己创建一个本地仓库 5.1.2 管理存档 5.2 通过Visual Studio Code使用 6. Git完成远…...

笔记六:单链表链表介绍与模拟实现

在他一生中&#xff0c;从来没有人能够像你们这样&#xff0c;以他的视角看待这个世界。 ---------《寻找天堂》 目录 文章目录 一、什么是链表&#xff1f; 二、为什么要使用链表&#xff1f; 三、 单链表介绍与使用 3.1 单链表 3.1.1 创建单链表节点 3.1.2 单链表的头插、…...

坐落于杭州的电商代运营公司品融电商

坐落于杭州的电商代运营公司品融电商 在中国电商行业蓬勃发展的浪潮中&#xff0c;品融电商&#xff08;PINKROON&#xff09;作为一家扎根杭州的新锐品牌管理公司&#xff0c;凭借其独特的全域增长方法论和实战经验&#xff0c;迅速崛起为行业标杆。自2020年成立以来&#x…...

微前端之 Garfish.js 的基础使用教程和进阶配置

前言 在现代前端开发中&#xff0c;微前端架构逐渐成为一种流行的解决方案。它允许将大型应用拆分成多个小型独立的子应用&#xff0c;从而提高开发效率和可维护性。Garfish.js 是一个强大的微前端框架&#xff0c;可以帮助我们轻松实现这一架构。在本文中&#xff0c;通过一个…...

图像的特征

图像的特征主要包括以下几类&#xff1a; 1. 颜色特征&#xff1a; 直方图&#xff1a;描述图像中颜色的分布。 颜色矩&#xff1a;通过颜色的均值、方差等统计量表示颜色分布。 主色调&#xff1a;图像中占主导地位的颜色。 2. 纹理特征&#xff1a; 灰度共生矩阵&#xff0…...

Spring上下文工具类

文章目录 获取ip地址请求上下文相关Spring上下文获取Bean对象 获取ip地址 public class IpUtils {private IpUtils() {}/*** 获取请求ip地址** return {link String}*/public static String getIpAddress() {HttpServletRequest request RequestContextHolderUtils.getReques…...

JSONUtil InvocationTargetException: null

使用JSONUtil时候报错&#xff0c;一般这时候你检查下自己代码里是不是重写了get或者set InvocationTargetException 是 Java 中的一个异常&#xff0c;通常在使用反射&#xff08;Reflection&#xff09;或动态代理&#xff08;Dynamic Proxy&#xff09;时抛出。它表示在调用…...

高压为什么cover不住低压的hold问题

常规下我们认为hold问题常发生在高压下&#xff0c;但很多情况下高压的hold无法cover低压的hold hold slack (tlaunch -tcapture) (tcqtcomb) -thold -tuncertainty (tlaunch -tcapture):代表时钟skew (tcqtcomb)&#xff1a;代表data path的长度 thold&#xff1a;代表查表…...

【算法学习之路】8.栈和队列

栈和队列 前言一.简介二.题目12 前言 我会将一些常用的算法以及对应的题单给写完&#xff0c;形成一套完整的算法体系&#xff0c;以及大量的各个难度的题目&#xff0c;目前算法也写了几篇&#xff0c;题单正在更新&#xff0c;其他的也会陆陆续续的更新&#xff0c;希望大家点…...

OpenMCU(三):STM32F103 FreeRTOS移植

概述 本文主要描述了STM32F103移植FreeRTOS的简要步骤。移植描述过程中&#xff0c;忽略了Keil软件的部分使用技巧。默认读者熟练使用Keil软件。本文的描述是基于OpenMCU_RTOS这个工程&#xff0c;该工程已经下载放好了移植STM32F103 FreeRTOS的所有文件 OpenMCU_RTOS工程的愿景…...

大数据 spark hive 总结

Apache Spark 简介 是一个开源的统一分析引擎&#xff0c;专为大规模数据处理而设计。它提供了高级API&#xff0c;支持Java、Scala、Python和R语言&#xff0c;并且包含了一个优化过的执行引擎&#xff0c;该引擎支持循环计算&#xff08;如机器学习算法&#xff09;和交互式…...

小程序开发总结

今年第一次帮别人做小程序。 从开始动手到完成上线&#xff0c;一共耗时两天。AI 让写代码变得简单、高效。 不过&#xff0c;小程序和 Flutter 等大厂开发框架差距实在太大&#xff0c;导致我一开始根本找不到感觉。 第一&#xff0c;IDE 不好用&#xff0c;各种功能杂糅在…...

QLoggingCategory类使用

QLoggingCategory类使用 QLoggingCategory的概述 QLoggingCategory是Qt的日志策略类&#xff1b;可以通过声明不同的日志策略对象来输出不同的日志信息。打印信息类型如下&#xff1a;宏 Q_DECLARE_LOGGING_CATEGORY(name) 定义一个返回QLoggingCategory对象函数&#xff0c;…...

GPU加速生信分析-宏基因组MAG去污染

Deepurify利用多模态深度语言模型来过滤污染的基因组&#xff0c;从而提高了宏基因组组装基因组&#xff08;MAGs&#xff09;的质量&#xff0c;并且可以利用GPU加速。 宏基因组组装的基因组 &#xff08;MAG&#xff09; 为使用宏基因组测序数据探索微生物暗物质提供了有价值…...

数据结构(蓝桥杯常考点)

数据结构 前言&#xff1a;这个是针对于蓝桥杯竞赛常考的数据结构内容&#xff0c;基础算法比如高精度这些会在下期给大家总结 数据结构 竞赛中&#xff0c;时间复杂度不能超过10的7次方&#xff08;1秒&#xff09;到10的8次方&#xff08;2秒&#xff09; 空间限制&#x…...

从0到1入门Linux

一、常用命令 ls 列出目录内容 cd切换目录mkdir创建新目录rm删除文件或目录cp复制文件或目录mv移动或重命名文件和目录cat查看文件内容grep在文件中查找指定字符串ps查看当前进程状态top查看内存kill终止进程df -h查看磁盘空间存储情况iotop -o直接查看比较高的磁盘读写程序up…...

灰色地带规避:知识产权校验API的商标库模糊匹配算法

在反向海淘或其他电商业务场景中&#xff0c;为了规避知识产权方面的灰色地带&#xff0c;开发知识产权校验 API 并运用商标库模糊匹配算法是很有必要的。以下将详细介绍商标库模糊匹配算法的设计与实现&#xff1a; 算法设计思路 商标库模糊匹配算法的核心目标是在给定一个待匹…...

React:类组件(中)

dangerouslySetInnerHTML React写进{}内的东西&#xff0c;不允许被当作代码块解析&#xff0c;是为了防止xss攻击和代码注入 XSS&#xff08;跨站脚本攻击&#xff0c;Cross-Site Scripting&#xff09; 是一种常见的安全漏洞&#xff0c;攻击者通过注入恶意脚本到网页中&…...

第六次CCF-CSP认证(含C++源码)

第六次CCF-CSP认证 数位之和&#xff08;easy&#xff09;思路及AC代码遇到的问题 开心消消乐&#xff08;easy&#xff09;思路及AC代码 画图&#xff08;mid&#xff09;思路及AC代码 数位之和&#xff08;easy&#xff09; 题目链接 思路及AC代码 既然题目要求我们输出各位…...

SpringBoot 如何调用 WebService 接口

前言 调用WebService接口的方式有很多&#xff0c;今天记录一下&#xff0c;使用 Spring Web Services 调用 SOAP WebService接口 一.导入依赖 <!-- Spring Boot Web依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId…...

算法 之 树形dp 树的中心、重心

文章目录 重心实践题目小红的陡峭值 在树的算法中&#xff0c;求解树的中心和重心是一类十分重要的算法 求解树的重心 树的重心的定义&#xff1a;重心是树中的一个节点&#xff0c;如果将这个点删除后&#xff0c;剩余各个连通块中点数的最大值最小&#xff0c;那么这个节点…...

Docker 配置镜像源

》》Daemon {"registry-mirrors": ["https://docker.1ms.run","https://docker.xuanyuan.me"] }》》》然后在重新 docker systemctl restart docker...

Linux 离线部署Ollama和DeepSeek-r1模型

都在复制粘贴联网状态下linux部署deepseek&#xff0c;离线状态下需要下载Ollama和DeepSeek模型&#xff0c;然后将下载包上传到linux中。 1、下载Ollama https://github.com/ollama/ollama/releases 注意&#xff1a;如果CentOS7建议安装V0.5.11版本&#xff0c;V0.5.13需要…...

SQLAlchemy系列教程:如何执行原生SQL

Python中的数据库交互提供了高级API。但是&#xff0c;有时您可能需要执行原始SQL以提高效率或利用数据库特定的特性。本指南介绍在SQLAlchemy框架内执行原始SQL。 在SQLAlchemy中执行原生SQL SQLAlchemy虽然以其对象-关系映射&#xff08;ORM&#xff09;功能而闻名&#xff…...

RuleOS:区块链开发的“新引擎”,点燃Web3创新之火

RuleOS&#xff1a;区块链开发的“新引擎”&#xff0c;点燃Web3创新之火 在区块链技术的浪潮中&#xff0c;RuleOS宛如一台强劲的“新引擎”&#xff0c;为个人和企业开发去中心化应用&#xff08;DApp&#xff09;注入了前所未有的动力。它以独特的设计理念和强大的功能特性&…...

【编译器】VSCODE烧录ESP32-C3——xiaozhi智能聊天机器人固件

【编译器】VSCODE烧录ESP32-C3——xiaozhi智能聊天机器人固件 文章目录 [TOC](文章目录) 前言一、方法一&#xff1a;使用固件烧录工具1. 安装CH340驱动2. 打开FLASH_DOWNLOAD文件3. 选择芯片类型和烧录方式4. 选择烧录文件5. 参数配置 二、方法二&#xff1a;VSCODE导入工程1.…...

设计模式文章汇总-Golang语言实现

Golang学习笔记_27——单例模式 Golang学习笔记_28——工厂方法模式 Golang学习笔记_29——抽象工厂模式 Golang学习笔记_30——建造者模式 Golang学习笔记_31——原型模式 Golang学习笔记_32——适配器模式 Golang学习笔记_33——桥接模式 Golang学习笔记_34——组合模式 Gola…...

显式 GC 的使用:留与去,如何选择?

目录 一、什么是显式 GC&#xff1f; &#xff08;一&#xff09; 垃圾回收的基本原理 &#xff08;二&#xff09;显式 GC 方法和行为 1. System.gc() 方法 2. 显式 GC 的行为 &#xff08;三&#xff09;显式 GC 的使用场景与风险 1. JVM 如何处理显式 GC 2. 显式 GC…...

SpringMVC概述以及入门案例

目录 SpringMVC概述 为什么需要Spring MVC&#xff1f; SpringMVC入门 工作流程分析 SpringMVC概述 SpringMVC技术与Servlet技术功能等同&#xff0c;均属于Web层开发技术。SpringMVC是一种基于java实现MVC模型的轻量级Web框架。 为什么需要Spring MVC&#xff1f; 在传统J…...