Skip to content

Commit c5ae04e

Browse files
committed
[Browser Rendering] Add an how to on how to use browser rendering together with LLMs
1 parent 9b17bb9 commit c5ae04e

File tree

1 file changed

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

1 file changed

+254
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
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. Add your Browser Rendering binding to your new `wrangler.toml` configuration:
34+
35+
```toml
36+
browser = { binding = "BROWSER" }
37+
```
38+
39+
4. In order to use [Workers AI](/workers-ai/), you need to get your [Account ID and API token](/get-started/rest-api/#1-get-api-token-and-account-id).
40+
Once you have those, create a [`.dev.vars`](/workers/configuration/environment-variables/#add-environment-variables-via-wrangler) file and set them there:
41+
42+
```
43+
ACCOUNT_ID=
44+
API_TOKEN=
45+
```
46+
47+
We use `.dev.vars` here since it's only for local development, otherwise you'd use [Secrets](/workers/configuration/secrets/).
48+
49+
## Load the page using Browser Rendering
50+
51+
In the code below, we launch a browser using `await puppeteer.launch(env.BROWSER)`, extract the rendered text and close the browser.
52+
Then, with the user prompt, the desired output schema and the rendered text, prepare a prompt to send to the LLM.
53+
54+
Replace the contents of `src/index.ts` with the following skeleton script:
55+
56+
```ts
57+
// src/index.ts
58+
import { z } from "zod";
59+
import puppeteer from "@cloudflare/puppeteer";
60+
import zodToJsonSchema from "zod-to-json-schema";
61+
62+
export default {
63+
async fetch(request, env) {
64+
const url = new URL(request.url);
65+
if (url.pathname != "/") {
66+
return new Response("Not found");
67+
}
68+
69+
// Your prompt and site to scrape
70+
const userPrompt = "Extract the first post";
71+
const targetUrl = "https://news.ycombinator.com/";
72+
73+
// Launch browser
74+
const browser = await puppeteer.launch(env.BROWSER);
75+
const page = await browser.newPage();
76+
await page.goto(targetUrl);
77+
78+
// Get website text
79+
const renderedText = await page.evaluate(() => {
80+
const body = document.querySelector("body");
81+
return body ? body.innerText : "";
82+
});
83+
// Close browser since we no longer need it
84+
await browser.close();
85+
86+
// define your desired json schema
87+
const outputSchema = zodToJsonSchema(
88+
z.object({ title: z.string(), url: z.string(), totalComments: z.number() })
89+
);
90+
91+
// Example prompt
92+
const prompt = `
93+
You are a sophisticated web scraper. You are given the user data extraction goal and the JSON schema for the output data format.
94+
Your task is to extract the requested information from the text and output it in the specified JSON schema format:
95+
${JSON.stringify(outputSchema)}
96+
User Data Extraction Goal: ${userPrompt}
97+
Text extracted from the webpage: ${renderedText}`;
98+
99+
// TODO call llm
100+
//const result = await this.getLLMResult(env, prompt, outputSchema);
101+
//return Response.json(result);
102+
}
103+
};
104+
```
105+
106+
## Call an LLM
107+
108+
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.
109+
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:
110+
111+
```ts
112+
async getLLMResult(env, prompt: string, schema?: any) {
113+
const model = "@hf/thebloke/deepseek-coder-6.7b-instruct-awq"
114+
const requestBody = {
115+
messages: [{
116+
role: "user",
117+
content: prompt
118+
}
119+
],
120+
};
121+
const aiUrl = `https://api.cloudflare.com/client/v4/accounts/${env.ACCOUNT_ID}/ai/run/${model}`
122+
123+
const response = await fetch(aiUrl, {
124+
method: "POST",
125+
headers: {
126+
"Content-Type": "application/json",
127+
Authorization: `Bearer ${env.API_TOKEN}`,
128+
},
129+
body: JSON.stringify(requestBody),
130+
});
131+
if (!response.ok) {
132+
console.log(JSON.stringify(await response.text(), null, 2));
133+
throw new Error(`LLM call failed ${aiUrl} ${response.status}`);
134+
}
135+
136+
// process response
137+
const data = await response.json();
138+
const text = data.result.response || '';
139+
const value = (text.match(/```(?:json)?\s*([\s\S]*?)\s*```/) || [null, text])[1];
140+
try {
141+
return JSON.parse(value);
142+
} catch(e) {
143+
console.error(`${e} . Response: ${value}`)
144+
}
145+
}
146+
```
147+
148+
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).
149+
150+
## Conclusion
151+
152+
The full Worker script now looks as follows:
153+
154+
```ts
155+
import { z } from "zod";
156+
import puppeteer from "@cloudflare/puppeteer";
157+
import zodToJsonSchema from "zod-to-json-schema";
158+
159+
export default {
160+
async fetch(request, env) {
161+
const url = new URL(request.url);
162+
if (url.pathname != "/") {
163+
return new Response("Not found");
164+
}
165+
166+
// Your prompt and site to scrape
167+
const userPrompt = "Extract the first post";
168+
const targetUrl = "https://news.ycombinator.com/";
169+
170+
// Launch browser
171+
const browser = await puppeteer.launch(env.BROWSER);
172+
const page = await browser.newPage();
173+
await page.goto(targetUrl);
174+
175+
// Get website text
176+
const renderedText = await page.evaluate(() => {
177+
const body = document.querySelector("body");
178+
return body ? body.innerText : "";
179+
});
180+
// Close browser since we no longer need it
181+
await browser.close();
182+
183+
// define your desired json schema
184+
const outputSchema = zodToJsonSchema(
185+
z.object({ title: z.string(), url: z.string(), totalComments: z.number() })
186+
);
187+
188+
// Example prompt
189+
const prompt = `
190+
You are a sophisticated web scraper. You are given the user data extraction goal and the JSON schema for the output data format.
191+
Your task is to extract the requested information from the text and output it in the specified JSON schema format:
192+
${JSON.stringify(outputSchema)}
193+
User Data Extraction Goal: ${userPrompt}
194+
Text extracted from the webpage: ${renderedText}`;
195+
196+
// call llm
197+
const result = await this.getLLMResult(env, prompt, outputSchema);
198+
return Response.json(result);
199+
},
200+
201+
async getLLMResult(env, prompt: string, schema?: any) {
202+
const model = "@hf/thebloke/deepseek-coder-6.7b-instruct-awq"
203+
const requestBody = {
204+
messages: [{
205+
role: "user",
206+
content: prompt
207+
}
208+
],
209+
};
210+
const aiUrl = `https://api.cloudflare.com/client/v4/accounts/${env.ACCOUNT_ID}/ai/run/${model}`
211+
212+
const response = await fetch(aiUrl, {
213+
method: "POST",
214+
headers: {
215+
"Content-Type": "application/json",
216+
Authorization: `Bearer ${env.LLM_API_KEY}`,
217+
},
218+
body: JSON.stringify(requestBody),
219+
});
220+
if (!response.ok) {
221+
console.log(JSON.stringify(await response.text(), null, 2));
222+
throw new Error(`LLM call failed ${aiUrl} ${response.status}`);
223+
}
224+
225+
// process response
226+
const data = await response.json();
227+
const text = data.result.response || '';
228+
const value = (text.match(/```(?:json)?\s*([\s\S]*?)\s*```/) || [null, text])[1];
229+
try {
230+
return JSON.parse(value);
231+
} catch(e) {
232+
console.error(`${e} . Response: ${value}`)
233+
}
234+
},
235+
};
236+
237+
238+
```
239+
240+
You can run this script to test it using Wrangler’s `--remote` flag:
241+
242+
```sh
243+
npx wrangler dev --remote
244+
```
245+
246+
With your script now running, you can go to `http://localhost:8787/` and should see something like the following:
247+
248+
```json
249+
{
250+
"title": "Debugging: Indispensable rules for finding even the most elusive problems",
251+
"url": "dwheeler.com",
252+
"totalComments": 143
253+
}
254+
```

0 commit comments

Comments
 (0)