Add OpenSERP SDK usage examples

This commit is contained in:
Rustem Kamalov
2026-06-09 04:14:35 +03:00
parent 11d02731b6
commit ffd3d4250c
35 changed files with 627 additions and 2 deletions

View File

@@ -69,9 +69,9 @@ Once the server is running, the interactive docs are available locally:
To browse the spec without running the server, see [docs/openapi.yaml](./docs/openapi.yaml). For a higher-level overview of how OpenSERP works internally, see the [architecture docs](https://openserp.org/docs/architecture/).
## SDKs & Integrations
## SDKs & Examples
Official client packages. Each works against your self-hosted server (point it at the server's base URL):
Official client packages. Each works against your self-hosted server (set `baseUrl`) or the [hosted API](https://openserp.org/cloud) (set `apiKey`):
| Type | Package | Install |
| --------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------- |
@@ -80,6 +80,8 @@ Official client packages. Each works against your self-hosted server (point it a
| MCP server (AI agents) | [`@openserp/mcp`](https://www.npmjs.com/package/@openserp/mcp) | `npx @openserp/mcp` |
| n8n community node | [`@openserp/n8n-nodes-openserp`](https://www.npmjs.com/package/@openserp/n8n-nodes-openserp) | Install via n8n community nodes |
See [**examples**](./examples) for small JavaScript and Python use cases covering search, AI grounding, SEO, content extraction, and image search.
```js
import { OpenSERP } from "@openserp/sdk";

90
examples/README.md Normal file
View File

@@ -0,0 +1,90 @@
# OpenSERP Examples
Short, copy-pasteable examples for using OpenSERP from JavaScript and Python.
Each one runs against a local server by default and works the same against the
[hosted API](#using-the-hosted-api) by swapping in an API key.
## Start a server
Most examples expect OpenSERP running on `http://localhost:7000`.
```bash
# Docker
docker run -p 127.0.0.1:7000:7000 -it karust/openserp serve -a 0.0.0.0 -p 7000
# Or from source
go build -o openserp . && ./openserp serve
```
Prefer not to run a server? Skip ahead to [Using the hosted API](#using-the-hosted-api).
## Try it without code
A search is a single HTTP GET, so you can check the server with `curl`:
```bash
# One engine
curl "http://localhost:7000/google/search?text=open+source+search+api&limit=10"
# Several engines at once
curl "http://localhost:7000/mega/search?text=open+source+search+api&engines=bing,duckduckgo&limit=10"
```
## SDKs and integrations
| Tool | Package | Install |
| --- | --- | --- |
| JavaScript / TypeScript | [`@openserp/sdk`](https://www.npmjs.com/package/@openserp/sdk) | `npm install @openserp/sdk` |
| Python | [`openserp`](https://pypi.org/project/openserp/) | `pip install openserp` |
| MCP server (AI agents) | [`@openserp/mcp`](https://www.npmjs.com/package/@openserp/mcp) | `npx @openserp/mcp` |
| n8n community node | [`@openserp/n8n-nodes-openserp`](https://www.npmjs.com/package/@openserp/n8n-nodes-openserp) | Install via n8n community nodes |
## Examples by question
### Getting started
- **How do I run a search from code?** — [JavaScript](quickstart/js-basic-search) · [Python](quickstart/python-basic-search)
- **How do I compare results across several engines?** — [JavaScript](search/js-multi-engine-compare)
- **How do I search a list of keywords and export them?** — [Python](search/python-keyword-csv)
### AI and LLM grounding
- **How do I ground an LLM answer in fresh search results?** — [JavaScript](ai/js-rag-context-builder)
- **How do I give a search tool to an agent?** — [Python](ai/python-agent-tool)
- **I want this inside Claude, Cursor, or another MCP client.** — [`@openserp/mcp`](https://www.npmjs.com/package/@openserp/mcp)
### SEO
- **Who are my real competitors for a set of keywords?** — [JavaScript](seo/js-competitor-overlap)
- **Where does my domain rank for each keyword?** — [Python](seo/python-rank-tracker)
### Content extraction
- **How do I search and read the page content in one call?** — [JavaScript](content/js-search-with-extract)
- **How do I turn a URL into clean Markdown?** — [Python](content/python-extract-markdown)
### Images
- **How do I search images and preview them?** — [JavaScript](media/js-image-gallery)
### Automation
- **I want to wire OpenSERP into a no-code workflow.** — [`@openserp/n8n-nodes-openserp`](https://www.npmjs.com/package/@openserp/n8n-nodes-openserp)
## Using the hosted API
Every example points at a local server. To use the managed API instead, get a key
from [openserp.org/dashboard/keys](https://openserp.org/dashboard/keys) and construct
the client with it — no base URL needed:
```js
const client = new OpenSERP({ apiKey: "<YOUR_API_TOKEN>" });
```
```python
client = OpenSERP(api_key="<YOUR_API_TOKEN>")
```
The endpoints and response shape are identical, so example code moves between
self-hosted and hosted by changing only that one line. Set **either** `baseUrl`
(self-hosted) **or** `apiKey` (hosted) — not both.

View File

@@ -0,0 +1,11 @@
# RAG context builder (JavaScript)
Searches for a question and formats the top results into a ready-to-use prompt
with numbered, citable sources — the retrieval step of a RAG pipeline.
```bash
npm install
node index.js
```
Edit the `question` in [index.js](index.js), then pass the printed prompt to your LLM.

View File

@@ -0,0 +1,27 @@
import { OpenSERP } from "@openserp/sdk";
// Hosted API instead? Get a key at https://openserp.org/dashboard/keys:
// const client = new OpenSERP({ apiKey: "<YOUR_API_TOKEN>" });
const client = new OpenSERP({ baseUrl: "http://localhost:7000" });
const question = "What is retrieval augmented generation?";
const { results } = await client.search({
engine: "google",
text: question,
limit: 10,
});
// Format the top results as numbered, citable context to drop into an LLM prompt.
const context = results
.map((r, i) => `[${i + 1}] ${r.title}\n${r.url}\n${r.snippet ?? ""}`)
.join("\n\n");
const prompt = `Answer the question using only the search results below. Cite sources as [n].
Question: ${question}
Search results:
${context}`;
console.log(prompt);

View File

@@ -0,0 +1,14 @@
{
"name": "openserp-example-js-rag-context-builder",
"private": true,
"type": "module",
"scripts": {
"start": "node index.js"
},
"engines": {
"node": ">=18"
},
"dependencies": {
"@openserp/sdk": "^0.2.0"
}
}

View File

@@ -0,0 +1,11 @@
# Agent search tool (Python)
A `web_search(query)` function shaped to register as a tool for an LLM agent.
It returns compact JSON-serializable results the model can read and cite.
```bash
pip install -r requirements.txt
python main.py
```
Import `web_search` from [main.py](main.py) and register it with your agent framework.

View File

@@ -0,0 +1,29 @@
import json
from openserp import OpenSERP
# A search function shaped for use as an LLM / agent tool: it takes a query
# and returns plain JSON-serializable results the model can read.
def web_search(query: str, limit: int = 10) -> list[dict]:
"""Search the web and return a compact list of results."""
# Hosted API instead? Get a key at https://openserp.org/dashboard/keys:
# with OpenSERP(api_key="<YOUR_API_TOKEN>") as client:
with OpenSERP(base_url="http://localhost:7000") as client:
response = client.search(engine="google", text=query, limit=limit)
return [
{
"rank": item.rank,
"title": item.title,
"url": item.url,
"snippet": item.snippet,
}
for item in response.results
]
if __name__ == "__main__":
# Register web_search as a tool with your LLM; here we just call it directly.
results = web_search("what is retrieval augmented generation")
print(json.dumps(results, indent=2))

View File

@@ -0,0 +1 @@
openserp>=0.2.0,<1

View File

@@ -0,0 +1,11 @@
# Search with extraction (JavaScript)
Runs a search with `extract: true`, so OpenSERP fetches the top pages and returns
their cleaned content alongside each result — search and scrape in one request.
```bash
npm install
node index.js
```
Edit the `text`, `extractTop`, or `extractMode` in [index.js](index.js) to tune it.

View File

@@ -0,0 +1,29 @@
import { OpenSERP } from "@openserp/sdk";
// Hosted API instead? Get a key at https://openserp.org/dashboard/keys:
// const client = new OpenSERP({ apiKey: "<YOUR_API_TOKEN>", timeoutMs: 60_000 });
const client = new OpenSERP({ baseUrl: "http://localhost:7000", timeoutMs: 60_000 });
// `extract: true` fetches the top pages and returns their cleaned content
// alongside each result, so you get the page text in a single request.
// `extractTop` (max 5) controls how many results are enriched.
const { results } = await client.search({
engine: "ecosia",
text: "what is a serp api",
extract: true,
extractTop: 2,
extractMode: "auto",
});
for (const item of results) {
console.log(`${item.rank}. ${item.title}`);
console.log(` ${item.url}`);
// When extracted, `item.extracted.content` holds the page body. Trim it to
// a short preview here.
const content = item.extracted?.content;
if (content) {
console.log(` ${content.slice(0, 300).trim()}`);
}
console.log();
}

View File

@@ -0,0 +1,14 @@
{
"name": "openserp-example-js-search-with-extract",
"private": true,
"type": "module",
"scripts": {
"start": "node index.js"
},
"engines": {
"node": ">=18"
},
"dependencies": {
"@openserp/sdk": "^0.2.0"
}
}

View File

@@ -0,0 +1,10 @@
# Extract a page to Markdown (Python)
Extracts one URL with `/extract` and writes the cleaned page content to `extracted.md`.
```bash
pip install -r requirements.txt
python main.py
```
Edit the `url` in [main.py](main.py) to extract a different page.

View File

@@ -0,0 +1,16 @@
from openserp import OpenSERP
url = "https://go.dev/doc/"
output_file = "extracted.md"
# Hosted API instead? Get a key at https://openserp.org/dashboard/keys:
# with OpenSERP(api_key="<YOUR_API_TOKEN>", timeout=60.0) as client:
with OpenSERP(base_url="http://localhost:7000", timeout=60.0) as client:
# /extract turns a single page into clean Markdown (or plain text).
result = client.extract(url=url, mode="auto", clean=True)
content = result.markdown or result.text or ""
with open(output_file, "w", encoding="utf-8") as handle:
handle.write(content)
print(f"Extracted {len(content)} characters from {url} into {output_file}")

View File

@@ -0,0 +1 @@
openserp>=0.2.0,<1

View File

@@ -0,0 +1,10 @@
# Image gallery (JavaScript)
Runs an image search and writes a self-contained `gallery.html` you can open in a browser.
```bash
npm install
node index.js
```
Edit the `query` in [index.js](index.js), then open `gallery.html`.

View File

@@ -0,0 +1,56 @@
import { writeFile } from "node:fs/promises";
import { OpenSERP } from "@openserp/sdk";
// Hosted API instead? Get a key at https://openserp.org/dashboard/keys:
// const client = new OpenSERP({ apiKey: "<YOUR_API_TOKEN>" });
const client = new OpenSERP({ baseUrl: "http://localhost:7000" });
const query = "go gopher mascot";
const { results } = await client.image({
engine: "bing",
text: query,
limit: 12,
});
const cards = results
.map((result) => {
const src = escapeHtml(result.image?.thumbnail ?? result.image?.url ?? "");
const title = escapeHtml(result.title ?? "Untitled");
const page = escapeHtml(result.source?.page_url ?? "#");
return `<a class="card" href="${page}"><img src="${src}" alt="${title}"><span>${title}</span></a>`;
})
.join("\n");
const html = `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${escapeHtml(query)}</title>
<style>
body { font-family: system-ui, sans-serif; margin: 32px; color: #17202a; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 16px; }
.card { color: inherit; text-decoration: none; border: 1px solid #d7dde5; border-radius: 8px; overflow: hidden; }
img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; background: #f2f4f7; display: block; }
span { display: block; padding: 10px; font-size: 14px; }
</style>
</head>
<body>
<h1>${escapeHtml(query)}</h1>
<div class="grid">${cards}</div>
</body>
</html>`;
await writeFile("gallery.html", html, "utf8");
console.log(`Wrote ${results.length} images to gallery.html — open it in a browser.`);
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, (char) => ({
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;",
})[char]);
}

View File

@@ -0,0 +1,14 @@
{
"name": "openserp-example-js-image-gallery",
"private": true,
"type": "module",
"scripts": {
"start": "node index.js"
},
"engines": {
"node": ">=18"
},
"dependencies": {
"@openserp/sdk": "^0.2.0"
}
}

View File

@@ -0,0 +1,11 @@
# Basic search (JavaScript)
Runs one Google search with `@openserp/sdk` and prints the titles and URLs.
```bash
npm install
node index.js
```
Edit the `text`, `engine`, or `limit` in [index.js](index.js) to change the search.
To use the hosted API instead of a local server, follow the commented line at the top of the file.

View File

@@ -0,0 +1,18 @@
import { OpenSERP } from "@openserp/sdk";
// Hosted API instead? Get a key at https://openserp.org/dashboard/keys:
// const client = new OpenSERP({ apiKey: "<YOUR_API_TOKEN>" });
const client = new OpenSERP({ baseUrl: "http://localhost:7000" });
const { results } = await client.search({
engine: "google",
text: "open source search api",
region: "US",
lang: "EN",
limit: 10,
});
for (const item of results) {
console.log(`${item.rank}. ${item.title}`);
console.log(` ${item.url}`);
}

View File

@@ -0,0 +1,14 @@
{
"name": "openserp-example-js-basic-search",
"private": true,
"type": "module",
"scripts": {
"start": "node index.js"
},
"engines": {
"node": ">=18"
},
"dependencies": {
"@openserp/sdk": "^0.2.0"
}
}

View File

@@ -0,0 +1,11 @@
# Basic search (Python)
Runs one Google search with the `openserp` SDK and prints the titles and URLs.
```bash
pip install -r requirements.txt
python main.py
```
Edit the `text`, `engine`, or `limit` in [main.py](main.py) to change the search.
To use the hosted API instead of a local server, follow the commented line at the top of the file.

View File

@@ -0,0 +1,16 @@
from openserp import OpenSERP
# Hosted API instead? Get a key at https://openserp.org/dashboard/keys:
# with OpenSERP(api_key="<YOUR_API_TOKEN>") as client:
with OpenSERP(base_url="http://localhost:7000") as client:
response = client.search(
engine="google",
text="open source search api",
region="US",
lang="EN",
limit=10,
)
for item in response.results:
print(f"{item.rank}. {item.title}")
print(f" {item.url}")

View File

@@ -0,0 +1 @@
openserp>=0.2.0,<1

View File

@@ -0,0 +1,11 @@
# Multi-engine compare (JavaScript)
Runs one query across several engines with `/mega/search` and groups the results
by domain, so you can see which sites the engines agree on.
```bash
npm install
node index.js
```
Edit the `query` and `engines` in [index.js](index.js) to compare your own.

View File

@@ -0,0 +1,34 @@
import { OpenSERP } from "@openserp/sdk";
// Hosted API instead? Get a key at https://openserp.org/dashboard/keys:
// const client = new OpenSERP({ apiKey: "<YOUR_API_TOKEN>" });
const client = new OpenSERP({ baseUrl: "http://localhost:7000" });
const query = "privacy focused search engine";
// /mega/search runs one query across several engines and merges the results.
const { results } = await client.megaSearch({
text: query,
engines: ["bing", "duckduckgo"],
region: "US",
limit: 10,
});
// Group results by domain to see which sites both engines agree on.
const byDomain = new Map();
for (const result of results) {
const domain = result.domain;
if (!domain) continue;
const stats = byDomain.get(domain) ?? { hits: 0, engines: new Set(), bestRank: Infinity };
stats.hits += 1;
stats.engines.add(result.engine);
stats.bestRank = Math.min(stats.bestRank, result.rank ?? Infinity);
byDomain.set(domain, stats);
}
console.log(`Domains found for "${query}":\n`);
const ranked = [...byDomain].sort((a, b) => a[1].bestRank - b[1].bestRank);
for (const [domain, stats] of ranked) {
console.log(`${domain} (rank ${stats.bestRank}, seen in ${[...stats.engines].join(", ")})`);
}

View File

@@ -0,0 +1,14 @@
{
"name": "openserp-example-js-multi-engine-compare",
"private": true,
"type": "module",
"scripts": {
"start": "node index.js"
},
"engines": {
"node": ">=18"
},
"dependencies": {
"@openserp/sdk": "^0.2.0"
}
}

View File

@@ -0,0 +1,10 @@
# Keyword list to CSV (Python)
Searches a list of keywords and writes every result to `keywords.csv`.
```bash
pip install -r requirements.txt
python main.py
```
Edit the `keywords` list in [main.py](main.py) to export your own.

View File

@@ -0,0 +1,31 @@
import csv
from openserp import OpenSERP
keywords = ["search api", "open source serp"]
output_file = "keywords.csv"
# Hosted API instead? Get a key at https://openserp.org/dashboard/keys:
#with OpenSERP(api_key="<YOUR_API_TOKEN>") as client:
with OpenSERP(base_url="http://localhost:7000") as client:
rows = []
for keyword in keywords:
response = client.search(engine="google", text=keyword, region="US", limit=10)
for result in response.results:
rows.append(
{
"keyword": keyword,
"rank": result.rank,
"title": result.title,
"url": result.url,
"domain": result.domain,
}
)
with open(output_file, "w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(handle, fieldnames=["keyword", "rank", "title", "url", "domain"])
writer.writeheader()
writer.writerows(rows)
print(f"Wrote {len(rows)} rows to {output_file}")

View File

@@ -0,0 +1 @@
openserp>=0.2.0,<1

View File

@@ -0,0 +1,11 @@
# Competitor overlap (JavaScript)
Searches several keywords and ranks the domains by how many of them they appear in —
the sites showing up across your keyword set are your real SERP competitors.
```bash
npm install
node index.js
```
Edit the `keywords` list in [index.js](index.js) to match your niche.

View File

@@ -0,0 +1,32 @@
import { OpenSERP } from "@openserp/sdk";
// Hosted API instead? Get a key at https://openserp.org/dashboard/keys:
// const client = new OpenSERP({ apiKey: "<YOUR_API_TOKEN>" });
const client = new OpenSERP({ baseUrl: "http://localhost:7000" });
// Domains that rank for several of your keywords are your real SERP competitors.
const keywords = ["serp api", "google search api", "scrape search results"];
const byDomain = new Map();
for (const keyword of keywords) {
const { results } = await client.search({
engine: "google",
text: keyword,
region: "US",
limit: 10,
});
for (const result of results) {
if (!result.domain) continue;
const stats = byDomain.get(result.domain) ?? { keywords: new Set(), bestRank: Infinity };
stats.keywords.add(keyword);
stats.bestRank = Math.min(stats.bestRank, result.rank ?? Infinity);
byDomain.set(result.domain, stats);
}
}
console.log(`Competitor overlap across ${keywords.length} keywords:\n`);
const ranked = [...byDomain].sort((a, b) => b[1].keywords.size - a[1].keywords.size);
for (const [domain, stats] of ranked) {
console.log(`${domain}${stats.keywords.size}/${keywords.length} keywords, best rank ${stats.bestRank}`);
}

View File

@@ -0,0 +1,14 @@
{
"name": "openserp-example-js-competitor-overlap",
"private": true,
"type": "module",
"scripts": {
"start": "node index.js"
},
"engines": {
"node": ">=18"
},
"dependencies": {
"@openserp/sdk": "^0.2.0"
}
}

View File

@@ -0,0 +1,11 @@
# Rank tracker (Python)
Checks where a target domain ranks for a list of keywords and writes the results
to `rankings.csv`. Handles `www.` and subdomains when matching the domain.
```bash
pip install -r requirements.txt
python main.py
```
Edit `target_domain` and `keywords` in [main.py](main.py) to track your own site.

View File

@@ -0,0 +1,38 @@
import csv
from openserp import OpenSERP
target_domain = "go.dev"
keywords = ["golang tutorial", "go programming language", "golang documentation"]
output_file = "rankings.csv"
def matches(domain: str | None, target: str) -> bool:
if not domain:
return False
domain = domain.lower().removeprefix("www.")
return domain == target or domain.endswith(f".{target}")
# Hosted API instead? Get a key at https://openserp.org/dashboard/keys:
# with OpenSERP(api_key="<YOUR_API_TOKEN>") as client:
with OpenSERP(base_url="http://localhost:7000") as client:
rows = []
for keyword in keywords:
response = client.search(engine="google", text=keyword, region="US", limit=20)
hit = next((r for r in response.results if matches(r.domain, target_domain)), None)
rows.append(
{
"keyword": keyword,
"rank": hit.rank if hit else "not in top 20",
"url": hit.url if hit else "",
}
)
print(f'"{keyword}": {rows[-1]["rank"]}')
with open(output_file, "w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(handle, fieldnames=["keyword", "rank", "url"])
writer.writeheader()
writer.writerows(rows)
print(f"\nSaved rankings for {target_domain} to {output_file}")

View File

@@ -0,0 +1 @@
openserp>=0.2.0,<1