Docs
Dashboard
Examples / Chat and researchBeta

Chat with a page

Open a page once in a cloud browser, then ask questions about it in the terminal. Each answer is read from the page that is open in the session, takes the chat so far into account, and quotes the words on the page it is based on. When the page does not say, the answer says so instead of guessing.

When to use it: You want to ask questions about one page and get answers that quote the page, or say it does not say.

Usesa session, goto, content, the extract action on the open page
Needsa model on the API (no model key of your own)
Sitea books.toscrape.com product page (a demo shop), or any page you give it
Outputoutput/result.json (the questions, answers and quotes), output/page.md (the page as Markdown)
LanguagesNode and Python

The session stays open while you chat (it ends after 5 idle minutes, at most 15 minutes in all), so the page is loaded once and every question reads the same page, scripts and all.

The code

The example as it is in its folder. Both versions do the same job and write the same output/, so one result check covers both.

/** * Chat with a page: open a page once in a session, then ask questions about it. Each answer comes from the page * that is open in the browser (session.extract), with the chat so far, and quotes the sentence it is based on. * *   npx tsx node/index.ts            (BOXLINE_API_KEY; PAGE_URL for another page) * * QUESTIONS="first?|second?" answers those and stops; without it, type questions and "exit" to stop. * Writes output/result.json and output/page.md (the page as the model read it). */import { mkdirSync, writeFileSync } from "node:fs";import { join } from "node:path";import { createInterface } from "node:readline/promises";import { Boxline } from "@boxline/sdk";const out = process.env.OUTPUT_DIR ?? "output";const url = process.env.PAGE_URL ?? "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html";const preset = process.env.QUESTIONS?.split("|").map((q) => q.trim()).filter(Boolean);const bx = new Boxline();type Answer = { answer: string; quote: string; found: boolean };const SCHEMA = {  type: "object",  properties: {    answer: { type: "string", description: "a short answer, from this page only" },    quote: { type: "string", description: "the exact words on the page the answer is based on, copied as they are; empty when the page does not say" },    found: { type: "boolean", description: "false when the page does not answer the question" },  },  required: ["answer", "quote", "found"],};const session = await bx.sessions.create({ timeout: 900, idleTimeout: 300, userMetadata: { example: "chat-with-a-page" } });console.log(`Session: ${session.id}`);const turns: { question: string; answer: string; quote: string; found: boolean }[] = [];let modelUsd = 0;try {  const opened = await session.goto(url, { waitUntil: "load" });  const page = await session.content("markdown");  console.log(`Opened ${page.url} ("${page.title}"), ${page.content.length} characters. Ask about it (type "exit" to stop).\n`);  mkdirSync(out, { recursive: true });  writeFileSync(join(out, "page.md"), page.content);  const rl = preset ? null : createInterface({ input: process.stdin, output: process.stdout });  for (let i = 0; ; i++) {    const question = preset ? preset[i] : (await rl!.question("You: ")).trim();    if (!question || question.toLowerCase() === "exit") break;    if (preset) console.log(`You: ${question}`);    // The chat so far goes into the instruction, so follow-up questions ("and the one after that?") make sense.    const history = turns.map((t) => `Q: ${t.question}\nA: ${t.answer}`).join("\n");    const r = await session.extract<Answer>(      `${history ? `The conversation so far:\n${history}\n\n` : ""}Answer this question about the page: ${question}`,      { schema: SCHEMA },    );    modelUsd += r.usage.costUsd;    if (!r.data.found && !r.data.answer.trim()) r.data.answer = "The page does not say.";    turns.push({ question, ...r.data });    console.log(`AI: ${r.data.answer}${r.data.quote ? `\n    (the page: "${r.data.quote}")` : ""}\n`);  }  rl?.close();  writeFileSync(join(out, "result.json"), JSON.stringify({ url, finalUrl: page.url, title: page.title, status: opened.status, turns, usage: { modelUsd } }, null, 2));  console.log(`${turns.length} answers for $${modelUsd.toFixed(4)} of model use.`);} finally {  await session.stop();}

Inputs

Environment variables the example reads:

  • PAGE_URL: the page to chat about (default https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html).
  • QUESTIONS: questions separated by | to answer without typing (the runner uses this); without it, type your questions and exit to stop.

Run it

Get the examples with git clone https://github.com/Boxline-dev/examples.git and open this one: cd examples/chat-with-a-page. Put your API key in BOXLINE_API_KEY (and BOXLINE_API_URL for another API), then run either version from the example’s folder. Both write to output/ and stop their sessions when they finish.

# Node 18 or newer, in the example's folderexport BOXLINE_API_KEY=…   # your API keynpm install @boxline/sdk tsxnpx tsx node/index.ts

Then check the result, from the same folder: npx tsx check.ts output. It exits with 0 only when the result is right.

What its check proves

check.ts reads what the example wrote and passes only when the result is right, not merely when the example ran.

Every answer the page supports quotes words that are really in page.md; for the default page and the runner's three questions: the price is £51.77, 22 are in stock, and the publisher's phone number is reported as not on the page.

Source

github.com/Boxline-dev/examples/chat-with-a-page on GitHub, with its README.md, check.ts and .env.example. The examples are open source. See all examples.

These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.