From 56849e71e9df1dd7ec11ddf75613173d101b9831 Mon Sep 17 00:00:00 2001 From: James Liounis Date: Wed, 29 Apr 2026 23:06:59 +0000 Subject: [PATCH] feat: add Perplexity Search API example (Python + TypeScript) Adds two examples in examples/perplexity-search-{js,python} demonstrating how to call the Perplexity Search API and analyze results inside an E2B sandbox. - Auth via PERPLEXITY_API_KEY (PPLX_API_KEY also accepted) - Helper supports query, max_results, search_domain_filter, search_recency_filter - Sandbox loads results.json and prints top domains + snippets - Wired into tests/examples.test.ts; PERPLEXITY_API_KEY added to tests/utils.ts env passthrough - README updated with links to both examples --- README.md | 1 + examples/perplexity-search-js/.env.template | 2 + examples/perplexity-search-js/README.md | 49 ++ examples/perplexity-search-js/index.ts | 104 +++ .../perplexity-search-js/package-lock.json | 688 ++++++++++++++++++ examples/perplexity-search-js/package.json | 27 + examples/perplexity-search-js/tsconfig.json | 15 + .../perplexity-search-python/.env.template | 2 + examples/perplexity-search-python/README.md | 50 ++ .../perplexity_e2b_search/__init__.py | 0 .../perplexity_e2b_search/main.py | 114 +++ .../perplexity-search-python/pyproject.toml | 20 + tests/examples.test.ts | 2 + tests/utils.ts | 1 + 14 files changed, 1075 insertions(+) create mode 100644 examples/perplexity-search-js/.env.template create mode 100644 examples/perplexity-search-js/README.md create mode 100644 examples/perplexity-search-js/index.ts create mode 100644 examples/perplexity-search-js/package-lock.json create mode 100644 examples/perplexity-search-js/package.json create mode 100644 examples/perplexity-search-js/tsconfig.json create mode 100644 examples/perplexity-search-python/.env.template create mode 100644 examples/perplexity-search-python/README.md create mode 100644 examples/perplexity-search-python/perplexity_e2b_search/__init__.py create mode 100644 examples/perplexity-search-python/perplexity_e2b_search/main.py create mode 100644 examples/perplexity-search-python/pyproject.toml diff --git a/README.md b/README.md index 5751550b..55193cb2 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,7 @@ Read more about E2B on the [E2B website](https://e2b.dev) and the official [E2B - Upload dataset and analyze it with Llama 3 - [Python](./examples/upload-dataset-code-interpreter) - Scrape Airbnb and analyze data with Claude 3 Opus and Firecrawl - [TypeScript](./examples/firecrawl-scrape-and-analyze-airbnb-data) - Visualize website topics with Claude 3.5 Sonnet and Firecrawl - [Python](./examples/claude-visualize-website-topics) +- Search the web with the Perplexity Search API and analyze results in a sandbox - [Python](./examples/perplexity-search-python) / [TypeScript](./examples/perplexity-search-js) - Next.js app with LLM + Code Interpreter and streaming - [TypeScript](./examples/nextjs-code-interpreter) - How to run a Docker container in E2B - [Python/TypeScript](./examples/docker-in-e2b) - How to run Playwright in E2B - [TypeScript](./examples/playwright-in-e2b) diff --git a/examples/perplexity-search-js/.env.template b/examples/perplexity-search-js/.env.template new file mode 100644 index 00000000..4862809b --- /dev/null +++ b/examples/perplexity-search-js/.env.template @@ -0,0 +1,2 @@ +E2B_API_KEY=your_e2b_api_key +PERPLEXITY_API_KEY=your_perplexity_api_key diff --git a/examples/perplexity-search-js/README.md b/examples/perplexity-search-js/README.md new file mode 100644 index 00000000..604ce3bd --- /dev/null +++ b/examples/perplexity-search-js/README.md @@ -0,0 +1,49 @@ +# Perplexity Search + E2B Sandbox (TypeScript) + +Use the [Perplexity Search API](https://docs.perplexity.ai/docs/search/quickstart) to fetch web sources, then analyze them inside an [E2B Sandbox](https://e2b.dev) with Python. + +## What it does + +1. Calls `POST https://api.perplexity.ai/search` with a query and optional filters (`max_results`, `search_domain_filter`, `search_recency_filter`). +2. Spawns an E2B sandbox and writes the JSON results to a file inside it. +3. Runs Python in the sandbox to count top domains and print result summaries. + +## Prerequisites + +- Node.js 18+ +- An [E2B API key](https://e2b.dev/dashboard?tab=keys) +- A [Perplexity API key](https://www.perplexity.ai/account/api/keys) + +## Setup + +```bash +cp .env.template .env +# fill in E2B_API_KEY and PERPLEXITY_API_KEY (PPLX_API_KEY is also accepted) +npm install +``` + +## Run + +```bash +npm start +# or with a custom query: +npm start -- "OpenAI DevDay 2026 announcements" +``` + +## Search options + +The `perplexitySearch` helper in `index.ts` supports: + +| Field | Type | Notes | +| ----------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------- | +| `query` | `string` | Required. | +| `max_results` | `number` | Default 10. Set explicitly to bound results. | +| `search_domain_filter` | `string[]` | Allowlist (`"nytimes.com"`) **or** denylist (`"-pinterest.com"`). Don't mix the two. | +| `search_recency_filter` | `"hour" \| "day" \| "week" \| "month" \| "year"` | Restrict results to a recent time window. | + +## API reference + +- Search quickstart: https://docs.perplexity.ai/docs/search/quickstart +- Search API reference: https://docs.perplexity.ai/api-reference/search-post +- Domain filter docs: https://docs.perplexity.ai/docs/search/filters/domain-filter +- Date / recency filter docs: https://docs.perplexity.ai/docs/search/filters/date-time-filters diff --git a/examples/perplexity-search-js/index.ts b/examples/perplexity-search-js/index.ts new file mode 100644 index 00000000..787dd6ac --- /dev/null +++ b/examples/perplexity-search-js/index.ts @@ -0,0 +1,104 @@ +import 'dotenv/config' +import { Sandbox } from '@e2b/code-interpreter' + +const PERPLEXITY_API_KEY = + process.env.PERPLEXITY_API_KEY || process.env.PPLX_API_KEY + +if (!PERPLEXITY_API_KEY) { + throw new Error( + 'Set PERPLEXITY_API_KEY (or PPLX_API_KEY). Get one at https://www.perplexity.ai/account/api/keys' + ) +} + +type SearchOptions = { + query: string + max_results?: number + search_domain_filter?: string[] + search_recency_filter?: 'hour' | 'day' | 'week' | 'month' | 'year' +} + +type SearchResult = { + title: string + url: string + snippet: string + date?: string +} + +async function perplexitySearch(opts: SearchOptions): Promise { + const body: Record = { query: opts.query } + if (opts.max_results !== undefined) body.max_results = opts.max_results + if (opts.search_domain_filter) + body.search_domain_filter = opts.search_domain_filter + if (opts.search_recency_filter) + body.search_recency_filter = opts.search_recency_filter + + const res = await fetch('https://api.perplexity.ai/search', { + method: 'POST', + headers: { + Authorization: `Bearer ${PERPLEXITY_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }) + + if (!res.ok) { + const text = await res.text() + throw new Error(`Perplexity Search API ${res.status}: ${text}`) + } + + const data = (await res.json()) as { results?: SearchResult[] } + return data.results ?? [] +} + +async function main() { + const query = + process.argv.slice(2).join(' ') || 'recent breakthroughs in quantum error correction' + + console.log(`Searching Perplexity for: ${query}`) + const results = await perplexitySearch({ + query, + max_results: 8, + search_recency_filter: 'month', + }) + console.log(`Got ${results.length} results from Perplexity Search.`) + + console.log('Spawning E2B sandbox to summarize results in-sandbox...') + const sandbox = await Sandbox.create() + try { + await sandbox.files.write( + '/home/user/results.json', + JSON.stringify(results, null, 2) + ) + + const execution = await sandbox.runCode(` +import json +from collections import Counter +from urllib.parse import urlparse + +with open("/home/user/results.json") as f: + results = json.load(f) + +print(f"Loaded {len(results)} results") +domains = Counter(urlparse(r["url"]).netloc for r in results) +print("Top domains:") +for domain, count in domains.most_common(5): + print(f" {domain}: {count}") + +print("\\nTop result snippets:") +for r in results[:3]: + print(f"- {r['title']}\\n {r['url']}\\n {r.get('snippet', '')[:160]}\\n") +`) + + console.log(execution.logs.stdout.join('')) + if (execution.logs.stderr.length) { + console.error(execution.logs.stderr.join('')) + } + } finally { + await sandbox.kill() + } +} + +main().catch((err) => { + console.error(err) + process.exit(1) +}) diff --git a/examples/perplexity-search-js/package-lock.json b/examples/perplexity-search-js/package-lock.json new file mode 100644 index 00000000..cd7556ac --- /dev/null +++ b/examples/perplexity-search-js/package-lock.json @@ -0,0 +1,688 @@ +{ + "name": "perplexity-search-js", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "perplexity-search-js", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@e2b/code-interpreter": "^1.0.1", + "dotenv": "^16.4.5" + }, + "devDependencies": { + "@types/node": "^20.16.6", + "tsx": "^4.7.3", + "typescript": "^5.4.5" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.12.0.tgz", + "integrity": "sha512-B/XlCaFIP8LOwzo+bz5uFzATYokcwCKQcghqnlfwSmM5eX/qTkvDBnDPs+gXtX/RyjxJ4DRikECcPJbyALA8FA==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@connectrpc/connect": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.0.0-rc.3.tgz", + "integrity": "sha512-ARBt64yEyKbanyRETTjcjJuHr2YXorzQo0etyS5+P6oSeW8xEuzajA9g+zDnMcj1hlX2dQE93foIWQGfpru7gQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^2.2.0" + } + }, + "node_modules/@connectrpc/connect-web": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-2.0.0-rc.3.tgz", + "integrity": "sha512-w88P8Lsn5CCsA7MFRl2e6oLY4J/5toiNtJns/YJrlyQaWOy3RO8pDgkz+iIkG98RPMhj2thuBvsd3Cn4DKKCkw==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^2.2.0", + "@connectrpc/connect": "2.0.0-rc.3" + } + }, + "node_modules/@e2b/code-interpreter": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@e2b/code-interpreter/-/code-interpreter-1.5.1.tgz", + "integrity": "sha512-mkyKjAW2KN5Yt0R1I+1lbH3lo+W/g/1+C2lnwlitXk5wqi/g94SEO41XKdmDf5WWpKG3mnxWDR5d6S/lyjmMEw==", + "license": "MIT", + "dependencies": { + "e2b": "^1.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "license": "MIT" + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/e2b": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/e2b/-/e2b-1.13.2.tgz", + "integrity": "sha512-m8acE/MzMAJo1A57DakR2X1Sl5Mt1tcQO2aJfygNaQHLXby/4xsjF0UeJUB70jF7xntiR41pAMbZEHnkzrT9tw==", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "^2.6.2", + "@connectrpc/connect": "2.0.0-rc.3", + "@connectrpc/connect-web": "2.0.0-rc.3", + "compare-versions": "^6.1.0", + "openapi-fetch": "^0.9.7", + "platform": "^1.3.6" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/openapi-fetch": { + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.9.8.tgz", + "integrity": "sha512-zM6elH0EZStD/gSiNlcPrzXcVQ/pZo3BDvC6CDwRDUt1dDzxlshpmQnpD6cZaJ39THaSmwVCxxRrPKNM1hHrDg==", + "license": "MIT", + "dependencies": { + "openapi-typescript-helpers": "^0.0.8" + } + }, + "node_modules/openapi-typescript-helpers": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.8.tgz", + "integrity": "sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g==", + "license": "MIT" + }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "license": "MIT" + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/perplexity-search-js/package.json b/examples/perplexity-search-js/package.json new file mode 100644 index 00000000..53162bd1 --- /dev/null +++ b/examples/perplexity-search-js/package.json @@ -0,0 +1,27 @@ +{ + "name": "perplexity-search-js", + "version": "1.0.0", + "description": "Use the Perplexity Search API together with E2B Sandbox to gather and analyze sources.", + "main": "index.ts", + "type": "module", + "scripts": { + "start": "tsx index.ts", + "build": "tsc --noEmit" + }, + "dependencies": { + "@e2b/code-interpreter": "^1.0.1", + "dotenv": "^16.4.5" + }, + "devDependencies": { + "@types/node": "^20.16.6", + "tsx": "^4.7.3", + "typescript": "^5.4.5" + }, + "keywords": [ + "e2b", + "perplexity", + "search", + "sandbox" + ], + "license": "MIT" +} diff --git a/examples/perplexity-search-js/tsconfig.json b/examples/perplexity-search-js/tsconfig.json new file mode 100644 index 00000000..c724d4ce --- /dev/null +++ b/examples/perplexity-search-js/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "allowImportingTsExtensions": false, + "noEmit": true, + "lib": ["ES2022", "DOM"] + }, + "include": ["index.ts"] +} diff --git a/examples/perplexity-search-python/.env.template b/examples/perplexity-search-python/.env.template new file mode 100644 index 00000000..4862809b --- /dev/null +++ b/examples/perplexity-search-python/.env.template @@ -0,0 +1,2 @@ +E2B_API_KEY=your_e2b_api_key +PERPLEXITY_API_KEY=your_perplexity_api_key diff --git a/examples/perplexity-search-python/README.md b/examples/perplexity-search-python/README.md new file mode 100644 index 00000000..9d38dcd2 --- /dev/null +++ b/examples/perplexity-search-python/README.md @@ -0,0 +1,50 @@ +# Perplexity Search + E2B Sandbox (Python) + +Use the [Perplexity Search API](https://docs.perplexity.ai/docs/search/quickstart) to fetch web sources, then analyze them inside an [E2B Sandbox](https://e2b.dev) with Python. + +## What it does + +1. Calls `POST https://api.perplexity.ai/search` with a query and optional filters (`max_results`, `search_domain_filter`, `search_recency_filter`). +2. Spawns an E2B sandbox, writes the JSON results to a file inside it, and runs analysis Python. +3. Prints the top domains and result snippets. + +## Prerequisites + +- Python 3.10+ +- [Poetry](https://python-poetry.org/) +- An [E2B API key](https://e2b.dev/dashboard?tab=keys) +- A [Perplexity API key](https://www.perplexity.ai/account/api/keys) + +## Setup + +```bash +cp .env.template .env +# fill in E2B_API_KEY and PERPLEXITY_API_KEY (PPLX_API_KEY is also accepted) +poetry install +``` + +## Run + +```bash +poetry run start +# or with a custom query: +poetry run start "OpenAI DevDay 2026 announcements" +``` + +## Search options + +The `perplexity_search` helper in `perplexity_e2b_search/main.py` supports: + +| Argument | Type | Notes | +| ----------------------- | ---------------- | -------------------------------------------------------------------------------------- | +| `query` | `str` | Required. | +| `max_results` | `int \| None` | Default 10. Set explicitly to bound results. | +| `search_domain_filter` | `list[str]` | Allowlist (`"nytimes.com"`) **or** denylist (`"-pinterest.com"`). Don't mix the two. | +| `search_recency_filter` | `str \| None` | One of `hour`, `day`, `week`, `month`, `year`. | + +## API reference + +- Search quickstart: https://docs.perplexity.ai/docs/search/quickstart +- Search API reference: https://docs.perplexity.ai/api-reference/search-post +- Domain filter docs: https://docs.perplexity.ai/docs/search/filters/domain-filter +- Date / recency filter docs: https://docs.perplexity.ai/docs/search/filters/date-time-filters diff --git a/examples/perplexity-search-python/perplexity_e2b_search/__init__.py b/examples/perplexity-search-python/perplexity_e2b_search/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/perplexity-search-python/perplexity_e2b_search/main.py b/examples/perplexity-search-python/perplexity_e2b_search/main.py new file mode 100644 index 00000000..f702ddd8 --- /dev/null +++ b/examples/perplexity-search-python/perplexity_e2b_search/main.py @@ -0,0 +1,114 @@ +"""Perplexity Search + E2B Sandbox example. + +Calls the Perplexity Search API for web results, then analyzes them inside an +E2B sandbox. + +Run: ``poetry run start`` or ``poetry run start "your custom query"``. +""" + +from __future__ import annotations + +import json +import os +import sys +from typing import Any + +import httpx +from dotenv import load_dotenv +from e2b_code_interpreter import Sandbox + +load_dotenv() + +PERPLEXITY_API_KEY = os.environ.get("PERPLEXITY_API_KEY") or os.environ.get( + "PPLX_API_KEY" +) + + +def perplexity_search( + query: str, + *, + max_results: int | None = None, + search_domain_filter: list[str] | None = None, + search_recency_filter: str | None = None, +) -> list[dict[str, Any]]: + """Call ``POST https://api.perplexity.ai/search`` and return ``results``. + + Filter rules: + - ``search_domain_filter`` may be an allowlist (``"nytimes.com"``) **or** a + denylist (``"-pinterest.com"``). Never mix the two in one call. + - ``search_recency_filter`` is one of ``hour|day|week|month|year``. + """ + if not PERPLEXITY_API_KEY: + raise RuntimeError( + "Set PERPLEXITY_API_KEY (or PPLX_API_KEY). " + "Get one at https://www.perplexity.ai/account/api/keys" + ) + + body: dict[str, Any] = {"query": query} + if max_results is not None: + body["max_results"] = max_results + if search_domain_filter is not None: + body["search_domain_filter"] = search_domain_filter + if search_recency_filter is not None: + body["search_recency_filter"] = search_recency_filter + + resp = httpx.post( + "https://api.perplexity.ai/search", + headers={ + "Authorization": f"Bearer {PERPLEXITY_API_KEY}", + "Content-Type": "application/json", + }, + json=body, + timeout=60.0, + ) + resp.raise_for_status() + return resp.json().get("results", []) + + +def main() -> None: + query = " ".join(sys.argv[1:]) or "recent breakthroughs in quantum error correction" + + print(f"Searching Perplexity for: {query}") + results = perplexity_search( + query, + max_results=8, + search_recency_filter="month", + ) + print(f"Got {len(results)} results from Perplexity Search.") + + print("Spawning E2B sandbox to summarize results in-sandbox...") + with Sandbox() as sbx: + sbx.files.write("/home/user/results.json", json.dumps(results, indent=2)) + + execution = sbx.run_code( + """ +import json +from collections import Counter +from urllib.parse import urlparse + +with open("/home/user/results.json") as f: + results = json.load(f) + +print(f"Loaded {len(results)} results") +domains = Counter(urlparse(r["url"]).netloc for r in results) +print("Top domains:") +for domain, count in domains.most_common(5): + print(f" {domain}: {count}") + +print("\\nTop result snippets:") +for r in results[:3]: + print(f"- {r['title']}") + print(f" {r['url']}") + print(f" {(r.get('snippet') or '')[:160]}") + print() +""" + ) + + for line in execution.logs.stdout: + print(line, end="") + for line in execution.logs.stderr: + print(line, end="", file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/examples/perplexity-search-python/pyproject.toml b/examples/perplexity-search-python/pyproject.toml new file mode 100644 index 00000000..ed505b2d --- /dev/null +++ b/examples/perplexity-search-python/pyproject.toml @@ -0,0 +1,20 @@ +[tool.poetry] +name = "perplexity-e2b-search" +version = "0.1.0" +description = "Perplexity Search API + E2B sandbox example" +authors = ["E2B Cookbook contributors"] +readme = "README.md" +package-mode = false + +[tool.poetry.dependencies] +python = "^3.10" +e2b-code-interpreter = "^1.1.1" +python-dotenv = "^1.0.1" +httpx = "^0.27.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +start = "perplexity_e2b_search.main:main" diff --git a/tests/examples.test.ts b/tests/examples.test.ts index c9dd3dbc..ce854f6e 100644 --- a/tests/examples.test.ts +++ b/tests/examples.test.ts @@ -39,6 +39,8 @@ const scripts = [ { name: 'mcp-browserbase-js', interpreter: 'npm', file: './examples/mcp-browserbase-js/' }, { name: 'mcp-groq-exa-js', interpreter: 'npm', file: './examples/mcp-groq-exa-js/' }, { name: 'sandbox-agent-sdk-js', interpreter: 'npm', file: './examples/sandbox-agent-sdk-js/' }, + { name: 'perplexity-search-js', interpreter: 'npm', file: './examples/perplexity-search-js/' }, + { name: 'perplexity-search-python', interpreter: 'poetry', file: './examples/perplexity-search-python/' }, ]; // We don't have integration tests for NextJS yet: diff --git a/tests/utils.ts b/tests/utils.ts index cd1bb1da..bde5f4c0 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -16,6 +16,7 @@ export function getApiKeys(): Record { WATSONX_PROJECT_ID: process.env.WATSONX_PROJECT_ID || '', WATSONX_URL: process.env.WATSONX_URL || '', WATSONX_API_KEY: process.env.WATSONX_API_KEY || '', + PERPLEXITY_API_KEY: process.env.PERPLEXITY_API_KEY || '', }; }