王剑编程网

分享专业编程知识与实战技巧

JAVA程序员自救之路——用SpringAI做个人每日新闻助手

现代人工作生活非常的紧凑,很多情况没有大块的时间进行阅读,所以我们可以借助SpringAI调用大模型来搭建一个个人的新闻助手。

首先,老生常谈,我们引用一下所需要的SpringAI相关的依赖。这里我们主要使用@Tool,只需要一些基本的依赖就可以。代码如下:

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-openai</artifactId>
        </dependency>

然后,我们开始编写代码:

首先,我们去官方网站里获取新闻。这里我们使用的是人民日报官网的新闻数据。我试了一下SpringAI自带的tika,感觉人民日报官网上的干扰数据太多,所以使用jsoup来解析页面数据。说句题外话,tika里解析网站数据也使用了jsoup。

版权问题,这里获取人民日报数据,只用于个人学习。不要用于商业用途。

代码如下:

    public void importNews(){
        String today = DateUtil.format(DateUtil.date(),  "yyyyMM/dd");
        log.info("正在导入 {}", today);
        for(int i=1;i<=20;i++) {
            String layout = "node_" + StrUtil.fill(""+i,'0', 2,  true) + ".html";
            String url = "https://paper.people.com.cn/rmrb/pc/layout/" + today + "/" + layout;
            String html = HttpUtil.get(url);
            // log.info("html: {}", html);
            Elements newsList = Jsoup.parse(html).select("ul.news-list > li");
            for (Element news : newsList) {
                Element link = news.select("a").first();
                String title = link.text();
                String href = link.attr("href").replace("../../..", "https://paper.people.com.cn/rmrb/pc");
                log.info("title: {}, href: {}", title, href);

                if (StrUtil.isNotBlank(href)) {
                    String subHtml = HttpUtil.get(href);
                    Elements articleList = Jsoup.parse(subHtml).select("div.article");
                    List<Document> docs = new ArrayList<>();
                    for (Element article : articleList) {
                        String content = article.text();
                         NewsEntity newsEntity = new NewsEntity();
                        newsEntity.setTitle(title);
                        newsEntity.setContent(content);
                        newsEntity.setLabel("news");
                        newsEntity.setDate(DateUtil.format(DateUtil.date(),  "yyyy-MM-dd")));
                        log.info("newsEntity: {}", newsEntity);
                        elasticsearchTemplate.save(newsEntity);
                    }
                }
            }
        }
    }

这里我使用了定时任务每天跑一次,拉取今日的数据,存到数据库中。

然后我们定义四个tools。

  • 获取今日时间;
  • 获取某日的新闻标题;
  • 获取某个新闻标题的总结;
  • 获取某个新闻标题的原文。

代码如下:

@Tool(description = "获取今日日期")
    public String getToday(){
        return DateUtil.format(DateUtil.date(), "yyyy-MM-dd");
    }

    @Tool(description = "获取某日的新闻标题")
    public List<String> getNewsByDate(@ToolParam(description = "日期") String date){
        // 构建 NativeQuery
        NativeQuery query = NativeQuery.builder()
                .withQuery(Query.of(q -> q.match(MatchQuery.of(mq-> mq
                        .field("date")
                        .query(date)
                        .boost(1.0f)
                )))).build();
        SearchHits<NewsEntity> searchHits = elasticsearchTemplate.search(query, NewsEntity.class);
        return searchHits.getSearchHits().stream().map(hit -> hit.getContent().getTitle()).toList();
    }

    @Tool(description = "获取某个新闻标题的总结")
    public String getSummary(@ToolParam(description = "新闻标题") String title){
        // 构建 NativeQuery
        NativeQuery query = NativeQuery.builder()
                .withQuery(Query.of(q -> q.match(MatchQuery.of(mq-> mq
                        .field("title")
                        .query(title)
                        .boost(1.0f)
                )))).build();
        SearchHits<NewsEntity> searchHits = elasticsearchTemplate.search(query, NewsEntity.class);
        String customTemplate = """
                阅读以下内容:
                {context_str}

                请用中文总结本节的核心要点(不超过100字):
                """;
        SummaryMetadataEnricher summaryMetadataEnricher = new SummaryMetadataEnricher(chatModel, ListUtil.of(SummaryMetadataEnricher.SummaryType.CURRENT),customTemplate, MetadataMode.ALL);

        List<Document> docs = searchHits.getSearchHits().stream().map(hit -> hit.getContent().getContent()).map(Document::new).toList();
        return summaryMetadataEnricher.apply(docs).stream().map(doc -> ""+doc.getMetadata().get("section_summary")).toList().get(0);
    }

    @Tool(description = "获取某个新闻标题的原文")
    public String getNewsByTitle(@ToolParam(description = "新闻标题") String title){
        // 构建 NativeQuery
        NativeQuery query = NativeQuery.builder()
                .withQuery(Query.of(q -> q.match(MatchQuery.of(mq-> mq
                        .field("title")
                        .query(title)
                        .boost(1.0f)
                )))).build();
        SearchHits<NewsEntity> searchHits = elasticsearchTemplate.search(query, NewsEntity.class);
        return searchHits.getSearchHits().stream().map(hit -> hit.getContent().getContent()).toList().get(0);
    }

这里四个工具需要解释下注意事项:

1 获取今日时间:

测试时发现大模型判断今日时间有事有错误。并且格式不太正确。所以加了这个工具。

2 获取某个新闻标题的总结:

这里用到一个SummaryMetadataEnricher工具类,他能通过大模型来辅助我们生成文章的总结。默认带的prompt不太适用,生成的数据是一些关键词加英文解释,很是奇怪。所以我们在这里换成新的prompt。

String customTemplate = """
                阅读以下内容:
                {context_str}

                请用中文总结本节的核心要点(不超过100字):
                """;

这样我们就可以用大模型来加载相关工具来生成对话了。代码如下:

    public String queryLLM(String question, String label) {
        // Calling the chat model with the question
        ChatResponse chatResponse =chatClient.prompt()
                .system("你是一个新闻评论员,请用中文回答相关时事问题。")
                .advisors(
                        new SimpleLoggerAdvisor()
                )
                .tools(newsTool)
                .user(question)
                .call().chatResponse();
        String response =  Optional.ofNullable(chatResponse)
                .map(ChatResponse::getResult)
                .map(Generation::getOutput)
                .map(AbstractMessage::getText)
                .orElse("");
        return response;
    }

注意这里的newsTool一定要用spring注入进来。而不是new一个对象。很多教程直接new的对象,很是误导,new一个对象就脱离了spring的管理,没办法在方法里注入bean了。

到这里,所有的代码就完成了。我们看一下效果。


我们debug看一下,大模型是否调用了我们写的tools。

通过下断点,我们发现,大模型根据我们的问题,先通过第一个工具获取了今日时间;其次,通过第二个工具获取到今日新闻的所有标题;再次,通过第三个工具获取第一个新闻的总结概要。最后大模型将通过工具获得的数据进行整理,反馈给我们。这样,我们就得到了一个简单的新闻助手。当然,为了实现更多的需求,我们可以加入多个tools来辅助大模型调用。还可以利用mcp来做分布式架构开发。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言