把 Claude Code 的 Computer Use base_url 改到 TaoToken,Playwright Browser-Agent 照样规划动作
2026/9/18 23:50:41 网站建设 项目流程

Claude Code 的 Computer Use 搬到本地浏览器后,Playwright 管执行,模型管规划;TaoToken 在这套分工里只给两样东西:一个 Key,一个 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 入口。原文那套 Python 脚本结构其实挺干净:XueDingMaoClientrequests往 OpenAI 兼容的chat/completions发消息,模型返回gotoclickfill三种动作,run_browser_actions()拿到数组后依次丢给 Playwright 执行,浏览器就真的动起来了。

麻烦的地方不在动作映射,而在中间那一段通道。XDM_API_KEYXDM_BASE_URL写在.env里看着很优雅,可这两个值一旦绑死在一个平台上,换模型、换通道、临时切一下备用线路,你改的就不是一行model,而是地址、密钥、甚至客户端里的路径拼接逻辑。更隐蔽的是拼接:有的代码写f"{base}/chat/completions",有的写f"{base}/v1/chat/completions",两边混着改,404 就是这么来的。

这篇顺着原脚本的调用链往下走,只动通道那一层:Key 换成自己在控制台创建的,Base URL 指向https://taotoken.net/apimodel先照原文写claude-sonnet-4-6(正式可用 ID 以模型广场当时列表为准),动作规划、JSON 解析、Playwright 执行循环全部保留原样。改完直接跑脚本,重点看两件事:plan_actions_with_llm()能不能吐回合法的 JSON 动作数组,run_browser_actions()能不能按goto → fill → click的顺序把页面走完。

1. 从 .env 里写死的 XDM_BASE_URL 说起

1.1 密钥和地址成对绑定,换一个就得翻代码

原文的调用链很短:读.env,实例化XueDingMaoClient,把「当前任务 + 页面状态」拼成 user message 发给模型,模型返回动作数组,Playwright 执行。问题在于XDM_API_KEYXDM_BASE_URL是成对的——Key 属于哪个平台,地址就得跟着是哪个平台。想临时把模型从claude-sonnet-4-6换成别的,或者某条通道今天不稳想切走,你要动的不只是 model 字段,还要确认新平台的鉴权头是不是同一个写法、路径是不是同一层、返回结构choices[0].message.content有没有变。

这种「地址 + 密钥 + 路径」三件套绑在一处的写法,在小脚本里无所谓,一旦你想做多模型协作——比如便宜模型负责第一步导航、强模型负责复杂表单判断——每次换模型都要重新配一遍通道,代码里就会堆出一串if platform == ...的分支。这跟 Computer Use 本身没关系,纯粹是接入层没解耦。

1.2 Computer Use 那套思路,落到浏览器上是三个动作

原脚本借的是 Computer Use 的分工思路,不是它的截图识别能力。模型不直接操作鼠标,而是先输出结构化动作数组:

[ {"action": "goto", "url": "https://example.com/login"}, {"action": "fill", "selector": "#username", "value": "demo_user"}, {"action": "fill", "selector": "#password", "value": "demo_pass"}, {"action": "click", "selector": "button[type=submit]"}, {"action": "wait", "ms": 1500} ]

scripts里定义好的run_browser_actions()负责翻译成 Playwright 调用,浏览器再去点、去填。所以模型侧真正的义务只有一条:稳定输出可被json.loads()解析的动作数组。至于这个模型是走哪条通道、用哪把 Key,跟动作规划的逻辑一点关系都没有——这正是接入层可以独立拆出去的原因。

2. TaoToken 只补两个变量,动作规划逻辑一行不动

2.1 该换的是 .env,不该动的是执行层

改之前先把边界划清楚。要动的东西只有三行环境变量:XDM_API_KEYXDM_BASE_URLXDM_MODEL。不要动的是:plan_actions_with_llm()里的 prompt 结构、run_browser_actions()goto/click/fill的映射关系、以及 Playwright 的启动参数。很多人在换通道时手一抖,把请求体也重写了,结果模型返回的字段名变了,解析直接崩,最后误以为是通道问题。

判断标准很简单:换完之后,模型的返回内容格式一模一样,Playwright 的输入一模一样,那么这次改动就是纯接入层改动。如果run_browser_actions()也要跟着改,说明你改多了。

2.2 model 先照原文写,正式 ID 去模型广场对一遍

原文用的claude-sonnet-4-6可以先原样保留在.env里,跑通链路优先。但正式接入时,model 字段以模型广场当时列表为准,不要凭记忆加日期后缀,也不要把别处看到的 ID 直接粘过来。模型广场里能看到当前可调用的模型名,复制粘贴最省事。如果列表里没有你想要的,说明当前通道没上这个模型,换一个能用的先跑通流程,比死磕一个不存在的 ID 划算。

3. 在 TaoToken 控制台建一把 Key,顺便对一下模型名

3.1 注册、创建 Key、复制到 .env

打开 TaoToken 注册登录,进控制台找到 API Keys 那一页,创建一个新 Key。创建时建议按用途命名,比如playwright-browser-agent,以后在看用量时能一眼分辨是哪套脚本在调。复制出来的值先别急着塞进代码,放.env里,别写死在.py文件——脚本改一版传一次 Git,Key 就跟着泄一次。

这一步拿到的东西就两样:一把 Key,一个要填进工具的 Base URL。第一样是YOUR_API_KEY,第二样是https://taotoken.net/api。注意后者是接口地址,末尾不带/v1,也不要往上加任何查询参数,它跟浏览器里打开的官网落地页不是同一个东西。

3.2 模型广场和用量页顺手看一眼

同一个入口里,模型广场列出当前可调用的模型 ID,复制你打算用的那个填到XDM_MODEL。用量页则建议在第一次跑通脚本之后就去看一眼,确认这次chat/completions调用有没有被记上——有记录说明地址和 Key 都对了;没记录但脚本又不报错,通常说明你看的是另一个账号或另一把 Key。

把这两个页面加进收藏夹,比每次翻聊天记录找地址要省事得多。

4. 改 XueDingMaoClient:base_url 指向 https://taotoken.net/api

4.1 .env 的最终形态

三个变量,一行一个,不要带引号,不要留行尾空格:

XDM_API_KEY=YOUR_API_KEY XDM_BASE_URL=https://taotoken.net/api XDM_MODEL=claude-sonnet-4-6

YOUR_API_KEY从 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 的控制台创建后复制过来;XDM_BASE_URL就写https://taotoken.net/api末尾不要带斜杠,也不要带/v1。原来那版如果写的是https://xxx.com/v1,这次把/v1整段删掉,只留到/api这一层。

4.2 拼接 chat/completions 时,别再多加一层 /v1

原文客户端里那行路径拼接是 404 的高发区。统一成一种写法,并且只保留一处拼接点:

import os import json import requests from dotenv import load_dotenv load_dotenv() class XueDingMaoClient: """负责把「任务 + 页面状态」发给模型,拿回动作数组。""" def __init__(self) -> None: self.api_key = os.environ["XDM_API_KEY"] # 末尾不带 /v1,也不带斜杠 self.base_url = os.environ.get("XDM_BASE_URL", "https://taotoken.net/api").rstrip("/") self.model = os.environ.get("XDM_MODEL", "claude-sonnet-4-6") def chat_endpoint(self) -> str: # 只在这里拼一次,路径是 base + /chat/completions return f"{self.base_url}/chat/completions" def _headers(self) -> dict: return { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", }

如果旧代码里写的是f"{base}/v1/chat/completions",把它改成f"{base}/chat/completions"。Base URL 那一层已经交代清楚了,路径里再叠一个/v1,请求就会打到/api/v1/chat/completions这种不存在的地址上,报错通常是干干净净的 404,看不出任何线索。

4.3 plan_actions_with_llm() 和它的 JSON 约束

请求体保持 OpenAI 兼容的写法,system prompt 里把「只输出 JSON 数组」讲死:

SYSTEM_PROMPT = """你是一个浏览器动作规划器。 只输出 JSON 数组,不要任何解释,不要 Markdown 代码块围栏。 数组元素只能是下面三种之一: {"action": "goto", "url": "..."} {"action": "fill", "selector": "...", "value": "..."} {"action": "click", "selector": "..."} """ def plan_actions_with_llm(self, task: str, page_state: str) -> list: payload = { "model": self.model, "messages": [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": f"任务:{task}\n当前页面:{page_state}"}, ], "temperature": 0, } resp = requests.post( self.chat_endpoint(), headers=self._headers(), json=payload, timeout=60, ) resp.raise_for_status() raw = resp.json()["choices"][0]["message"]["content"] # 有些模型仍会习惯性加上围栏,这里兜一层 text = raw.strip().removeprefix("```json").removeprefix("```").removesuffix("```").strip() return json.loads(text)

注意temperature压到 0,浏览器动作规划不需要创意,需要的是同一页面状态下尽量给出同一套动作。返回结构仍走choices[0].message.content,这部分不用因为换了通道而重写。

5. run_browser_actions() 照旧:goto、fill、click 按序执行

5.1 动作数组是模型和执行层之间的唯一契约

接入层换掉之后,模型和执行层之间的接口必须保持不变,也就是那个 JSON 数组。它是整套脚本的契约:模型只管写数组,Playwright 只管读数组。契约不变,换通道就是纯粹的配置改动。

数组里的selector建议用稳定的id或语义化的 CSS,不要用一层层div > div:nth-child(3)。模型看不见你的 DOM,只能靠你在 user message 里贴进去的页面片段来猜选择器,选择器越脆弱,模型猜错的概率越高,最后表现成「通道好像有问题,返回的动作点不动」,其实是选择器写飘了。

5.2 执行循环保持原样,只补一个未知动作兜底

from playwright.sync_api import sync_playwright def run_browser_actions(actions: list, headless: bool = False) -> None: with sync_playwright() as p: browser = p.chromium.launch(headless=headless) page = browser.new_page() try: for step in actions: action = step.get("action") if action == "goto": page.goto(step["url"], wait_until="domcontentloaded") elif action == "fill": page.fill(step["selector"], step["value"]) elif action == "click": page.click(step["selector"]) elif action == "wait": page.wait_for_timeout(int(step.get("ms", 500))) else: raise ValueError(f"未知动作: {step}") finally: browser.close()

这个循环跟模型走哪条通道毫无关系。goto先打开页面,fill按顺序填两个输入框,click提交,中间夹一个wait等页面响应——顺序错了不是模型的问题,多半是你在 user message 里贴的页面状态过期了。先让模型重新看一眼当前页面,再让它出下一步动作,比一次性要求它规划五步要稳。

6. 跑一遍:动作数组回来了,页面也跟着动了

6.1 第一次运行盯三个点

第一步,只打印不执行:把plan_actions_with_llm()的返回值print出来,确认它是一个 list,元素是 dict,action字段在goto/fill/click三者之内。第二步,接上run_browser_actions(),打开headless=False,肉眼看着浏览器把动作走完。第三步,回 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 的用量页,确认这次调用被记上账。

三步都过,说明接入层已经通了:Key 有效、地址正确、路径没多带/v1、返回结构没变。

6.2 顺手把 Claude Code 指到同一条通道

如果你平时也用 Claude Code 帮着改这套脚本,可以让它走同一把 Key、同一个地址,省得在两套配置之间来回切。环境变量写法:

export ANTHROPIC_BASE_URL=https://taotoken.net/api export ANTHROPIC_AUTH_TOKEN=YOUR_API_KEY export ANTHROPIC_MODEL=claude-sonnet-4-6

或者写进~/.claude/settings.jsonenv字段,键名同样是ANTHROPIC_BASE_URLANTHROPIC_AUTH_TOKENANTHROPIC_MODEL。这里地址不带任何查询参数,就是纯的https://taotoken.net/api;模型名以模型广场为准。Claude Code 在这里的角色是帮你读报错、改脚本,浏览器里的点击和填写仍然由本地 Playwright 完成。

7. 报错对照:401、404、JSON 解析失败

7.1 认证和路径类

现象常见原因处理方式
401 UnauthorizedKey 复制时带了空格或换行,或请求头没写成Bearer YOUR_API_KEY重新从控制台复制 Key,检查Authorization
404 Not Found,路径里出现/api/v1/chat/completions路径拼接时多叠了一层/v1改成base + /chat/completions,Base URL 保持https://taotoken.net/api
403 或权限类提示Key 属于另一个账号,或该 Key 被限制可用模型换一把新 Key,并核对模型广场里的模型名

这三类都属于配置问题,不是 Computer Use 逻辑问题。判断方法:把同样的地址、Key、模型名塞进一次最简单的curl或对话测试里,能通就是脚本写错了,不通就是配置写错了。

7.2 模型和输出类

model not found一类的报错,基本就是XDM_MODEL的值不在模型广场列表里。改成列表里能看到的 ID,跑通之后再考虑替换。

json.decoder.JSONDecodeError则是另一个方向的问题:模型确实返回了内容,但内容不是纯 JSON。可能是它加了解释文字,也可能包了一层代码块围栏。先看removeprefix那一层兜底有没有生效;如果还不行,往 system prompt 里再强调一次「只输出 JSON 数组」,并且把temperature保持为 0。这类错误跟通道无关,换哪家都会遇到,属于提示词层面的活。

8. 换模型只改 model 字段,通道不用重配

8.1 多模型协作的切换成本降到一行

接入层拆开之后,「便宜模型做初筛、强模型做复杂表单」这种协作模式就好实现了:两个XueDingMaoClient实例,共用同一份XDM_BASE_URLXDM_API_KEY,只是初始化时传入不同的model。换通道、换模型都不用碰run_browser_actions(),也不需要改路径拼接。

这是这次改造最实在的收益:把「通道」和「模型」拆成两个独立变量。以前它俩焊在一起,每换一次都要重新验证一遍路径。

8.2 跑通之后,这几个页面值得打开

脚本跑出第一组动作数组之后,别急着写更长的流程。先用 TaoToken 模型对话 拿同一把 Key 发一条测试消息,确认模型 ID 和地址是一对儿;如果打算把这类浏览器 Agent 长期挂着跑,去 Coding Plan 看一眼套餐是否够用;需要另建一把专用 Key 就去 控制台 API Keys 创建;想把这套流程和 Claude Code 的用法对应起来,参数对照在 Claude Code 接入文档 里。

回到脚本本身,接下来要做的事只有一件:把 user message 里的页面状态贴得更准一点。模型的动作规划质量,八成取决于你喂给它的页面信息,而不是取决于它走哪条通道。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询