PDF to summary
Download a PDF in the shell, turn it into text with pdftotext, and have a model sum it up in 5 bullet points from the session's browser.
When to use it: You want a short summary of a long PDF.
| Uses | exec (curl, pdftotext), files.readText, the evaluate and extract actions |
|---|---|
| Needs | a plan with shell sessions and a model on the API |
| Site | arXiv ("Attention Is All You Need", 1706.03762) |
| Output | output/document.txt and output/summary.md |
| Languages | Node and Python |
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.
/** * PDF to summary: download a PDF in the session's shell, turn it into text with pdftotext, and have a model sum it up * in 5 bullet points from the session's browser. The default is "Attention Is All You Need" on arXiv. * * npx tsx node/index.ts (BOXLINE_API_KEY; PDF_URL for another PDF; a plan with shell sessions) * * Writes output/document.txt and output/summary.md. */import { mkdirSync, writeFileSync } from "node:fs";import { join } from "node:path";import { Boxline } from "@boxline/sdk";const out = process.env.OUTPUT_DIR ?? "output";const pdfUrl = process.env.PDF_URL ?? "https://arxiv.org/pdf/1706.03762";const bx = new Boxline();const session = await bx.sessions.create({ shell: true, timeout: 600, userMetadata: { example: "pdf-to-summary" } });console.log(`Session: ${session.id}`);try { // 1. The shell: download the PDF and turn it into text (poppler's pdftotext is installed). const r = await session.exec( 'mkdir -p downloads output && curl -sSfL --max-time 120 -o downloads/document.pdf "$PDF_URL" && pdftotext -layout downloads/document.pdf output/document.txt && wc -c < downloads/document.pdf', { env: { PDF_URL: pdfUrl }, timeoutMs: 300_000 }, ); if (r.exitCode !== 0) throw new Error(`the download or pdftotext failed: ${r.stderr.trim()}`); const pdfBytes = Number(r.stdout.trim()); const text = await session.files.readText("output/document.txt"); console.log(`${Math.round(pdfBytes / 1024)} KB PDF → ${text.length} characters of text`); // 2. The browser shows the text (the first 40,000 characters), and a model summarises the page. await session.evaluate( `document.title = "document.txt"; document.body.innerHTML = "<pre></pre>"; document.querySelector("pre").textContent = ${JSON.stringify(text.slice(0, 40_000))}; true`, ); const { data, model, usage } = await session.extract<{ title: string; bullets: string[] }>("The document's title, and a summary of the document in exactly 5 short bullet points.", { schema: { type: "object", properties: { title: { type: "string" }, bullets: { type: "array", items: { type: "string" }, minItems: 5, maxItems: 5 } }, required: ["title", "bullets"] }, }); const summary = `# ${data.title}\n\n${data.bullets.map((b) => `- ${b}`).join("\n")}\n`; console.log(`\n${summary}`); mkdirSync(out, { recursive: true }); writeFileSync(join(out, "document.txt"), text); writeFileSync(join(out, "summary.md"), summary); writeFileSync(join(out, "result.json"), JSON.stringify({ pdfUrl, pdfBytes, textChars: text.length, title: data.title, bullets: data.bullets, model, usage: { modelUsd: usage.costUsd } }, null, 2));} finally { await session.stop();}"""PDF to summary: download a PDF in the session's shell, turn it into text with pdftotext, and have a model sum itup in 5 bullet points from the session's browser. The default is "Attention Is All You Need" on arXiv. python python/main.py (BOXLINE_API_KEY; PDF_URL for another PDF; a plan with shell sessions)Writes output/document.txt and output/summary.md."""import jsonimport osfrom pathlib import Pathfrom boxline import Boxlineout = Path(os.environ.get("OUTPUT_DIR", "output"))pdf_url = os.environ.get("PDF_URL", "https://arxiv.org/pdf/1706.03762")bx = Boxline()with bx.sessions.create(shell=True, timeout=600, user_metadata={"example": "pdf-to-summary"}) as session: print(f"Session: {session.id}", flush=True) # 1. The shell: download the PDF and turn it into text (poppler's pdftotext is installed). r = session.exec( 'mkdir -p downloads output && curl -sSfL --max-time 120 -o downloads/document.pdf "$PDF_URL" ' "&& pdftotext -layout downloads/document.pdf output/document.txt && wc -c < downloads/document.pdf", env={"PDF_URL": pdf_url}, timeout_ms=300_000, ) if r["exitCode"] != 0: raise SystemExit(f"the download or pdftotext failed: {r['stderr'].strip()}") pdf_bytes = int(r["stdout"].strip()) text = session.files.read_text("output/document.txt") print(f"{pdf_bytes // 1024} KB PDF -> {len(text)} characters of text") # 2. The browser shows the text (the first 40,000 characters), and a model summarises the page. session.evaluate( 'document.title = "document.txt"; document.body.innerHTML = "<pre></pre>"; ' f'document.querySelector("pre").textContent = {json.dumps(text[:40_000])}; true' ) r = session.extract( "The document's title, and a summary of the document in exactly 5 short bullet points.", schema={ "type": "object", "properties": {"title": {"type": "string"}, "bullets": {"type": "array", "items": {"type": "string"}, "minItems": 5, "maxItems": 5}}, "required": ["title", "bullets"], }, ) data = r["data"] summary = f"# {data['title']}\n\n" + "\n".join(f"- {b}" for b in data["bullets"]) + "\n" print("\n" + summary) out.mkdir(parents=True, exist_ok=True) (out / "document.txt").write_text(text) (out / "summary.md").write_text(summary) result = {"pdfUrl": pdf_url, "pdfBytes": pdf_bytes, "textChars": len(text), "title": data["title"], "bullets": data["bullets"], "model": r["model"], "usage": {"modelUsd": r["usage"]["costUsd"]}} (out / "result.json").write_text(json.dumps(result, indent=2, ensure_ascii=False))Inputs
Environment variables the example reads:
PDF_URL: the PDF (defaulthttps://arxiv.org/pdf/1706.03762).
Run it
Get the examples with git clone https://github.com/Boxline-dev/examples.git and open this one: cd examples/pdf-to-summary. 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# Python 3.9 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keypip install boxline-sdkpython python/main.pyThen 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.
The text is the paper, and the summary has 5 bullet points about the Transformer and attention.
Source
github.com/Boxline-dev/examples/pdf-to-summary 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.