Repository navigation
docs(examples): add web research example using web_search #2517
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+1,759
−0
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
417507a
docs(examples): add web research example using web_search
ben-reitz cc62755
feat(examples): refresh web-research UI, add search settings and Deep…
ben-reitz 7630c59
fix(examples): wrap web-research model view at word boundaries
ben-reitz c42c463
fix(examples): switch web-research back to Kimi K2.7
ben-reitz 57a05c9
docs(examples): use pnpm in web-research README
ben-reitz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| ``` | ||
|
|
||
| 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. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 not shown.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.