通过OpenAI Agents SDK调用自然语言指令,驱动Playwright完成完整CAPTCHA解决流程,包括reCAPTCHA v2和图片验证码两种类型,并给出403和Pydantic ValidationError两种常见错误解法。
将完整的 Playwright 页面工作流——打开、求解、应用结果、提交、验证——封装为一个 Composio 自定义工具,OpenAI Agents SDK 可以通过自然语言指令调用它。
示例覆盖两种挑战类型:返回 token 的 reCAPTCHA v2,以及使用 ImageToTextTask 进行图片验证码识别,作为独立工具注册在同一个 session 中。
工作流直接连接 OpenAI 官方 API,并使用 Playwright 进行浏览器自动化。
两个常见的集成失败场景是:没有 sessions: write 权限的 Composio key(返回 403),以及工具第一个参数缺少 Pydantic BaseModel 注解(触发 ValidationError)。
按照代码从依赖安装到 reCAPTCHA v2、ImageToTextTask,以及两种最常见的 Composio 集成错误的顺序来讲解。
本指南将 CapSolver 与 Composio 集成为 agent 工具,完成 reCAPTCHA v2 工作流。该工具不仅返回 token,而是运行完整页面序列,并将页面的实际响应作为成功条件。OpenAI Agents SDK 决定何时调用该工具,而 Playwright 浏览器自动化保留了用于提交和验证的页面上下文。
此模式仅用于合法、合理、负责且经用户授权的工作流。技术能力不赋予访问私人、受限、敏感或未授权数据的权限;部署前请查阅相关 AI 自动化指南。
运行脚本
-> OpenAI Agents SDK 决定调用哪个工具
-> Composio 自定义工具:complete_recaptcha_v2
-> Playwright 打开页面
-> capsolver.solve(...) 返回 gRecaptchaResponse
-> 将 token 应用到 g-recaptcha-response
-> Playwright 提交并等待页面
-> 读取页面并判断是否通过
-> 工具返回 {"accepted": ..., "message": ...}
-> Agent 根据 accepted 报告结果
各组件职责如下:
pip install composio composio-openai-agents openai-agents capsolver pydantic playwright
playwright install chromium
每个依赖都有其特定作用:
# API keys.
COMPOSIO_API_KEY = "ak_..."
OPENAI_API_KEY = "sk-..." # Your official OpenAI API key.
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY # OpenAI SDK reads the key from env.
# Configure CapSolver and Composio.
capsolver.api_key = "CAP-..."
composio = Composio(
api_key=COMPOSIO_API_KEY,
provider=OpenAIAgentsProvider(),
)
配置说明:OPENAI_API_KEY 必须写入环境变量,因为 SDK 从中读取密钥;OpenAIAgentsProvider 使 session.tools() 返回的工具与 Agent 兼容;Composio key 需要 sessions: write 权限,否则 session 创建会返回 403。
当前的 Composio OpenAI provider 和 OpenAI Agents SDK 参考文档解释了此配置使用的 provider 和 agent 边界。
赎回你的 CapSolver bonus code 立即提升你的自动化预算!在充值 CapSolver 账户时使用 bonus code CAP26,每次充值可获得额外的 5% bonus——无上限。立即在你的 CapSolver Dashboard 中兑换
停止条件:仅当页面包含预期的成功文本时,工具才报告成功。finally 块在成功和失败路径中都关闭浏览器。
import os
from typing import List, cast
import capsolver
from agents import Agent, Runner, SQLiteSession
from composio import Composio
from composio.core.models.custom_tool import CustomTool
from composio.core.models.tool_router import ToolRouterExperimentalConfig
from composio_openai_agents import OpenAIAgentsProvider
from playwright.sync_api import sync_playwright
from pydantic import BaseModel, Field
# API keys.
COMPOSIO_API_KEY = "ak_..."
OPENAI_API_KEY = "sk-..." # Your official OpenAI API key.
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
# Configure CapSolver and Composio.
capsolver.api_key = "CAP-..."
composio = Composio(
api_key=COMPOSIO_API_KEY,
provider=OpenAIAgentsProvider(),
)
# Input schema for the custom tool; Composio requires a Pydantic BaseModel here.
class CompleteRecaptchaInput(BaseModel):
target_url: str = Field(
default="https://www.google.com/recaptcha/api2/demo",
description="Page URL containing the reCAPTCHA v2 demo",
)
website_key: str = Field(
default="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
description="reCAPTCHA v2 website key from the current page",
)
# Register the whole flow as one Composio tool the agent can call.
# The first parameter's type annotation is required by Composio to infer the schema.
@composio.experimental.tool(preload=True)
def complete_recaptcha_v2(input: CompleteRecaptchaInput, _ctx):
"""Open the page with Playwright, solve reCAPTCHA v2, submit, and verify."""
with sync_playwright() as p:
browser = p.chromium.launch(headless=False) # Set headless=True to hide the window.
page = browser.new_page()
try:
page.goto(input.target_url)
# Ask CapSolver to solve the reCAPTCHA v2 challenge.
solution = capsolver.solve(
{
"type": "ReCaptchaV2TaskProxyLess",
"websiteURL": input.target_url,
"websiteKey": input.website_key,
}
)
token = solution.get("gRecaptchaResponse")
page.evaluate(
"""
(token) => {
const textarea = document.getElementById('g-recaptcha-response');
if (textarea) {
textarea.value = token;
}
}
""",
token,
)
page.click("#recaptcha-demo-submit")
page.wait_for_load_state("networkidle")
result_page = page.content()
# Success only if the page actually shows the success text
accepted = "Verification Success" in result_page
return {
"accepted": accepted,
"message": (
"Verification Success"
if accepted
else "The page did not report Verification Success"
),
}
finally:
browser.close()
def main():
experimental: ToolRouterExperimentalConfig = {
"custom_tools": cast(List[CustomTool], [complete_recaptcha_v2]),
}
session = composio.sessions.create(
user_id="playwright-recaptcha-demo-user",
experimental=experimental,
sandbox={"enable": False}, # Run the tool in this process, not a sandbox.
)
agent = Agent(
name="Playwright reCAPTCHA Assistant",
instructions=(
"When the user asks to run the demo, call complete_recaptcha_v2 "
"with its default values. Report success only when accepted is true."
),
model="gpt-5.2",
tools=session.tools(),
)
# Memory for multi-turn conversation
memory = SQLiteSession("conversation")
print("Composio + Playwright reCAPTCHA v2 demo running once...")
user_input = (
"Call complete_recaptcha_v2 now with its default target_url "
"and website_key. Do not ask for confirmation."
同一模式可以通过注册第二个 Composio 工具来处理标准图片文字验证码。此示例使用 BotDetect CAPTCHA Demo:图片元素是 #demoCaptcha_CaptchaImage,输入框是 #captchaCode,验证按钮是 #validateCaptchaButton。

ImageToTextTask 请求通过 body 提交 Base64 图片。与基于 token 的任务不同,此任务直接返回识别出的文本,无需单独的轮询循环。
image_src = page.locator("#demoCaptcha_CaptchaImage").get_attribute("src")
if not image_src or "," not in image_src:
raise RuntimeError("A valid CAPTCHA image Data URL was not found")
base64_image = image_src.split(",", 1)[1] # Strip the "data:image/...;base64," prefix.
class CompleteImageCaptchaInput(BaseModel):
target_url: str = Field(
default="https://captcha.com/demos/features/captcha-demo.aspx",
description="Image CAPTCHA demo page URL",
)
module: str = Field(
default="common",
description="CapSolver ImageToTextTask recognition module",
)
@composio.experimental.tool(preload=True)
def complete_image_captcha(input: CompleteImageCaptchaInput, _ctx):
"""Open the page with Playwright, recognize the image CAPTCHA, submit, and verify."""
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
try:
page.goto(input.target_url)
page.wait_for_selector("#demoCaptcha_CaptchaImage", state="visible")
# The image src is already a data URL; strip the prefix to get Base64.
image_src = page.locator("#demoCaptcha_CaptchaImage").get_attribute("src")
if not image_src or "," not in image_src:
raise RuntimeError("A valid CAPTCHA image Data URL was not found")
base64_image = image_src.split(",", 1)[1]
solution = capsolver.solve(
{
"type": "ImageToTextTask",
"websiteURL": input.target_url,
"module": input.module,
"body": base64_image,
}
)
captcha_text = solution.get("text")
if not isinstance(captcha_text, str) or not captcha_text:
raise RuntimeError("CapSolver did not return recognized text")
page.fill("#captchaCode", captcha_text) # Fill the recognized text.
page.click("#validateCaptchaButton")
page.wait_for_load_state("networkidle")
result_page = page.content()
# The demo page shows "Correct!" on success, "Incorrect!" on failure.
accepted = "Correct!" in result_page
return {
"accepted": accepted,
"recognized_text": captcha_text,
"message": "Correct!" if accepted else "The page did not report Correct!",
}
finally:
browser.close()
Playwright 打开 CAPTCHA 页面
-> 等待 #demoCaptcha_CaptchaImage 可见
-> 读取 src(data URL)并移除前缀获取 Base64
-> capsolver.solve(ImageToTextTask) 返回文本
-> page.fill 将结果写入 #captchaCode
-> page.click 激活 #validateCaptchaButton
-> page.content 检查 Correct! 或 Incorrect!
-> finally 关闭浏览器
module 参数是可选的,默认为 common。如果 CAPTCHA 只包含数字,使用 number。特殊样式可在适当时候使用文档中记录的非依赖模型。

例如,对纯数字识别使用以下未更改的源代码:
solution = capsolver.solve({
"type": "ImageToTextTask",
"module": "number",
"images": [base64_image],
})
answers = solution["answers"]
number 模型支持单次提交多张图片,且每张图片可包含最多 9 个 Base64 字符串。支持的模型名称和用例列在上述 CapSolver ImageToTextTask 页面中。
experimental.tool: first parameter of "complete_recaptcha_v2" must be
annotated with a Pydantic BaseModel subclass. Got: <class 'inspect._empty'>
Composio 从第一个参数的类型注解推断输入 schema,因此不能省略 input: CompleteRecaptchaInput。这是一个功能注解,不是可选的类型提示。Pydantic BaseModel 参考文档描述了用于 schema 的模型类型。
Session 创建可能返回以下错误:
403 APIKey_InsufficientPermissions
This route requires "sessions" write access
原因是 composio.sessions.create() 需要 key 对 sessions 有写访问权限,而当前 key 是只读访问。key 是有效的,但其权限范围不足,因此返回 403 而不是 401。
打开 Composio dashboard,进入相关项目的 API Keys 设置。
将当前 key 的 sessions 权限从 read 改为 write。
如果权限无法编辑,创建一个具有 sessions: write 的新 key,并替换脚本顶部的 COMPOSIO_API_KEY。
再次运行脚本。如果没有 403 到达交互式流程,则确认权限已生效。
此集成的核心是一个封装为一个 Composio 工具的完整业务工作流:
Composio tool = Playwright 页面操作 + CapSolver 结果 + 页面状态验证
Composio 将 Python 函数转换为 agent 可调用的自定义工具,并处理 schema 推断和执行。
Playwright 打开页面、应用结果、提交表单并读取最终状态。
CapSolver 为此特定工作流处理 reCAPTCHA v2 和图片验证码识别。
仅在你拥有或已授权自动化的页面和流程上运行示例。使用环境变量或密钥管理器管理凭证,当页面未达到预期业务状态时停止,并审查重复失败而不是无限重试。
对于需要专注 CAPTCHA 基础设施层的授权 Composio agent 工作流,使用你自己的受控页面测试 CapSolver,并在每次求解后验证应用程序结果。
Composio 在此集成中处理什么?
Composio 将 Python 函数注册为 agent 可调用的自定义工具,创建 session,暴露工具 schema,并路由来自 OpenAI agent 的执行。
为什么第一个工具参数必须是 Pydantic BaseModel?
Composio 使用该注解来推断工具的输入 schema。省略它会阻止 schema 构建,并在浏览器工作流开始前抛出验证错误。
reCAPTCHA v2 工具在 CapSolver 返回 token 后是否停止?
不。未更改的代码会应用 token、提交演示表单、读取结果 HTML,仅当页面包含预期的 Verification Success 文本时才报告成功。
ImageToTextTask 是否需要单独的轮询循环?
不需要。在此工作流中,官方 SDK 直接返回识别出的文本。然后工具填充输入、提交页面并检查 Correct! 作为停止条件。
此工作流可以用于任何网站吗?
不可以。仅用于合法、合理、负责且经用户授权的自动化。尊重站点条款、适用法律、速率限制和数据最小化要求。