赞
踩
大学开学了,博主的堂弟现在正值大四,论文是分毫未动,想要用国内网上的AI辅助写作拟一篇文章进行完成,刚好聊天了解此事,我有点感兴趣,去百度了一下,各个AI生成网站价格不菲,临时起意想做一个AI代码生成程序,当然这种生成的论文问题还是特别多,无法和成熟的软件相比不过胜在成本低,可塑性强。
首先我们需要对接现有大模型的接口,国内的各个大模型都有自己的商场,GPT-4在理解和生成自然语言方面比国内的模型在具有更强的能力,因此作者嫌麻烦花了10米去搞了个国内的中转接口去调用GPT-4的api,否则你就需要魔法上网,去弄openAI的接口了。
首先简单讲解一下AI对话的原理,首先要了解tokens,引用一句AI的解释:
在AI和机器学习的上下文中,"tokens" 通常是指文本数据被预处理后得到的基本单位。这些单位可以是单词、子词(subwords)、字符或更复杂的结构,取决于你选择的分词方法。Tokenization是自然语言处理中的一个重要步骤,它有助于将文本转换为机器学习模型可以理解和处理的形式。
简单来说就是你发送的消息和AI返回给你的内容都与tokens有关,字符越多,tokens越大,一般来说400个汉字,相当于1000tokens
而对话简单来讲就是你请求的时候会携带上文AI的回答或者你的塑造的上下文进行调用,如果不加限制就很容易消耗特别多的tokens,有些网站都是将次数限制到10条可能就是这个原因。
论文这个东西文本实在太多,使用我直接不考虑带上文,这里是我的创建对话的代码实现:
- private ChatResponseWrapper createChat(String message, String model) {
- try {
- if (!chatConfig.isEnable()) {
- log.warn("createChat fail,ai is unable!");
- throw new AIExecuteException("check ai is enable!");
- }
- List<ChatMessagesDto> messages = new ArrayList<>();
- ChatMessagesDto systemMessage = new ChatMessagesDto(role, message);
- messages.add(systemMessage);
- //model 代表gpt当前的模型,我开发时使用的是3.5,role代表当前的角色
- ChatRequest chatCompletionRequest = ChatRequest.builder().model(model).messages(messages).user(role).maxTokens(4096).temperature(BigDecimal.ONE).build();
-
- ObjectMapper objectMapper = new ObjectMapper();
- //对接大模型的url
- HttpRequest request = HttpUtil.createPost(chatConfig.getUrl());
- request.setConnectionTimeout(TIMEOUT);
- request.body(objectMapper.writeValueAsBytes(chatCompletionRequest));
- // 设置请求头
- request.header("Content-Type", "application/json");
- //携带token
- request.header("Authorization", "Bearer " + chatConfig.getAccessKey());
- String body;
- try (HttpResponse response = request.execute()) {
- body = response.body();
- }
- if (log.isDebugEnabled()) {
- log.debug("get Ai response body:{}", body);
- }
- ChatResponseWrapper wrapper = new ChatResponseWrapper();
- wrapper.setResponse(objectMapper.readValue(body, ChatResponse.class));
- wrapper.setQuestion(message);
- return wrapper;
- } catch (JsonProcessingException e) {
- log.error("Json Parse error,message:{}", message, e);
- throw new AIExecuteException(e.getMessage());
- } catch (Exception e) {
- log.error("createChat error,message:{}", message, e);
- throw new AIExecuteException(e.getMessage());
- }
- }
接下来创建生成大纲的接口,入参是标题,也就是前端传入的值
- public List<ThesisOutline> genThesisOutline(String message) {
- try {
- String template = "以《{}》为题目,给我一篇计算类论文的大纲,使用中文回答,一级目录使用编号1,二级目录使用编号1.1,以此类推";
- //对ai的问句
- String askQuestion = StringUtil.format(template, message);
- ChatResponseWrapper wrapper = aiClient.createChat35(askQuestion);
- ChatResponse response = wrapper.getResponse();
- if (response == null) {
- throw new AIExecuteException();
- }
- List<ThesisOutline> thesisOutlines = new ArrayList<>();
- for (ChatChoice choice : response.getChoices()) {
- ChatMessagesDto choiceMessage = choice.getMessage();
- String content = choiceMessage.getContent();
- if (StringUtil.isEmpty(content)) {
- continue;
- }
- //过滤为空的行和不为数字的行
- List<String> outlines = StringUtil.split(content, "\n").stream().filter(StringUtil::isNotBlank).filter(StringUtil::isStartsWithDigit).map(String::trim).collect(Collectors.toList());
- for (String outlineContent : outlines) {
- ThesisOutline outline = new ThesisOutline();
- outline.setContent(outlineContent);
- thesisOutlines.add(outline);
- }
- }
- return thesisOutlines;
- } catch (Exception e) {
- log.error("genThesisOutline error", e);
- return Collections.emptyList();
- }
- }
-
- /**
- * 论文标题实体
- * @author zwh
- */
- @Data
- public class ThesisOutline {
-
- /**
- * 层级
- */
- private Integer level;
-
- /**
- * 标题内容
- */
- private String content;
- }
这个接口将生成的数据返回给前端
随手写的前端,样式什么的也懒的调了,能用就行,代码如下:
- <template>
- <div class="app-container home">
- <el-card style="height: 95vh">
- <el-button @click="genThesisOutline" :disabled="showText">生成论文大纲</el-button>
- <el-button @click="toText" v-if="!showText">变更为文本</el-button>
- <el-button @click="toList" v-if="showText">保存并展示为列表</el-button>
- <el-button @click="downloadByOutline" icon="el-icon-download" v-if="!showText">生成并下载论文</el-button>
-
-
- <div style="margin-top: 20px">
- <div v-show="!showText">
- <el-row style="margin-bottom: 10px">
- <el-col :span="4">标题</el-col>
- <el-col :span="20">{{ thesis.title }}</el-col>
- </el-row>
- <el-table ref="table" :data="outlineData" max-height="1100px">
- <el-table-column type="index" width="100"/>
- <el-table-column label="大纲内容" prop="content"/>
- </el-table>
- </div>
-
- <el-form v-show="showText">
- <el-form-item label="标题">
- <el-input v-model="thesis.title"/>
- </el-form-item>
- <el-form-item label="大纲">
- <el-input type="textarea" v-model="outlineText" rows="45"/>
- </el-form-item>
- </el-form>
- </div>
-
- </el-card>
-
-
- <el-dialog title="生成论文大纲" :visible.sync="outline.visible">
- <el-form>
- <el-form-item label="论文题目">
- <el-input v-model="outline.content"/>
- </el-form-item>
- </el-form>
- <div slot="footer" class="dialog-footer">
- <el-button @click="outline.visible = false">取 消</el-button>
- <el-button type="primary" @click="genOutline">确认生成大纲</el-button>
- </div>
- </el-dialog>
- </div>
- </template>
-
- <script>
- import {downloadWord, genOutline} from "@/api";
-
- export default {
- name: "Index",
- data() {
- return {
- outline: {
- visible: false,
- content: null,
- },
- outlineData: [],
- thesis: {
- title: null,
- messages: []
- },
- outlineText: null,
- showText: false,
- };
- },
- methods: {
- genThesisOutline() {
- this.outline.visible = true;
- },
- genOutline() {
- genOutline(this.outline.content).then(resp => {
- this.outlineData = resp.data
- this.thesis.title = this.outline.content
- this.outline.content = null
- this.outline.visible = false
- })
- },
- toText() {
- const fieldValues = this.outlineData.map(obj => obj.content);
- this.outlineText = fieldValues.join('\n');
- this.showText = true;
- },
- toList() {
- // 使用 split 方法按换行符分割字符串
- const lines = this.outlineText.split('\n');
- // 过滤掉空字符串,并将每个字符串转化回对象
- // 将每行转化为一个对象,该对象具有指定的字段名和内容
- this.outlineData = lines
- .filter(line => line.trim() !== '')
- .map(line => ({content: line}));
- this.showText = false;
- },
- downloadByOutline() {
- this.thesis.messages = this.outlineData.map(obj => obj.content);
- downloadWord(this.thesis).then(resp => {
- // 创建一个blob对象URL
- const url = window.URL.createObjectURL(new Blob([resp]));
- // 创建一个下载链接元素
- const link = document.createElement('a');
- link.href = url;
- link.setAttribute('download', this.thesis.title+'.docx'); // 设置下载文件名
- // 触发下载
- document.body.appendChild(link);
- link.click();
- // 清理
- window.URL.revokeObjectURL(url);
- })
- }
- },
- };
- </script>
正文生成,因为是要对大纲的内容进行循环的请求,使用需要异步请求chatgpt,因为每一个论点请求花费的时间都十分之长,计算了一下一个论点大约30s左右,所以我们这里需要异步去请求论文返回的结果,我这里是使用CompletableFuture去异步请求,方法通过异步方式与AI交互,生成论文文本,并将结果导出为Word文档供用户下载。
- @Override
- public void genThesisText(List<String> messages, String title, HttpServletResponse resp) {
- try {
- String template = "请继续以{}为题生成关于:{}的内容,内容需要在500字以上";
- List<CompletableFuture<ThesisTextModel>> futures = new ArrayList<>(messages.size());
- // 每个标题异步创建会话
- for (String message : messages) {
- String question = StringUtil.format(template, title, message);
- CompletableFuture<ThesisTextModel> future = CompletableFuture.supplyAsync(() -> ThesisTextModel.builder().wrapper(aiClient.createChat35(question)).message(message).build(), executor).exceptionally(e -> {
- log.error("createChat sync execute error", e);
- return null;
- });
- futures.add(future);
- }
-
- // 获取所有消息内容 (key: 问题, value: 回答)
- Map<String, List<String>> allContent = new LinkedHashMap<>();
- for (CompletableFuture<ThesisTextModel> future : futures) {
- ThesisTextModel model = future.get();
- ChatResponse response = model.getWrapper().getResponse();
- List<ChatChoice> choices = response.getChoices();
- String outline = model.getMessage();
- ArrayList<String> perContent = new ArrayList<>();
- for (ChatChoice choice : choices) {
- ChatMessagesDto choiceMessage = choice.getMessage();
- // 获取某个标题的回答内容
- String content = choiceMessage.getContent().replaceAll("\n+", "\n");
- if (StringUtil.isEmpty(content)) {
- continue;
- }
- perContent.add(content);
- }
- allContent.put(outline, perContent);
- }
- // 生成 word 文档
- ThesisBuildHelper.exportWordDocument(resp, allContent);
- } catch (Exception e) {
- log.error("genThesisText error", e);
- }
- }
调用ThesisBuildHelper设置好相应的格式,这里贴上我大学时使用的论文格式(ps.还是从吃灰的老电脑里翻到的)
经过两天的努力,到现在程序已经能够生成结构清晰、内容丰富的论文。同时,通过导出为Word文档的形式,使得我能够方便地查看和编辑生成的论文。
这样生成的论文成本也特别的低,我开发中使用的是gpt3.5,大概2w字的论文花费就2毛左右,可想而知百度到的论文生成网站里面利润之高,其实我还想了将gpt3.5生成的文章交给gpt4润色,加入参考文献,不过嫌麻烦,也没有好的思路,就懒得做了,说到底也是心血来潮。
最后,附以一张我毕业的论题生成的论文,27页字数23184,虽然不是标准的论文,但是再也不需要去冥思苦想论文该咋写了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。