INTEGRITY Dokumentace

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:

  1. Vyhledání relevantních repozitářů na GitHubu
  2. Načtěte podrobnosti o každém z nich (hvězdy, forky, aktivita)
  3. Analyzujte je a porovnejte
  4. 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/durableAgent

Tuto 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

  1. Zaregistrujte si účet Cloudflare ↗.
  2. 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

  1. Nový projekt Worker vytvoříte spuštěním následujícího příkazu:

    npm create cloudflare@latest -- durable-ai-agent

    Př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).
  2. Přejděte do svého projektu:

    cd durable-ai-agent
  3. 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.

  1. Vytvořit src/tools.ts se dvěma doplňujícími se nástroji:

    src/tools.ts
    export 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.

Jemnější úvod najdete v Vytvoření prvního Workflow.

Vytvořit src/workflow.ts:

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:

To je obzvlášť důležité v následujících případech:

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:

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

  1. Otevřete wrangler.jsonc a 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" ]
  2. Vygenerujte typy pro vaše bindings:

    npx wrangler types

    Tím se vytvoří worker-configuration.d.ts soubor pomocí Env typ, 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:

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ě

  1. Vytvořte .env soubor pro místní vývoj:

    .env
    ANTHROPIC_API_KEY=your-api-key-here
  2. Spusťte vývojový server:

    npx wrangler dev
  3. 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" }
  4. 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í

  1. Nasaďte Worker:

    npx wrangler deploy
  2. Přidejte svůj API klíč jako secret:

    npx wrangler secret put ANTHROPIC_API_KEY
  3. 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"}'
  4. Zkontrolujte běhy workflow pomocí CLI:

    npx wrangler workflows instances describe research-workflow latest

    Zobrazuje 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).

Zjistit více

Agents SDK Workflows

Úplná referenční dokumentace API pro AgentWorkflow, callbacky životního cyklu a obousměrnou komunikaci.

Události a parametry

Předávejte data do Workflows a pozastavujte provádění do příchodu externích událostí pomocí waitForEvent.

Uspávání a opakování pokusů

Konfigurace chování opakování a vzorců pozastavení.

Workers API

Prozkoumejte kompletní API Workflows pro programové řízení.

Agents SDK

Pro interaktivní agenty s chatem v reálném čase a připojeními přes WebSocket.