1. 从一次被 Merge 的 PR 说起:JManus 的 playwright 浏览器下载问题
Alibaba JManus 是 Spring AI Alibaba 生态里的一个 AI Agent 项目,它内置了一个浏览器操作工具,底层用的是 playwright。也就是说,当 Agent 需要打开网页、搜索实时信息、抓取页面内容时,实际执行动作的是 playwright 驱动的浏览器。这个设计本身没问题,但第一次运行时会触发一个很具体的体验问题:playwright 默认会把 chrome、firefox、webkit 三个浏览器全部下载一遍,而 JManus 实际只需要其中一个。
这个问题的来源在 playwright 的 Java 绑定层。调用Playwright.create()时,底层会走DriverJar.installBrowsers(),而它执行的安装命令是固定的install,没有留出追加浏览器名称的入口。官方 CLI 是支持mvn exec:java -e -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install chromium"这种写法的,但 JManus 不可能要求每个使用者在启动前先手动跑一遍命令,那样体验就断了。
我提交的 PR 思路是:既然DriverJar本身没有扩展点,那就看它的实例化过程有没有突破口。源码里DriverJar创建实例时会读取环境变量playwright.driver.impl,value 是一个类名,没配就默认用DriverJar。于是我定义了一个ManusDriverJar继承DriverJar,重写installBrowsers(),在install后面按配置追加目标浏览器名,再通过环境变量让这个子类生效。PR 合并后,本地复现的关键就变成了两件事:让 JManus 走通 playwright 工具链,以及让模型调用链路有一个稳定的 Key 通道。这篇就围绕这两点,把可复制的配置和验证动作写清楚。
2. 前置准备:用 TaoToken 统一 Key 接入 JManus 的模型通道
JManus 作为 Agent,除了浏览器工具,还需要一个能持续调用的模型通道。本地复现时最容易卡住的不是 playwright,而是模型 Key 的配置方式:不同工具、不同脚本各配一套 Key,改起来容易漏。我现在的做法是用 TaoToken 做统一入口,官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,一个 Key 覆盖对话、编码、Agent 这几类调用。
你需要先拿到 Key,入口在 API Keys 页面:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。拿到之后不要急着写进代码,先确认两件事:一是这个 Key 对应的额度是否覆盖你要跑的模型,二是 base_url 要写成https://taotoken.net/api,不要带多余路径。JManus 这类项目通常读的是 OpenAI 兼容格式的配置,所以 base_url 和 api_key 这两个字段是核心。
如果你只是想先验证模型通道是否通,可以直接用模型对话页面发一条消息:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。这一步能排除掉「Key 本身不可用」这种低级问题,再去调 JManus 会省很多时间。长期跑编码和 Agent 任务的话,Coding Plan 更适合持续调用:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。
注意:不要把 Key 硬编码进提交到 Git 的配置文件里。本地复现可以用环境变量,或者放在不被版本控制的
settings.local.json中。
3. 可复制配置:config.toml 与 settings.json 骨架
JManus 的配置分两层:一层是模型通道,一层是 playwright 驱动。下面这份config.toml是模型通道的骨架,字段名按 OpenAI 兼容格式写,你按自己实际使用的模型名替换model即可。
# config.toml [llm] provider = "openai-compatible" base_url = "https://taotoken.net/api" api_key = "${TAOTOKEN_API_KEY}" model = "your-model-name" temperature = 0.3 max_tokens = 4096 timeout_seconds = 120 [agent] name = "jmanus-local" max_iterations = 15 tool_timeout_seconds = 180 [browser] enabled = true driver_impl = "com.alibaba.jmanus.browser.ManusDriverJar" install_browser = "chromium" headless = true这里有两个关键点。第一,api_key用${TAOTOKEN_API_KEY}占位,实际值从环境变量注入,避免明文。第二,driver_impl指向自定义的ManusDriverJar,install_browser指定只装 chromium,这正是 PR 合并后生效的配置入口。
然后是settings.json,它负责把环境变量和运行参数串起来。如果你用的是 IDE 或本地脚本启动,这份骨架可以直接参考。
{ "env": { "TAOTOKEN_API_KEY": "sk-your-key-here", "PLAYWRIGHT_DRIVER_IMPL": "com.alibaba.jmanus.browser.ManusDriverJar", "PLAYWRIGHT_INSTALL_BROWSER": "chromium", "PLAYWRIGHT_BROWSERS_PATH": "./.playwright-browsers" }, "runtime": { "java_opts": "-Xmx2g -Dfile.encoding=UTF-8", "working_dir": "./jmanus-local" }, "browser": { "headless": true, "download_timeout_ms": 300000 } }PLAYWRIGHT_BROWSERS_PATH建议单独指定一个目录,这样浏览器文件不会散落在用户主目录里,清理和复现都方便。PLAYWRIGHT_DRIVER_IMPL和配置文件里的driver_impl保持一致,避免两处配置打架。
环境变量注入命令如下,Linux/macOS 和 Windows 分开写。
# Linux / macOS export TAOTOKEN_API_KEY="sk-your-key-here" export PLAYWRIGHT_DRIVER_IMPL="com.alibaba.jmanus.browser.ManusDriverJar" export PLAYWRIGHT_INSTALL_BROWSER="chromium" export PLAYWRIGHT_BROWSERS_PATH="./.playwright-browsers"# Windows PowerShell $env:TAOTOKEN_API_KEY="sk-your-key-here" $env:PLAYWRIGHT_DRIVER_IMPL="com.alibaba.jmanus.browser.ManusDriverJar" $env:PLAYWRIGHT_INSTALL_BROWSER="chromium" $env:PLAYWRIGHT_BROWSERS_PATH="./.playwright-browsers"配置写完后,先别急着跑完整 Agent,先单独验证 playwright 是否只下载 chromium。这一步能直接确认 PR 的修改是否在你的本地生效。
4. 验证请求:确认只下载 chromium 且模型通道可用
验证分两步,先验 playwright,再验模型通道。playwright 这一步的核心是观察下载日志里出现的浏览器数量。如果你看到只出现 chromium 相关的下载条目,说明ManusDriverJar已经接管了安装逻辑。
// PlaywrightInstallCheck.java import com.microsoft.playwright.Playwright; public class PlaywrightInstallCheck { public static void main(String[] args) { System.out.println("driver impl = " + System.getenv("PLAYWRIGHT_DRIVER_IMPL")); System.out.println("install browser = " + System.getenv("PLAYWRIGHT_INSTALL_BROWSER")); try (Playwright playwright = Playwright.create()) { System.out.println("playwright created, browser type = chromium"); playwright.chromium().launch(); System.out.println("chromium launched successfully"); } } }运行前确认 classpath 里已经包含 JManus 的 browser 模块和 playwright 依赖。运行后重点看两处输出:driver impl是否为你配置的类名,以及下载日志里是否只有 chromium。如果driver impl打印为 null,说明环境变量没注入成功,回到上一节检查 export 命令。
模型通道的验证用一个最小请求即可,确认 base_url 和 Key 都能通。
curl -X POST "https://taotoken.net/api/v1/chat/completions" \ -H "Authorization: Bearer ${TAOTOKEN_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "model": "your-model-name", "messages": [{"role": "user", "content": "reply with ok"}], "max_tokens": 16 }'返回里能看到choices字段就说明通道正常。如果返回 401,检查 Key 是否复制完整;如果返回 404,检查 base_url 是否多写了/v1之外的路径。TaoToken 的接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各语言的最小示例,排障时可以对照。
两步都通过后,再启动 JManus 跑一个「打开网页并搜索」的任务,观察 Agent 是否调用了浏览器工具、是否返回了实时结果。这一步成功,就说明 PR 合并后的本地复现完整走通了。
5. 本篇常见错排查清单
复现过程中最容易踩的坑集中在配置读取和驱动加载这两块,下面按现象列出来。
现象一:仍然下载三个浏览器。说明ManusDriverJar没有生效。检查PLAYWRIGHT_DRIVER_IMPL的值是否和类全名完全一致,包括包名大小写。再检查该类是否在 classpath 中,如果 JManus 是分模块构建的,确认 browser 模块已经编译并打进依赖。
现象二:启动时报ClassNotFoundException。自定义驱动类没有被加载。确认构建产物里包含ManusDriverJar.class,并且driver_impl指向的类名没有拼写错误。如果是 IDE 运行,检查运行配置里的环境变量是否真的传进去了。
现象三:模型请求返回 401 或 403。Key 无效或没有对应模型权限。先用模型对话页面单独发一条消息验证 Key:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。如果那边正常,问题就在 JManus 的配置读取上,检查config.toml里的api_key占位是否被正确替换。
现象四:playwright 下载超时。浏览器文件较大,网络波动时容易超时。把download_timeout_ms调大,或者提前手动把浏览器文件放到PLAYWRIGHT_BROWSERS_PATH目录下,让安装逻辑跳过下载。
现象五:Agent 不调用浏览器工具。检查[browser] enabled是否为 true,以及工具注册是否在 Agent 启动时完成。有些情况下模型没有选择工具,可以换一个更明确的指令,比如「打开 example.com 并返回页面标题」。
现象六:headless 模式下启动失败。某些环境缺少系统依赖库。先切到headless = false看是否能启动,如果能,说明是显示相关依赖缺失,按 playwright 官方提示补齐系统库即可。
提示:排障时优先看日志里
driver impl和install browser这两行输出,它们能直接告诉你配置有没有被读到。
6. 后续:把统一 Key 用在长期编码与 Agent 任务上
PR 合并只是起点,真正日常跑起来之后,你会发现模型调用是持续发生的:Agent 每轮推理、每次工具调用后的总结,都在消耗通道。这时候统一 Key 的价值就体现出来了,不用在多个工具之间来回换配置。如果你后面要跑更长时间的编码任务或 Agent 循环,Coding Plan 的额度模型更适合:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。
控制台里可以看调用记录和额度使用情况:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。本地复现完成后,建议把config.toml和settings.json里的敏感字段全部改成环境变量引用,再提交到自己的仓库,这样换机器时只需要重新注入环境变量,配置本身不用动。
如果你也在跟 JManus 或类似的 Agent 项目,建议从 playwright 这类具体工具链入手,先把一个工具的配置跑通,再逐步接入模型通道。这样出问题时排查范围小,复现也快。