当前位置: 代码网 > it编程>编程语言>Java > Spring AI 2.0.0 + Playwright实现联网检索工具

Spring AI 2.0.0 + Playwright实现联网检索工具

2026年07月31日 Java 我要评论
一、整体方案说明spring ai 2.0.0:提供 toolcallback 工具回调机制,将联网能力封装为 ai 可调用工具;playwright java:浏览器爬取网页,解决 js 动态渲染页

一、整体方案说明

  • spring ai 2.0.0:提供 toolcallback 工具回调机制,将联网能力封装为 ai 可调用工具;
  • playwright java:浏览器爬取网页,解决 js 动态渲染页面(普通 httpclient 拿不到动态内容);
  • 流程:大模型判断需要联网 → 调用自定义 playwright 网页检索工具 → 抓取网页正文 → 把网页内容喂给大模型生成答案。

二、依赖引入(maven)

<dependency>
    <groupid>org.projectlombok</groupid>
    <artifactid>lombok</artifactid>
    <optional>true</optional>
</dependency>
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-web</artifactid>
</dependency>
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-test</artifactid>
    <scope>test</scope>
</dependency>
<dependency>
    <groupid>org.springframework.ai</groupid>
    <artifactid>spring-ai-starter-model-ollama</artifactid>
    <version>${spring-ai.version}</version>
</dependency>
<dependency>
    <groupid>com.microsoft.playwright</groupid>
    <artifactid>playwright</artifactid>
    <version>${playwright.version}</version>
</dependency>

三、spring ai 自定义 toolcallback 联网检索工具

spring ai 2.0 工具标准:实现 toolcallback,搭配 @tool 注解定义工具入参描述,大模型自动识别调用。

浏览器搜索服务统一接口

package com.ybw.tool.search;

import com.ybw.model.searchresult;

import java.util.list;

/**
 * 浏览器搜索服务统一接口
 */
public interface browsersearchservice {

    /**
     * 执行搜索并返回结果列表
     *
     * @param query 搜索关键词
     * @return 搜索结果列表
     */
    list<searchresult> search(string query);
}

无头浏览器搜索工具(后台运行,不显示窗口)

package com.ybw.tool.search;

import com.microsoft.playwright.browser;
import com.microsoft.playwright.page;
import com.ybw.model.searchresult;
import lombok.extern.slf4j.slf4j;
import org.springframework.ai.tool.annotation.tool;
import org.springframework.stereotype.component;

import java.net.urlencoder;
import java.nio.charset.standardcharsets;
import java.util.arraylist;
import java.util.list;

/**
 * 无头浏览器搜索工具(后台运行,不显示窗口)
 */
@slf4j
@component("browsersearch")
public class browsersearchtool implements browsersearchservice {

    private final browser browser;

    public browsersearchtool(browser browser) {
        this.browser = browser;
    }

    @override
    @tool(description = "使用浏览器进行网络搜索,获取最新信息")
    public list<searchresult> search(string query) {
        log.info("开始浏览器搜索: {}", query);
        list<searchresult> results = new arraylist<>();

        try (page page = browser.newpage()) {
            string encodedquery = urlencoder.encode(query, standardcharsets.utf_8);
            string searchurl = "https://www.baidu.com/s?wd=" + encodedquery;

            page.navigate(searchurl);
            page.waitforloadstate();
            page.waitfortimeout(3000);

            list<string> titles = page.locator("h3").alltextcontents();

            list<string> links = page.locator("h3 > a").all().stream()
                    .map(element -> element.getattribute("href"))
                    .tolist();

            list<string> summaries = page.locator("div.c-abstract").alltextcontents();
            if (summaries.isempty()) {
                summaries = page.locator("div.result-op").alltextcontents();
            }

            int count = math.min(titles.size(), math.min(links.size(), summaries.size()));
            for (int i = 0; i < count && i < 5; i++) {
                results.add(new searchresult(
                        titles.get(i),
                        links.get(i),
                        summaries.get(i)
                ));
            }

            log.info("搜索完成,共找到 {} 条结果", results.size());
        } catch (exception e) {
            log.error("浏览器搜索失败: {}", e.getmessage(), e);
        }

        return results;
    }
}

可见浏览器搜索工具(显示浏览器窗口,用户可观察搜索过程)

package com.ybw.tool.search;

import com.microsoft.playwright.browser;
import com.microsoft.playwright.page;
import com.ybw.model.searchresult;
import lombok.extern.slf4j.slf4j;
import org.springframework.ai.tool.annotation.tool;
import org.springframework.beans.factory.annotation.qualifier;
import org.springframework.stereotype.component;

import java.net.urlencoder;
import java.nio.charset.standardcharsets;
import java.util.arraylist;
import java.util.list;

/**
 * 可见浏览器搜索工具(显示浏览器窗口,用户可观察搜索过程)
 */
@slf4j
@component("browservisiblesearch")
public class browservisiblesearchtool implements browsersearchservice {

    private final browser visiblebrowser;

    public browservisiblesearchtool(@qualifier("visiblebrowser") browser visiblebrowser) {
        this.visiblebrowser = visiblebrowser;
    }

    @override
    @tool(description = "使用可见浏览器窗口进行网络搜索,用户可以看到搜索过程")
    public list<searchresult> search(string query) {
        log.info("开始可见浏览器搜索: {}", query);
        list<searchresult> results = new arraylist<>();

        try (page page = visiblebrowser.newpage()) {
            page.navigate("https://www.baidu.com");
            page.waitforloadstate();
            page.waitfortimeout(2000);

            try {
                page.evaluate("document.getelementbyid('kw').value = '" + escapequery(query) + "'");
                page.click("input#su", new page.clickoptions().setforce(true));
            } catch (exception e) {
                log.warn("表单方式搜索失败,切换到url直接导航: {}", e.getmessage());
                string encodedquery = urlencoder.encode(query, standardcharsets.utf_8);
                page.navigate("https://www.baidu.com/s?wd=" + encodedquery);
            }

            page.waitforloadstate();
            page.waitfortimeout(3000);

            list<string> titles = page.locator("h3").alltextcontents();

            list<string> links = page.locator("h3 > a").all().stream()
                    .map(element -> element.getattribute("href"))
                    .tolist();

            list<string> summaries = page.locator("div.c-abstract").alltextcontents();
            if (summaries.isempty()) {
                summaries = page.locator("div.result-op").alltextcontents();
            }

            int count = math.min(titles.size(), math.min(links.size(), summaries.size()));
            for (int i = 0; i < count && i < 5; i++) {
                results.add(new searchresult(
                        titles.get(i),
                        links.get(i),
                        summaries.get(i)
                ));
            }

            page.waitfortimeout(2000);

            log.info("可见浏览器搜索完成,共找到 {} 条结果", results.size());
        } catch (exception e) {
            log.error("可见浏览器搜索失败: {}", e.getmessage(), e);
        }

        return results;
    }

    private string escapequery(string query) {
        return query.replace("\\", "\\\\").replace("'", "\\'").replace("\"", "\\\"");
    }
}

浏览器搜索工具工厂

package com.ybw.tool.factory;

import com.ybw.tool.search.browsersearchservice;
import com.ybw.tool.search.browsersearchtool;
import com.ybw.tool.search.browservisiblesearchtool;
import org.springframework.stereotype.component;

/**
 * 浏览器搜索工具工厂
 * <p>
 * 根据搜索模式(无头/可见)返回对应的 browsersearchservice 实现。
 * 使用方式:
 * <pre>{@code
 *   browsersearchservice service = factory.createsearchtool(browsersearchmode.headless);
 *   list<searchresult> results = service.search("关键词");
 * }</pre>
 */
@component
public class browsersearchtoolfactory {

    private final browsersearchtool headlesstool;
    private final browservisiblesearchtool visibletool;

    public browsersearchtoolfactory(browsersearchtool headlesstool,
                                    browservisiblesearchtool visibletool) {
        this.headlesstool = headlesstool;
        this.visibletool = visibletool;
    }

    /**
     * 根据搜索模式创建对应的搜索工具实例
     *
     * @param mode 搜索模式:headless(无头模式)或 visible(可见浏览器模式)
     * @return 对应的 browsersearchservice 实现
     */
    public browsersearchservice createsearchtool(browsersearchmode mode) {
        return switch (mode) {
            case headless -> headlesstool;
            case visible -> visibletool;
        };
    }

    /**
     * 浏览器搜索模式枚举
     */
    public enum browsersearchmode {
        /** 无头模式:后台运行,不显示浏览器窗口 */
        headless,
        /** 可见模式:显示浏览器窗口,用户可观察搜索过程 */
        visible
    }
}

搜索结果数据模型

package com.ybw.model;

import lombok.allargsconstructor;
import lombok.data;

/**
 * 搜索结果数据模型
 */
@data
@allargsconstructor
public class searchresult {
    private string title;
    private string url;
    private string summary;

    @override
    public string tostring() {
        return "标题: " + title + "\n链接: " + url + "\n摘要: " + summary;
    }
}

四、封装 playwright 网页抓取工具类

spring ai 2.0 工具标准:实现 toolcallback,搭配 @tool 注解定义工具入参描述,大模型自动识别调用。

package com.ybw.config;

import com.microsoft.playwright.browser;
import com.microsoft.playwright.browsertype;
import com.microsoft.playwright.playwright;
import jakarta.annotation.predestroy;
import lombok.extern.slf4j.slf4j;
import org.springframework.beans.factory.annotation.value;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;

@slf4j
@configuration
public class browsersearchconfig {

    private playwright playwright;

    @value("${spring.ai.browser.timeout:30000}")
    private int timeout;

    /**
     * 初始化 playwright
     *
     * @methodname: playwright
     * @return: com.microsoft.playwright.playwright
     * @author: ybw
     * @date: 2026/7/26
     **/
    @bean(destroymethod = "")
    public playwright playwright() {
        log.info("初始化 playwright");
        this.playwright = playwright.create();
        return playwright;
    }

    /**
     * 创建浏览器实例-无头模式
     *
     * @param playwright playwright 实例
     * @methodname: browser
     * @return: com.microsoft.playwright.browser
     * @author: ybw
     * @date: 2026/7/26
     **/
    @bean
    public browser browser(playwright playwright) {
        log.info("创建浏览器实例(headless={})", true);
        return playwright.chromium().launch(new browsertype.launchoptions()
                .setheadless(true)
                .settimeout(timeout)
        );
    }

    /**
     * 创建浏览器实例-可见模式
     *
     * @param playwright playwright 实例
     * @methodname: visiblebrowser
     * @return: com.microsoft.playwright.browser
     * @author: ybw
     * @date: 2026/7/26
     **/
    @bean("visiblebrowser")
    public browser visiblebrowser(playwright playwright) {
        log.info("创建可见浏览器实例(headless=false)");
        return playwright.chromium().launch(new browsertype.launchoptions()
                .setheadless(false)
                .settimeout(timeout)
        );
    }

    @predestroy
    public void destroy() {
        if (playwright != null) {
            log.info("关闭 playwright");
            playwright.close();
        }
    }
}

五、spring ai 客户端装配工具,实现联网问答

package com.ybw.config;

import com.ybw.tool.search.browsersearchtool;
import com.ybw.tool.search.browservisiblesearchtool;
import org.springframework.ai.chat.client.chatclient;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.context.annotation.primary;

@configuration
public class chatclientconfig {

    @bean
    @primary
    chatclient chatclient(chatclient.builder builder, browsersearchtool browsersearchtool) {
        return builder
                .defaultsystem("你是一个智能助手,可以调用浏览器搜索工具获取最新信息。" +
                        "当用户问的问题需要最新的信息或者你不确定答案时,请使用 search 工具进行搜索。" +
                        "工具返回的搜索结果包含标题、链接和摘要,请根据搜索结果进行回答。" +
                        "必须中文回复。")
                .defaulttools(browsersearchtool)
                .build();
    }

    @bean("visiblechatclient")
    chatclient visiblechatclient(chatclient.builder builder, browservisiblesearchtool browservisiblesearchtool) {
        return builder
                .defaultsystem("你是一个智能助手,可以调用可见浏览器搜索工具获取最新信息。" +
                        "当用户问的问题需要最新的信息或者你不确定答案时,请使用 search 工具进行搜索。" +
                        "工具返回的搜索结果包含标题、链接和摘要,请根据搜索结果进行回答。" +
                        "搜索过程会打开浏览器窗口显示。" +
                        "必须中文回复。")
                .defaulttools(browservisiblesearchtool)
                .build();
    }
}

六、测试用例

可见浏览器搜索测试 - 显示浏览器窗口,可观察搜索过程

package com.ybw.agent.visible;

import com.ybw.tool.factory.browsersearchtoolfactory;
import com.ybw.tool.factory.browsersearchtoolfactory.browsersearchmode;
import com.ybw.model.searchresult;
import com.ybw.tool.search.browsersearchservice;
import jakarta.annotation.resource;
import lombok.extern.slf4j.slf4j;
import org.junit.jupiter.api.displayname;
import org.junit.jupiter.api.test;
import org.springframework.ai.chat.client.chatclient;
import org.springframework.boot.test.context.springboottest;

import java.util.list;

/**
 * 可见浏览器搜索测试 - 显示浏览器窗口,可观察搜索过程
 * <p>
 * 通过工厂模式创建 {@link browsersearchmode#visible} 模式的搜索服务进行测试。
 */
@springboottest
@slf4j
@displayname("可见浏览器搜索测试")
public class visiblebrowsersearchtest {

    @resource(name = "visiblechatclient")
    private chatclient visiblechatclient;

    @resource
    private browsersearchtoolfactory searchtoolfactory;

    @test
    @displayname("直接调用搜索工具 - 搜索 spring ai 最新版本")
    void testdirectsearch() {
        log.info("========== [可见模式] 直接测试可见浏览器搜索工具 ==========");

        browsersearchservice searchservice = searchtoolfactory.createsearchtool(browsersearchmode.visible);
        list<searchresult> results = searchservice.search("spring ai 最新版本");

        printsearchresults(results);
    }

    @test
    @displayname("ai 对话 + 可见浏览器搜索 - 2026年ai发展趋势")
    void testchatwithsearch() {
        log.info("========== [可见模式] 测试 ai 对话 + 可见浏览器搜索 ==========");
        string userquery = "2026年人工智能最新发展趋势?";

        string response = visiblechatclient.prompt()
                .user(userquery)
                .call()
                .content();

        log.info("用户问题: {}", userquery);
        log.info("ai 回答: {}", response);
    }

    private void printsearchresults(list<searchresult> results) {
        if (results.isempty()) {
            log.info("搜索结果为空");
            return;
        }

        for (int i = 0; i < results.size(); i++) {
            searchresult result = results.get(i);
            log.info("结果 {}:", i + 1);
            log.info("标题: {}", result.gettitle());
            log.info("链接: {}", result.geturl());
            log.info("摘要: {}", result.getsummary());
            log.info("----------------------------------------");
        }
    }
}

七、关键优化点与问题解决

1. playwright 性能优化

  • 全局单例浏览器,避免每次检索启动 / 关闭浏览器(开销极大);
  • waituntil("networkidle") 等待页面 js、接口全部加载完毕,适配 spa 前端页面(vue/react 官网、资讯站)。

2. spring ai 2.0 工具机制关键点

  1. 使用 @tool + @toolparam 注解,自动生成工具元数据,大模型自动识别何时调用;
  2. defaulttools() 全局注册工具,所有对话均可使用联网检索;
  3. 工具返回纯文本,过滤 html 标签,减少 token 消耗。

3. 扩展:搜索引擎前置(可选)

当前示例仅支持传入 url 爬取,如需输入关键词自动搜网页,可增加一层搜索引擎接口(百度 / 谷歌搜索 api):

  1. ai 传入关键词 → 调用搜索 api 返回多条网页 url;
  2. 循环调用 playwright 抓取每个页面内容;
  3. 汇总所有网页文本给大模型总结。

八、适用场景 & 局限性

适用

  • 需要实时网络资讯、动态渲染网页(前端 js 页面);
  • spring ai 2.0 工具调用联网增强 rag;

局限

  1. 仅支持传入 url 抓取,需额外对接搜索引擎实现关键词搜索;
  2. playwright 占用内存较高,高并发场景建议浏览器池化管理;
  3. 部分网站存在反爬(ua、验证码、ip 封禁),可补充代理池、验证码识别。

以上就是spring ai 2.0.0 + playwright实现联网检索工具的详细内容,更多关于spring ai playwright联网检索工具的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com