← Создание агентов на Cloudflare / agents / examples
Агент браузера
Создайте агента, который умеет просматривать веб, изучать страницы, делать снимки экрана и отлаживать проблемы фронтенда с помощью Browser Run инструменты. Бета
Вместо фиксированного набора действий браузера (click, screenshot, navigate) LLM пишет код JavaScript, который выполняет команды CDP в живой сессии браузера, получая доступ ко всем доменам, командам, событиям и типам протокола.
Предоставляются два инструмента:
| Инструмент | Описание |
|---|---|
browser_search |
Запрашивайте спецификацию CDP, чтобы получить список команд, событий и типов. Спецификация динамически загружается с CDP-эндпойнта браузера и кэшируется. |
browser_execute |
Выполняйте команды CDP в живом браузере через cdp вспомогательная функция. Каждый вызов открывает новую сессию браузера, выполняет код и затем закрывает её. |
Когда использовать инструменты браузера
Инструменты браузера полезны, когда вашему агенту нужно:
- Просмотр веб-страниц : Структура DOM, вычисленные стили, дерево доступности
- Отладка проблем на фронтенде : сетевые водопады, ошибки консоли, трассировки производительности
- Извлечение структурированных данных : извлекает содержимое из отрендеренных страниц
- Делайте снимки экрана или создавайте PDF-файлы : визуальные снимки веб-контента
- Профилирование производительности : Core Web Vitals, профилирование JavaScript, анализ памяти
Для простой загрузки страниц, где не нужен отрендеренный DOM, используйте fetch() взамен.
Установить
Инструменты браузера требуют Agents SDK и @cloudflare/codemode:
npm install agents @cloudflare/codemode ai zodбыстрый старт
1. Настройте привязки
Добавьте привязки Browser Run (ранее Browser Rendering) и Worker Loader в конфигурацию wrangler:
{
"compatibility_flags": ["nodejs_compat"],
"browser": {
"binding": "BROWSER",
},
"worker_loaders": [
{
"binding": "LOADER",
},
],
}compatibility_flags = [ "nodejs_compat" ]
[browser]
binding = "BROWSER"
[[worker_loaders]]
binding = "LOADER"2. Создайте инструменты браузера
import { createBrowserTools } from "agents/browser/ai";
const browserTools = createBrowserTools({
browser: env.BROWSER,
loader: env.LOADER,
});import { createBrowserTools } from "agents/browser/ai";
const browserTools = createBrowserTools({
browser: env.BROWSER,
loader: env.LOADER,
});Чтобы подключиться к пользовательской конечной точке CDP вместо привязки Browser Run, передайте cdpUrl.
3. Используйте с streamText
Передайте инструменты браузера вместе с остальными инструментами. Эти model может быть любым провайдером AI SDK, в данном случае используется Workers AI:
import { streamText } from "ai";
import { createWorkersAI } from "workers-ai-provider";
const workersai = createWorkersAI({ binding: env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
system: "You are a helpful assistant that can inspect web pages.",
messages,
tools: {
...browserTools,
...otherTools,
},
});import { streamText } from "ai";
import { createWorkersAI } from "workers-ai-provider";
const workersai = createWorkersAI({ binding: env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
system: "You are a helpful assistant that can inspect web pages.",
messages,
tools: {
...browserTools,
...otherTools,
},
});Оба инструмента принимают code параметр, содержащий асинхронную стрелочную функцию JavaScript. Sandbox внедряет глобальные переменные в зависимости от инструмента: spec для browser_search и cdp для browser_execute.
Когда LLM использует browser_search, код опрашивает спецификацию CDP через внедрённый spec объект:
async () => {
const s = await spec.get();
return s.domains
.find((d) => d.name === "Network")
.commands.map((c) => ({ method: c.method, description: c.description }));
};Когда LLM использует browser_execute, код выполняет команды CDP через внедрённый cdp вспомогательная функция:
async () => {
const { targetId } = await cdp.send("Target.createTarget", {
url: "https://example.com",
});
const sessionId = await cdp.attachToTarget(targetId);
const { root } = await cdp.send("DOM.getDocument", {}, { sessionId });
const { outerHTML } = await cdp.send(
"DOM.getOuterHTML",
{ nodeId: root.nodeId },
{ sessionId },
);
await cdp.send("Target.closeTarget", { targetId });
return outerHTML;
};Используйте с Agent
Типичный паттерн заключается в создании браузерных инструментов внутри AIChatAgent обработчик сообщений, который обеспечивает сохранение сообщений и потоковую передачу:
import { AIChatAgent } from "@cloudflare/ai-chat";
import { createBrowserTools } from "agents/browser/ai";
import { createWorkersAI } from "workers-ai-provider";
import { streamText, convertToModelMessages, stepCountIs } from "ai";
export class MyAgent extends AIChatAgent {
async onChatMessage() {
const workersai = createWorkersAI({ binding: this.env.AI });
const browserTools = createBrowserTools({
browser: this.env.BROWSER,
loader: this.env.LOADER,
});
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
system: "You can browse the web and inspect pages.",
messages: await convertToModelMessages(this.messages),
tools: {
...browserTools,
},
stopWhen: stepCountIs(10),
});
return result.toUIMessageStreamResponse();
}
}import { AIChatAgent } from "@cloudflare/ai-chat";
import { createBrowserTools } from "agents/browser/ai";
import { createWorkersAI } from "workers-ai-provider";
import { streamText, convertToModelMessages, stepCountIs } from "ai";
export class MyAgent extends AIChatAgent<Env> {
async onChatMessage() {
const workersai = createWorkersAI({ binding: this.env.AI });
const browserTools = createBrowserTools({
browser: this.env.BROWSER,
loader: this.env.LOADER,
});
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
system: "You can browse the web and inspect pages.",
messages: await convertToModelMessages(this.messages),
tools: {
...browserTools,
},
stopWhen: stepCountIs(10),
});
return result.toUIMessageStreamResponse();
}
}TanStack AI
Для TanStack AI используйте /tanstack-ai экспорт:
import { createBrowserTools } from "agents/browser/tanstack-ai";
import { chat, workersAIText } from "@tanstack/ai";
const browserTools = createBrowserTools({
browser: env.BROWSER,
loader: env.LOADER,
});
const stream = chat({
adapter: workersAIText(env.AI, "@cf/zai-org/glm-4.7-flash"),
tools: [...browserTools, ...otherTools],
messages,
});import { createBrowserTools } from "agents/browser/tanstack-ai";
import { chat, workersAIText } from "@tanstack/ai";
const browserTools = createBrowserTools({
browser: env.BROWSER,
loader: env.LOADER,
});
const stream = chat({
adapter: workersAIText(env.AI, "@cf/zai-org/glm-4.7-flash"),
tools: [...browserTools, ...otherTools],
messages,
});Модель выполнения
browser_searchполучает текущий протокол CDP из браузера с помощью/json/protocolэндпойнт и ненадолго кэширует его.browser_executeоткрывает новую сессию браузера для каждого вызова, предоставляет небольшойcdpвспомогательный API изолированному коду и закрывает сессию по завершении выполнения.- Код, сгенерированный LLM, выполняется в песочнице Worker. Трафик CDP не покидает основной Worker.
Вспомогательный API CDP
Внутри browser_execute, изолированному коду доступны следующие функции.
cdp.send(method, params?, options?)
Отправьте команду CDP и дождитесь ответа.
| Параметр | Тип | Описание |
|---|---|---|
method |
string |
метод CDP, например "DOM.getDocument" или "Network.enable" |
params |
unknown |
Параметры метода |
options.timeoutMs |
number |
Тайм-аут на команду (по умолчанию: 10 секунд) |
options.sessionId |
string |
ID целевой сессии (обязателен для команд с привязкой к странице) |
cdp.attachToTarget(targetId, options?)
Присоединиться к цели и получить ID сессии. Использует Target.attachToTarget с flatten: true.
| Параметр | Тип | Описание |
|---|---|---|
targetId |
string |
Цель для подключения |
options.timeoutMs |
number |
Тайм-аут для команды attach |
Возвращает sessionId строка.
cdp.getDebugLog(limit?)
Получить последние записи отладочного журнала CDP (отправки, получения, ошибки). По умолчанию последние 50 записей, максимум 400.
cdp.clearDebugLog()
Очистить буфер журнала отладки.
Конфигурация
createBrowserTools(options)
Возвращает инструменты AI SDK (browser_search и browser_execute).
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
browser |
Fetcher |
, | Привязка Browser Run |
cdpUrl |
string |
, | Необязательное переопределение для пользовательского CDP-эндпойнта |
cdpHeaders |
Record<string, string> |
, | Заголовки для обнаружения URL CDP (например, Cloudflare Access) |
loader |
WorkerLoader |
обязательно | Привязка Worker Loader для выполнения в sandbox |
timeout |
number |
30000 |
Тайм-аут выполнения в миллисекундах |
Либо browser или cdpUrl должен быть указан. Если заданы оба значения, cdpUrl имеет приоритет.
Низкоуровневый доступ
Для пользовательских интеграций импортируйте строительные блоки напрямую:
import {
CdpSession,
connectBrowser,
connectUrl,
createBrowserToolHandlers,
} from "agents/browser";
// Connect to a custom CDP endpoint
const session = await connectUrl("http://localhost:9222");
const version = await session.send("Browser.getVersion");
session.close();import {
CdpSession,
connectBrowser,
connectUrl,
createBrowserToolHandlers,
} from "agents/browser";
// Connect to a custom CDP endpoint
const session = await connectUrl("http://localhost:9222");
const version = await session.send("Browser.getVersion");
session.close();Локальная разработка
Последние версии Wrangler поддерживают Browser Run при локальной разработке. npx wrangler dev автоматически подготавливает браузер, поэтому тот же browser: env.BROWSER настройка работает как локально, так и после развёртывания.
Используйте cdpUrl только если вы намеренно хотите подключиться к другому CDP-совместимому эндпойнту браузера, например к туннелю или вручную управляемому экземпляру Chrome.
Соображения по безопасности
- Код, сгенерированный LLM, выполняется в изолированные sandbox Worker : каждое выполнение получает собственный экземпляр Worker
- Внешний доступ к сети (
fetch,connect) это заблокировано в песочнице на уровне рантайма - Команды CDP отправляются через Workers RPC: WebSocket работает в хосте, а не в sandbox
- Спецификация CDP остаётся на сервере: до LLM доходят только результаты запросов
- Ответы усекаются примерно до 6,000 токенов, чтобы предотвратить переполнение контекстного окна
Текущие ограничения
- Одна сессия на каждый вызов execute : каждое
browser_executeвызов открывает новую сессию браузера. Многошаговые workflow должны выполняться в пределах одного блока кода. - Нет аутентифицированных сессий : браузер запускается без файлов cookie и без сохранённого состояния входа.
- Требует
@cloudflare/codemodeкак peer-зависимость. - В песочнице поддерживается выполнение только JavaScript (синтаксис TypeScript не поддерживается).
Прямое использование Puppeteer
Если вы предпочитаете управлять браузером программно без кода, сгенерированного LLM, можно использовать Puppeteer с Browser Run API напрямую.
npm i -D @cloudflare/puppeteerimport puppeteer from "@cloudflare/puppeteer";
export class MyAgent extends Agent {
async browse(browserInstance, urls) {
let responses = [];
for (const url of urls) {
const browser = await puppeteer.launch(browserInstance);
const page = await browser.newPage();
await page.goto(url);
await page.waitForSelector("body");
const bodyContent = await page.$eval(
"body",
(element) => element.innerHTML,
);
let resp = await this.env.AI.run("@cf/zai-org/glm-4.7-flash", {
messages: [
{
role: "user",
content: `Return a JSON object with the product names, prices and URLs from the website content below. <content>${bodyContent}</content>`,
},
],
});
responses.push(resp);
await browser.close();
}
return responses;
}
}import puppeteer from "@cloudflare/puppeteer";
interface Env {
BROWSER: Fetcher;
AI: Ai;
}
export class MyAgent extends Agent<Env> {
async browse(browserInstance: Fetcher, urls: string[]) {
let responses = [];
for (const url of urls) {
const browser = await puppeteer.launch(browserInstance);
const page = await browser.newPage();
await page.goto(url);
await page.waitForSelector("body");
const bodyContent = await page.$eval(
"body",
(element) => element.innerHTML,
);
let resp = await this.env.AI.run("@cf/zai-org/glm-4.7-flash", {
messages: [
{
role: "user",
content: `Return a JSON object with the product names, prices and URLs from the website content below. <content>${bodyContent}</content>`,
},
],
});
responses.push(resp);
await browser.close();
}
return responses;
}
}Добавьте привязку browser в конфигурацию wrangler:
{
"ai": {
"binding": "AI",
},
"browser": {
"binding": "BROWSER",
},
}[ai]
binding = "AI"
[browser]
binding = "BROWSER"Использование Browserbase
Также можно использовать Browserbase ↗ с помощью API Browserbase непосредственно из вашего Agent.
Как только у вас появится API-ключ Browserbase ↗, вы можете добавить его в свой Agent, создав secret:
cd your-agent-project-folder
npx wrangler@latest secret put BROWSERBASE_API_KEYУстановите @cloudflare/puppeteer пакет и используйте его внутри вашего Agent для вызова API Browserbase:
npm i @cloudflare/puppeteerimport puppeteer from "@cloudflare/puppeteer";
export class MyAgent extends Agent {
async browse(url) {
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://connect.browserbase.com?apiKey=${this.env.BROWSERBASE_API_KEY}`,
});
const page = await browser.newPage();
await page.goto(url);
const content = await page.content();
await browser.close();
return content;
}
}import puppeteer from "@cloudflare/puppeteer";
interface Env {
BROWSERBASE_API_KEY: string;
}
export class MyAgent extends Agent<Env> {
async browse(url: string) {
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://connect.browserbase.com?apiKey=${this.env.BROWSERBASE_API_KEY}`,
});
const page = await browser.newPage();
await page.goto(url);
const content = await page.content();
await browser.close();
return content;
}
}