← Создание агентов на Cloudflare / agents / harnesses / think
Workflows
ThinkWorkflow связывает Think с Cloudflare Workflows, когда устойчивой задаче нужен один шаг рассуждения, управляемый моделью.
Используйте это, если процессом управляет Workflow:
- устойчивая многошаговая оркестрация
- шлюзы подтверждения или долгие ожидания
- повторяемые детерминированные побочные эффекты
- ход Think, который должен выдать типизированный структурированный вывод
Оформляйте повторяющиеся запросы как запланированные задачи, а простые одноразовые фоновые ходы оставьте на submitMessages(). Workflows предназначены для задач, где важны отдельные шаги.
API
Импортируйте из @cloudflare/think/workflows:
import { ThinkWorkflow } from "@cloudflare/think/workflows";Расширить ThinkWorkflow и вызвать step.prompt() внутри run():
import { z } from "zod";
import { ThinkWorkflow } from "@cloudflare/think/workflows";
const draftSchema = z.object({
title: z.string(),
summary: z.string(),
labels: z.array(z.string()),
});
export class TriageWorkflow extends ThinkWorkflow {
async run(event, step) {
const draft = await step.prompt("triage-issue", {
prompt: `Triage issue #${event.payload.issueNumber}`,
output: draftSchema,
timeout: "3 days",
});
await step.do("apply-labels", async () => {
await this.agent.applyLabels(draft.labels);
});
}
}import { z } from "zod";
import { ThinkWorkflow } from "@cloudflare/think/workflows";
import type { ThinkWorkflowStep } from "@cloudflare/think/workflows";
import type { AgentWorkflowEvent } from "agents/workflows";
const draftSchema = z.object({
title: z.string(),
summary: z.string(),
labels: z.array(z.string()),
});
export class TriageWorkflow extends ThinkWorkflow<TriageAgent, Params> {
async run(event: AgentWorkflowEvent<Params>, step: ThinkWorkflowStep) {
const draft = await step.prompt("triage-issue", {
prompt: `Triage issue #${event.payload.issueNumber}`,
output: draftSchema,
timeout: "3 days",
});
await step.do("apply-labels", async () => {
await this.agent.applyLabels(draft.labels);
});
}
}Запустите Workflow изнутри вашего Think Agent с помощью runWorkflow():
export class TriageAgent extends Think {
async triageIssue(issueNumber) {
return this.runWorkflow(
"TRIAGE_WORKFLOW",
{ issueNumber },
{ metadata: { issueNumber } },
);
}
}export class TriageAgent extends Think<Env> {
async triageIssue(issueNumber: number): Promise<string> {
return this.runWorkflow(
"TRIAGE_WORKFLOW",
{ issueNumber },
{ metadata: { issueNumber } },
);
}
}runWorkflow() создаёт экземпляр Workflow и внедряет идентификатор Agent, который ThinkWorkflow необходимо переподключиться к this.agent внутри run(). Предпочитайте этот способ прямому вызову привязки Workflows:
// Avoid this for Agent workflows. It does not include Agent context.
await this.env.TRIAGE_WORKFLOW.create({ params: { issueNumber } });Используйте sendWorkflowEvent() из Agent, когда ожидающему Workflow требуется внешний сигнал, например, подтверждение человеком:
await this.sendWorkflowEvent("TRIAGE_WORKFLOW", workflowId, {
type: "approval",
payload: { approved: true },
});step.prompt() принимает строку запроса и объектную схему Zod. Перед тем как Workflow вызывает Agent, схема преобразуется в JSON Schema. Затем Think выполняет полный агентный ход: Agent может использовать свои инструменты в несколько шагов и возвращает структурированный результат, вызывая внутренний final_answer инструмент, аргументы которого соответствуют схеме. Здесь используется обычный вызов инструмента, а не потоковый response_format, чтобы это работало у всех провайдеров, которые поддерживает Think, включая Workers AI, который отклоняет ответы в формате JSON Schema при потоковых запросах. Когда Workflow возобновляется, полезная нагрузка снова проверяется по исходной схеме Zod, прежде чем будет возвращено типизированное значение.
Неподдерживаемые функции Zod, которые нельзя представить как JSON Schema, приводят к ошибке при создании шага промпта. Think не исправляет некорректный вывод модели незаметно. Если модель не выдаёт корректный final_answer вызова заявка переходит в конечное состояние ошибки, и step.prompt() выбрасывает исключение.
Замечания о поведении
- Agent может сначала использовать свои инструменты. Есть
step.prompt()ход представляет собой полноценный агентный ход: Agent может в несколько шагов вызывать собственные инструменты, а затем вызвать инструмент финального ответа. Разрешите как минимумmaxSteps: 2если вы ожидаете, что агент перед ответом использует инструмент, сmaxSteps: 1она вынуждена ответить уже на первом шаге и не может вызвать никакой другой инструмент. - Использование инструментов принудительно включено во время структурированного хода. Чтобы гарантировать, что Agent завершает работу структурированным ответом, а не обычным текстом, Think задаёт
toolChoiceдля хода. Не переопределяйтеtoolChoiceотbeforeTurnнаstep.prompt()ход, иначе это может помешать Agent вызвать инструмент финального ответа, из-за чего промпт завершится ошибкой. think_final_answerзарезервирован. Think добавляет внутреннийthink_final_answerинструмент для переноса структурированного результата. Это имя (и любойthink_final_answer_*вариант) зарезервирован: его вызов и результат удаляются из сохранённого разговора, поэтому транскрипт и последующие ходы не видят внутреннюю механику Think.- Модель должна поддерживать потоковые вызовы инструментов. Think передаёт каждый ход потоково, поэтому
step.prompt()работает только с моделями, которые надежно генерируют принудительный вызов инструмента во время потоковой передачи. Модели с сильной поддержкой вызова инструментов (например OpenAIgpt-4o-mini, Anthropicclaude-haiku-4-5, и Workers AI@cf/moonshotai/kimi-k2.6) проверены и работают. Некоторые модели учитывают принудительныйtoolChoiceтолько для не потоковых запросов и отвечает обычным текстом, а при потоковой передаче останавливается: например, Workers AI@cf/meta/llama-3.3-70b-instruct-fp8-fast. С этими моделями ход завершается безthink_final_answerвызов иstep.prompt()завершается сбоем (Model ended the turn without calling the think_final_answer tool); вместо этого используйте модель с рабочей потоковой передачей вызовов инструментов.
Как это выполняется
Вызов выглядит как блокирующий шаг, но на деле не удерживает долгоживущий RPC к Durable Object открытым.
step.do("<name>:submit", ...)создаёт или находит идемпотентную отправку Think.- Think прогоняет отправленный ход через обычную очередь отправки.
- Когда отправка достигает
completed,error,aborted, илиskipped, Think записывает ожидающее уведомление workflow. - Think очищает outbox уведомлений с помощью
sendWorkflowEvent()и будильники Durable Object, пока доставка не завершится успешно. step.waitForEvent("<name>:wait", ...)возобновляет Workflow.step.prompt()проверяет структурированный вывод или выбрасывает типизированную ошибку.
Машиночитаемый вывод передаётся в payload ожидающего уведомления и в payload события Workflow. Think не хранит отдельный output_json столбец в реестре отправок и очищает payload уведомления после доставки. После доставки устойчивый результат принадлежит Workflow.
Идемпотентность
По умолчанию step.prompt() выводит ключ идемпотентности из идентичности Workflow и имени шага:
think-workflow:<workflowName>:<workflowId>:<stepName>Для циклов передайте строку key чтобы различать повторные использования одного и того же имени шага:
await step.prompt("summarize-file", {
key: file.path,
prompt: `Summarize ${file.path}`,
output: summarySchema,
});Текст промпта не входит в выводимый ключ, но Think сохраняет метаданные workflow и отпечаток промпта и конфигурации для диагностики.
Тайм-ауты
Передайте timeout чтобы управлять тем, как долго Workflow ожидает терминальное событие. Если время ожидания истекает, step.prompt() по умолчанию отменяет отправку Think и выбрасывает ThinkPromptTimeoutError.
Задайте cancelOnTimeout: false когда вы намеренно хотите, чтобы отправка Think продолжалась после того, как Workflow перестанет ждать.
Граница с другими примитивами
Используйте getScheduledTasks() для повторяющихся отправок промптов или детерминированных запланированных обработчиков:
getScheduledTasks() {
return {
dailySummary: {
schedule: "every day at 09:00",
timezone: "UTC",
prompt: "Generate the daily report."
},
dailyWorkflow: {
schedule: "every day at 09:00",
timezone: "UTC",
retry: { maxAttempts: 3 },
handler: async ({ idempotencyKey, scheduledFor, timezone }) => {
await this.env.REPORT_WORKFLOW.create({
id: idempotencyKey,
params: { scheduledFor, timezone }
});
}
}
};
}Используйте submitMessages() для устойчивых одноразовых ходов, когда вызывающая сторона может позже проверить статус отправки.
Используйте startFiber() для идемпотентных заданий Agent, принадлежащих приложению и требующих восстановления внутри Agent. Доставка уведомлений workflow в Think не использует fiber: вместо этого применяется приватный outbox, поскольку событие нужно хранить до тех пор, пока доставка не завершится успешно.
Используйте Workflows, если процесс включает несколько детерминированных шагов, долгие ожидания или подтверждение от человека.