LangChain
Two LangChain tools backed by Boxline, given to a LangChain agent (createAgent / create_agent) on your own OpenAI key: boxline_fetch reads a page in a sandboxed browser, and boxline_browser_agent hands a whole browser task (clicking, typing, several pages) to a Boxline agent run.
How it works
tool() from langchain with a zod schema (Node) or @tool from langchain.tools (Python); ChatOpenAI with the Responses API (useResponsesApi: true / use_responses_api=True), which newer OpenAI models need for function tools.
| Needs | any plan (the browser agent tool needs a model on the Boxline API); your own OpenAI key. |
|---|---|
| Site | books.toscrape.com or quotes.toscrape.com, demo sites made for scraping practice. |
| Environment | BOXLINE_API_KEY, OPENAI_API_KEY (your OpenAI key, for the LangChain agent's model), LC_MODEL (the OpenAI model; default gpt-6-luna), QUESTION (your own question). |
| Packages | @boxline/sdk, @langchain/core@1.2.13, @langchain/openai@1.6.0, langchain@1.5.14, tsx@4.23.15, zod@4.4.3, boxline-sdk, langchain==1.4.3, langchain-openai==1.6.6 |
The code
/** * LangChain with Boxline: two LangChain tools backed by Boxline, given to a LangChain agent (createAgent) on your own * OpenAI key: `boxline_fetch` reads a page in a sandboxed browser, and `boxline_browser_agent` hands a whole browser * task (clicking, typing, several pages) to a Boxline agent run. * * npm install && npx tsx index.ts (BOXLINE_API_KEY and OPENAI_API_KEY in the environment; LC_MODEL, QUESTION) * * Writes output/result.json: the answer and every tool call. */import { mkdirSync, writeFileSync } from "node:fs";import { join } from "node:path";import { Boxline } from "@boxline/sdk";import { ChatOpenAI } from "@langchain/openai";import { createAgent, tool } from "langchain";import { z } from "zod";const out = process.env.OUTPUT_DIR ?? "output";const question = process.env.QUESTION ?? 'On books.toscrape.com, what does "Sapiens: A Brief History of Humankind" cost, and how many are in stock?';const bx = new Boxline();const boxlineRuns: { id: string }[] = [];const boxlineFetch = tool( async ({ url }) => { const page = await bx.fetch(url, { format: "markdown", links: true }); return JSON.stringify({ url: page.finalUrl, status: page.status, title: page.title, content: page.content.slice(0, 12_000), links: (page.links ?? []).slice(0, 80) }); }, { name: "boxline_fetch", description: "Open a web page in a real browser (in a Boxline sandbox) and return it as Markdown with its links.", schema: z.object({ url: z.string().describe("an http(s) address") }), },);const boxlineBrowserAgent = tool( async ({ task }) => { const run = await bx.agent.run({ task, maxSteps: 20 }); boxlineRuns.push({ id: run.id }); const done = await bx.agent.wait(run.id, { timeoutMs: 8 * 60_000 }); return done.status === "completed" ? (done.result ?? "") : `The browser agent ${done.status}: ${done.error ?? ""}`; }, { name: "boxline_browser_agent", description: "Give a browser task that needs clicking, typing or several pages to an AI agent with its own browser. Returns its answer.", schema: z.object({ task: z.string().describe("what to do, with the site's address") }), },);const agent = createAgent({ // The Responses API: newer OpenAI models take function tools only there. model: new ChatOpenAI({ model: process.env.LC_MODEL ?? "gpt-6-luna", useResponsesApi: true }), tools: [boxlineFetch, boxlineBrowserAgent], systemPrompt: "Answer from pages you read with the tools. Prefer boxline_fetch; use boxline_browser_agent only when a task needs clicking or typing.",});const result = await agent.invoke({ messages: [{ role: "user", content: question }] });const calls = result.messages.flatMap((m: any) => (m.tool_calls ?? []).map((c: any) => ({ tool: c.name, input: c.args })));const answer = result.messages.at(-1)?.text ?? ""; // .text joins the message's text blocksfor (const c of calls) console.log(`→ ${c.tool} ${JSON.stringify(c.input)}`);console.log(`\n${answer}`);mkdirSync(out, { recursive: true });writeFileSync(join(out, "result.json"), JSON.stringify({ question, answer, calls, agentRuns: boxlineRuns }, null, 2));"""LangChain with Boxline: two LangChain tools backed by Boxline, given to a LangChain agent (create_agent) on yourown OpenAI key: `boxline_fetch` reads a page in a sandboxed browser, and `boxline_browser_agent` hands a whole browsertask (clicking, typing, several pages) to a Boxline agent run. pip install -r requirements.txt && python main.py (BOXLINE_API_KEY and OPENAI_API_KEY; LC_MODEL, QUESTION)Writes output/result.json: the answer and every tool call."""import jsonimport osfrom pathlib import Pathfrom langchain.agents import create_agentfrom langchain.tools import toolfrom langchain_openai import ChatOpenAIfrom boxline import Boxlineout = Path(os.environ.get("OUTPUT_DIR", "output"))question = os.environ.get("QUESTION", 'On books.toscrape.com, what does "Sapiens: A Brief History of Humankind" cost, and how many are in stock?')bx = Boxline()boxline_runs = []@tooldef boxline_fetch(url: str) -> str: """Open a web page in a real browser (in a Boxline sandbox) and return it as Markdown with its links.""" page = bx.fetch(url, format="markdown", links=True) return json.dumps({"url": page["finalUrl"], "status": page["status"], "title": page["title"], "content": page["content"][:12_000], "links": (page.get("links") or [])[:80]})@tooldef boxline_browser_agent(task: str) -> str: """Give a browser task that needs clicking, typing or several pages to an AI agent with its own browser. Returns its answer.""" run = bx.agent.run(task, max_steps=20) boxline_runs.append({"id": run["id"]}) done = bx.agent.wait(run["id"], timeout=480) if done["status"] != "completed": return f"The browser agent {done['status']}: {done.get('error') or ''}" return done["result"] or ""agent = create_agent( # The Responses API: newer OpenAI models take function tools only there. ChatOpenAI(model=os.environ.get("LC_MODEL", "gpt-6-luna"), use_responses_api=True), tools=[boxline_fetch, boxline_browser_agent], system_prompt="Answer from pages you read with the tools. Prefer boxline_fetch; use boxline_browser_agent only when a task needs clicking or typing.",)result = agent.invoke({"messages": [{"role": "user", "content": question}]})calls = [{"tool": c["name"], "input": c["args"]} for m in result["messages"] for c in (getattr(m, "tool_calls", None) or [])]answer = result["messages"][-1].text # joins the message's text blocksfor c in calls: print(f"-> {c['tool']} {json.dumps(c['input'])}")print(f"\n{answer}")out.mkdir(parents=True, exist_ok=True)(out / "result.json").write_text(json.dumps({"question": question, "answer": answer, "calls": calls, "agentRuns": boxline_runs}, indent=2, ensure_ascii=False))Install and run
The packages are pinned at the versions the example’s check ran with. It writes to output/ and stops its session when it finishes.
# Node 22 or newerexport BOXLINE_API_KEY=… # your Boxline API keyexport OPENAI_API_KEY=… # your own OpenAI keynpm install @boxline/sdk @langchain/core@1.2.13 @langchain/openai@1.6.0 langchain@1.5.14 tsx@4.23.15 zod@4.4.3npx tsx index.ts# Python 3.10 or newerexport BOXLINE_API_KEY=… # your Boxline API keyexport OPENAI_API_KEY=… # your own OpenAI keypip install boxline-sdk langchain==1.4.3 langchain-openai==1.6.6python main.pyWhat its check proves
It checks that the LangChain agent called a Boxline tool and answered with the price and stock the demo shop shows. To run it from a clone of the examples (git clone https://github.com/Boxline-dev/examples.git), go to examples/integrations/langchain, run the commands above, then check the result from the same folder: npx tsx check.ts output.
Source: github.com/Boxline-dev/examples/integrations/langchain on GitHub. See all integrations.
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.