Browse documentation
LlamaIndex
A retriever with no index to build, or the MCP tool spec.
As a retriever
The natural fit. Hits are already passages, so there is nothing to chunk, embed or store — the retriever is a function call.
import os, httpx
from llama_index.core.retrievers import BaseRetriever
from llama_index.core.schema import NodeWithScore, TextNode
class UnlobRetriever(BaseRetriever):
def __init__(self, top_k: int = 8, **kwargs):
self.top_k = top_k
self._client = httpx.Client(
base_url="https://api.unlob.com",
headers={"x-api-key": os.environ["UNLOB_API_KEY"]},
timeout=20,
)
super().__init__(**kwargs)
def _retrieve(self, query_bundle) -> list[NodeWithScore]:
r = self._client.get("/search", params={
"q": query_bundle.query_str,
"limit": self.top_k,
# One row per near-duplicate cluster, so the context window does not
# fill with the same syndicated article.
"collapse": "story",
})
r.raise_for_status()
body = r.json()
if body.get("partial"):
# Incomplete, not empty. Worth surfacing rather than silently
# returning three nodes as though that were the whole corpus.
print("unlob: partial results — part of the corpus was unreachable")
return [
NodeWithScore(
node=TextNode(
text=h["snippet"],
id_=h["id"],
metadata={k: h.get(k) for k in
("url", "host", "title", "independent_sources")},
),
score=h["score"],
)
for h in body["results"]
]
from llama_index.core.query_engine import RetrieverQueryEngine
engine = RetrieverQueryEngine.from_args(UnlobRetriever(top_k=8))
print(engine.query("How does tokio schedule tasks?"))
score is comparable within a response, not between responses — do not threshold on an
absolute value. Use min_quality or min_host_rank if you want a floor that means the
same thing every time.
As MCP tools
llama-index-tools-mcp converts the server into a tool spec, which gives an agent all
eleven rather than search alone:
from llama_index.tools.mcp import BasicMCPClient, McpToolSpec
mcp_client = BasicMCPClient(
"https://api.unlob.com/mcp",
headers={"x-api-key": os.environ["UNLOB_API_KEY"]},
)
tools = McpToolSpec(client=mcp_client).to_tool_list()
That is worth it when the agent should be able to corroborate a claim or brief itself on an entity, not only search.
Skipping the pipeline
If the question is “what should I know about X”, assemble_context does the whole
retrieval loop server-side and returns a packed, deduplicated, trust-ranked reading set —
one request instead of retrieve-rerank-synthesise.
pack = client.get("/assemble_context",
params={"q": question, "budget": 4000}).json()