Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions examples/web-research/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Web Research Example

A chat agent that answers questions from the live web. It searches with the `web_search` tool from `agents/websearch/ai-sdk`, which wraps [Cloudflare's Web Search API](https://developers.cloudflare.com/web-search/), and cites what it finds.

Every search also shows up in the panel next to the chat, so you can see three views of the same call:

- **Results**: the ranked pages the search returned.
- **Model saw**: the trimmed text the model actually read (the tool's `toModelOutput`).
- **You got**: the full structured response your code keeps in the message history.

## What it demonstrates

**Server (`src/server.ts`):**

- `webSearchTool()` on the Worker's `AI` binding, with the host deciding the provider, the result cap, and how much of each result the model reads
- Provider and result cap kept in the agent's state, changed from the UI with `setState` and checked in `validateStateChange`
- `convertToModelMessages(messages, { tools })`, so earlier searches are replayed to the model as the trimmed text it first saw, not as the full JSON
- A custom `onError` that shows why a search or model call failed (for example, missing AI Gateway credits) instead of the AI SDK's generic "An error occurred."
- A system prompt with today's date and inline numbered citations

**Client (`src/client.tsx`):**

- Typed `tool-web_search` parts via `useAgentChat<unknown, ResearchMessage>()`
- Search settings (provider, max results) in the panel, synced through `useAgent` state
- `renderWebSearchResults()` from `agents/websearch` to show the exact text the model saw, using the same `maxDescriptionChars` as the server (`src/shared.ts`)
- A feed that follows the stream only while you're at the bottom, so you can scroll up and read mid-reply
- Reusable chat pieces (feed, composer, markdown, tool rows) in `src/chat-ui.tsx`, built on Kumo

## Running

```bash
pnpm install
pnpm start
```
Comment thread
ben-reitz marked this conversation as resolved.

You need:

- **Wrangler 4.141.0 or later** (the example uses 4.145). Older runtimes don't have `env.AI.websearch()`.
- **A Cloudflare account that can pay for searches.** Searches are billed to the default AI Gateway on the account the Worker runs on, from AI Gateway credits or a provider key stored on the gateway (BYOK). Without either, every search fails with `web_search_payment_required` and the agent tells you so.

The `AI` binding has no local implementation, so `pnpm start` runs it remotely against the account you're logged in to with `wrangler login`. If you belong to more than one account, set `CLOUDFLARE_ACCOUNT_ID`. If your `workers.dev` subdomain is behind Cloudflare Access, sign in through the browser when Wrangler asks.

The model is `@cf/moonshotai/kimi-k2.7-code` on Workers AI (`MODEL` in `src/shared.ts`).

## Try it

- "Who won the most recent Formula 1 Grand Prix?": the model usually searches more than once, rephrasing as it goes
- "What's new in the Cloudflare Agents SDK this month?": open a search and compare **Model saw** with **You got** to see how much the tool trims
- Ask a follow-up question. Earlier searches stay in context, so the model can answer without searching again.

## Choosing a provider

The default is `ceramic`. `exa` and `linkup` cost more per search but often return better results for specific factual questions. Switch between them under **Search settings** to compare; the change applies from your next message.
14 changes: 14 additions & 0 deletions examples/web-research/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable */
// Generated by Wrangler by running `wrangler types --include-runtime=false env.d.ts` (hash: bb8556b413ae1555c2819a24ef846761)
interface __BaseEnv_Env {
AI: Ai;
ResearchAgent: DurableObjectNamespace<import("./src/server").ResearchAgent>;
}
declare namespace Cloudflare {
interface GlobalProps {
mainModule: typeof import("./src/server");
durableNamespaces: "ResearchAgent";
}
interface Env extends __BaseEnv_Env {}
}
interface Env extends __BaseEnv_Env {}
20 changes: 20 additions & 0 deletions examples/web-research/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<title>Web Research</title>
<script>
(() => {
const mode = localStorage.getItem("theme") || "light";
document.documentElement.setAttribute("data-mode", mode);
document.documentElement.style.colorScheme = mode;
})();
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
38 changes: 38 additions & 0 deletions examples/web-research/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@cloudflare/agents-web-research-example",
"description": "Chat agent that answers from the live web with the web_search tool and cites its sources",
"private": true,
"type": "module",
"version": "0.0.0",
"scripts": {
"start": "vite dev",
"deploy": "vite build && wrangler deploy",
"types": "wrangler types env.d.ts --include-runtime false"
},
"dependencies": {
"@cloudflare/ai-chat": "*",
"@cloudflare/kumo": "^2.6.0",
"@phosphor-icons/react": "^2.1.10",
"@streamdown/code": "^1.1.1",
"agents": "*",
"ai": "^7.0.0",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"streamdown": "^2.5.0",
"workers-ai-provider": "^4.0.0",
"zod": "^4.5.4"
},
"devDependencies": {
"@cloudflare/vite-plugin": "1.62.3",
"@cloudflare/workers-types": "^5.20260729.1",
"@tailwindcss/vite": "^4",
"@types/node": "^26.0.1",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.3",
"tailwindcss": "^4.3.2",
"typescript": "^6.0.3",
"vite": "^8.1.0",
"wrangler": "^4.145.0"
}
}
Binary file added examples/web-research/public/favicon.ico
Binary file not shown.
Loading
Loading