Code Examples: Build Your Own Search UI

Documentation > WEB CRAWLER-Code Examples > Code Examples: Build Your Own Search UI

Code Examples: Build Your Own Search UI

Complete, copy-paste-ready code examples for querying your Opensolr Web Crawler index with full hybrid search (keyword + AI vector). Every example below includes the exact same parameters used by the built-in Opensolr Search UI.


How Hybrid Search Works (The Flow)

Every search request follows this flow:

  1. User types a query (e.g., "warm beverage for cold weather")
  2. Your app calls the Opensolr Embed API to convert the query text into a 1024-dimensional vector embedding
  3. Your app sends the hybrid query to Solr — combining the vector embedding (semantic search) with the keyword query (lexical search)
  4. Solr returns results ranked by a combined score of both vector similarity and keyword relevance

The embedding API endpoint is:

https://api.opensolr.com/solr_manager/api/embed?payload=your+search+query

It returns:

{
  "embedding": [0.0234, -0.0567, 0.1234, ... ]   // 1024 floats
}

This vector is then passed into the vectorQuery parameter of your Solr request.


The Complete Hybrid Search Parameters

These are ALL the parameters used by the Opensolr Search UI. You can see them yourself by clicking the Solr Query Inspector (magnifying glass icon, bottom-right corner) on your search page at https://search.opensolr.com/YOUR_INDEX.

Core Query

q = {!func}sum(product(1, query($vectorQuery)), product(1, div(log(sum(1, query($lexicalQuery))), sum(log(sum(1, query($lexicalQuery))), 20))))

vectorQuery = {!knn f=embeddings topK=250}[1024-dim embedding vector here]

lexicalQuery = {!edismax
  qf="title^5 description^4 uri^0.5 text^0.01"
  pf="title^10 description^8 uri^1 text^0.02"
  pf2="title^5 description^4 uri^0.5 text^0.01"
  pf3="title^2.5 description^2 uri^0.25 text^0.005"
  ps=0 ps2=1 ps3=2
  mm="2<-1 5<-2"
}your search terms here

df = title
rows = 50
start = 0

Filter Queries

fq = content_type:text*
fq = -uri_s:"https://yoursite.com/sitemap.xml"

Field List

fl = id,uri,title,description,text,og_image,meta_icon,content_type,creation_date,timestamp,meta_domain,meta_*,score,price_f,currency_s

Highlighting (all 15 parameters)

hl = true
hl.q = {!edismax qf="title^5 description^4 uri^0.5 text^0.01"}your search terms here
hl.fl = uri,title,description,text
hl.method = unified
hl.fragsize = 200
hl.snippets = 1
hl.bs.type = SENTENCE
hl.defaultSummary = false
hl.fragAlignRatio = 0.33
hl.tag.pre = <em>
hl.tag.post = </em>
hl.tag.ellipsis = ...
hl.requireFieldMatch = false
hl.highlightMultiTerm = true
hl.usePhraseHighlighter = true
hl.maxAnalyzedChars = 10000

Faceting

facet = true
facet.field = meta_detected_language
facet.field = currency_s
facet.mincount = 1
facet.sort = index

Stats

stats = true
stats.field = price_f

Spellcheck

spellcheck = true
spellcheck.q = your search terms here
spellcheck.onlyMorePopular = false
spellcheck.extendedResults = false
spellcheck.count = 5
spellcheck.collate = true
spellcheck.collateExtendedResults = false
spellcheck.maxCollationTries = 15
spellcheck.maxCollations = 3

CORS: Client-Side Only (No Server Needed)

If your domain is CORS-whitelisted by Opensolr, your JavaScript running in the browser can talk directly to both the Embed API and the Solr API — no backend server needed at all.

What is CORS? By default, browsers block web pages from making requests to a different domain (security feature). CORS lets the Opensolr server explicitly allow your domain. Contact Opensolr support to get your domain whitelisted.


JS Example 1: Pure JavaScript — Full Hybrid Search

This is the real deal. A single HTML file with full hybrid vector + keyword search, all parameters included.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My Hybrid Search</title>
    <style>
        body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; }
        .search-box { display: flex; gap: 8px; margin-bottom: 24px; }
        .search-box input { flex: 1; padding: 12px 16px; font-size: 16px; border: 2px solid #ddd; border-radius: 6px; }
        .search-box input:focus { border-color: #e8650a; outline: none; }
        .search-box button { padding: 12px 28px; font-size: 16px; background: #e8650a; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 600; }
        .result { margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #eee; }
        .result h3 { margin: 0 0 4px; } .result h3 a { color: #1a0dab; text-decoration: none; }
        .result .url { color: #006621; font-size: 13px; margin-bottom: 4px; }
        .result .snippet { color: #545454; font-size: 14px; line-height: 1.5; }
        .result em { font-weight: bold; font-style: normal; }
        .info { color: #666; font-size: 14px; margin-bottom: 16px; }
        .spellcheck a { color: #e8650a; font-weight: 600; text-decoration: none; }
    </style>
</head>
<body>
    <h1>Search</h1>
    <div class="search-box">
        <input type="text" id="q" placeholder="Search..." autofocus>
        <button onclick="search()">Search</button>
    </div>
    <div id="results"></div>

<script>
// =====================================================================
// CONFIGURATION — Replace these with YOUR Opensolr index details
// =====================================================================
var SOLR_URL  = "https://YOUR_HOST/solr/YOUR_INDEX/select";
var SOLR_USER = "opensolr";
var SOLR_PASS = "YOUR_API_KEY";
var EMBED_URL = "https://api.opensolr.com/solr_manager/api/embed";

document.getElementById("q").addEventListener("keydown", function(e) {
    if (e.key === "Enter") search();
});

function search(startOffset) {
    var query = document.getElementById("q").value.trim();
    if (!query) return;
    var start = startOffset || 0;

    document.getElementById("results").innerHTML = "<p class="info">Searching...</p>";

    // Step 1: Get the embedding vector for the search query
    fetch(EMBED_URL + "?payload=" + encodeURIComponent(query))
    .then(function(r) { return r.json(); })
    .then(function(embedData) {
        var vector = embedData.embedding;
        if (!vector || !vector.length) {
            // Fallback to keyword-only if embedding fails
            return keywordOnlySearch(query, start);
        }

        var vectorStr = "[" + vector.join(",") + "]";

        // Step 2: Build the FULL hybrid search query with ALL parameters
        var params = new URLSearchParams();

        // --- Core hybrid query ---
        params.set("q",
            "{!func}sum(" +
                "product(1, query($vectorQuery)), " +
                "product(1, div(log(sum(1, query($lexicalQuery))), sum(log(sum(1, query($lexicalQuery))), 20)))" +
            ")"
        );
        params.set("vectorQuery", "{!knn f=embeddings topK=250}" + vectorStr);
        params.set("lexicalQuery",
            "{!edismax " +
                "qf="title^5 description^4 uri^0.5 text^0.01" " +
                "pf="title^10 description^8 uri^1 text^0.02" " +
                "pf2="title^5 description^4 uri^0.5 text^0.01" " +
                "pf3="title^2.5 description^2 uri^0.25 text^0.005" " +
                "ps=0 ps2=1 ps3=2 " +
                "mm="2<-1 5<-2"" +
            "}" + query
        );
        params.set("df", "title");
        params.set("rows", "10");
        params.set("start", start);
        params.set("wt", "json");

        // --- Filter queries ---
        params.append("fq", "content_type:text*");

        // --- Field list ---
        params.set("fl", "id,uri,title,description,text,og_image,meta_icon,content_type,creation_date,timestamp,meta_domain,meta_*,score,price_f,currency_s");

        // --- Highlighting (all 15 params) ---
        params.set("hl", "true");
        params.set("hl.q", "{!edismax qf="title^5 description^4 uri^0.5 text^0.01"}" + query);
        params.set("hl.fl", "uri,title,description,text");
        params.set("hl.method", "unified");
        params.set("hl.fragsize", "200");
        params.set("hl.snippets", "1");
        params.set("hl.bs.type", "SENTENCE");
        params.set("hl.defaultSummary", "false");
        params.set("hl.fragAlignRatio", "0.33");
        params.set("hl.tag.pre", "<em>");
        params.set("hl.tag.post", "</em>");
        params.set("hl.tag.ellipsis", "... ");
        params.set("hl.requireFieldMatch", "false");
        params.set("hl.highlightMultiTerm", "true");
        params.set("hl.usePhraseHighlighter", "true");
        params.set("hl.maxAnalyzedChars", "10000");

        // --- Faceting ---
        params.set("facet", "true");
        params.append("facet.field", "meta_detected_language");
        params.append("facet.field", "currency_s");
        params.set("facet.mincount", "1");
        params.set("facet.sort", "index");

        // --- Stats ---
        params.set("stats", "true");
        params.set("stats.field", "price_f");

        // --- Spellcheck ---
        params.set("spellcheck", "true");
        params.set("spellcheck.q", query);
        params.set("spellcheck.onlyMorePopular", "false");
        params.set("spellcheck.extendedResults", "false");
        params.set("spellcheck.count", "5");
        params.set("spellcheck.collate", "true");
        params.set("spellcheck.collateExtendedResults", "false");
        params.set("spellcheck.maxCollationTries", "15");
        params.set("spellcheck.maxCollations", "3");

        // Step 3: Send to Solr
        return fetch(SOLR_URL + "?" + params.toString(), {
            headers: { "Authorization": "Basic " + btoa(SOLR_USER + ":" + SOLR_PASS) }
        })
        .then(function(r) { return r.json(); })
        .then(function(data) { renderResults(data, query); });
    })
    .catch(function(err) {
        document.getElementById("results").innerHTML = "<p>Error: " + err.message + "</p>";
    });
}

function renderResults(data, query) {
    var docs = data.response.docs;
    var hl = data.highlighting || {};
    var total = data.response.numFound;
    var html = "<p class="info">Found " + total.toLocaleString() + " results for <strong>" + esc(query) + "</strong></p>";

    docs.forEach(function(doc) {
        var h = hl[doc.id] || {};
        var title = (h.title && h.title[0]) || esc(doc.title || "Untitled");
        var snippet = (h.description && h.description[0]) || (h.text && h.text[0]) || esc(doc.description || "");

        html += "<div class="result">"
             +  "<h3><a href="" + esc(doc.uri) + "" target="_blank">" + title + "</a></h3>"
             +  "<div class="url">" + esc(doc.uri) + "</div>"
             +  "<div class="snippet">" + snippet + "</div>"
             +  "</div>";
    });

    document.getElementById("results").innerHTML = html;
}

function esc(s) { if (!s) return ""; var d = document.createElement("div"); d.textContent = s; return d.innerHTML; }
</script>
</body>
</html>

This is a real, working hybrid search. The JavaScript calls the Embed API to get the vector, then sends the full hybrid query to Solr with all 40+ parameters. Semantic search works out of the box.


PHP Example 2: PHP — Full Hybrid Search

Complete PHP backend with all hybrid search parameters. Save as search.php and call it with search.php?q=your+query.

<?php
// search.php — Full Hybrid Search with Opensolr Web Crawler Index

// =====================================================================
// CONFIGURATION — Replace these with YOUR Opensolr index details
// =====================================================================
$solr_url  = "https://YOUR_HOST/solr/YOUR_INDEX/select";
$solr_user = "opensolr";
$solr_pass = "YOUR_API_KEY";
$embed_url = "https://api.opensolr.com/solr_manager/api/embed";

$query = trim($_GET["q"] ?? "");
$page  = max(1, intval($_GET["page"] ?? 1));
$rows  = 10;
$start = ($page - 1) * $rows;

if (empty($query)) {
    header("Content-Type: application/json");
    echo json_encode(["error" => "No query provided"]);
    exit;
}

// Step 1: Get the embedding vector
$embed_response = file_get_contents($embed_url . "?payload=" . urlencode($query));
$embed_data = json_decode($embed_response, true);
$vector = $embed_data["embedding"] ?? null;

// Step 2: Build the FULL hybrid query with ALL parameters
$params = [];

if ($vector && is_array($vector) && count($vector) === 1024) {
    $vector_str = "[" . implode(",", $vector) . "]";

    // Core hybrid scoring formula
    $params["q"] = "{!func}sum("
        . "product(1, query($vectorQuery)), "
        . "product(1, div(log(sum(1, query($lexicalQuery))), sum(log(sum(1, query($lexicalQuery))), 20)))"
        . ")";
    $params["vectorQuery"] = "{!knn f=embeddings topK=250}" . $vector_str;
    $params["lexicalQuery"] = "{!edismax"
        . " qf="title^5 description^4 uri^0.5 text^0.01""
        . " pf="title^10 description^8 uri^1 text^0.02""
        . " pf2="title^5 description^4 uri^0.5 text^0.01""
        . " pf3="title^2.5 description^2 uri^0.25 text^0.005""
        . " ps=0 ps2=1 ps3=2"
        . " mm="2<-1 5<-2""
        . "}" . $query;

    // Highlighting uses the lexical query only (not the vector)
    $params["hl.q"] = "{!edismax qf="title^5 description^4 uri^0.5 text^0.01"}" . $query;
} else {
    // Fallback: keyword-only if embedding fails
    $params["q"] = $query;
    $params["defType"] = "edismax";
    $params["qf"] = "title^5 description^4 uri^0.5 text^0.01";
    $params["pf"] = "title^10 description^8 uri^1 text^0.02";
    $params["mm"] = "2<-1 5<-2";
    $params["hl.q"] = $query;
}

// Common parameters (same for hybrid and fallback)
$params["df"] = "title";
$params["rows"] = $rows;
$params["start"] = $start;
$params["wt"] = "json";

// Filter queries
$params["fq"] = "content_type:text*";

// Field list
$params["fl"] = "id,uri,title,description,text,og_image,meta_icon,content_type,creation_date,timestamp,meta_domain,meta_*,score,price_f,currency_s";

// Highlighting — all 15 parameters
$params["hl"]                       = "true";
$params["hl.fl"]                    = "uri,title,description,text";
$params["hl.method"]                = "unified";
$params["hl.fragsize"]              = 200;
$params["hl.snippets"]              = 1;
$params["hl.bs.type"]               = "SENTENCE";
$params["hl.defaultSummary"]        = "false";
$params["hl.fragAlignRatio"]        = "0.33";
$params["hl.tag.pre"]               = "<em>";
$params["hl.tag.post"]              = "</em>";
$params["hl.tag.ellipsis"]          = "... ";
$params["hl.requireFieldMatch"]     = "false";
$params["hl.highlightMultiTerm"]    = "true";
$params["hl.usePhraseHighlighter"]  = "true";
$params["hl.maxAnalyzedChars"]      = 10000;

// Faceting
$params["facet"]           = "true";
$params["facet.mincount"]  = 1;
$params["facet.sort"]      = "index";

// Stats
$params["stats"]       = "true";
$params["stats.field"] = "price_f";

// Spellcheck
$params["spellcheck"]                         = "true";
$params["spellcheck.q"]                       = $query;
$params["spellcheck.onlyMorePopular"]         = "false";
$params["spellcheck.extendedResults"]         = "false";
$params["spellcheck.count"]                   = 5;
$params["spellcheck.collate"]                 = "true";
$params["spellcheck.collateExtendedResults"]  = "false";
$params["spellcheck.maxCollationTries"]       = 15;
$params["spellcheck.maxCollations"]           = 3;

// Build URL — http_build_query + manually append multi-value params
$url = $solr_url . "?" . http_build_query($params)
     . "&facet.field=meta_detected_language"
     . "&facet.field=currency_s";

// Step 3: Query Solr
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_USERPWD, $solr_user . ":" . $solr_pass);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

header("Content-Type: application/json");
echo $response;

N.js Example 3: Node.js (Express) — Full Hybrid Search

// server.js — Node.js Full Hybrid Search with Opensolr
const express = require("express");
const app = express();

// =====================================================================
// CONFIGURATION — Replace these with YOUR Opensolr index details
// =====================================================================
const SOLR_URL  = "https://YOUR_HOST/solr/YOUR_INDEX/select";
const SOLR_AUTH = "Basic " + Buffer.from("opensolr:YOUR_API_KEY").toString("base64");
const EMBED_URL = "https://api.opensolr.com/solr_manager/api/embed";

app.get("/search", async (req, res) => {
    const query = (req.query.q || "").trim();
    const start = parseInt(req.query.start) || 0;
    const rows  = parseInt(req.query.rows) || 10;

    if (!query) return res.json({ error: "No query provided" });

    try {
        // Step 1: Get the embedding vector
        const embedResp = await fetch(EMBED_URL + "?payload=" + encodeURIComponent(query));
        const embedData = await embedResp.json();
        const vector = embedData.embedding;

        // Step 2: Build ALL hybrid search parameters
        const params = new URLSearchParams();

        if (vector && vector.length === 1024) {
            const vectorStr = "[" + vector.join(",") + "]";

            params.set("q",
                "{!func}sum(" +
                    "product(1, query($vectorQuery)), " +
                    "product(1, div(log(sum(1, query($lexicalQuery))), " +
                    "sum(log(sum(1, query($lexicalQuery))), 20))))"
            );
            params.set("vectorQuery", "{!knn f=embeddings topK=250}" + vectorStr);
            params.set("lexicalQuery",
                "{!edismax " +
                    "qf="title^5 description^4 uri^0.5 text^0.01" " +
                    "pf="title^10 description^8 uri^1 text^0.02" " +
                    "pf2="title^5 description^4 uri^0.5 text^0.01" " +
                    "pf3="title^2.5 description^2 uri^0.25 text^0.005" " +
                    "ps=0 ps2=1 ps3=2 " +
                    "mm="2<-1 5<-2"" +
                "}" + query
            );
            params.set("hl.q", "{!edismax qf="title^5 description^4 uri^0.5 text^0.01"}" + query);
        } else {
            // Fallback: keyword-only
            params.set("q", query);
            params.set("defType", "edismax");
            params.set("qf", "title^5 description^4 uri^0.5 text^0.01");
            params.set("pf", "title^10 description^8 uri^1 text^0.02");
            params.set("mm", "2<-1 5<-2");
            params.set("hl.q", query);
        }

        // Common params
        params.set("df", "title");
        params.set("rows", rows);
        params.set("start", start);
        params.set("wt", "json");
        params.append("fq", "content_type:text*");
        params.set("fl", "id,uri,title,description,text,og_image,meta_icon,content_type,creation_date,timestamp,meta_domain,meta_*,score,price_f,currency_s");

        // Highlighting — all 15 parameters
        params.set("hl", "true");
        params.set("hl.fl", "uri,title,description,text");
        params.set("hl.method", "unified");
        params.set("hl.fragsize", "200");
        params.set("hl.snippets", "1");
        params.set("hl.bs.type", "SENTENCE");
        params.set("hl.defaultSummary", "false");
        params.set("hl.fragAlignRatio", "0.33");
        params.set("hl.tag.pre", "<em>");
        params.set("hl.tag.post", "</em>");
        params.set("hl.tag.ellipsis", "... ");
        params.set("hl.requireFieldMatch", "false");
        params.set("hl.highlightMultiTerm", "true");
        params.set("hl.usePhraseHighlighter", "true");
        params.set("hl.maxAnalyzedChars", "10000");

        // Faceting
        params.set("facet", "true");
        params.append("facet.field", "meta_detected_language");
        params.append("facet.field", "currency_s");
        params.set("facet.mincount", "1");
        params.set("facet.sort", "index");

        // Stats
        params.set("stats", "true");
        params.set("stats.field", "price_f");

        // Spellcheck
        params.set("spellcheck", "true");
        params.set("spellcheck.q", query);
        params.set("spellcheck.onlyMorePopular", "false");
        params.set("spellcheck.extendedResults", "false");
        params.set("spellcheck.count", "5");
        params.set("spellcheck.collate", "true");
        params.set("spellcheck.collateExtendedResults", "false");
        params.set("spellcheck.maxCollationTries", "15");
        params.set("spellcheck.maxCollations", "3");

        // Step 3: Query Solr
        const solrResp = await fetch(SOLR_URL + "?" + params.toString(), {
            headers: { "Authorization": SOLR_AUTH }
        });
        const data = await solrResp.json();
        res.json(data);
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
});

app.listen(3000, () => console.log("Hybrid search server running on :3000"));

Py Example 4: Python — Full Hybrid Search

# search.py — Python Full Hybrid Search with Opensolr
import requests
import json
from urllib.parse import urlencode

# =====================================================================
# CONFIGURATION — Replace these with YOUR Opensolr index details
# =====================================================================
SOLR_URL  = "https://YOUR_HOST/solr/YOUR_INDEX/select"
SOLR_USER = "opensolr"
SOLR_PASS = "YOUR_API_KEY"
EMBED_URL = "https://api.opensolr.com/solr_manager/api/embed"

def hybrid_search(query, start=0, rows=10):
    # Step 1: Get the embedding vector
    embed_resp = requests.get(EMBED_URL, params={"payload": query})
    embed_data = embed_resp.json()
    vector = embed_data.get("embedding")

    # Step 2: Build ALL hybrid search parameters
    params = []

    if vector and len(vector) == 1024:
        vector_str = "[" + ",".join(str(v) for v in vector) + "]"

        params.append(("q",
            "{!func}sum("
            "product(1, query($vectorQuery)), "
            "product(1, div(log(sum(1, query($lexicalQuery))), "
            "sum(log(sum(1, query($lexicalQuery))), 20))))"
        ))
        params.append(("vectorQuery", "{!knn f=embeddings topK=250}" + vector_str))
        params.append(("lexicalQuery",
            "{!edismax "
            "qf="title^5 description^4 uri^0.5 text^0.01" "
            "pf="title^10 description^8 uri^1 text^0.02" "
            "pf2="title^5 description^4 uri^0.5 text^0.01" "
            "pf3="title^2.5 description^2 uri^0.25 text^0.005" "
            "ps=0 ps2=1 ps3=2 "
            "mm="2<-1 5<-2""
            "}" + query
        ))
        params.append(("hl.q", "{!edismax qf="title^5 description^4 uri^0.5 text^0.01"}" + query))
    else:
        # Fallback: keyword-only
        params.append(("q", query))
        params.append(("defType", "edismax"))
        params.append(("qf", "title^5 description^4 uri^0.5 text^0.01"))
        params.append(("pf", "title^10 description^8 uri^1 text^0.02"))
        params.append(("mm", "2<-1 5<-2"))
        params.append(("hl.q", query))

    # Common params
    params += [
        ("df", "title"), ("rows", rows), ("start", start), ("wt", "json"),
        ("fq", "content_type:text*"),
        ("fl", "id,uri,title,description,text,og_image,meta_icon,content_type,creation_date,timestamp,meta_domain,meta_*,score,price_f,currency_s"),
        # Highlighting
        ("hl", "true"), ("hl.fl", "uri,title,description,text"), ("hl.method", "unified"),
        ("hl.fragsize", 200), ("hl.snippets", 1), ("hl.bs.type", "SENTENCE"),
        ("hl.defaultSummary", "false"), ("hl.fragAlignRatio", "0.33"),
        ("hl.tag.pre", "<em>"), ("hl.tag.post", "</em>"), ("hl.tag.ellipsis", "... "),
        ("hl.requireFieldMatch", "false"), ("hl.highlightMultiTerm", "true"),
        ("hl.usePhraseHighlighter", "true"), ("hl.maxAnalyzedChars", 10000),
        # Faceting
        ("facet", "true"), ("facet.field", "meta_detected_language"),
        ("facet.field", "currency_s"), ("facet.mincount", 1), ("facet.sort", "index"),
        # Stats
        ("stats", "true"), ("stats.field", "price_f"),
        # Spellcheck
        ("spellcheck", "true"), ("spellcheck.q", query),
        ("spellcheck.onlyMorePopular", "false"), ("spellcheck.extendedResults", "false"),
        ("spellcheck.count", 5), ("spellcheck.collate", "true"),
        ("spellcheck.collateExtendedResults", "false"),
        ("spellcheck.maxCollationTries", 15), ("spellcheck.maxCollations", 3),
    ]

    # Step 3: Query Solr
    resp = requests.get(SOLR_URL, params=params, auth=(SOLR_USER, SOLR_PASS), timeout=10)
    return resp.json()

# Usage
if __name__ == "__main__":
    import sys
    query = " ".join(sys.argv[1:]) or "test"
    data = hybrid_search(query)
    print(f"Found {data["response"]["numFound"]} results")
    for doc in data["response"]["docs"][:5]:
        print(f"  {doc.get("score", 0):.4f}  {doc.get("title", "N/A")}")
        print(f"         {doc.get("uri", "")}")

$_ Example 5: curl — Full Hybrid Search (Command Line)

For quick testing. First get the embedding, then search:

# Step 1: Get the embedding vector and save to file
curl -s "https://api.opensolr.com/solr_manager/api/embed?payload=warm+beverage+for+cold+weather" \
  | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin)["embedding"]))" > /tmp/vector.json

# Step 2: Build and execute the hybrid search
VECTOR=$(cat /tmp/vector.json)

curl -s -u "opensolr:YOUR_API_KEY" \
  --data-urlencode "q={!func}sum(product(1, query($vectorQuery)), product(1, div(log(sum(1, query($lexicalQuery))), sum(log(sum(1, query($lexicalQuery))), 20))))" \
  --data-urlencode "vectorQuery={!knn f=embeddings topK=250}${VECTOR}" \
  --data-urlencode "lexicalQuery={!edismax qf="title^5 description^4 uri^0.5 text^0.01" pf="title^10 description^8 uri^1 text^0.02" pf2="title^5 description^4 uri^0.5 text^0.01" pf3="title^2.5 description^2 uri^0.25 text^0.005" ps=0 ps2=1 ps3=2 mm="2<-1 5<-2"}warm beverage for cold weather" \
  --data-urlencode "df=title" \
  --data-urlencode "rows=5" \
  --data-urlencode "wt=json" \
  --data-urlencode "fq=content_type:text*" \
  --data-urlencode "fl=id,uri,title,description,score" \
  --data-urlencode "hl=true" \
  --data-urlencode "hl.fl=title,description,text" \
  --data-urlencode "hl.method=unified" \
  -G "https://YOUR_HOST/solr/YOUR_INDEX/select" \
  | python3 -m json.tool

Quick Reference: What to Replace

In every example above, replace these placeholders:

Placeholder Where to Find It
YOUR_HOST Your Index Control Panel → "Hostname"
YOUR_INDEX Your Index name
YOUR_API_KEY Control Panel → Dashboard → "Secret API Key"

The Embed API endpoint (api.opensolr.com/solr_manager/api/embed) is the same for everyone — no configuration needed.