branch:
README.md
3998 bytesRaw
## Human in the Loop with Cloudflare Agents
This guide demonstrates human-in-the-loop patterns using `AIChatAgent` from `@cloudflare/ai-chat`. Tools can require user approval before executing, and tools that need browser APIs are handled client-side.
### Key Patterns
1. **`needsApproval`** -- Server-side tools that pause for user approval before executing
2. **`onToolCall`** -- Client-side tool execution for tools that need browser APIs
3. **`addToolApprovalResponse`** -- Client responds to approval requests
### Server
Tools are defined on the server. Use `needsApproval` for tools requiring human confirmation:
```ts
import { AIChatAgent } from "@cloudflare/ai-chat";
import { streamText, convertToModelMessages, tool } from "ai";
import { z } from "zod";
export class HumanInTheLoop extends AIChatAgent {
async onChatMessage() {
const result = streamText({
model: openai("gpt-4o"),
messages: await convertToModelMessages(this.messages),
tools: {
// Requires approval -- needsApproval: true
getWeather: tool({
description: "Get weather for a city",
inputSchema: z.object({ city: z.string() }),
needsApproval: true,
execute: async ({ city }) => `The weather in ${city} is sunny.`
}),
// Client-side tool -- no execute function
getLocalTime: tool({
description: "Get local time for a location",
inputSchema: z.object({ location: z.string() })
}),
// Automatic -- no approval, runs server-side
getNews: tool({
description: "Get news for a location",
inputSchema: z.object({ location: z.string() }),
execute: async ({ location }) => `${location} news: all good!`
})
},
maxSteps: 5
});
return result.toUIMessageStreamResponse();
}
}
```
### Client
Handle approvals with `addToolApprovalResponse` and client-side tools with `onToolCall`:
```tsx
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
function Chat() {
const agent = useAgent({ agent: "human-in-the-loop" });
const { messages, sendMessage, addToolApprovalResponse } = useAgentChat({
agent,
onToolCall: async ({ toolCall, addToolOutput }) => {
if (toolCall.toolName === "getLocalTime") {
const time = new Date().toLocaleTimeString();
addToolOutput({ toolCallId: toolCall.toolCallId, output: time });
}
}
});
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>
{msg.parts.map((part, i) => {
if (part.type === "text") return <p key={i}>{part.text}</p>;
// Tool approval request
if ("approval" in part && part.state === "approval-requested") {
return (
<div key={part.toolCallId}>
<p>Approve {getToolName(part)}?</p>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval.id,
approved: true
})
}
>
Approve
</button>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval.id,
approved: false
})
}
>
Reject
</button>
</div>
);
}
return null;
})}
</div>
))}
</div>
);
}
```
### Running
```bash
npm install
npm run dev
```
Requires `OPENAI_API_KEY` in `.env`.
### Learn More
- [Human in the Loop docs](../../docs/human-in-the-loop.md)
- [Client Tools & Auto-Continuation](../../docs/client-tools-continuation.md)
- [@cloudflare/ai-chat README](../../packages/ai-chat/README.md)