Microsoft NLWeb
Register GoodMem as an NLWeb retrieval provider from YAML — Schema.org retrieval, server-side site filtering, and object lookup backed by a GoodMem space.
Overview
GoodMem is a memory layer for AI agents that handles embedding, vector search, and reranking
server-side. This integration serves NLWeb retrieval from a
GoodMem space: GoodMemRetrievalProvider implements nlweb_core.retriever.RetrievalProvider, so
search() returns real RetrievedItem objects, and GoodMemObjectLookupProvider implements
ObjectLookupProvider for full-object lookup by URL. Both are registered purely by configuration
— NLWeb imports the class by path from YAML, so nothing is added to NLWeb's own source tree.
The integration targets nlweb-core, the pip-installable package with the config-driven
provider architecture. The nlweb-ai/NLWeb reference implementation has a different interface and
a hardcoded provider table, so a third-party package cannot register with it.
Installation
pip install goodmem-nlwebThe pip package is goodmem-nlweb; the import name is goodmem_nlweb. It depends on
nlweb-core and the goodmem Python SDK, and a running GoodMem server is required.
Configuration
NLWeb's ask handler asks for the retrieval provider named default, so register the provider
under that name:
retrieval:
default:
import_path: goodmem_nlweb
class_name: GoodMemRetrievalProvider
base_url: https://your-goodmem-server.example.com
api_key: gm_xxxxxxxxxxxxxxxxxxxxxxxx
space_name: nlwebEvery key beside import_path and class_name is passed to the constructor as a keyword
argument. NLWeb reads a key ending in _env from the environment instead, so
api_key_env: GOODMEM_API_KEY keeps the key out of the file.
| Option | Description |
|---|---|
base_url | Base URL of the GoodMem API server. |
api_key | API key used to authenticate requests. |
space_name | Name of the GoodMem space to search. Resolved to an id on first use; an ambiguous name raises GoodMemSpaceError. |
space_id | Space UUID, as an alternative to space_name. One of the two is required. |
create_space | Create the space named space_name if it does not exist (default: false). |
embedder_id | Embedder UUID used when creating the space; an existing space built on a different embedder is an error rather than a silent mismatch. |
reranker_id | Reranker UUID that reorders matched chunks by relevance. |
fetch_k | Number of chunks fetched from the server before deduplication by URL. |
site_field | Metadata field that stores the NLWeb site (default: site). |
url_field | Metadata field that stores the item URL (default: url). |
verify_ssl | Set to false to skip TLS verification for self-signed dev certs (default: true). |
timeout | Request timeout in seconds (default: 60.0). |
space_id, embedder_id, and reranker_id must be UUIDs; any other value raises ValueError
when the provider is built, before a request is made. In code you can also pass client= to
inject your own AsyncGoodmem client, which the provider borrows but never closes
(GoodMemConnection handles this ownership).
Local development. The example above uses a TLS-verified production URL. If you are running
GoodMem locally with a self-signed certificate, use https://localhost:8080 and set
verify_ssl: false. Keep TLS verification enabled for any deployed server.
Searching by site
NLWeb filters every call by site; GoodMem has no such concept, so a site is stored as memory metadata and filtered server-side with GoodMem's filter grammar. Values are escaped, never interpolated.
await provider.search("noodle soup", "recipes.example.com") # one site
await provider.search("noodle soup", ["a.com", "b.com"]) # OR across sites
await provider.search("noodle soup", "all") # every site
await provider.search_all_sites("noodle soup") # same thing
await provider.get_sites() # ['a.com', 'b.com']Results keep the server's ordering — a GoodMem vector score is a negative inner product and a
reranker score is a different scale, so neither is exposed as an NLWeb relevance number. When the
server reports a problem, whatever it did return is still returned and the statuses are logged; a
degraded retrieval that returned nothing yields an empty list plus a UserWarning, never an
exception, so an ask request can still answer from another endpoint.
Metadata filters
search() also takes metadata_filter, a mapping of metadata field to value. Each pair becomes a
server-side equality, AND-ed with the others and with the site filter. GoodMem compares metadata
under an explicit cast, so each value is cast by its Python type:
| Value | Compared as | Sent to the server |
|---|---|---|
str | text | CAST(val('$.cuisine') AS TEXT) = 'Malaysian' |
bool | boolean | CAST(val('$.vegetarian') AS BOOLEAN) = true |
int, float | number | CAST(val('$.servings') AS NUMERIC) = 4 |
None and any other type (a list, a dict, …) raise ValueError before a request is made.
await provider.search("noodle soup", "recipes.example.com",
metadata_filter={"vegetarian": True, "servings": 4})metadata_filter is an argument to search(), not a provider option: a metadata_filter: key in
the provider's YAML entry is ignored.
Ingesting Schema.org documents
RetrievalProvider only reads, so ingestion is a helper rather than part of the interface:
from goodmem_nlweb import GoodMemRetrievalProvider, upload_documents
provider = GoodMemRetrievalProvider(
space_name="nlweb",
base_url="https://your-goodmem-server.example.com",
api_key="gm_xxxxxxxxxxxxxxxxxxxxxxxx",
)
await upload_documents(provider, [
{"@type": "Recipe", "url": "https://ex.com/r/laksa", "name": "Singapore Laksa",
"description": "A coconut curry noodle soup."},
], site="recipes.example.com")Each document maps onto a GoodMem memory:
| NLWeb | GoodMem |
|---|---|
url | metadata["url"] — the item's identity, and what NLWeb dedupes on |
site | metadata["site"] — what every search filters on |
raw_schema_object | metadata["schema_json"] |
| (text to embed) | the memory's content |
The embedded text is not the raw JSON: it is extracted from name, headline,
description, articleBody, and similar fields, while the untouched object is kept alongside for
NLWeb to return verbatim. The mapping functions schema_to_text and to_memory_fields are
exported if you need them directly.
upload_documents waits for indexing by default (wait=True, timeout=180.0) and returns the
created memory ids. It inspects every item of the batch response — the server returns HTTP 200
even when an item failed — and a partial failure raises GoodMemUploadError, which carries the
ids that did land in created_memory_ids.
Looking objects up by URL
NLWeb enriches a truncated search result with the complete Schema.org object when an
object_storage provider named default is configured:
object_storage:
default:
import_path: goodmem_nlweb
class_name: GoodMemObjectLookupProvider
base_url: https://your-goodmem-server.example.com
api_key: gm_xxxxxxxxxxxxxxxxxxxxxxxx
space_name: nlwebfrom goodmem_nlweb import GoodMemObjectLookupProvider
lookup = GoodMemObjectLookupProvider(
space_name="nlweb",
base_url="https://your-goodmem-server.example.com",
api_key="gm_xxxxxxxxxxxxxxxxxxxxxxxx",
)
await lookup.get_by_id("https://ex.com/r/laksa") # the full Schema.org objectThe id NLWeb passes here is the item's URL, not a GoodMem id, so it is not required to be a UUID.
Learn more
The full README, examples, and API reference are available on the PyPI package page.
Microsoft Agent Framework
Give Microsoft Agent Framework agents persistent semantic memory through a GoodMem client, context provider, and function tools.
Spring AI
Plug GoodMem's RAG memory into Spring AI as a DocumentRetriever for RAG pipelines, plus agent tools for search, administration, and file uploads.