← Создание агентов на Cloudflare / agents / harnesses
Think
@cloudflare/think позволяет создать чат-агента ИИ с сохранением состояния, то есть агента, который передаёт ответы потоково, запоминает разговор и вызывает инструменты, путём расширения одного базового класса. Модели вы передаёте getModel(), а остальной жизненный цикл чата настраивает за вас Think: агентный цикл (модель вызывает инструменты, читает результаты и продолжает работу, пока не получит ответ), сохранение сообщений, потоковую передачу, клиентские инструменты, возобновление потока и расширения. Всё это на базе Durable Object SQLite.
Think работает одновременно как агент верхнего уровня (обмен сообщениями по WebSocket с браузерными клиентами через useAgentChat) и дочерний агент (дочерний агент, которым другой агент управляет по RPC через chat()).
быстрый старт
Установить
npm i @cloudflare/think @cloudflare/ai-chat agents ai @cloudflare/shell zod workers-ai-providerThink поддерживает AI SDK v6 и v7. Используйте ai@^6 с @ai-sdk/react@^3, или используйте ai@^7 с @ai-sdk/react@^4. Используйте пакеты AI SDK с совпадающими основными версиями во всём проекте.
Сервер
import { Think } from "@cloudflare/think";
import { createWorkersAI } from "workers-ai-provider";
import { routeAgentRequest } from "agents";
export class MyAgent extends Think {
getModel() {
return createWorkersAI({ binding: this.env.AI })(
"@cf/moonshotai/kimi-k2.6",
);
}
}
export default {
async fetch(request, env) {
return (
(await routeAgentRequest(request, env)) ||
new Response("Not found", { status: 404 })
);
},
};import { Think } from "@cloudflare/think";
import { createWorkersAI } from "workers-ai-provider";
import { routeAgentRequest } from "agents";
export class MyAgent extends Think<Env> {
getModel() {
return createWorkersAI({ binding: this.env.AI })(
"@cf/moonshotai/kimi-k2.6",
);
}
}
export default {
async fetch(request: Request, env: Env) {
return (
(await routeAgentRequest(request, env)) ||
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;Вот и всё. Think берёт на себя протокол чата через WebSocket, сохранение сообщений, agentic-цикл, санацию сообщений, возобновление потока, поддержку клиентских инструментов и файловые инструменты рабочего пространства.
Клиент
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
function Chat() {
const agent = useAgent({ agent: "MyAgent" });
const { messages, sendMessage, status } = useAgentChat({ agent });
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>
<strong>{msg.role}:</strong>
{msg.parts.map((part, i) =>
part.type === "text" ? <span key={i}>{part.text}</span> : null,
)}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault();
const input = e.currentTarget.elements.namedItem("input");
sendMessage({ text: input.value });
input.value = "";
}}
>
<input name="input" placeholder="Send a message..." />
<button type="submit">Send</button>
</form>
</div>
);
}import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
function Chat() {
const agent = useAgent({ agent: "MyAgent" });
const { messages, sendMessage, status } = useAgentChat({ agent });
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>
<strong>{msg.role}:</strong>
{msg.parts.map((part, i) =>
part.type === "text" ? <span key={i}>{part.text}</span> : null,
)}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault();
const input = e.currentTarget.elements.namedItem(
"input",
) as HTMLInputElement;
sendMessage({ text: input.value });
input.value = "";
}}
>
<input name="input" placeholder="Send a message..." />
<button type="submit">Send</button>
</form>
</div>
);
}Конфигурация
{
"$schema": "./node_modules/wrangler/config-schema.json",
// Set this to today's date
"compatibility_date": "2026-08-28",
"compatibility_flags": [
"nodejs_compat"
],
"ai": {
"binding": "AI"
},
"durable_objects": {
"bindings": [
{
"class_name": "MyAgent",
"name": "MyAgent"
}
]
},
"migrations": [
{
"new_sqlite_classes": [
"MyAgent"
],
"tag": "v1"
}
]
}# Set this to today's date
compatibility_date = "2026-08-28"
compatibility_flags = ["nodejs_compat"]
[ai]
binding = "AI"
[[durable_objects.bindings]]
class_name = "MyAgent"
name = "MyAgent"
[[migrations]]
new_sqlite_classes = ["MyAgent"]
tag = "v1"Трассировка
Think использует wrapAISDK() внутренне для инструментирования ходов модели, вызовов инструментов и сегментов жизненного цикла подтверждений. Оборачивать AI SDK или настраивать адаптер не требуется. Чтобы отправить invoke_agent, chat, execute_tool, а также tool_approval spans в Workers Observability, включите Workers traces:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"observability": {
"traces": {
"enabled": true
}
}
}[observability.traces]
enabled = trueТрассировки отображаются на вкладке Agents в Cloudflare Dashboard. Вы можете просматривать диалоги и временные шкалы трассировок. Чтобы отключить трассировку, задайте observability.traces.enabled к false. Вам не нужно менять класс агента Think.
Сведения об атрибутах span, управлении полезной нагрузкой, экспорте трасировок и прямой настройке AI SDK v6 или v7 см. в Трассировка.
Think в сравнении с AIChatAgent
И Think, и AIChatAgent расширьте Agent и используют один и тот же cf_agent_chat_* протокол WebSocket. У них разные задачи.
AIChatAgent это адаптер протокола. Вы переопределяете onChatMessage и отвечают за вызов streamText, подключая инструменты, преобразуя сообщения и возвращая Response. AIChatAgent берёт на себя всю техническую часть: сохранение сообщений, стриминг, прерывание, возобновление, но вызов LLM остаётся полностью на вашей стороне.
Think это фреймворк с готовыми решениями. Он принимает решения за вас: getModel() возвращает model, getSystemPrompt() или configureSession() задаёт промпт, getTools() возвращает tools. По умолчанию onChatMessage выполняет полный agentic loop. Вы переопределяете отдельные части, а не весь pipeline целиком.
| Проблема | AIChatAgent | Think |
|---|---|---|
| Минимальный подкласс | ~15 строк (wire streamText + инструменты + системный промпт + ответ) |
3 строки (getModel() только) |
| Хранилище | Плоская таблица SQL | Session: древовидные сообщения, контекстные блоки, компактизация, FTS5 |
| Регенерация | Деструктивное (старый ответ удаляется) | Неразрушающее ветвление (старые ответы сохраняются) |
| Управление контекстом | Вручную | Блоки контекста с постоянной памятью, доступной для записи LLM |
| RPC дочернего агента | Не встроено | chat() с StreamCallback |
| Программные ходы | saveMessages() |
saveMessages(), submitMessages(), continueLastTurn() |
| Компактизация | maxPersistedMessages (удаляет самые старые) |
Неразрушающие сводки через оверлеи |
| Поиск | Недоступно | Полнотекстовый поиск FTS5 в рамках сессии и между сессиями |
Когда использовать AIChatAgent
- Вам нужен полный контроль над вызовом LLM (RAG, несколько моделей, собственная потоковая передача)
- Вы хотите
Responseтип возвращаемого значения для HTTP middleware или тестирования - Вы создаёте простого чат-бота без требований к памяти
Когда использовать Think
- Вы хотите быстро выпустить решение (трёхстрочный подкласс со всем уже подключённым)
- Вам нужна постоянная память (блоки контекста, которые модель может читать и записывать)
- Вам нужны длинные разговоры (недеструктивная компактизация)
- Вам нужен поиск по переписке (FTS5)
- Вы создаёте систему дочерних агентов (RPC между родителем и потомком с потоковой передачей)
- Вам нужны проактивные агенты (программные ходы, запускаемые по расписанию или через webhook)
- Вам нужна устойчивая асинхронная отправка для вызывающих сторон через webhook или RPC
Выберите API ходов
У Think есть несколько способов начать ход или продолжить его. Все они сходятся в одной публичной точке входа: runTurn(options) : а старые методы остаются как удобные сокращения.
runTurn()
runTurn() это единое API приёма хода. Один метод, три режима, которые выбираются по options.mode:
| Режим | Когда использовать | Возвращает | Сокращение для |
|---|---|---|---|
"wait" (по умолчанию) |
Вызывающая сторона может блокироваться до завершения ответа модели | Promise<TurnResult> |
saveMessages() |
"submit" |
Вызывающей стороне нужно быстрое устойчивое подтверждение приёма и статус, который можно проверить позже | Promise<SubmitMessagesResult> |
submitMessages() |
"stream" |
Вызывающая сторона хочет получать ответ потоково через колбэк (RPC) | Promise<void> |
chat() |
input принимает строку, UIMessage, массив сообщений или, в wait и stream режимах: функция (current) => UIMessage[] оценивается при приёме. (submit не принимает входные данные функции.)
export class Assistant extends Think {
async examples(inboundEventId) {
// wait — block for the result
const result = await this.runTurn({ input: "Summarize the latest thread" });
if (result.status === "completed") {
// result.message is the assistant message; result.continuation is false
}
// submit — durable acceptance, check status later
const submission = await this.runTurn({
mode: "submit",
input: "Process this webhook",
idempotencyKey: inboundEventId, // dedupe; safe to retry
});
// submission.accepted is true on first accept; submission.status is "pending"
// stream — drive a callback (the same surface as chat())
await this.runTurn({
mode: "stream",
input: "Stream me",
callback: {
onStart({ requestId }) {},
onEvent(json) {}, // UIMessageChunk JSON
onDone() {},
onError(error) {},
},
});
// continuation — continue the last assistant turn instead of sending input
await this.runTurn({ continuation: true });
}
}export class Assistant extends Think<Env> {
async examples(inboundEventId: string) {
// wait — block for the result
const result = await this.runTurn({ input: "Summarize the latest thread" });
if (result.status === "completed") {
// result.message is the assistant message; result.continuation is false
}
// submit — durable acceptance, check status later
const submission = await this.runTurn({
mode: "submit",
input: "Process this webhook",
idempotencyKey: inboundEventId, // dedupe; safe to retry
});
// submission.accepted is true on first accept; submission.status is "pending"
// stream — drive a callback (the same surface as chat())
await this.runTurn({
mode: "stream",
input: "Stream me",
callback: {
onStart({ requestId }) {},
onEvent(json) {}, // UIMessageChunk JSON
onDone() {},
onError(error) {},
},
});
// continuation — continue the last assistant turn instead of sending input
await this.runTurn({ continuation: true });
}
}Основные особенности поведения:
- Блокирующие режимы нельзя вкладывать друг в друга. Вызов
wait/stream/continuation(или эквивалентное сочетание клавиш) из внутри активный ход, например изexecute: выбрасывает исключение, поскольку это вызвало бы дедлок очереди ходов. Изнутри хода используйтеrunTurn({ mode: "submit" })(устойчивый, выполняется после того, как текущий ход освобождает очередь), илиaddMessages()(только транскрипт, без инференса). submitидемпотентен. ПроходsubmissionIdи/илиidempotencyKey; повторная отправка известного ключа возвращает существующую запись сaccepted: falseвместо начала второго хода. См. Программные отправки.- Безопасно для восстановления. Заголовок
wait,stream, и опустевшиеsubmitпути выполняют инференс внутри recovery fiber, поэтому прерванный ход возобновляется после вытеснения.
runTurn экспортируется вместе с типами его опций и результата: RunTurnOptions, RunTurnWait, RunTurnSubmit, RunTurnStream, TurnInputMessages, а также TurnResult.
Выберите сокращение
Таблица ниже сопоставляет каждый сценарий с наиболее прямым вызовом. Сигнатура каждого шортката не меняется, используйте их, когда нужна более узкая поверхность, или используйте runTurn() когда нужна единая ментальная модель.
| Сценарий использования | API |
|---|---|
| Пользователь браузера отправляет сообщения чата | useAgentChat через протокол чата WebSocket |
| Код сервера может дождаться ответа модели | saveMessages() |
| Коду сервера нужны быстрое устойчивое принятие запроса и последующий статус | submitMessages() |
| Код должен создавать повторяющиеся ходы, управляемые промптом, или обработчики | getScheduledTasks() |
| Родительскому коду требуется прямой потоковый RPC к конкретному дочернему агенту | subAgent(...).chat() |
| Родительский агент делегирует работу удерживаемому дочернему агенту | agentTool() или runAgentTool() |
| Оборачивает ход идемпотентными побочными эффектами на стороне приложения | startFiber() |
| Координируйте устойчивую многошаговую оркестрацию | Workflows |
| Добавьте контекст или сообщения, не начиная ход модели | addMessages() |
| Продвинутый подкласс или код восстановления продолжает ход ассистента | continueLastTurn() |
Используйте saveMessages() когда вызывающий код сам управляет триггером и может дождаться завершения хода. Используйте submitMessages() когда неоднозначность тайм-аута делает повторные попытки небезопасными.
Добавьте сообщения без хода
Используйте addMessages() чтобы записать в транскрипт без в начале хода модели: для импорта предыдущей истории или добавления фонового контекста, который должен увидеть следующий ход:
export class Assistant extends Think {
async importContext() {
await this.addMessages([
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Imported context" }],
},
]);
}
}export class Assistant extends Think<Env> {
async importContext() {
await this.addMessages([
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Imported context" }],
},
]);
}
}addMessages() добавляет (или обновляет через upsert) в дерево Session:
- Это делает не запускает inference и не попадают в очередь ходов, поэтому это безопасно вызывать изнутри
executeбез взаимной блокировки. - Элементы массива добавляются линейно (каждое присоединяется к предыдущему), поэтому импортированная история остаётся единой веткой. По умолчанию первое сообщение присоединяется к последнему зафиксированному листу; передайте
parentIdчтобы подключить в другом месте, илиnullдля корневого сообщения. - Добавления идемпотентность по ID сообщения. Передайте
{ mode: "upsert" }чтобы вместо этого обновить существующее сообщение на месте.
Поддерживаемый паттерн: «добавить контекст, затем выполнить ход». Вызовите addMessages(), затем runTurn().
Используйте chat() для низкоуровневой потоковой передачи от родителя к потомку, когда ваш код сам управляет пересылкой, отменой и политикой повтора. Используйте Agents как инструменты когда родительская модель или workflow делегирует работу дочернему агенту и вам нужны сохранённые дочерние запуски, event replay, проброс abort и детализация в UI.
Используйте startFiber() вне Think, когда устойчивой единицей является задача приложения вокруг хода: однократный приём webhook, восстановление сериализованного канала или целевого треда, публикация видимого ответа либо запись политики восстановления на уровне приложения. Отправки в Think отвечают за приём разговора и сериализацию хода; управляемые fiber отвечают за приём внешних задач, идемпотентные побочные эффекты и восстановление приложения.
В этом разделе
Конфигурация
Инструменты
Действия
Каналы
Хуки жизненного цикла
Клиентские инструменты
Мессенджеры
Запланированные задачи
Workflows
RPC дочернего агента
Программные отправки
Устойчивое восстановление
Agent Skills
Благодарности
Дизайн Think вдохновлён Pi ↗.
Пример
Пример assistant
См. также
- Сеансы : блоки контекста, компакция, поиск, несколько сессий (слой хранения, на котором построен Think)
- Дочерние агенты,
subAgent(),abortSubAgent(),deleteSubAgent()(базовые методы Agent для создания дочерних агентов) - Чат-агенты,
AIChatAgentдля случаев, когда нужен полный контроль над вызовом LLM - Долгоживущие агенты : паттерны делегирования дочерним агентам для многонедельных жизненных циклов агента
- Устойчивое выполнение,
runFiber()и восстановление после сбоев (используетсяchatRecovery) - Просматривать веб : полный справочник вспомогательного API CDP