← Cloudflare Workflows / workflows / get-started
Vytvoření trvalého AI agenta
V tomto průvodci vytvoříte AI agenta, který zkoumá repozitáře na GitHubu. Zadejte mu úkol, například "Compare open-source LLM projects", a agent:
- Vyhledání relevantních repozitářů na GitHubu
- Načtěte podrobnosti o každém z nich (hvězdy, forky, aktivita)
- Analyzujte je a porovnejte
- Vrácení doporučení
Každé volání LLM a volání nástroje se stává krok, samostatná jednotka práce, kterou lze opakovat nezávisle na ostatních. Pokud některý krok selže, Workflows jej automaticky zopakuje. Pokud celý Workflow zhavaruje uprostřed úlohy, pokračuje od posledního úspěšně dokončeného kroku.
| Challenge | Řešení pomocí Workflows |
|---|---|
| Dlouhotrvající smyčky agenta | Odolné provádění, které přežije jakékoli přerušení |
| Nespolehlivá volání LLM a API | Automatické opakování s nezávislými kontrolními body |
| Čekání na schválení člověkem | waitForEvent() pozastaví na hodiny nebo dny |
| Dotazování na dokončení úlohy | step.sleep() mezi kontrolami, aniž by spotřebovával prostředky |
Tento návod používá Agents SDK s Workflows pro aktualizace průběhu v reálném čase a Anthropic SDK pro volání LLM. Stejné postupy platí pro jakékoli LLM SDK (OpenAI, Google AI, Mistral atd.).
Rychlý start
Pokud chcete kroky přeskočit a stáhnout kompletního agenta využívajícího AI Gateway, spusťte následující příkaz:
npm create cloudflare@latest -- --template cloudflare/docs-examples/workflows/durableAgentTuto možnost použijte, pokud znáte Cloudflare Workflows nebo chcete nejprve prozkoumat kód.
Podle následujících kroků se naučíte, jak od začátku vytvořit odolného AI agenta.
Předpoklady
- Zaregistrujte si účet Cloudflare ↗.
- Nainstalujte
Node.js↗.
Správce verzí Node.js
Použijte správce verzí Node, jako je Volta ↗ nebo nvm ↗ abyste se vyhnuli problémům s oprávněními a mohli měnit verze Node.js. Wrangler, o kterém se dozvíte dále v této příručce, vyžaduje verzi Node 16.17.0 nebo novější.
Budete také potřebovat API klíč Anthropic ↗ za volání LLM. Nové účty obsahují bezplatné kredity.
1. Vytvořte nový projekt Worker
-
Nový projekt Worker vytvoříte spuštěním následujícího příkazu:
npm create cloudflare@latest -- durable-ai-agentPři nastavení vyberte následující možnosti:
- Pro S čím byste chtěli začít?, vyberte
Hello World example. - Pro Jakou šablonu chcete použít?, vyberte
Worker only. - Pro Jaký jazyk chcete použít?, vyberte
TypeScript. - Pro Chcete používat git pro správu verzí?, vyberte
Yes. - Pro Chcete nasadit svou aplikaci?, vyberte
No(před nasazením provedeme ještě několik změn).
- Pro S čím byste chtěli začít?, vyberte
-
Přejděte do svého projektu:
cd durable-ai-agent -
Nainstalujte závislosti:
npm install agents @anthropic-ai/sdk
2. Definujte své nástroje
Nástroje jsou funkce, které může LLM volat pro interakci s externími systémy. Definujete schéma (jaké vstupy nástroj přijímá) a implementaci (co dělá). LLM na základě úkolu rozhoduje, kdy který nástroj použije.
-
Vytvořit
src/tools.tsse dvěma doplňujícími se nástroji:src/tools.tsexport interface SearchReposInput { query: string; limit?: number; } export interface GetRepoInput { owner: string; repo: string; } interface GitHubSearchResponse { items: Array<{ full_name: string; stargazers_count: number }>; } interface GitHubRepoResponse { full_name: string; description: string; stargazers_count: number; forks_count: number; open_issues_count: number; language: string; license: { name: string } | null; updated_at: string; } export const searchReposTool = { name: "search_repos" as const, description: "Search GitHub repositories by keyword. Returns top results. Use get_repo for details.", input_schema: { type: "object" as const, properties: { query: { type: "string", description: "Search query (e.g., 'typescript orm')", }, limit: { type: "number", description: "Max results (default 5)" }, }, required: ["query"], }, run: async (input: SearchReposInput): Promise<string> => { const response = await fetch( `https://api.github.com/search/repositories?q=${encodeURIComponent(input.query)}&sort=stars&per_page=${input.limit ?? 5}`, { headers: { Accept: "application/vnd.github+json", "User-Agent": "DurableAgent/1.0", }, }, ); if (!response.ok) return `Search failed: ${response.status}`; const data = await response.json<GitHubSearchResponse>(); return JSON.stringify( data.items.map((r) => ({ name: r.full_name, stars: r.stargazers_count, })), ); }, }; export const getRepoTool = { name: "get_repo" as const, description: "Get detailed info about a GitHub repository including stars, forks, and description.", input_schema: { type: "object" as const, properties: { owner: { type: "string", description: "Repository owner (e.g., 'cloudflare')", }, repo: { type: "string", description: "Repository name (e.g., 'workers-sdk')", }, }, required: ["owner", "repo"], }, run: async (input: GetRepoInput): Promise<string> => { const response = await fetch( `https://api.github.com/repos/${input.owner}/${input.repo}`, { headers: { Accept: "application/vnd.github+json", "User-Agent": "DurableAgent/1.0", }, }, ); if (!response.ok) return `Repo not found: ${input.owner}/${input.repo}`; const data = await response.json<GitHubRepoResponse>(); return JSON.stringify({ name: data.full_name, description: data.description, stars: data.stargazers_count, forks: data.forks_count, issues: data.open_issues_count, language: data.language, license: data.license?.name ?? "None", updated: data.updated_at, }); }, }; export const tools = [searchReposTool, getRepoTool];
Tyto nástroje se vzájemně doplňují: search_repos vyhledá repozitáře a get_repo načte podrobnosti o konkrétních z nich.
3. Napište svůj Workflow
AgentWorkflow třída ze sady Agents SDK rozšiřuje Cloudflare Workflows o obousměrnou komunikaci s Agentem. Váš Workflow může hlásit průběh, vysílat zprávy klientům WebSocket a volat metody Agenta prostřednictvím RPC.
-
stepobjekt poskytuje metody pro definici trvalých kroků. step.do(name, callback)spustí kód a uloží výsledek. Pokud dojde k přerušení Workflow, pokračuje od posledního úspěšně dokončeného kroku.this.reportProgress()odesílá aktualizace průběhu Agentovi (nikoli trvale).this.broadcastToClients()odesílá zprávy všem připojeným klientům WebSocket (nikoli trvale).
Jemnější úvod najdete v Vytvoření prvního Workflow.
Vytvořit src/workflow.ts:
import { AgentWorkflow } from "agents/workflows";
import type { AgentWorkflowEvent, AgentWorkflowStep } from "agents/workflows";
import Anthropic from "@anthropic-ai/sdk";
import {
tools,
searchReposTool,
getRepoTool,
type SearchReposInput,
type GetRepoInput,
} from "./tools";
import type { ResearchAgent } from "./agent";
type Params = { task: string };
export class ResearchWorkflow extends AgentWorkflow<ResearchAgent, Params> {
async run(event: AgentWorkflowEvent<Params>, step: AgentWorkflowStep) {
const client = new Anthropic({ apiKey: this.env.ANTHROPIC_API_KEY });
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: event.payload.task },
];
const toolDefinitions = tools.map(({ run, ...rest }) => rest);
// Durable agent loop - each turn is checkpointed
for (let turn = 0; turn < 10; turn++) {
// Report progress to Agent and connected clients
await this.reportProgress({
step: `llm-turn-${turn}`,
status: "running",
percent: turn / 10,
message: `Processing turn ${turn + 1}...`,
});
const response = (await step.do(
`llm-turn-${turn}`,
{ retries: { limit: 3, delay: "10 seconds", backoff: "exponential" } },
async () => {
const msg = await client.messages.create({
model: "claude-sonnet-4-5-20250929",
max_tokens: 4096,
tools: toolDefinitions,
messages,
});
// Serialize for Workflow state
return JSON.parse(JSON.stringify(msg));
},
)) as Anthropic.Message;
if (!response || !response.content) continue;
messages.push({ role: "assistant", content: response.content });
if (response.stop_reason === "end_turn") {
const textBlock = response.content.find(
(b): b is Anthropic.TextBlock => b.type === "text",
);
const result = {
status: "complete",
turns: turn + 1,
result: textBlock?.text ?? null,
};
// Report completion (durable)
await step.reportComplete(result);
return result;
}
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type !== "tool_use") continue;
// Broadcast tool execution to clients
this.broadcastToClients({
type: "tool_call",
tool: block.name,
turn,
});
const result = await step.do(
`tool-${turn}-${block.id}`,
{ retries: { limit: 2, delay: "5 seconds" } },
async () => {
switch (block.name) {
case "search_repos":
return searchReposTool.run(block.input as SearchReposInput);
case "get_repo":
return getRepoTool.run(block.input as GetRepoInput);
default:
return `Unknown tool: ${block.name}`;
}
},
);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: result,
});
}
messages.push({ role: "user", content: toolResults });
}
return { status: "max_turns_reached", turns: 10 };
}
}Proč oddělovat kroky pro LLM a nástroje?
Každý step.do() vytvoří kontrolní bod. Pokud váš Workflow havaruje nebo se Worker restartuje:
- Po kroku LLM: Odpověď se uloží. Při obnovení přeskočí volání LLM a přejde rovnou k provedení nástroje.
- Po kroku nástroje: Výsledek se uloží. Pokud pozdější nástroj selže, dřívější nástroje se znovu nespouští.
To je obzvlášť důležité v následujících případech:
- Volání LLM: Nákladné a pomalé, nemělo by se zbytečně opakovat
- Externí API: Může mít omezení četnosti požadavků nebo vedlejší účinky
- Idempotence: Některé nástroje (například odesílání e-mailů) by se neměly spouštět dvakrát
4. Napište svého Agenta
Agent zpracovává HTTP požadavky, WebSocket připojení a události životního cyklu Workflow. Spouští instanci workflow runWorkflow() a přijímá aktualizace průběhu prostřednictvím callbacků.
Vytvořit src/agent.ts:
import { Agent } from "agents";
type State = {
currentWorkflow?: string;
status?: string;
};
export class ResearchAgent extends Agent<Env, State> {
initialState: State = {};
// Start a research task - called via HTTP or WebSocket
async startResearch(task: string) {
const instanceId = await this.runWorkflow("RESEARCH_WORKFLOW", { task });
this.setState({
...this.state,
currentWorkflow: instanceId,
status: "running",
});
return { instanceId };
}
// Get status of a workflow
async getResearchStatus(instanceId: string) {
return this.getWorkflow(instanceId);
}
// Called when workflow reports progress
async onWorkflowProgress(
workflowName: string,
instanceId: string,
progress: unknown,
) {
// Broadcast to all connected WebSocket clients
this.broadcast(JSON.stringify({ type: "progress", instanceId, progress }));
}
// Called when workflow completes
async onWorkflowComplete(
workflowName: string,
instanceId: string,
result?: unknown,
) {
this.setState({ ...this.state, status: "complete" });
this.broadcast(JSON.stringify({ type: "complete", instanceId, result }));
}
// Called when workflow errors
async onWorkflowError(
workflowName: string,
instanceId: string,
error: string,
) {
this.setState({ ...this.state, status: "error" });
this.broadcast(JSON.stringify({ type: "error", instanceId, error }));
}
}5. Nakonfigurujte svůj projekt
-
Otevřete
wrangler.jsonca přidejte konfiguraci Agent a Workflow:{ "$schema": "node_modules/wrangler/config-schema.json", "name": "durable-ai-agent", "main": "src/index.ts", // Set this to today's date "compatibility_date": "2026-08-28", "observability": { "enabled": true }, "durable_objects": { "bindings": [ { "name": "ResearchAgent", "class_name": "ResearchAgent" } ] }, "workflows": [ { "name": "research-workflow", "binding": "RESEARCH_WORKFLOW", "class_name": "ResearchWorkflow" } ], "migrations": [ { "tag": "v1", "new_sqlite_classes": ["ResearchAgent"] } ] }"$schema" = "node_modules/wrangler/config-schema.json" name = "durable-ai-agent" main = "src/index.ts" # Set this to today's date compatibility_date = "2026-08-28" [observability] enabled = true [[durable_objects.bindings]] name = "ResearchAgent" class_name = "ResearchAgent" [[workflows]] name = "research-workflow" binding = "RESEARCH_WORKFLOW" class_name = "ResearchWorkflow" [[migrations]] tag = "v1" new_sqlite_classes = [ "ResearchAgent" ] -
Vygenerujte typy pro vaše bindings:
npx wrangler typesTím se vytvoří
worker-configuration.d.tssoubor pomocíEnvtyp, který obsahuje vaše bindings.
6. Napište své API
Worker směruje požadavky na Agent, který spravuje životní cyklus workflow. Použijte routeAgentRequest() pro WebSocket připojení a getAgentByName() pro RPC volání na straně serveru.
Nahraďte src/index.ts:
import { getAgentByName, routeAgentRequest } from "agents";
export { ResearchAgent } from "./agent";
export { ResearchWorkflow } from "./workflow";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// Route WebSocket connections to /agents/research-agent/{name}
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// HTTP API for starting research tasks
if (request.method === "POST" && url.pathname === "/research") {
const { task, agentId } = await request.json<{
task: string;
agentId?: string;
}>();
// Get agent instance by name (creates if doesn't exist)
const agent = await getAgentByName(
env.ResearchAgent,
agentId ?? "default",
);
// Start the research workflow via RPC
const result = await agent.startResearch(task);
return Response.json(result);
}
// Check workflow status
if (url.pathname === "/status") {
const instanceId = url.searchParams.get("instanceId");
const agentId = url.searchParams.get("agentId") ?? "default";
if (!instanceId) {
return Response.json({ error: "instanceId required" }, { status: 400 });
}
const agent = await getAgentByName(env.ResearchAgent, agentId);
const status = await agent.getResearchStatus(instanceId);
return Response.json(status);
}
return new Response("POST /research with { task } to start", {
status: 400,
});
},
} satisfies ExportedHandler<Env>;7. Vyvíjejte lokálně
-
Vytvořte
.envsoubor pro místní vývoj:.envANTHROPIC_API_KEY=your-api-key-here -
Spusťte vývojový server:
npx wrangler dev -
Spusťte výzkumnou úlohu:
curl -X POST http://localhost:8787/research \ -H "Content-Type: application/json" \ -d '{"task": "Compare open-source LLM projects"}'{ "instanceId": "abc-123-def" } -
Zkontrolujte průběh (dokončení může trvat několik sekund):
curl "http://localhost:8787/status?instanceId=abc-123-def"
Agent vyhledá repozitáře, načte podrobnosti a vrátí porovnání. Průběžné aktualizace se odesílají všem připojeným klientům WebSocket.
8. Nasazení
-
Nasaďte Worker:
npx wrangler deploy -
Přidejte svůj API klíč jako secret:
npx wrangler secret put ANTHROPIC_API_KEY -
Spusťte výzkumnou úlohu na nasazeném Workeru:
curl -X POST https://durable-ai-agent.<your-subdomain>.workers.dev/research \ -H "Content-Type: application/json" \ -d '{"task": "Compare open-source LLM projects"}' -
Zkontrolujte běhy workflow pomocí CLI:
npx wrangler workflows instances describe research-workflow latestZobrazuje se tak každý krok, který agent provedl, včetně volání LLM, spouštění nástrojů, časování a případných opakování.
Zobrazit to můžete také v Cloudflare dashboardu v sekci research-workflow.
Přejděte na Workflows ↗
Integrace klienta v reálném čase
Připojte se ke svému Agentovi přes WebSocket a získávejte aktualizace průběhu v reálném čase. Toto useAgent hook se připojuje k /agents/{agent-name}/{instance-name}:
/agents/research-agent/default → ResearchAgent instance "default"
/agents/research-agent/user-123 → ResearchAgent instance "user-123"import { useState } from "react";
import { useAgent } from "agents/react";
function ResearchUI({ agentId = "default" }) {
const [progress, setProgress] = useState(null);
const { state } = useAgent({
agent: "research-agent", // Maps to ResearchAgent class
name: agentId, // Instance name
onMessage: (message) => {
const data = JSON.parse(message.data);
if (data.type === "progress") {
setProgress(data.progress);
}
},
});
return (
<div>
{progress && (
<p>
{progress.message} ({Math.round(progress.percent * 100)}%)
</p>
)}
</div>
);
}Názvy tříd Agent se pro URL automaticky převádějí na kebab-case (ResearchAgent → research-agent).