Spring AI 应用 - 智能记者

在这里插入图片描述
参考实现: https://github.com/mshumer/ai-journalist

上面是通过 Claude 配合 SERP 搜索 API,使用 Python 语言实现的,本文通过 GitHub Copilot 辅助改为了基于 Spring AI 的 Java 版本,本文使用的 OpenAI。

AIJournalist 实现

基本定义如下:

/*** 记者类,用于撰写和编辑文章*/
public class AIJournalist {private ChatClient   chatClient; // 聊天客户端private RestTemplate restTemplate = new RestTemplate(); // 用于发送HTTP请求的模板private HttpHeaders  serpApiHeader; // SERP API的请求头/*** 构造函数,初始化聊天客户端和SERP API的请求头** @param chatClient 聊天客户端* @param serpApiKey SERP API的密钥*/public AIJournalist(ChatClient chatClient, String serpApiKey) {this.chatClient = chatClient;this.serpApiHeader = new HttpHeaders();serpApiHeader.setContentType(MediaType.APPLICATION_JSON);serpApiHeader.set("X-API-KEY", serpApiKey);}

为了方便外部替换 ChatClient 实现,作为参数传递进去使用,搜索使用的 SERP,可以免费申请初始的额度用于搜索。

下面是 main 方法调用:

public static void main(String[] args) {// 可选 HTTPSSystem.setProperty("https.proxyHost", "localhost");System.setProperty("https.proxyPort", "7890");// 创建聊天客户端var openAiApi = new OpenAiApi("替换token");var chatClient = new OpenAiChatClient(openAiApi, OpenAiChatOptions.builder().withModel("gpt-4-turbo").withTemperature(0.4F).build());// 启动AIJournalist journalist = new AIJournalist(chatClient, "替换token");journalist.start();
}

这里选择的 gpt-4-turbo,在实现中会通过搜索引擎获取大量上下文,因此需要支持更大上下文的模型,gpt-4-turbo 支持的 token 上限为 128,000,如果遇到超出上下文的情况,还可以考虑尝试 gpt-4-32k 来支持 32,768 tokens。

下面看串起整个调用的 start() 方法。

/*** 开始撰写和编辑文章的过程*/
public void start() {// User inputScanner scanner = new Scanner(System.in);System.out.print("输入要写的主题:");String topic = scanner.nextLine();System.out.print("初稿完成后,是否要进行自动编辑?这可能会提高性能,但有点不可靠。回答“是”或“否”:");String doEdit = scanner.nextLine();

首先通过控制台输入主题,以及是否要进行自动编辑。比如输入如下信息:

输入要写的主题:How to use Obsidian?
初稿完成后,是否要进行自动编辑?这可能会提高性能,但有点不可靠。回答“是”或“否”:是

输入主题后,通过 getSearchTerms 方法根据主题生成搜索词:

// Generate search terms
List<String> searchTerms = getSearchTerms(topic);
System.out.println("\n------------------------------");
System.out.println("\n搜索词 '" + topic + "':");
System.out.println(String.join(", ", searchTerms));

getSearchTerms 方法会调用 AI 根据提示词生成:

/*** 根据给定的主题生成搜索词** @param topic 主题* @return 搜索词列表*/
public List<String> getSearchTerms(String topic) {List<Message> messages = new ArrayList<>();messages.add(new SystemMessage("你是一位世界级的记者。生成一个包含5个搜索词的列表,用于研究和撰写关于该主题的文章。"));messages.add(new UserMessage("主题: " + topic + "\n\n请提供一个与'" + topic + "'相关的5个搜索词的列表,用于研究和撰写文章。以逗号分隔的Java可解析列表形式回复。"));String responseText = call(messages);return Arrays.asList(responseText.replace("[", "").replace("]", "").replace("\"", "").split(","));
}

根据前面输入的主题,这里响应结果如下:

搜索词 'How to use Obsidian?':
Obsidian app tutorial,  Obsidian note-taking features,  Obsidian plugins,  Obsidian sync capabilities,  Obsidian vs Notion comparison

接下要搜调用搜索 API 分别搜索这几个搜索词

// Perform searches and select relevant URLs
List<String> relevantUrls = new ArrayList<>();
for (String term : searchTerms) {List<Map<String, Object>> searchResults = getSearchResults(term);List<String> urls = selectRelevantUrls(searchResults);relevantUrls.addAll(urls);
}

通过 getSearchResults 方法搜索(因为AI会用我们的语言编写文章,所以搜索英文资料会比中文效果更好):

/*** 根据给定的搜索词获取搜索结果** @param searchTerm 搜索词* @return 搜索结果*/
@SuppressWarnings({"unchecked", "rawtypes"})
public List<Map<String, Object>> getSearchResults(String searchTerm) {// Create request bodyString body = "{\"q\":\"" + searchTerm + "\",\"hl\":\"en\",\"num\":10}";// Create entityHttpEntity<String> entity = new HttpEntity<>(body, serpApiHeader);// Execute requestResponseEntity<Map> response = restTemplate.exchange("https://google.serper.dev/search",HttpMethod.POST,entity,Map.class);return (List<Map<String, Object>>) response.getBody().get("organic");
}

然后通过方法 selectRelevantUrls 使用 AI 筛选搜索结果中的 URL:

/*** 从给定的搜索结果中选择相关的URL** @param searchResults 搜索结果* @return 相关的URL列表*/
public List<String> selectRelevantUrls(List<Map<String, Object>> searchResults) {List<Message> messages = new ArrayList<>();messages.add(new SystemMessage("你是一位记者助手。从给定的搜索结果中,选择出看起来最相关和信息丰富的 URL,用于撰写关于该主题的文章。"));StringBuilder searchResultsText = new StringBuilder();for (int i = 0; i < searchResults.size(); i++) {searchResultsText.append(i + 1).append(". ").append(searchResults.get(i).get("link")).append("\n");}messages.add(new UserMessage("搜索结果:\n" + searchResultsText + "\n\n请选择看起来最相关和信息丰富的 URL 的编号,用于撰写关于该主题的文章。以逗号分隔的 Java 可解析列表形式回复(如 [1,2,4])。"));String responseText = call(messages);String[] numbers = responseText.replace("[", "").replace("]", "").replace("\"", "").split(",");List<String> relevantUrls = new ArrayList<>();for (String num : numbers) {int index = Integer.parseInt(num.trim()) - 1;relevantUrls.add((String) searchResults.get(index).get("link"));}return relevantUrls;
}

循环多个搜索关键字,拿到所有可以参考的 URL 链接,然后通过下面代码输出:

String urls = IntStream.range(0, relevantUrls.size()).mapToObj(i -> (i + 1) + ". " + relevantUrls.get(i)).collect(Collectors.joining("\n"));
System.out.println("\n------------------------------");
System.out.println("要阅读的相关 URL:\n" + urls);

当前主题输出的示例如下:

要阅读的相关 URL:
1. https://obsidian.rocks/getting-started-with-obsidian-a-beginners-guide/
2. https://bobbypowers.net/beginners-guide-to-obsidian/
3. https://thetotalliving.medium.com/the-ultimate-guide-to-obsidian-8de0a5ea5c20
4. https://obsidian.md/
5. https://www.makeuseof.com/what-is-obsidian-note-taking/
6. https://www.cloudwards.net/obsidian-review/
7. https://obsidian.md/plugins
8. https://github.com/obsidianmd/obsidian-releases
9. https://obsidianninja.com/best-obsidian-plugins/
10. https://www.dsebastien.net/2022-10-19-the-must-have-obsidian-plugins/
11. https://help.obsidian.md/Obsidian+Sync/Introduction+to+Obsidian+Sync
12. https://help.obsidian.md/Getting+started/Sync+your+notes+across+devices
13. https://help.obsidian.md/Obsidian+Sync/Sync+limitations
14. https://www.nuclino.com/solutions/obsidian-vs-notion
15. https://plaky.com/blog/obsidian-vs-notion/
16. https://clickup.com/blog/obsidian-vs-notion/
17. https://www.techrepublic.com/article/obsidian-vs-notion/
18. https://www.androidauthority.com/obsidian-vs-notion-3319050/

接下来获取 URL 的内容:

// Get article text from relevant URLs
List<String> articleTexts = new ArrayList<>();
for (String url : relevantUrls) {try {String text = getArticleText(url);if (text.length() > 75) {articleTexts.add(text);}} catch (Exception e) {e.printStackTrace();}
}

getArticleText 方法使用 Jsoup 获取并解析 html:

/*** 从给定的URL获取文章文本** @param url 文章的URL* @return 文章的文本*/
public String getArticleText(String url) {try {Document doc = Jsoup.connect(url).get();return doc.body().text();} catch (Exception e) {System.out.println("解析URL" + url + " 错误: " + e.getMessage());return "";}
}

控制输出参考文章:

System.out.println("\n------------------------------");
System.out.println("参考文章:" + articleTexts);

示例如下(太长,截取部分)

参考文章:[Obsidian Rocks Exploring knowledge management with Ob
- [[Interests MOC]]
- [[Work MOC]]
- [[Home MOC]]The text above may confuse you. What’s with the funky square br
2. Item two
3. Item three To create an unordered list, simply use asterisks 
* Item two
* Item three Blockquotes: To create a blockquote, simply type > 
> --Abraham Lincoln Note: Obsidian also has callouts, which are 

开始根据提供的上下文写作:

System.out.println("\n\n正在写文章...");
// Write the article
String article = writeArticle(topic, articleTexts);
System.out.println("\n------------------------------");
System.out.println("\n生成的文章:");
System.out.println(article);

通过提示词模板调用AI编写:

/*** 根据给定的主题和参考文章文本撰写文章** @param topic        主题* @param articleTexts 参考文章文本* @return 撰写的文章*/
public String writeArticle(String topic, List<String> articleTexts) {List<Message> messages = new ArrayList<>();messages.add(new SystemMessage("你是一位世界级的记者。根据以下的参考文章和主题,撰写一篇关于该主题的文章。"));StringBuilder articlesText = new StringBuilder();for (int i = 0; i < articleTexts.size(); i++) {String article = articleTexts.get(i);articlesText.append(i + 1).append(". ").append(article).append("\n");}messages.add(new UserMessage("参考文章:\n" + articlesText + "\n\n主题: " + topic + "\n\n请撰写一篇关于该主题的文章。"));return call(messages);
}

输出的内容放在了这里:掌握Obsidian:从入门到精通的全面指南

判断是否需要对写好的文章进行编辑:

if (doEdit.toLowerCase().contains("是")) {// Edit the articleString editedArticle = editArticle(article);System.out.println("\n------------------------------");System.out.println("\n编辑文章:");System.out.println(editedArticle);
}

编辑方法 editArticle 如下:

/*** 编辑文章以提高其质量** @param article 要编辑的文章* @return 编辑后的文章*/
public String editArticle(String article) {List<Message> messages = new ArrayList<>();messages.add(new SystemMessage("你是一位世界级的编辑。根据以下的文章,进行编辑以提高其质量。"));messages.add(new UserMessage("请编辑以下文章以提高其质量:\n" + article));return call(messages);
}

前面提示词是记者,这里是世界级编辑。

编辑后的内容看这里:全面掌握Obsidian:从新手到专家的实用指南

至此完成了文章的编写,编写的内容不一定很好,个人感觉搜索引擎搜索的结果以及从网页提取的方式对整个结果有很大的影响,如果上下文提供的好,效果应该能改善。

在本文中,Spring AI 只是起到了一个 Chat 的作用,Chat 提供了搜索词、筛选URL,以记者身份编写内容,以编辑身份修改内容。除此之外还用到了搜索 API,使用Jsoup解析HTML来提供上下文。一个复杂的 AI 就是以不同提示词、用法、身份进行多轮交互来产生最终的结果,没有特别复杂的东西。

本文代码较长,完整内容放在了 gist,地址如下:

https://gist.github.com/abel533/300e642cb4e2548830981ce824036586

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/816988.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Tomcat源码解析——源码环境搭建

一、源码下载 在进行源码阅读前&#xff0c;先下载源码包&#xff0c;这样便于做笔记和debug。 我所用的版本是Tomcat7.0.68&#xff0c; Tomcat7.0.68下载地址&#xff1a;Index of /dist/tomcat/tomcat-7/v7.0.68/src 所有Tomcat的源码包下载地址&#xff1a;Index of /dist/…

第6章:6.4.2 案例二:爬取成语网站数据 (MATLAB入门课程)

讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 本案例用到的网址为&#xff1a;成语大全列表成语大全列表https…

NSA发布《在数据支柱中推进零信任成熟度》报告

4月9日&#xff0c;美国国家安全局&#xff08;NSA&#xff09;发布了题为《在数据支柱中推进零信任成熟度》的报告&#xff0c;旨在于数据安全层面提供指导&#xff0c;以增强数据整体安全性并保护静态和传输中的数据。(如下图&#xff09; 一、主要内容 报告中的建议侧重于将…

企业电子招标采购系统源码之从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理

功能描述 1、门户管理&#xff1a;所有用户可在门户页面查看所有的公告信息及相关的通知信息。主要板块包含&#xff1a;招标公告、非招标公告、系统通知、政策法规。 2、立项管理&#xff1a;企业用户可对需要采购的项目进行立项申请&#xff0c;并提交审批&#xff0c;查看所…

ChatGPT在线网页版

ChatGPT镜像 今天在知乎看到一个问题&#xff1a;“平民不参与内测的话没有账号还有机会使用ChatGPT吗&#xff1f;” 从去年GPT大火到现在&#xff0c;关于GPT的消息铺天盖地&#xff0c;真要有心想要去用&#xff0c;途径很多&#xff0c;别的不说&#xff0c;国内GPT的镜像…

Linux:Redis7.2.4的源码包部署(2)

本章使用的是centos9进行部署 1.获取rpm安装包 Index of /releases/ (redis.io)https://download.redis.io/releases/这个网站有历史的版本&#xff0c;我这里使用的是最新版7.2.4进行安装 点击即可进行下载 方进Linux中&#xff0c;如果你的Linux中可以直接使用wget去下载 2…

SQLite、MySQL 和 PostgreSQL 数据库速度比较(本文阐述时间很早比较,不具有最新参考性)(二十五)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;用于 SQLite 的异步 I/O 模块&#xff08;二十四&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 注意&#xff1a;本文档非常非常旧。它描述了速度比较 SQLite、MySQL 和 PostgreSQL 的古老版本。 这里…

系统架构最佳实践 -- 供应链系统架构

供应链系统是现代企业管理中不可或缺的一部分&#xff0c;它涉及到从原材料采购到产品销售的整个生产流程。一个高效的供应链系统可以帮助企业实现成本控制、库存优化和客户满意度提升等目标。在本文中&#xff0c;我们将讨论供应链系统的设计与实践。 一、供应链系统设计 业务…

如何用 Python 批量循环读取 Excel ?

在使用 Python 批量处理 Excel 时经常需要批量读取数据&#xff0c;常见的方式是结合glob模块&#xff0c;可以实现将当前文件夹下的所有csv批量读取&#xff0c;并且合并到一个大的DataFrame中 df_list [] for file in glob.glob("*.csv"):df_list.append(pd.read…

解决动态规划问题

文章目录 动态规划的定义动态规划的核心思想青蛙跳阶问题解法一&#xff1a;暴力递归解法二&#xff1a;带备忘录的递归解法&#xff08;自顶向下&#xff09;解法三&#xff1a;动态规划&#xff08;自底向上&#xff09; 动态规划的解题套路什么样的问题考虑使用动态规划&…

Java GUI制作双人对打游戏(上)

文章目录 前言什么是Java GUI一、打开IDEA 新建一个Maven项目(后续可以打包、引入相关依赖也很容易)二、引入依赖三.绘制UI界面四.绘制JPanel面板总结 前言 什么是Java GUI Java UI&#xff0c;即Java用户界面&#xff0c;是指使用Java编程语言创建的图形用户界面&#xff08…

springBoot+vue编程中使用mybatis-plus遇到的问题

mybatis-plus中遇到的问题Code Companion Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)…

02 SQL基础 -- 初识SQL

一、初识 SQL 1.1 概念介绍 数据库中存储的表结构类似于 excel 中的行和列,在数据库中,行称为记录,它相当于一条结论,列称为字段,它代表了表中存储的数据项目 行和列交汇的地方称为单元格,一个单元格只能输入一条记录 SQL是为操作数据库而开发的语言。国际标准化组织(…

Rust语言入门第二篇-Cargo教程

文章目录 Rust语言入门第二篇-Cargo教程一&#xff0c;Cargo 是什么二&#xff0c;Cargo教程Cargo.toml文件src/main.rs 文件构建并运行Cargo项目 Rust语言入门第二篇-Cargo教程 本节提供对cargo命令行工具的快速了解。我们演示了它为我们生成新包的能力&#xff0c;它在包内编…

windows如何卸载干净 IDEA

Windows 系统要想彻底卸载 IDEA, 步骤如下&#xff1a; 1、卸载 IDEA 程序 点击屏幕左下角 Windows 图标 -> 设置&#xff1a; 在应用中找到 IDEA, 单击它会出现卸载按钮&#xff0c;点击开始卸载&#xff1a; 勾选第一栏 Delete IntelliJ IDEA 2022.2 caches and local hi…

Go语言开发工具Vscode配置

Go语言开发工具Vscode配置方法分享&#xff1a; 1.下载安装vscode https://code.visualstudio.com/ 2.汉化vscode 3.vscode中安装Go语言插件 源自&#xff1a;大地老师Golang语言beego入门实战视频教程下载地址

【noVNC】使用noVNC实现浏览器远程VNC(基于web的远程桌面)

一、操作的环境 windows 10系统乌班图 Ubuntu 22 二、noVNC 部署方式 原理&#xff1a;开启 Websockify 代理来做 WebSocket 和 TCP Socket 之间的转换 2.1 noVNC和VNC服务端同一台机器 使用方式&#xff0c;查看另一篇博文 &#xff1a;【noVNC】使用noVNC实现浏览器网页访…

双向链表的实现(详解)

目录 前言初始化双向链表的结构为双向链表的节点开辟空间头插尾插打印链表尾删头删查找指定位置之后的插入删除pos节点销毁双向链表 前言 链表的分类&#xff1a; 带头 不带头 单向 双向 循环 不循环 一共有 (2 * 2 * 2) 种链表 带头指的是&#xff1a;带有哨兵位节点 哨兵位&a…

基于springboot实现人事管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现人事管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;作为学校以及一些培训机构&#xff0c;都在用信息化战术来部署线上学习以及线上考试&#xff0c;可以与线下的考试有机的结合在一起&#xff0c;实现基于vue的人事系统在技术…

numpy学习笔记(3),数组连接

6. 连接数组 6.1. 连接数组&#xff0c; 6.2. 分割数组&#xff0c; 6.3. 算术运算&#xff0c; 6.4. 广播&#xff08;重点&#xff09; 6.1 连接数组 concatenatehstackvstack 6.1.1 使用concatenate函数 沿指定轴连接多个数组&#xff0c;语法格式如下&#xff1a; num…