Skip to content

Commit 458633e

Browse files
[Browser Rendering] Add an how to on how to use browser rendering tog… (#19180)
* [Browser Rendering] Add a tutorial to on how to use browser rendering together with LLMs * Update src/content/docs/browser-rendering/how-to/ai.mdx Co-authored-by: hyperlint-ai[bot] <154288675+hyperlint-ai[bot]@users.noreply.github.com> * [Browser Rendering] fix link * [Browser Rendering] Improve tutorial --------- Co-authored-by: Sofia Cardita <[email protected]> Co-authored-by: hyperlint-ai[bot] <154288675+hyperlint-ai[bot]@users.noreply.github.com>
1 parent 4889852 commit 458633e

File tree

1 file changed

+274
-0
lines changed
  • src/content/docs/browser-rendering/how-to

1 file changed

+274
-0
lines changed
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
---
2+
title: Use browser rendering with AI
3+
sidebar:
4+
order: 2
5+
---
6+
7+
import { Aside } from "~/components";
8+
9+
The ability to browse websites can be crucial when building workflows with AI. Here, we provide an example where we use Browser Rendering to visit
10+
`https://news.ycombinator.com/` and then, using a machine learning model available in [Workers AI](/workers-ai/), extract the first post as JSON with a specified schema.
11+
12+
## Prerequisites
13+
14+
1. Use the `create-cloudflare` CLI to generate a new Hello World Cloudflare Worker script:
15+
16+
```sh
17+
npm create cloudflare@latest -- browser-worker
18+
```
19+
20+
2. Install `@cloudflare/puppeteer`, which allows you to control the Browser Rendering instance:
21+
22+
```sh
23+
npm i @cloudflare/puppeteer
24+
```
25+
26+
2. Install `zod` so we can define our output format and `zod-to-json-schema` so we can convert it into a JSON schema format:
27+
28+
```sh
29+
npm i zod
30+
npm i zod-to-json-schema
31+
```
32+
33+
3. Activate the nodejs compatibility flag and add your Browser Rendering binding to your new `wrangler.toml` configuration:
34+
35+
```toml
36+
compatibility_flags = [ "nodejs_compat" ]
37+
```
38+
39+
```toml
40+
[browser]
41+
binding = "MY_BROWSER"
42+
```
43+
44+
4. In order to use [Workers AI](/workers-ai/), you need to get your [Account ID and API token](/workers-ai/get-started/rest-api/#1-get-api-token-and-account-id).
45+
Once you have those, create a [`.dev.vars`](/workers/configuration/environment-variables/#add-environment-variables-via-wrangler) file and set them there:
46+
47+
```
48+
ACCOUNT_ID=
49+
API_TOKEN=
50+
```
51+
52+
We use `.dev.vars` here since it's only for local development, otherwise you'd use [Secrets](/workers/configuration/secrets/).
53+
54+
## Load the page using Browser Rendering
55+
56+
In the code below, we launch a browser using `await puppeteer.launch(env.MY_BROWSER)`, extract the rendered text and close the browser.
57+
Then, with the user prompt, the desired output schema and the rendered text, prepare a prompt to send to the LLM.
58+
59+
Replace the contents of `src/index.ts` with the following skeleton script:
60+
61+
```ts
62+
import { z } from "zod";
63+
import puppeteer from "@cloudflare/puppeteer";
64+
import zodToJsonSchema from "zod-to-json-schema";
65+
66+
export default {
67+
async fetch(request, env) {
68+
const url = new URL(request.url);
69+
if (url.pathname != "/") {
70+
return new Response("Not found");
71+
}
72+
73+
// Your prompt and site to scrape
74+
const userPrompt = "Extract the first post only.";
75+
const targetUrl = "https://labs.apnic.net/";
76+
77+
// Launch browser
78+
const browser = await puppeteer.launch(env.MY_BROWSER);
79+
const page = await browser.newPage();
80+
await page.goto(targetUrl);
81+
82+
// Get website text
83+
const renderedText = await page.evaluate(() => {
84+
// @ts-ignore js code to run in the browser context
85+
const body = document.querySelector("body");
86+
return body ? body.innerText : "";
87+
});
88+
// Close browser since we no longer need it
89+
await browser.close();
90+
91+
// define your desired json schema
92+
const outputSchema = zodToJsonSchema(
93+
z.object({ title: z.string(), url: z.string(), date: z.string() })
94+
);
95+
96+
// Example prompt
97+
const prompt = `
98+
You are a sophisticated web scraper. You are given the user data extraction goal and the JSON schema for the output data format.
99+
Your task is to extract the requested information from the text and output it in the specified JSON schema format:
100+
101+
${JSON.stringify(outputSchema)}
102+
103+
DO NOT include anything else besides the JSON output, no markdown, no plaintext, just JSON.
104+
105+
User Data Extraction Goal: ${userPrompt}
106+
107+
Text extracted from the webpage: ${renderedText}`;
108+
109+
// TODO call llm
110+
//const result = await getLLMResult(env, prompt, outputSchema);
111+
//return Response.json(result);
112+
}
113+
114+
} satisfies ExportedHandler<Env>;
115+
116+
```
117+
118+
## Call an LLM
119+
120+
Having the webpage text, the user's goal and output schema, we can now use an LLM to transform it to JSON according to the user's request.
121+
The example below uses `@hf/thebloke/deepseek-coder-6.7b-instruct-awq` but other [models](/workers-ai/models/), or services like OpenAI, could be used with minimal changes:
122+
123+
```ts
124+
async getLLMResult(env, prompt: string, schema?: any) {
125+
const model = "@hf/thebloke/deepseek-coder-6.7b-instruct-awq"
126+
const requestBody = {
127+
messages: [{
128+
role: "user",
129+
content: prompt
130+
}
131+
],
132+
};
133+
const aiUrl = `https://api.cloudflare.com/client/v4/accounts/${env.ACCOUNT_ID}/ai/run/${model}`
134+
135+
const response = await fetch(aiUrl, {
136+
method: "POST",
137+
headers: {
138+
"Content-Type": "application/json",
139+
Authorization: `Bearer ${env.API_TOKEN}`,
140+
},
141+
body: JSON.stringify(requestBody),
142+
});
143+
if (!response.ok) {
144+
console.log(JSON.stringify(await response.text(), null, 2));
145+
throw new Error(`LLM call failed ${aiUrl} ${response.status}`);
146+
}
147+
148+
// process response
149+
const data = await response.json();
150+
const text = data.result.response || '';
151+
const value = (text.match(/```(?:json)?\s*([\s\S]*?)\s*```/) || [null, text])[1];
152+
try {
153+
return JSON.parse(value);
154+
} catch(e) {
155+
console.error(`${e} . Response: ${value}`)
156+
}
157+
}
158+
```
159+
160+
If you want to use Browser Rendering with OpenAI instead you'd just need to change the `aiUrl` endpoint and `requestBody` (or check out the [llm-scraper-worker](https://www.npmjs.com/package/llm-scraper-worker) package).
161+
162+
## Conclusion
163+
164+
The full Worker script now looks as follows:
165+
166+
```ts
167+
import { z } from "zod";
168+
import puppeteer from "@cloudflare/puppeteer";
169+
import zodToJsonSchema from "zod-to-json-schema";
170+
171+
export default {
172+
async fetch(request, env) {
173+
const url = new URL(request.url);
174+
if (url.pathname != "/") {
175+
return new Response("Not found");
176+
}
177+
178+
// Your prompt and site to scrape
179+
const userPrompt = "Extract the first post only.";
180+
const targetUrl = "https://labs.apnic.net/";
181+
182+
// Launch browser
183+
const browser = await puppeteer.launch(env.MY_BROWSER);
184+
const page = await browser.newPage();
185+
await page.goto(targetUrl);
186+
187+
// Get website text
188+
const renderedText = await page.evaluate(() => {
189+
// @ts-ignore js code to run in the browser context
190+
const body = document.querySelector("body");
191+
return body ? body.innerText : "";
192+
});
193+
// Close browser since we no longer need it
194+
await browser.close();
195+
196+
// define your desired json schema
197+
const outputSchema = zodToJsonSchema(
198+
z.object({ title: z.string(), url: z.string(), date: z.string() })
199+
);
200+
201+
// Example prompt
202+
const prompt = `
203+
You are a sophisticated web scraper. You are given the user data extraction goal and the JSON schema for the output data format.
204+
Your task is to extract the requested information from the text and output it in the specified JSON schema format:
205+
206+
${JSON.stringify(outputSchema)}
207+
208+
DO NOT include anything else besides the JSON output, no markdown, no plaintext, just JSON.
209+
210+
User Data Extraction Goal: ${userPrompt}
211+
212+
Text extracted from the webpage: ${renderedText}`;
213+
214+
// call llm
215+
const result = await getLLMResult(env, prompt, outputSchema);
216+
return Response.json(result);
217+
}
218+
219+
} satisfies ExportedHandler<Env>;
220+
221+
222+
async function getLLMResult(env, prompt: string, schema?: any) {
223+
const model = "@hf/thebloke/deepseek-coder-6.7b-instruct-awq"
224+
const requestBody = {
225+
messages: [{
226+
role: "user",
227+
content: prompt
228+
}
229+
],
230+
};
231+
const aiUrl = `https://api.cloudflare.com/client/v4/accounts/${env.ACCOUNT_ID}/ai/run/${model}`
232+
233+
const response = await fetch(aiUrl, {
234+
method: "POST",
235+
headers: {
236+
"Content-Type": "application/json",
237+
Authorization: `Bearer ${env.API_TOKEN}`,
238+
},
239+
body: JSON.stringify(requestBody),
240+
});
241+
if (!response.ok) {
242+
console.log(JSON.stringify(await response.text(), null, 2));
243+
throw new Error(`LLM call failed ${aiUrl} ${response.status}`);
244+
}
245+
246+
// process response
247+
const data = await response.json() as { result: { response: string }};
248+
const text = data.result.response || '';
249+
const value = (text.match(/```(?:json)?\s*([\s\S]*?)\s*```/) || [null, text])[1];
250+
try {
251+
return JSON.parse(value);
252+
} catch(e) {
253+
console.error(`${e} . Response: ${value}`)
254+
}
255+
}
256+
```
257+
258+
You can run this script to test it using Wrangler's `--remote` flag:
259+
260+
```sh
261+
npx wrangler dev --remote
262+
```
263+
264+
With your script now running, you can go to `http://localhost:8787/` and should see something like the following:
265+
266+
```json
267+
{
268+
"title": "IP Addresses in 2024",
269+
"url": "http://example.com/ip-addresses-in-2024",
270+
"date": "11 Jan 2025"
271+
}
272+
```
273+
274+
For more complex websites or prompts, you might need a better model. Check out the latest models in [Workers AI](/workers-ai/models/).

0 commit comments

Comments
 (0)