开发流程
第一天的时候组长是给了我一个关于实习生阶段的一个培养计划,这里面就涉及了一些主流的技术栈。对我而言,因为之前是自己有学习过大部分的技术栈的,包括(springboot、mysql、mybatis、mybaits-plus、redis的使用),不太熟悉的就是 springcloudAlibaba微服务框架以及rocketMq消息中间件,那这一块就是说我针对自己不熟悉的技术栈进行了一个快速深入的学习并做好笔记,主要的学习方式是通过看官方文档来进行一个学习,对于一些不好理解的概念和代码自己进行一个实际操作进而梳理与总结,加深印象。通过搭建demo的方式来熟悉基础操作。
我大概是花了4天的时间把这些基础东西给过了一遍,在自己脑子里有个使用的印象。那做完这些呢,组长给我安排了一个题目是《AI文章总结》的一个需求,具体要求是:1.编写接口代码获取用户上传的文档,交于AI大模型解析,最后得出文档总结,作为接口返回。2.制作一个docker镜像,并上传到公开容器仓库。
在这里需要强调一下,明确需求是很重要的一件事情,我们不应该就是说模糊需求就去进行开发,这样整体的效率和质量都是不高的。其次,这个需求是之前完全没有接触过的新需求,可能很多人对于一个新的技术和新的需求是比较茫然的、无从下手的一个状态,在这里我首先是去查阅资料了解关于AI大模型开发的一些相关信息,知道他的一个整体的概念,有什么用,怎么用,可以用在哪些地方,国内的AI大模型有很多,可以选择一个自己感兴趣的大模型,一般他都会有一个自己的官方网站,我们进行注册,新用户一般是有体验资格的,可以免费注册一个大模型来进行练习是非常不错的,这里我就用智谱 AI 大模型为例。它的官网是有一个开发文档,里面有一个使用指南是对我们大模型版本进行一个介绍,每个大模型的功能,特点等。看完这些,我们对每个大模型有了初步的认识,接下来就是看接口文档,这里是教我们如何在项目中引入大模型,官方是提供了两种语言java和python,我们需要先引入对应的sdk,然后他有一个示例代码,我们拿到项目中去,填写自己注册的大模型APIKey那些东西,然后就可以进行简单的对话使用。这样我们就完成了一个大模型的调用。
<!-- BigModel -->
<dependency>
<groupId>cn.bigmodel.openapi</groupId>
<artifactId>oapi-java-sdk</artifactId>
<version>release-V4-2.0.2</version>
</dependency>
回到我们的需求里面,我这里是对需求进行了一个拆分,上传文件并提取文档内容,封装工具类;封装AI工具类;定义业务层;定义接口;这样拆分下来的话,我们就可以很明确自己需要做的事情,有条不紊的写代码。首先我是做了一个word和pdf的文件处理问题,这一块也是没有接触过的新东西,我是直接用AI去查询了一下,java如何实现word和pdf文件的内容提取,AI会给到你一些示例代码,以及会用到的SDK,我拿到这些代码在项目里面进行修改是发现有报错,发现是依赖问题,我再次去搜索对应的处理过程,最后是通过修改依赖版本对应sprinboot3的一个版本来解决的,因为AI大模型目前是只支持jdk17,对应我们的一个sprinboot3的一个开发框架。
以下是我的一个依赖版本、AI类和文档处理工具类的创建:
package com.free.demo.ai;
import com.zhipu.oapi.ClientV4;
import com.zhipu.oapi.Constants;
import com.zhipu.oapi.service.v4.model.*;
import io.reactivex.Flowable;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 智谱AI通用方法
*
* @author free
*/
@Component
public class AiManager {
@Resource
private ClientV4 client;
// 默认的是0.95,认为此时是稳定的
// 较稳定的随机数
public static final float STABLE_TEMPERATURE = 0.05f;
// 不稳定的随机数
public static final float UNSTABLE_TEMPERATURE = 0.99f;
/**
* 通用请求方法
*
* @param aiChatMessages AI聊天消息
* @param stream 是否开启流式
* @param temperature 随机性
* @return AI响应信息
*/
public String doRequest(List<ChatMessage> aiChatMessages, Boolean stream, Float temperature) {
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model(Constants.ModelChatGLM4)
.stream(stream)
.invokeMethod(Constants.invokeMethod)
.temperature(temperature)
.messages(aiChatMessages)
.build();
try {
ModelApiResponse invokeModelApiResp = client.invokeModelApi(chatCompletionRequest);
return invokeModelApiResp.getData().getChoices().get(0).getMessage().toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 简化消息传递
*
* @param systemMessage 系统信息
* @param userMessage 用户信息
* @param stream 是否开启流式
* @param temperature 随机性
* @return AI响应信息
*/
public String doRequest(String systemMessage, String userMessage, Boolean stream, Float temperature) {
// 构造请求
List<ChatMessage> aiChatMessages = new ArrayList<>();
ChatMessage systemChatMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), systemMessage);
ChatMessage userChatMessage = new ChatMessage(ChatMessageRole.USER.value(), userMessage);
aiChatMessages.add(systemChatMessage);
aiChatMessages.add(userChatMessage);
return doRequest(aiChatMessages, stream, temperature);
}
/**
* 同步请求
*
* @param systemMessage 系统信息
* @param userMessage 用户信息
* @param temperature 随机性
* @return AI响应信息
*/
public String doSyncRequest(String systemMessage, String userMessage, Float temperature) {
return doRequest(systemMessage, userMessage, Boolean.FALSE, temperature);
}
/**
* 同步请求(答案较稳定)
*
* @param systemMessage 系统信息
* @param userMessage 用户信息
* @return AI响应信息
*/
public String doSyncStableRequest(String systemMessage, String userMessage) {
return doRequest(systemMessage, userMessage, Boolean.FALSE, STABLE_TEMPERATURE);
}
/**
* 同步请求(答案较随机)
*
* @param systemMessage 系统信息
* @param userMessage 用户信息
* @return AI响应信息
*/
public String
doSyncUnStableRequest(String systemMessage, String userMessage) {
return doRequest(systemMessage, userMessage, Boolean.FALSE, UNSTABLE_TEMPERATURE);
}
/**
* 通用流式请求
*
* @param aiChatMessages AI聊天消息
* @param temperature 随机性
* @return AI响应信息(流式)
*/
public Flowable<ModelData> doStreamRequest(List<ChatMessage> aiChatMessages, Float temperature) {
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model(Constants.ModelChatGLM4)
.stream(Boolean.TRUE)
.invokeMethod(Constants.invokeMethod)
.temperature(temperature)
.messages(aiChatMessages)
.build();
ModelApiResponse modelApiResponse = client.invokeModelApi(chatCompletionRequest);
return modelApiResponse.getFlowable();
}
/**
* 通用流式请求(简化消息传递)
*
* @param systemMessage 系统信息
* @param userMessage 用户信息
* @param temperature 随机性
* @return AI响应信息(流式)
*/
public Flowable<ModelData> doStreamRequest(String systemMessage, String userMessage, Float temperature) {
List<ChatMessage> aiChatMessages = new ArrayList<>();
ChatMessage systemChatMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), systemMessage);
ChatMessage userChatMessage = new ChatMessage(ChatMessageRole.USER.value(), userMessage);
aiChatMessages.add(systemChatMessage);
aiChatMessages.add(userChatMessage);
return doStreamRequest(aiChatMessages, temperature);
}
}
<!-- Apache POI for Word -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
<!-- Apache PDFBox for PDF -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.27</version>
</dependency>
package com.free.demo.util;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
/**
* @author free
* date 2024/8/25 10:12
* description 文档处理
* version 1.0
*/
public class DocumentUtils {
/**
* 从word文件中提取文本
*
* @param file
* @return
* @throws IOException
*/
public static String extractTextFromWord(MultipartFile file) throws IOException {
StringBuilder text = new StringBuilder();
try (InputStream inp = file.getInputStream()) {
HWPFDocument document = new HWPFDocument(inp);
WordExtractor extractor = new WordExtractor(document);
String wordDocumentText = extractor.getText();
text.append(wordDocumentText);
return text.toString();
}
}
/**
* 从PDF文件中提取文本
*
* @param file
* @return
* @throws IOException
*/
public static String extractTextFromPdf(MultipartFile file) throws IOException {
try (PDDocument document = PDDocument.load(file.getInputStream())) {
if (!document.isEncrypted()) {
PDFTextStripper stripper = new PDFTextStripper();
return stripper.getText(document);
}
return "加密 PDF";
}
}
}
然后是我们的一个接口创建,根据请求的参数进行设计:
/**
* 文件上传AI流式解析输出
*
* @param file
* @param response
* @return
*/
@PostMapping(value = "/upload", produces = "text/event-stream;charset=UTF-8")
public SseEmitter uploadAndExtractText(@RequestParam("file") MultipartFile file, HttpServletResponse response) {
return aiService.flowType(file, response);
}
业务层代码:
public interface AiService {
SseEmitter flowType(MultipartFile file, HttpServletResponse response);
}
业务实现层代码:
package com.free.demo.service.impl;
import com.free.demo.ai.AiManager;
import com.free.demo.enums.AppHttpCodeEnum;
import com.free.demo.exception.SystemException;
import com.free.demo.service.AiService;
import com.zhipu.oapi.service.v4.model.ModelData;
import io.reactivex.Flowable;
import jakarta.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import static com.free.demo.constant.SystemConstants.AI_PROMPT_WORD;
import static com.free.demo.constant.SystemConstants.TEMPERATURE;
import static com.free.demo.util.DocumentUtils.extractTextFromWord;
/**
* @author free
* date 2024/8/25 11:04
* description
*/
@Service
public class AiServiceImpl implements AiService {
@Autowired
private AiManager aiManager;
/**
* AI流式输出
*
* @param file
* @param response
* @return
*/
@Override
public SseEmitter flowType(MultipartFile file, HttpServletResponse response) {
String fileName = Objects.requireNonNull(file.getOriginalFilename()).toLowerCase();
if (!fileName.endsWith(".doc") && !fileName.endsWith(".pdf")) {
throw new SystemException(AppHttpCodeEnum.FILE_TYPE_ERROR);
}
try {
if (fileName.endsWith(".doc")) {
return getSseEmitter(file, response);
} else if (fileName.endsWith(".pdf")) {
return getSseEmitter(file, response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}
@NotNull
private SseEmitter getSseEmitter(MultipartFile file, HttpServletResponse response) throws IOException {
String text;
text = extractTextFromWord(file);
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE); // 设置超时时间
Flowable<ModelData> modelDataFlowable = aiManager.doStreamRequest(AI_PROMPT_WORD, text, TEMPERATURE);
modelDataFlowable.subscribe(
out -> {
try {
// 确保字符串是以UTF-8编码
String content = new String(out.getChoices().get(0).getDelta().getContent().getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
emitter.send(SseEmitter.event().data(content + "\n\n"));
} catch (IOException e) {
emitter.completeWithError(e);
}
},
emitter::completeWithError,
emitter::complete
);
// 设置响应的字符编码,虽然通常来说通过produces设置已经足够
response.setCharacterEncoding("UTF-8");
return emitter;
}
}
最后进行一个接口的测试,我这里是用的apifox,输出结果就是流式输出格式。
那到这里为止呢,就算是完成了一个最基本的AI大模型调用实践了,下一步就是进行镜像创建发布。
首先注册一个腾讯云服务器,一般是用Linux系统的。
然后通过maven自带的打包工具对项目进行打jar包,我们把jar包上传到云服务器。
通过编写Dockerfile文件,指定
FROM openjdk:17-jdk
LABEL author=free
COPY demo-0.0.1-SNAPSHOT.jar /app.jar
EXPOSE 8080
# 设置工作目录
WORKDIR /usr/app
# 为JAR包运行命令
ENTRYPOINT ["java","-jar","/app.jar"]
指定一个打包流程,
然后创建镜像:
docker build -f Dockerfile -t mytest .
运行容器:
docker run -d --name mytest1 -p 8081:8080 mytest
这样就可以通过服务器进行接口访问了。
个人收获
在公司开发和以前在学校里开发个人项目的区别是很大的,虽然目前还没有接触到项目,通过组长根哥对我的作业安排,按模块的完成,我感觉自己的技术得到不小的提升。根哥人幽默风趣,技术一流,经验丰富,我觉得自己挺幸运的是他带我一个实习的开发,以下是近期的一个总结性概述。
对业务的层次划分更加清晰。 通过对springboot框架搭建并整合mybatis-plus、mysql、redis,在业务层面上个人项目使用的是mvc架构,controller、service、mapper去写,以及一些AI工具类和配置类的封装。
开发规范得到提高。 开发规范不仅仅是代码规范,还包括了日志规范、maven配置文件等的规范。为什么需要规范呢?第一、在团队协作开发的过程中大家遵守同样的规范能使别人更好读懂你的代码,提高开发效率,第二、遵守规范的代码能避免很多莫名其妙难以排查的问题,比如不要使用魔法值,他能提高可读性,另外如果在发起请求的时候你通过map传参,你的变量名使用魔法值的时候某个字母打错了导致最终接受到不到,我相信这个问题被找出来肯定要耗费不少时间。
利用AI排查问题的能力提高。 对于一些新的技术,我们应该先学会查看官方文档,怎么用,首先引入对应的依赖包,然后设计出最基础的使用demo,最后是去模仿别人写的代码,提高自己的技术。现在AI大模型的出现,又增进了不少新的机遇与挑战,学会用AI排错,改代码已经是必备技能。在代码里尽可能的去把日志打得更加详细,最后通过判断代码执行到哪了,请求是什么,响应是什么,各种环境参数是什么...改bug是很困难折磨人的,但是我觉得这个就像是学习英语,不像数学一两个月就可以快速提高分数,多做工作笔记,线上排查问题的能力也会实打实的得到提升。
沟通能力得到提升。第一,沟通能力是面对同事之间在业务方面对接时,你能尽可能的描述出产品、前端、测试这些功能以及存在的问题,以便能利于彼此更好的解决; 其次是面对不同业务方面,需要积极的与团队成员沟通项目进度,同步产品问题,以便能有效的完成任务; 最后,面对问题时,不要只按照自己的理解和看法就去一意孤行,更改代码,重要的是及时与团队沟通问题,拿出最合适的解决方案后执行,这样也不会浪费太多时间,从而提高了工作效率。当然面对涉及到的问题,也需要自己及时的总结和反思,对应的代码对于自己来说也会跑一遍更加印象深刻。