← Создание агентов на Cloudflare / agents / getting-started
быстрый старт
Создавайте ИИ-агентов, которые сохраняются, думают и действуют. Agents работают в глобальной сети Cloudflare, сохраняют состояние между запросами и подключаются к клиентам в реальном времени через WebSockets.
Что вы создадите: Агент-счётчик с сохраняемым состоянием, которое синхронизируется с фронтендом на React в реальном времени.
Время: ~10 минут
Создайте новый проект
npm create cloudflare@latest -- --template cloudflare/agents-starterЗатем установите зависимости и запустите dev-сервер:
cd agents-starter
npm install
npm run devЭто создаёт проект с:
src/server.ts: код вашего агентаsrc/client.tsx: фронтенд на Reactwrangler.jsonc: Конфигурация Cloudflaretsconfig.json: расширяетagents/tsconfigдля правильных настроек декоратора и модуляvite.config.ts: включаетagents/viteплагин для поддержки декораторов
Стартовый шаблон включает две важные интеграции SDK. Если вы настраиваете проект вручную, добавьте обе:
tsconfig.json : расширяет agents/tsconfig, которая задаёт target: "ES2021" и другие рекомендуемые параметры:
{
"extends": "agents/tsconfig"
}vite.config.ts : включает agents() плагин, который обрабатывает трансформации декораторов TC39 (требуется для @callable() в Vite 8):
import { cloudflare } from "@cloudflare/vite-plugin";
import react from "@vitejs/plugin-react";
import agents from "agents/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [agents(), react(), cloudflare()],
});Откройте http://localhost:5173 ↗ чтобы увидеть своего агента в действии.
Ваш первый агент
Создайте простого агента-счётчика с нуля. Замените src/server.ts:
import { Agent, routeAgentRequest, callable } from "agents";
// Define the state shape
// Create the agent
export class CounterAgent extends Agent {
// Initial state for new instances
initialState = { count: 0 };
// Methods marked with @callable can be called from the client
@callable()
increment() {
this.setState({ count: this.state.count + 1 });
return this.state.count;
}
@callable()
decrement() {
this.setState({ count: this.state.count - 1 });
return this.state.count;
}
@callable()
reset() {
this.setState({ count: 0 });
}
}
// Route requests to agents
export default {
async fetch(request, env, ctx) {
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
};import { Agent, routeAgentRequest, callable } from "agents";
// Define the state shape
export type CounterState = {
count: number;
};
// Create the agent
export class CounterAgent extends Agent<Env, CounterState> {
// Initial state for new instances
initialState: CounterState = { count: 0 };
// Methods marked with @callable can be called from the client
@callable()
increment() {
this.setState({ count: this.state.count + 1 });
return this.state.count;
}
@callable()
decrement() {
this.setState({ count: this.state.count - 1 });
return this.state.count;
}
@callable()
reset() {
this.setState({ count: 0 });
}
}
// Route requests to agents
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;Обновите wrangler.jsonc чтобы зарегистрировать агента:
{
"name": "my-agent",
"main": "src/server.ts",
// Set this to today's date
"compatibility_date": "2026-08-28",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [
{
"name": "CounterAgent",
"class_name": "CounterAgent",
},
],
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["CounterAgent"],
},
],
}name = "my-agent"
main = "src/server.ts"
# Set this to today's date
compatibility_date = "2026-08-28"
compatibility_flags = [ "nodejs_compat" ]
[[durable_objects.bindings]]
name = "CounterAgent"
class_name = "CounterAgent"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "CounterAgent" ]Основные моменты:
nameв привязках становится свойствомenv(например,env.CounterAgent)class_nameдолжен точно совпадать с именем экспортируемого вами классаnew_sqlite_classesвключает хранилище SQLite для сохранения состоянияnodejs_compatфлаг обязателен для пакета agents
Подключение из React
Замените src/client.tsx:
import "./styles.css";
import { createRoot } from "react-dom/client";
import { useState } from "react";
import { useAgent } from "agents/react";
import type { CounterAgent, CounterState } from "./server";
export default function App() {
const [count, setCount] = useState(0);
// Connect to the Counter agent
const agent = useAgent<CounterAgent, CounterState>({
agent: "CounterAgent",
onStateUpdate: (state) => setCount(state.count),
});
return (
<div style={{ padding: "2rem", fontFamily: "system-ui" }}>
<h1>Counter Agent</h1>
<p style={{ fontSize: "3rem" }}>{count}</p>
<div style={{ display: "flex", gap: "1rem" }}>
<button onClick={() => agent.stub.decrement()}>-</button>
<button onClick={() => agent.stub.reset()}>Reset</button>
<button onClick={() => agent.stub.increment()}>+</button>
</div>
</div>
);
}
const root = createRoot(document.getElementById("root")!);
root.render(<App />);Основные моменты:
useAgentподключается к вашему агенту через WebSocketonStateUpdateсрабатывает при каждом изменении состояния агентаagent.stub.methodName()вызывает методы, помеченные@callable()в вашем Agent
Как это работает
Когда вы нажали кнопку:
- Клиент называется
agent.stub.increment()через WebSocket - Agent выполнил
increment(), обновлённое состояние сsetState() - Состояние автоматически сохраняется в SQLite
- Рассылка отправленный всем подключённым клиентам
- React обновляется через
onStateUpdate
flowchart LR
A["Browser<br/>(React)"] <-->|WebSocket| B["Agent<br/>(Counter)"]
B --> C["SQLite<br/>(State)"]
Основные понятия
| Понятие | Что это значит |
|---|---|
| Экземпляр Agent | Каждое уникальное имя получает собственного агента. CounterAgent:user-123 отделён от CounterAgent:user-456 |
| Постоянное состояние | Состояние переживает перезапуски, повторные развёртывания и гибернацию. Оно хранится в SQLite |
| Синхронизация в реальном времени | Все клиенты, подключённые к одному и тому же агенту, мгновенно получают обновления состояния |
| Гибернация | Когда ни один клиент не подключён, агент переходит в спящий режим (бесплатно). Он просыпается при следующем запросе |
Подключение из чистого JavaScript
Если вы не используете React:
import { AgentClient } from "agents/client";
const agent = new AgentClient({
agent: "CounterAgent",
name: "my-counter", // optional, defaults to "default"
onStateUpdate: (state) => {
console.log("New count:", state.count);
},
});
// Call methods
await agent.call("increment");
await agent.call("reset");import { AgentClient } from "agents/client";
const agent = new AgentClient({
agent: "CounterAgent",
name: "my-counter", // optional, defaults to "default"
onStateUpdate: (state) => {
console.log("New count:", state.count);
},
});
// Call methods
await agent.call("increment");
await agent.call("reset");Deploy to Cloudflare
npm run deployТеперь ваш агент работает в глобальной сети Cloudflare, максимально близко к вашим пользователям.
Типичные паттерны интеграции
Agents за аутентификацией
Перед маршрутизацией к агентам проверьте аутентификацию:
export default {
async fetch(request, env) {
// Check auth for agent routes
if (request.url.includes("/agents/")) {
const authResult = await checkAuth(request, env);
if (!authResult.valid) {
return new Response("Unauthorized", { status: 401 });
}
}
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// ... rest of routing
},
};export default {
async fetch(request: Request, env: Env) {
// Check auth for agent routes
if (request.url.includes("/agents/")) {
const authResult = await checkAuth(request, env);
if (!authResult.valid) {
return new Response("Unauthorized", { status: 401 });
}
}
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// ... rest of routing
},
} satisfies ExportedHandler<Env>;Пользовательский префикс пути агента
По умолчанию агенты маршрутизируются по адресу /agents/{agent-name}/{instance-name}. Вы можете настроить это:
import { routeAgentRequest } from "agents";
const agentResponse = await routeAgentRequest(request, env, {
prefix: "/api/agents", // Now routes at /api/agents/{agent-name}/{instance-name}
});import { routeAgentRequest } from "agents";
const agentResponse = await routeAgentRequest(request, env, {
prefix: "/api/agents", // Now routes at /api/agents/{agent-name}/{instance-name}
});См. Маршрутизация для дополнительных опций, включая CORS, пользовательское именование инстансов и подсказки о местоположении.
Доступ к агентам из серверного кода
Вы можете взаимодействовать с агентами прямо из кода Worker:
import { getAgentByName } from "agents";
export default {
async fetch(request, env) {
if (request.url.endsWith("/api/increment")) {
// Get a specific agent instance
const counter = await getAgentByName(env.CounterAgent, "shared-counter");
const newCount = await counter.increment();
return Response.json({ count: newCount });
}
// ...
},
};import { getAgentByName } from "agents";
export default {
async fetch(request: Request, env: Env) {
if (request.url.endsWith("/api/increment")) {
// Get a specific agent instance
const counter = await getAgentByName(env.CounterAgent, "shared-counter");
const newCount = await counter.increment();
return Response.json({ count: newCount });
}
// ...
},
} satisfies ExportedHandler<Env>;Добавление нескольких агентов
Добавьте больше агентов, расширив конфигурацию:
// src/agents/chat.ts
export class Chat extends Agent {
// ...
}
// src/agents/scheduler.ts
export class Scheduler extends Agent {
// ...
}// src/agents/chat.ts
export class Chat extends Agent {
// ...
}
// src/agents/scheduler.ts
export class Scheduler extends Agent {
// ...
}Обновите файл конфигурации Wrangler:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"durable_objects": {
"bindings": [
{
"name": "CounterAgent",
"class_name": "CounterAgent"
},
{
"name": "Chat",
"class_name": "Chat"
},
{
"name": "Scheduler",
"class_name": "Scheduler"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": [
"CounterAgent",
"Chat",
"Scheduler"
]
}
]
}[[durable_objects.bindings]]
name = "CounterAgent"
class_name = "CounterAgent"
[[durable_objects.bindings]]
name = "Chat"
class_name = "Chat"
[[durable_objects.bindings]]
name = "Scheduler"
class_name = "Scheduler"
[[migrations]]
tag = "v1"
new_sqlite_classes = ["CounterAgent", "Chat", "Scheduler"]Экспортируйте все агенты из точки входа:
export { CounterAgent } from "./agents/counter";
export { Chat } from "./agents/chat";
export { Scheduler } from "./agents/scheduler";export { CounterAgent } from "./agents/counter";
export { Chat } from "./agents/chat";
export { Scheduler } from "./agents/scheduler";Устранение неполадок
Agent не найден, или ошибки 404
- Проверьте экспорт - класс Agent должен экспортироваться из главной точки входа.
- Проверьте привязку -
class_nameв конфигурационном файле Wrangler должно точно совпадать с именем экспортируемого класса. - Проверьте маршрут - маршрут по умолчанию:
/agents/{'{agent-name}'}/{'{instance-name}'}. Имя агента на клиенте совпадает с именем класса (без учёта регистра).
Ошибка «No such Durable Object class»
Добавьте миграцию в файл конфигурации Wrangler:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": [
"YourAgentClass"
]
}
]
}[[migrations]]
tag = "v1"
new_sqlite_classes = ["YourAgentClass"]Соединение WebSocket не устанавливается
Убедитесь, что ваша маршрутизация передаёт ответ без изменений:
// Correct - return the response directly
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// Wrong - this breaks WebSocket connections
if (agentResponse) return new Response(agentResponse.body);// Correct - return the response directly
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// Wrong - this breaks WebSocket connections
if (agentResponse) return new Response(agentResponse.body);Состояние не сохраняется
Проверьте, что:
- Вы вызываете
this.setState(), а не изменяяthis.stateнапрямую. - Класс агента находится в
new_sqlite_classesв миграциях. - Вы подключаетесь к тому же имени экземпляра агента.
-
onStateUpdateколбэк подключён в вашем клиенте. - Соединение WebSocket установлено (проверьте инструменты разработчика в браузере).
ошибки вида «Method X is not callable»
Убедитесь, что ваши методы декорированы @callable():
import { Agent, callable } from "agents";
export class MyAgent extends Agent {
@callable()
increment() {
// ...
}
}import { Agent, callable } from "agents";
export class MyAgent extends Agent {
@callable()
increment() {
// ...
}
}Ошибки типов с agent.stub
Добавьте параметры типов агента и состояния:
import { useAgent } from "agents/react";
// Pass the agent and state types to useAgent
const agent = useAgent({
agent: "CounterAgent",
onStateUpdate: (state) => setCount(state.count),
});
// Now agent.stub is fully typed
agent.stub.increment();import { useAgent } from "agents/react";
import type { CounterAgent, CounterState } from "./server";
// Pass the agent and state types to useAgent
const agent = useAgent<CounterAgent, CounterState>({
agent: "CounterAgent",
onStateUpdate: (state) => setCount(state.count),
});
// Now agent.stub is fully typed
agent.stub.increment();SyntaxError: Invalid or unexpected token с @callable()
Если ваш dev-сервер завершается ошибкой с SyntaxError: Invalid or unexpected token, задайте "target": "ES2021" в вашем tsconfig.json. Это гарантирует, что транспилятор esbuild в Vite преобразует декораторы TC39 в более раннюю версию синтаксиса, вместо того чтобы передавать их как нативный синтаксис.
{
"compilerOptions": {
"target": "ES2021"
}
}Следующие шаги
Теперь, когда у вас есть работающий агент, изучите следующие темы:
Типичные следующие шаги
| Узнайте, как | См. |
|---|---|
| Добавьте возможности ИИ/LLM | Использование ИИ-моделей |
| Предоставление инструментов через MCP | MCP-серверы |
| Запуск фоновых задач | Планирование задач |
| Обрабатывайте письма | Маршрутизация электронной почты |
| Используйте Cloudflare Workflows | Запуск Workflows |