LlamaIndex#

pip install "wardowl[llamaindex]"

A permission-aware retriever for query and chat engines#

WardowlRetriever is a LlamaIndex retriever for one subject:

from llama_index.core.query_engine import RetrieverQueryEngine

from wardowl import MetadataACLAuthorizer, PermissionedRetriever, Subject
from wardowl.llamaindex import WardowlRetriever

permissioned = PermissionedRetriever(backend, MetadataACLAuthorizer())
retriever = WardowlRetriever(permissioned, Subject("bob", ["engineering"]), k=4)

nodes = retriever.retrieve("How long is annual leave?")
engine = RetrieverQueryEngine.from_args(retriever)  # uses your configured LLM

Create one per request or session, from the authenticated user. Returned nodes carry the chunk’s score and the metadata named in metadata_fields, none by default: WardowlRetriever(permissioned, subject, metadata_fields=["title", "source"]). Permission fields such as allowed_users, allowed_groups and tenant say who may read what, and nodes reach the model, prompts and traces, so leave those out. aretrieve uses wardowl’s async path.

Adding permissions to an existing pipeline#

PermissionNodePostprocessor drops the nodes a subject may not read, after whatever retrieval your pipeline already does:

from wardowl.llamaindex import PermissionNodePostprocessor

postprocessor = PermissionNodePostprocessor(authorizer=authorizer, subject=subject)
engine = index.as_query_engine(node_postprocessors=[postprocessor])

It post-filters, so it can return fewer nodes than were retrieved; WardowlRetriever with a pre-filtering backend does not have that problem. Like the retriever, it fails closed: an authorizer error, a missing decision or a non-boolean decision raises AuthorizationError.

The authorizer sees each node’s full metadata, but the nodes the postprocessor keeps carry only the metadata named in metadata_fields, none by default, in their relationships too: PermissionNodePostprocessor(authorizer=authorizer, subject=subject, metadata_fields=["title"]). LlamaIndex puts a node’s metadata into the prompt by default, so permission fields would otherwise reach the model and traces. The kept nodes are copies; the nodes passed in are not changed.

Any LlamaIndex index as a backend#

from wardowl.llamaindex import LlamaIndexBackend

backend = LlamaIndexBackend(index)  # anything with as_retriever(similarity_top_k=...)
retriever = PermissionedRetriever(backend, authorizer)  # post-filtered