1. 从 LangGraph 状态图视角理解 langgraph-cua 是什么
如果你写过 LangGraph,应该熟悉那种把任务拆成节点、用边串起来的感觉。langgraph-cua 就是把这套思路搬到了「让 AI 操作电脑」这件事上。它是什么?一句话:一个基于 LangGraph 的 Python 库,用来编排 CUA(Computer Use Agent,计算机使用代理),让模型能像人一样在浏览器里点击、输入、翻页、填表。适合谁?适合已经会写 Python、想把手里的重复网页操作交给代理跑、又不想自己从零搭状态机的开发者。
CUA 这个词这两年出现得很多,但真正落地时会发现难点不在模型,而在「怎么把一次任务拆成可观测、可中断、可恢复的步骤」。langgraph-cua 的价值就在这里:它把 CUA 的执行流程抽象成一张状态图,每个节点负责一件事,比如创建虚拟机、执行动作、判断是否完成。你不需要自己写 while 循环去轮询浏览器状态,图本身会帮你推进。
它默认配合 Scrapybara 沙箱使用。Scrapybara 提供的是远程虚拟机环境,里面跑着浏览器,代理的所有点击和输入都发生在那个隔离环境里,不会碰到你本机的桌面。这个配合场景很关键:本地跑浏览器自动化容易污染环境、留下登录态、甚至误操作真实账号;放到沙箱里,任务结束直接销毁,干净利落。
从 LangGraph 视角看,langgraph-cua 的图大致长这样:入口节点接收 messages,然后 create_vm_instance 节点拉起沙箱,接着进入一个循环——模型决定下一步动作,动作在沙箱里执行,执行结果回灌给模型,直到模型认为任务完成。这个循环在 LangGraph 里就是条件边加状态累积。你如果读过 LangGraph 的 ReAct 示例,会发现结构非常像,只是「工具」变成了浏览器操作。
我试过把它接进一个查价格的流程,最直观的感受是:以前写 Selenium 要处理等待、元素定位、异常重试,现在这些都被代理的决策层吸收了,你更多是在调图和看流式输出。当然代价是你要接受模型偶尔点错,所以状态图的可中断设计就很重要——你可以在某个节点后插入人工确认,这也是 LangGraph 的强项。
理解它是什么之后,下一步就是把它跑起来。下面从环境依赖开始,一步步给你可复制的配置。
2. TaoToken 前置准备与 langgraph-cua 环境依赖安装
在真正写图之前,先把两件事准备好:模型调用的入口,以及 Python 环境。langgraph-cua 本身不绑定某一家模型服务,它需要一个兼容 OpenAI 接口的 endpoint。这里我用 TaoToken 作为统一入口,原因是它同时提供对话模型和编码场景的额度,后面如果你想把这个 CUA 流程接到更长的自动化任务里,不用再换一套鉴权。
先拿 Key。打开 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite ,登录后创建一个 API Key,复制出来。这个 Key 就是后面环境变量里的 OPENAI_API_KEY。注意不要把它写进代码提交到仓库,用 .env 管理。
接着是 Scrapybara 的 Key。langgraph-cua 默认用 Scrapybara 做沙箱,你需要去它的控制台注册并拿到 SCRAPYBARA_API_KEY。这一步不能省,因为 create_vm_instance 节点会真的去拉起一台远程虚拟机。免费额度通常够跑几次验证,正式用再考虑升级。
Python 环境建议 3.10 以上,3.11 更稳。用 venv 隔离:
python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate pip install --upgrade pip pip install langgraph-cua python-dotenv装完之后确认一下版本:
pip show langgraph-cua你会看到版本号,目前公开的是 0.0.1 这个早期版本,接口可能还会变,所以下面给的配置以「能跑通」为目标,不追求覆盖所有参数。
然后建一个 .env 文件,放在项目根目录:
OPENAI_API_KEY=你的_taotoken_key OPENAI_BASE_URL=https://taotoken.net/api SCRAPYBARA_API_KEY=你的_scrapybara_key这里有个容易踩的点:langgraph-cua 内部调用模型时读的是 OPENAI_API_KEY 和 OPENAI_BASE_URL,如果你只设了 Key 没设 Base URL,它会默认打到官方地址,而你的 Key 是 TaoToken 的,结果就是 401。所以 Base URL 一定要显式写。
如果你更习惯用配置文件而不是环境变量,也可以在代码里显式传参,但环境变量方式对 dotenv 最友好。装依赖这一步看起来简单,实际报错大多出在 Python 版本和依赖冲突上,下一节我会把配置片段和常见冲突一起讲。
3. 可复制的 langgraph-cua 最小状态图配置
现在进入核心部分:写一个最小的 CUA 图并跑起来。langgraph-cua 暴露的入口是 create_cua(),它返回一个编译好的 LangGraph 图对象。你不需要自己定义 State,库内部已经封装好了 messages 的累积逻辑。
先看完整的最小可运行脚本,保存为 cua_demo.py:
import asyncio import os from dotenv import load_dotenv from langgraph_cua import create_cua load_dotenv() # 确认环境变量已加载,避免 401 assert os.getenv("OPENAI_API_KEY"), "缺少 OPENAI_API_KEY" assert os.getenv("OPENAI_BASE_URL"), "缺少 OPENAI_BASE_URL" assert os.getenv("SCRAPYBARA_API_KEY"), "缺少 SCRAPYBARA_API_KEY" cua_graph = create_cua() messages = [ { "role": "system", "content": "You're an advanced AI assistant with browser access.", }, { "role": "user", "content": "打开 example.com,读出页面标题并返回。", }, ] async def main(): stream = cua_graph.astream( {"messages": messages}, stream_mode="updates", ) async for update in stream: for node_name, node_output in update.items(): print(f"[节点] {node_name}") if node_name == "create_vm_instance": stream_url = node_output.get("stream_url") print(f"[沙箱] stream_url = {stream_url}") if "messages" in node_output: last = node_output["messages"][-1] print(f"[消息] {getattr(last, 'content', last)}") if __name__ == "__main__": asyncio.run(main())这段代码里有几个关键点值得展开。第一,stream_mode="updates" 让你能看到每个节点执行后的增量输出,这对调试 CUA 特别重要,因为代理的决策是逐步产生的,一次性等最终结果你根本不知道它卡在哪。第二,create_vm_instance 节点的输出里带 stream_url,这是沙箱的实时画面地址,你可以用浏览器打开看代理在干什么,排查点击错位时非常有用。第三,messages 的 system 提示词决定了代理的行为风格,官方示例用的是「advanced AI assistant with browser access」,你可以按任务改,但别删掉 browser access 这个语义,否则模型可能不知道自己有浏览器。
如果你想把模型 ID 显式指定,而不是用默认值,可以在 create_cua 里传参。不同版本的参数名可能不同,稳妥做法是先看签名:
import inspect from langgraph_cua import create_cua print(inspect.signature(create_cua))打印出来你会看到它接受哪些关键字参数,常见的有 model、zdr_enabled 等。zdr_enabled=True 表示零数据保留模式,每次请求都把完整消息历史传给模型,而不是复用之前的响应 ID,适合对隐私敏感的场景,代价是 token 消耗更高。
关于 Scrapybara 沙箱的配合,这里再补一句:create_vm_instance 默认拉起的是一台带浏览器的 Ubuntu 实例。你不需要在本地装 Chrome 或 Playwright,所有操作都在远端。这也是它和本地 Selenium 最大的区别——环境是临时的、隔离的,任务跑完可以销毁,不会在你机器上留下 cookie 和缓存。
配置写好后,先别急着跑复杂任务,用上面那个「读页面标题」的小任务验证链路。下一节讲怎么确认它真的成功了。
4. 本地运行验证与流式输出结果解读
运行命令很简单:
python cua_demo.py第一次跑会慢一些,因为要等沙箱创建。你会在终端看到类似这样的输出顺序:
[节点] create_vm_instance [沙箱] stream_url = https://... [节点] call_model [消息] ... [节点] execute_action [消息] ... [节点] call_model [消息] 页面标题是 Example Domain这个顺序就是状态图的推进过程。create_vm_instance 完成后,图进入 call_model,模型根据当前 messages 决定下一步动作,比如「导航到 example.com」;然后 execute_action 节点在沙箱里执行这个动作;执行结果作为新消息回灌,再次进入 call_model,直到模型输出最终答案。
怎么判断成功?看两个信号。一是最后一条消息里包含了你要求的信息,比如「Example Domain」这个标题。二是整个流程没有卡在某个节点反复循环。如果 call_model 和 execute_action 来回超过十几次还没结束,通常是任务描述太模糊,模型在反复试探。
把 stream_url 复制到浏览器打开,你能看到沙箱的实时画面。这个画面是验证代理行为最直接的方式。有时候模型说「已点击按钮」,但画面上根本没动,那就是动作映射出了问题,可能是坐标偏移或者页面还没加载完。看到画面你就能判断是模型决策错还是执行层错。
流式输出里还有一个细节:node_output 的结构在不同节点不一样。create_vm_instance 返回的是沙箱信息,call_model 返回的是 messages,execute_action 返回的可能是动作执行结果。所以遍历 update.items() 时要做类型判断,不能假设每个节点都有 messages 字段。上面脚本里用 getattr 做了兜底,就是这个原因。
如果你跑通了这个小任务,可以试着把 user 消息换成稍微复杂一点的,比如「打开 example.com,找到页面上的 More information 链接,读出它的 href」。这会触发多步操作:导航、定位元素、读取属性。观察它需要几轮 call_model 才完成,你就能对代理的能力边界有个直观感受。
验证通过后,说明你的 Key、Base URL、沙箱三件套都配对了。接下来把常见报错过一遍,省得你遇到时抓瞎。
5. langgraph-cua 常见报错排查:401、local proxy failed 与 reading choices
这一节按真实会遇到的报错来。第一个,401 Unauthorized。这个几乎都是 Key 或 Base URL 的问题。检查顺序:.env 里 OPENAI_API_KEY 是不是 TaoToken 的 Key,OPENAI_BASE_URL 是不是写成了 https://taotoken.net/api(注意结尾不要多加 /v1,除非文档明确要求)。如果你在代码里手动传了 api_key 参数,确认它没覆盖环境变量。还有一种情况是 dotenv 没加载成功,load_dotenv() 要在读取环境变量之前调用,且 .env 文件要在当前工作目录。
第二个,local proxy failed 或连接超时。这个报错通常出现在沙箱创建阶段,说明你的网络到 Scrapybara 的连接不稳定,或者 SCRAPYBARA_API_KEY 无效。先确认 Key 没写错,再去 Scrapybara 控制台看额度是否用完。如果额度正常,重试一次往往就好了,沙箱服务偶发波动是正常的。注意这里不要试图用任何网络代理工具去「解决」,问题一般出在 Key 或服务端,不在你的本地网络配置。
第三个,reading 'choices' 相关的报错,完整信息可能是 Cannot read properties of undefined (reading 'choices') 或者 Python 侧的 KeyError: 'choices'。这个错误的本质是模型返回体不符合预期,最常见原因是 Base URL 指向了一个不兼容 OpenAI 接口的地址,或者模型 ID 写错了。langgraph-cua 内部按 OpenAI 的响应格式解析 choices 字段,如果你的 endpoint 返回的是别的结构,就会在这里炸。解决办法:确认 OPENAI_BASE_URL 是 https://taotoken.net/api,确认你用的模型 ID 在 TaoToken 的模型列表里存在。可以在 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 查可用模型。
第四个,OAuth 或鉴权跳转类报错。如果你在任务里让代理登录某个网站,可能会遇到 OAuth 回调失败。这不是 langgraph-cua 的 bug,而是沙箱环境和目标站点的交互问题。处理方式是尽量用不需要登录的页面做验证,或者用 Scrapybara 的持久化会话功能保存登录态。早期版本对复杂登录流程支持有限,别一上来就挑战硬骨头。
第五个,图跑起来但一直不结束。前面提过,这通常是任务描述模糊。把 user 消息改得更具体,比如加上「只做一步」「完成后立即返回」这类约束。另外可以在 create_cua 时设置最大迭代次数(如果版本支持),防止无限循环。
排查时有个通用技巧:把 stream_mode 改成 "values",看完整状态快照,而不是增量。这样你能看到 messages 数组的全貌,定位是哪一步的消息导致了异常。两种模式配合用,调试效率会高很多。
6. 把 langgraph-cua 接入你的自动化流程:从验证到长期编码
跑通最小示例后,你大概能判断它适不适合自己的场景了。如果你的需求是「偶尔跑一次的网页信息收集」,那直接用脚本调 create_cua 就够了,不需要更复杂的编排。但如果你要把它做成长期运行的服务,或者和编码任务结合,那就值得考虑更稳定的额度方案。
langgraph-cua 的图是可以嵌套的。你可以把它作为一个子图,嵌进更大的 LangGraph 应用里。比如外层图负责调度多个 CUA 任务,内层图负责单个网页操作。这种组合方式在 LangGraph 里很自然,因为图本身就是可组合的。实际写的时候,把 create_cua() 返回的对象当作一个节点加进外层图即可,状态通过 messages 传递。
对于需要长期跑编码和代理任务的场景,TaoToken 的 Coding Plan 更合适,额度模型和按次调用不同,适合持续性的工作负载。你可以在这里了解:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。如果你的 CUA 任务需要频繁调用模型做决策,用包月方案比按 token 计费更可控。
接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有 Base URL、鉴权方式、模型列表的完整说明。遇到接口层面的疑问先查文档,比在代码里猜要快。
最后给一个实用建议:把沙箱的 stream_url 记录下来,配合日志一起存。CUA 的调试成本主要在「不知道代理为什么这么做」,有了画面回放和节点日志,复盘会轻松很多。你可以写个小函数,在每个节点输出时把 node_name 和时间戳打到文件里,跑几次之后就能看出哪些任务容易卡住。
如果你还没决定要不要深入,先用本文的最小示例跑三个不同类型的任务:读页面信息、填一个简单表单、点击多步导航。跑完你心里就有数了。需要开始的话,先去 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 拿 Key,把环境变量配好,剩下的就是调图的事了。